1mod args;
55mod arrays;
56mod backup;
57mod bits;
58mod blocking;
59mod bloom;
60mod client;
61mod clients;
62mod cms;
63mod cpu;
64mod cuckoo;
65mod geo;
66mod graph;
67mod hashes;
68mod himport;
69mod hll;
70mod indexing;
71mod json;
72mod keyspace;
73mod lists;
74mod lua;
75mod migrate;
76mod misses;
77mod multi;
78mod notify;
79mod pubsub;
80mod scan;
81mod scripting;
82mod search;
83mod server;
84mod sets;
85mod streams;
86mod strings;
87mod suggest;
88pub mod table;
89mod tdigest;
90mod topk;
91mod ts;
92mod vectors;
93mod vfilter;
94mod zsets;
95
96pub use args::Args;
97pub use blocking::{Parked, Waiters};
98pub use clients::Client;
99pub(crate) use pubsub::Envelope;
100pub use server::parse_memory;
101pub use table::{COMMANDS, Spec, arity_ok, lookup};
102
103use crate::reply::Out;
104use std::cell::Cell;
105use std::path::{Path, PathBuf};
106use std::sync::Arc;
107use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
108use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize};
109use yo_common::lock::{Held, Lock};
110use yo_common::{Code, Error};
111use yo_kv::cold::Store;
112use yo_kv::lookups;
113use yo_kv::{Clock, Db, Keyspace};
114use yo_search::Registry;
115
116use multi::Watches;
117use search::cursor::Cursors;
118
119pub const DATABASES: usize = 16;
126
127const ALL_DATABASES: u64 = if DATABASES == 64 {
134 u64::MAX
135} else {
136 (1u64 << DATABASES) - 1
137};
138const _: () = assert!(DATABASES <= 64);
139
140const EVICT_BUDGET: usize = 64;
152
153const NO_MAXSTORE: u64 = u64::MAX;
161
162const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum Flow {
171 Continue,
173 Close,
175 Block,
182 Hold,
189}
190
191#[derive(Debug, Default)]
201pub struct Counter(AtomicU64);
202
203impl Counter {
204 fn bump(&self) {
206 self.0.store(self.get().wrapping_add(1), Relaxed);
207 }
208
209 fn drop_one(&self) {
216 self.0.store(self.get().saturating_sub(1), Relaxed);
217 }
218
219 fn get(&self) -> u64 {
221 self.0.load(Relaxed)
222 }
223
224 fn zero(&self) {
226 self.0.store(0, Relaxed);
227 }
228}
229
230#[derive(Debug, Default)]
236pub struct Stats {
237 clients: Counter,
239 connections: Counter,
241 commands: Counter,
243}
244
245impl Stats {
246 pub fn opened(&self) {
248 self.clients.bump();
249 self.connections.bump();
250 }
251
252 pub fn closed(&self) {
254 self.clients.drop_one();
255 }
256}
257
258#[derive(Debug, Clone, Copy, Default)]
260pub struct Totals {
261 pub clients: u64,
263 pub connections: u64,
265 pub commands: u64,
267}
268
269thread_local! {
270 static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
278}
279
280#[derive(Debug)]
291#[repr(align(64))]
292struct Local {
293 stats: Stats,
295 cmdstats: CommandStats,
297 dirty: AtomicU64,
306 turn: AtomicU64,
319 parked: AtomicUsize,
331}
332
333impl Default for Local {
334 fn default() -> Local {
335 Local {
336 stats: Stats::default(),
337 cmdstats: CommandStats::default(),
338 dirty: AtomicU64::new(0),
339 turn: AtomicU64::new(ALL_DATABASES),
340 parked: AtomicUsize::new(0),
341 }
342 }
343}
344
345impl Local {
346 fn mark(&self, dbs: u64) {
348 self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
349 }
350
351 fn note(&self, dbs: u64) {
353 self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
354 }
355
356 fn done(&self, at: usize) {
358 self.turn
359 .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
360 }
361
362 fn wanted(&self, at: usize) -> bool {
364 self.turn.load(Relaxed) & (1u64 << at) != 0
365 }
366
367 fn blocked(&self, n: usize) {
369 self.parked
370 .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
371 }
372
373 fn woke(&self, n: usize) {
375 self.parked
376 .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
377 }
378}
379
380fn one_thread() -> Box<[Local]> {
382 slots(1)
383}
384
385fn slots(threads: usize) -> Box<[Local]> {
387 (0..threads.max(1)).map(|_| Local::default()).collect()
388}
389
390fn working_dir() -> PathBuf {
397 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
398}
399
400#[derive(Debug, Clone, Copy, Default)]
409pub struct CommandStat {
410 pub calls: u64,
412 pub rejected: u64,
415 pub failed: u64,
417}
418
419impl CommandStat {
420 const fn seen(&self) -> bool {
426 self.calls != 0 || self.rejected != 0 || self.failed != 0
427 }
428}
429
430#[derive(Debug, Default)]
435struct Row {
436 calls: Counter,
438 rejected: Counter,
440 failed: Counter,
442}
443
444#[derive(Debug)]
451struct CommandStats(Box<[Row]>);
452
453impl Default for CommandStats {
454 fn default() -> CommandStats {
455 CommandStats((0..table::count()).map(|_| Row::default()).collect())
456 }
457}
458
459impl CommandStats {
460 fn at(&self, spec: &'static Spec) -> &Row {
462 &self.0[table::index_of(spec)]
463 }
464}
465
466pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
472
473const _: () = {
478 const fn shareable<T: Send + Sync>() {}
479 shareable::<Server>();
480};
481
482pub struct Server {
490 dbs: Vec<Db>,
491 width: usize,
497 clock: Clock,
498 started_ms: u64,
499 next_db: AtomicUsize,
508 conn_bytes: AtomicUsize,
525 maxmemory: AtomicU64,
533 store: Lock<Option<Box<StoreSource>>>,
550 maxstore: AtomicU64,
572 used: AtomicUsize,
589 evict_db: AtomicUsize,
599 expire_db: AtomicUsize,
606 expire_ms: AtomicU64,
616 waiters: Lock<Waiters>,
624 parked: AtomicUsize,
637 peers: Lock<migrate::Peers>,
649 locals: Box<[Local]>,
655 claimed: AtomicUsize,
657 next_client: AtomicU64,
665 dir: PathBuf,
673 backup: Lock<backup::State>,
683 sealed: AtomicBool,
693 search: Lock<Registry>,
708 cursors: Lock<Cursors>,
717 scripts: Lock<lua::Scripts>,
729 libraries: Lock<lua::library::Libraries>,
736 stopping: AtomicBool,
745 watches: Lock<Watches>,
752 watched: AtomicUsize,
759 pubsub: Lock<pubsub::Registry>,
766 subs: AtomicUsize,
772 mail: Box<[pubsub::Mailbox]>,
778 notify: AtomicU32,
785 clients: Lock<clients::Clients>,
792 kills: AtomicUsize,
797 pause: AtomicU64,
807}
808
809impl Server {
810 #[must_use]
812 pub fn new() -> Server {
813 let clock = Clock::system();
814 Server {
815 dbs: (0..DATABASES)
816 .map(|_| Db::with_clock(clock.clone(), 1))
817 .collect(),
818 width: 1,
819 started_ms: clock.now_ms(),
820 clock,
821 next_db: AtomicUsize::new(0),
822 conn_bytes: AtomicUsize::new(0),
823 maxmemory: AtomicU64::new(0),
824 store: Lock::new(None),
825 maxstore: AtomicU64::new(NO_MAXSTORE),
826 used: AtomicUsize::new(0),
827 evict_db: AtomicUsize::new(0),
828 expire_db: AtomicUsize::new(0),
829 expire_ms: AtomicU64::new(0),
830 waiters: Lock::default(),
831 parked: AtomicUsize::new(0),
832 peers: Lock::default(),
833 locals: one_thread(),
834 claimed: AtomicUsize::new(0),
835 next_client: AtomicU64::new(1),
836 dir: working_dir(),
837 backup: Lock::default(),
838 sealed: AtomicBool::new(false),
839 search: Lock::new(Registry::new()),
840 cursors: Lock::default(),
841 scripts: Lock::default(),
842 libraries: Lock::default(),
843 stopping: AtomicBool::new(false),
844 watches: Lock::default(),
845 watched: AtomicUsize::new(0),
846 pubsub: Lock::default(),
847 subs: AtomicUsize::new(0),
848 notify: AtomicU32::new(0),
849 clients: Lock::default(),
850 kills: AtomicUsize::new(0),
851 pause: AtomicU64::new(0),
852 mail: pubsub::boxes(1),
853 }
854 }
855
856 #[must_use]
867 pub fn with_width(width: usize) -> Server {
868 let mut server = Server::new();
869 let clock = server.clock.clone();
873 server.dbs = (0..DATABASES)
874 .map(|_| Db::with_clock(clock.clone(), width))
875 .collect();
876 server.width = server.dbs[0].width();
877 server
878 }
879
880 #[must_use]
882 pub fn with_clock(clock: Clock) -> Server {
883 Server {
884 dbs: (0..DATABASES)
885 .map(|_| Db::with_clock(clock.clone(), 1))
886 .collect(),
887 width: 1,
888 started_ms: clock.now_ms(),
889 clock,
890 next_db: AtomicUsize::new(0),
891 conn_bytes: AtomicUsize::new(0),
892 maxmemory: AtomicU64::new(0),
893 store: Lock::new(None),
894 maxstore: AtomicU64::new(NO_MAXSTORE),
895 used: AtomicUsize::new(0),
896 evict_db: AtomicUsize::new(0),
897 expire_db: AtomicUsize::new(0),
898 expire_ms: AtomicU64::new(0),
899 waiters: Lock::default(),
900 parked: AtomicUsize::new(0),
901 peers: Lock::default(),
902 locals: one_thread(),
903 claimed: AtomicUsize::new(0),
904 next_client: AtomicU64::new(1),
905 dir: working_dir(),
906 backup: Lock::default(),
907 sealed: AtomicBool::new(false),
908 search: Lock::new(Registry::new()),
909 cursors: Lock::default(),
910 scripts: Lock::default(),
911 libraries: Lock::default(),
912 stopping: AtomicBool::new(false),
913 watches: Lock::default(),
914 watched: AtomicUsize::new(0),
915 pubsub: Lock::default(),
916 subs: AtomicUsize::new(0),
917 notify: AtomicU32::new(0),
918 clients: Lock::default(),
919 kills: AtomicUsize::new(0),
920 pause: AtomicU64::new(0),
921 mail: pubsub::boxes(1),
922 }
923 }
924
925 pub fn striped(&self, i: usize) -> &Db {
949 self.mine().mark(1u64 << i);
950 &self.dbs[i]
951 }
952
953 fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
960 self.dbs
961 .iter()
962 .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
963 }
964
965 const fn slots(&self) -> usize {
971 DATABASES * self.width
972 }
973
974 const fn slot_db(&self, i: usize) -> usize {
976 i / self.width
977 }
978
979 fn slot(&self, i: usize) -> Held<'_, Keyspace> {
981 let (db, stripe) = (i / self.width, i % self.width);
982 self.dbs[db].hold_stripe(stripe)
983 }
984
985 #[must_use]
987 pub fn dir(&self) -> &Path {
988 &self.dir
989 }
990
991 pub fn set_dir(&mut self, dir: PathBuf) {
998 self.dir = dir;
999 }
1000
1001 pub fn backup_expire(&self) {
1007 backup::expire(self);
1008 }
1009
1010 pub fn stop(&self) {
1016 self.stopping.store(true, Release);
1017 }
1018
1019 #[must_use]
1025 pub fn stopping(&self) -> bool {
1026 self.stopping.load(Acquire)
1027 }
1028
1029 #[must_use]
1036 pub fn striped_ref(&self, i: usize) -> &Db {
1037 &self.dbs[i]
1038 }
1039
1040 fn settings(&self) -> Held<'_, Keyspace> {
1048 self.dbs[0].hold_stripe(0)
1049 }
1050
1051 pub fn refresh_clock(&self) {
1062 self.clock.refresh();
1063 }
1064
1065 pub fn advance_clock_ms(&self, ms: u64) {
1071 let now = self.clock.now_ms() + ms;
1072 self.set_clock_ms(now);
1073 }
1074
1075 pub fn set_clock_ms(&self, ms: u64) {
1083 self.clock.set(ms);
1084 }
1085
1086 #[must_use]
1088 pub fn uptime_secs(&self) -> u64 {
1089 self.clock.now_ms().saturating_sub(self.started_ms) / 1000
1090 }
1091
1092 #[must_use]
1100 pub fn memory_bytes(&self) -> usize {
1101 self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1102 }
1103
1104 #[must_use]
1110 pub fn dataset_bytes(&self) -> usize {
1111 self.keyspaces()
1112 .map(|db| db.map().arena().live_bytes() as usize)
1113 .sum()
1114 }
1115
1116 #[must_use]
1118 pub fn arena_bytes(&self) -> usize {
1119 self.keyspaces()
1120 .map(|db| db.map().arena().reserved_bytes() as usize)
1121 .sum()
1122 }
1123
1124 #[must_use]
1126 pub fn index_bytes(&self) -> usize {
1127 self.keyspaces()
1128 .map(|db| db.map().index().memory_bytes())
1129 .sum()
1130 }
1131
1132 #[must_use]
1139 pub fn compaction(&self) -> yo_kv::Compaction {
1140 self.keyspaces().map(|db| db.map().compaction()).fold(
1141 yo_kv::Compaction::default(),
1142 |a, b| yo_kv::Compaction {
1143 walked: a.walked + b.walked,
1144 moved: a.moved + b.moved,
1145 bytes: a.bytes + b.bytes,
1146 },
1147 )
1148 }
1149
1150 #[must_use]
1152 pub fn segment_count(&self) -> usize {
1153 self.keyspaces()
1154 .map(|db| db.map().arena().resident_segments())
1155 .sum()
1156 }
1157
1158 #[must_use]
1160 pub fn conn_bytes(&self) -> usize {
1161 self.conn_bytes.load(Relaxed)
1162 }
1163
1164 pub fn note_conn_bytes(&self, delta: isize) {
1172 self.conn_bytes
1178 .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1179 }
1180
1181 #[must_use]
1183 pub fn expired_keys(&self) -> u64 {
1184 self.keyspaces().map(|db| db.expired_keys()).sum()
1185 }
1186
1187 #[must_use]
1189 pub fn expired_fields(&self) -> u64 {
1190 self.keyspaces().map(|db| db.expired_fields()).sum()
1191 }
1192
1193 #[must_use]
1195 pub fn expired_fields_active(&self) -> u64 {
1196 self.keyspaces().map(|db| db.expired_fields_active()).sum()
1197 }
1198
1199 #[must_use]
1201 pub fn evicted_keys(&self) -> u64 {
1202 self.keyspaces().map(|db| db.evicted_keys()).sum()
1203 }
1204
1205 #[must_use]
1207 pub fn keyspace_hits(&self) -> u64 {
1208 self.keyspaces().map(|db| db.hits()).sum()
1209 }
1210
1211 #[must_use]
1213 pub fn keyspace_misses(&self) -> u64 {
1214 self.keyspaces().map(|db| db.misses()).sum()
1215 }
1216
1217 pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1223 (0..table::count())
1224 .map(|at| (table::name_at(at), self.command_stat(at)))
1225 .filter(|(_, row)| row.seen())
1226 }
1227
1228 fn command_stat(&self, at: usize) -> CommandStat {
1230 let mut sum = CommandStat::default();
1231 for thread in &self.locals {
1232 let row = &thread.cmdstats.0[at];
1233 sum.calls += row.calls.get();
1234 sum.rejected += row.rejected.get();
1235 sum.failed += row.failed.get();
1236 }
1237 sum
1238 }
1239
1240 pub fn counted(&self) -> &Stats {
1249 &self.mine().stats
1250 }
1251
1252 pub fn next_client(&self) -> u64 {
1257 self.next_client.fetch_add(1, Relaxed)
1258 }
1259
1260 pub fn my_slot(&self) -> usize {
1267 self.mine_at()
1268 }
1269
1270 fn mine(&self) -> &Local {
1272 &self.locals[self.mine_at()]
1273 }
1274
1275 fn mine_at(&self) -> usize {
1282 let mut slot = SLOT.get();
1283 if slot == usize::MAX {
1284 slot = self.claimed.fetch_add(1, Relaxed);
1285 SLOT.set(slot);
1286 }
1287 slot % self.locals.len()
1288 }
1289
1290 #[must_use]
1292 pub fn totals(&self) -> Totals {
1293 let mut sum = Totals::default();
1294 for thread in &self.locals {
1295 sum.clients += thread.stats.clients.get();
1296 sum.connections += thread.stats.connections.get();
1297 sum.commands += thread.stats.commands.get();
1298 }
1299 sum
1300 }
1301
1302 pub fn reset_stats(&self) {
1309 for thread in &self.locals {
1310 thread.stats.connections.zero();
1311 thread.stats.commands.zero();
1312 }
1313 for mut db in self.keyspaces() {
1318 db.zero_stats();
1319 }
1320 }
1321
1322 pub fn set_threads(&mut self, threads: usize) {
1329 self.locals = slots(threads);
1330 self.mail = pubsub::boxes(threads);
1331 self.claimed = AtomicUsize::new(0);
1332 }
1333
1334 #[must_use]
1336 pub fn maxmemory(&self) -> u64 {
1337 self.maxmemory.load(Relaxed)
1338 }
1339
1340 pub fn set_maxmemory(&self, bytes: u64) {
1354 self.maxmemory.store(bytes, Relaxed);
1355 for db in &self.dbs {
1356 db.track_memory(bytes != 0);
1357 }
1358 self.used.store(self.settled_memory(), Relaxed);
1359 }
1360
1361 pub fn set_store_source(
1371 &mut self,
1372 source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1373 ) {
1374 *self.store.lock() = Some(Box::new(source));
1375 }
1376
1377 #[must_use]
1379 pub fn has_store_source(&self) -> bool {
1380 self.store.lock().is_some()
1381 }
1382
1383 fn attach_store(&self, at: usize) {
1390 if self.slot(at).store_bytes().is_some() {
1391 return;
1392 }
1393 let mut source = self.store.lock();
1397 let Some(source) = source.as_mut() else {
1398 return;
1399 };
1400 if let Some(blocks) = source(at) {
1401 self.slot(at).attach(blocks);
1402 }
1403 }
1404
1405 #[must_use]
1407 pub fn maxstore(&self) -> Option<u64> {
1408 match self.maxstore.load(Relaxed) {
1409 NO_MAXSTORE => None,
1410 bytes => Some(bytes),
1411 }
1412 }
1413
1414 pub fn set_maxstore(&self, bytes: Option<u64>) {
1420 self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1421 }
1422
1423 #[must_use]
1429 pub fn store_bytes(&self) -> u64 {
1430 self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1431 }
1432
1433 #[must_use]
1448 pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1449 let mut total = yo_kv::tier::Stats::default();
1450 for db in self.keyspaces() {
1451 let Some(tier) = db.tier() else { continue };
1452 let s = tier.stats();
1453 total.demoted += s.demoted;
1454 total.promoted += s.promoted;
1455 total.faults += s.faults;
1456 total.served += s.served;
1457 total.bytes_out += s.bytes_out;
1458 total.bytes_in += s.bytes_in;
1459 }
1460 total
1461 }
1462
1463 #[must_use]
1470 pub fn regime(&self) -> &'static str {
1471 if (0..self.slots()).any(|at| self.migrates(at)) {
1472 "migrate"
1473 } else {
1474 "evict"
1475 }
1476 }
1477
1478 fn migrates(&self, at: usize) -> bool {
1490 let cap = self.maxstore();
1491 if cap == Some(0) {
1492 return false;
1493 }
1494 let bytes = self.slot(at).store_bytes();
1498 match bytes {
1499 Some(held) => cap.is_none_or(|cap| held < cap),
1500 None => self.store.lock().is_some(),
1504 }
1505 }
1506
1507 pub fn refresh_memory(&self) {
1512 if self.maxmemory() != 0 {
1513 self.used.store(self.settled_memory(), Relaxed);
1514 }
1515 }
1516
1517 fn settled_memory(&self) -> usize {
1524 self.keyspaces()
1525 .map(|mut db| db.settled_memory_bytes())
1526 .sum::<usize>()
1527 + self.conn_bytes()
1528 }
1529
1530 pub fn make_room(&self) -> bool {
1564 let limit = self.maxmemory();
1565 if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1566 return true;
1567 }
1568 let mut used = self.settled_memory();
1573 self.used.store(used, Relaxed);
1574 let mut budget = EVICT_BUDGET;
1575 while used as u64 > limit {
1576 let over = used - limit as usize;
1577 if !self.relieve_step(over) {
1578 return false;
1579 }
1580 self.compact_hard_step();
1581 used = self.settled_memory();
1582 self.used.store(used, Relaxed);
1583 budget -= 1;
1584 if budget == 0 {
1585 break;
1586 }
1587 }
1588 true
1589 }
1590
1591 fn relieve_step(&self, over: usize) -> bool {
1608 let from = self.evict_db.load(Relaxed);
1609 for turn in 0..self.slots() {
1610 let i = (from + turn) % self.slots();
1611 let used = !self.slot(i).is_empty();
1614 let gave = if used && self.migrates(i) {
1615 self.attach_store(i);
1616 self.slot(i)
1621 .relieve(over)
1622 .is_ok_and(yo_kv::tier::Relief::made_room)
1623 } else {
1624 let armed = notify::arm(self, self.slot_db(i));
1630 let gone = self.slot(i).evict_one();
1631 notify::drain(self, armed);
1632 gone
1633 };
1634 if gave {
1635 self.evict_db.store((i + 1) % self.slots(), Relaxed);
1636 self.mine().mark(1u64 << self.slot_db(i));
1637 return true;
1638 }
1639 }
1640 false
1641 }
1642
1643 pub fn expire_slice(&self, budget: usize) -> usize {
1658 let now = self.clock.now_ms();
1659 if now == self.expire_ms.load(Relaxed) {
1660 return 0;
1661 }
1662 self.expire_ms.store(now, Relaxed);
1663 self.expire_step(budget)
1664 }
1665
1666 pub fn expire_step(&self, budget: usize) -> usize {
1682 let mut spent = 0;
1683 let from = self.expire_db.load(Relaxed);
1684 for turn in 0..self.slots() {
1685 if spent >= budget {
1686 break;
1687 }
1688 let i = (from + turn) % self.slots();
1689 let armed = notify::arm(self, self.slot_db(i));
1694 let c = self.slot(i).expire_cycle(budget - spent);
1695 let left = (budget - spent).saturating_sub(c.examined);
1701 let fields = self.slot(i).field_expire_cycle(left);
1702 notify::drain(self, armed);
1703 spent += c.examined + fields;
1704 if c.expired > 0 {
1705 self.expire_db.store((i + 1) % self.slots(), Relaxed);
1706 self.mine().note(1u64 << self.slot_db(i));
1707 }
1708 }
1709 spent
1710 }
1711
1712 fn compact_hard_step(&self) -> Option<usize> {
1718 let from = self.next_db.load(Relaxed);
1719 for turn in 0..self.slots() {
1720 let i = (from + turn) % self.slots();
1721 if let Some(moved) = self.slot(i).compact_hard() {
1722 self.next_db.store((i + 1) % self.slots(), Relaxed);
1723 return Some(moved);
1724 }
1725 }
1726 None
1727 }
1728
1729 fn collect_marks(&self) {
1738 let mut marked = 0;
1739 for thread in &self.locals {
1740 marked |= thread.dirty.swap(0, Relaxed);
1741 }
1742 self.mine().note(marked);
1743 }
1744
1745 pub fn compact_step(&self) -> Option<usize> {
1758 self.collect_marks();
1759 let mine = self.mine();
1760 let from = self.next_db.load(Relaxed);
1761 for turn in 0..self.slots() {
1762 let i = (from + turn) % self.slots();
1763 let at = self.slot_db(i);
1767 if !mine.wanted(at) {
1768 continue;
1769 }
1770 if let Some(moved) = self.slot(i).compact_step() {
1771 self.next_db.store((i + 1) % self.slots(), Relaxed);
1772 return Some(moved);
1773 }
1774 if i % self.width == self.width - 1 {
1778 mine.done(at);
1779 }
1780 }
1781 None
1782 }
1783}
1784
1785impl Server {
1786 fn watching(&self) -> bool {
1795 self.watched.load(Relaxed) != 0
1796 }
1797
1798 pub(crate) fn notify_flags(&self) -> u32 {
1805 self.notify.load(Relaxed)
1806 }
1807
1808 pub(crate) fn set_notify_flags(&self, flags: u32) {
1810 self.notify.store(flags, Relaxed);
1811 }
1812
1813 fn recount(&self, watches: &Watches) {
1818 self.watched.store(watches.len(), Relaxed);
1819 }
1820}
1821
1822impl Default for Server {
1823 fn default() -> Server {
1824 Server::new()
1825 }
1826}
1827
1828pub struct Session {
1830 db: usize,
1831 id: u64,
1832 conn: u32,
1840 name: Vec<u8>,
1841 sets: himport::Fieldsets,
1847 scripted: bool,
1857 multi: Option<multi::Queue>,
1863 watching: Vec<multi::Watched>,
1870 running: bool,
1877 replay: crate::request::Argv,
1886 subs: Option<Box<pubsub::Subs>>,
1894 lib_name: Vec<u8>,
1902 lib_ver: Vec<u8>,
1903 no_evict: bool,
1910 no_touch: bool,
1913 reply: Reply,
1915 sock: Arc<Client>,
1924}
1925
1926#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
1933pub enum Reply {
1934 #[default]
1936 On,
1937 Off,
1939 SkipNext,
1941 SkipNow,
1943}
1944
1945impl Session {
1946 #[must_use]
1948 pub fn new(id: u64) -> Session {
1949 Session {
1950 db: 0,
1951 id,
1952 conn: u32::MAX,
1953 name: Vec::new(),
1954 sets: himport::Fieldsets::default(),
1955 scripted: false,
1956 multi: None,
1957 watching: Vec::new(),
1958 running: false,
1959 replay: crate::request::Argv::new(),
1960 subs: None,
1961 lib_name: Vec::new(),
1962 lib_ver: Vec::new(),
1963 no_evict: false,
1964 no_touch: false,
1965 reply: Reply::On,
1966 sock: Arc::new(Client::new(id)),
1967 }
1968 }
1969
1970 #[must_use]
1976 pub fn row(&self) -> &Arc<Client> {
1977 &self.sock
1978 }
1979
1980 pub fn opened(&mut self, now_ms: u64) {
1986 self.sock.since_ms.store(now_ms, Relaxed);
1987 self.sock.last_ms.store(now_ms, Relaxed);
1988 }
1989
1990 pub fn set_socket(&mut self, peer: &str, local: &str, fd: i32, unix: bool) {
1997 yo_alloc::allow(|| {
1998 let mut text = self.sock.text.lock();
1999 text.peer.clear();
2000 text.peer.extend_from_slice(peer.as_bytes());
2001 text.local.clear();
2002 text.local.extend_from_slice(local.as_bytes());
2003 });
2004 self.sock.fd.store(fd, Relaxed);
2005 self.sock.set_flag(clients::UNIX, unix);
2006 }
2007
2008 pub fn read_bytes(&mut self, n: usize) {
2010 let row = &self.sock;
2011 row.net_in
2012 .store(row.net_in.load(Relaxed) + n as u64, Relaxed);
2013 row.reads.store(row.reads.load(Relaxed) + 1, Relaxed);
2014 }
2015
2016 pub fn wrote_bytes(&mut self, n: usize) {
2018 let row = &self.sock;
2019 row.net_out
2020 .store(row.net_out.load(Relaxed) + n as u64, Relaxed);
2021 }
2022
2023 pub fn note_buffers(&mut self, waiting: usize, room: usize, held: usize, reply: usize) {
2031 let row = &self.sock;
2032 row.qbuf.store(waiting as u64, Relaxed);
2033 row.qbuf_free.store(room as u64, Relaxed);
2034 row.obl.store(held as u64, Relaxed);
2035 row.rbs.store(reply as u64, Relaxed);
2036 row.rbp
2037 .store(row.rbp.load(Relaxed).max(reply as u64), Relaxed);
2038 }
2039
2040 pub fn note_proto(&mut self, version: i64) {
2046 self.sock.resp.store(version as u32, Relaxed);
2047 }
2048
2049 pub(crate) fn ran(&mut self, at: usize, sub: Option<&[u8]>, argv: u64, now_ms: u64) {
2056 self.sock.last_ms.store(now_ms, Relaxed);
2057 self.sock.argv_mem.store(argv, Relaxed);
2058 self.sock.note_command(at, sub);
2059 }
2060
2061 pub fn finished(&mut self) {
2076 let (sub, psub, ssub) = self.sub_counts();
2077 let (multi, multi_mem) = self.queued();
2078 let subscribed = self.subscribed();
2079 let in_multi = self.in_multi();
2080 let watching = self.watching.len();
2081 let db = self.db;
2082 let row = &self.sock;
2083 row.cmds.store(row.cmds.load(Relaxed) + 1, Relaxed);
2084 row.db.store(db as u32, Relaxed);
2085 row.sub.store(sub as u32, Relaxed);
2086 row.psub.store(psub as u32, Relaxed);
2087 row.ssub.store(ssub as u32, Relaxed);
2088 row.watch.store(watching as u32, Relaxed);
2089 row.multi.store(multi, Relaxed);
2090 row.multi_mem.store(multi_mem, Relaxed);
2091 row.set_flag(clients::SUBSCRIBED, subscribed);
2092 row.set_flag(clients::IN_MULTI, in_multi);
2093 }
2094
2095 #[must_use]
2097 pub const fn reply_mode(&self) -> Reply {
2098 self.reply
2099 }
2100
2101 pub const fn step_reply(&mut self) {
2106 self.reply = match self.reply {
2107 Reply::SkipNext => Reply::SkipNow,
2108 Reply::SkipNow => Reply::On,
2109 other => other,
2110 };
2111 }
2112
2113 pub(crate) const fn scripted(&self) -> bool {
2115 self.scripted
2116 }
2117
2118 pub(crate) const fn running(&self) -> bool {
2120 self.running
2121 }
2122
2123 pub(crate) fn set_conn(&mut self, conn: u32) {
2129 self.conn = conn;
2130 self.sock.conn.store(conn, Relaxed);
2131 }
2132
2133 #[must_use]
2135 pub const fn id(&self) -> u64 {
2136 self.id
2137 }
2138
2139 #[must_use]
2141 pub const fn db(&self) -> usize {
2142 self.db
2143 }
2144
2145 #[must_use]
2147 pub fn name(&self) -> &[u8] {
2148 &self.name
2149 }
2150
2151 pub fn reset(&mut self) {
2156 self.db = 0;
2157 self.name.clear();
2158 self.sock.set_text(|text| &mut text.name, b"");
2159 self.sets.clear();
2163 self.reply = Reply::On;
2168 self.set_no_evict(false);
2169 self.set_no_touch(false);
2170 }
2171
2172 fn set_name(&mut self, name: &[u8]) {
2174 yo_alloc::allow(|| {
2175 self.name.clear();
2176 self.name.extend_from_slice(name);
2177 });
2178 self.sock.set_text(|text| &mut text.name, name);
2179 }
2180
2181 fn set_lib_name(&mut self, value: &[u8]) {
2183 yo_alloc::allow(|| {
2184 self.lib_name.clear();
2185 self.lib_name.extend_from_slice(value);
2186 });
2187 self.sock.set_text(|text| &mut text.lib_name, value);
2188 }
2189
2190 fn set_lib_ver(&mut self, value: &[u8]) {
2192 yo_alloc::allow(|| {
2193 self.lib_ver.clear();
2194 self.lib_ver.extend_from_slice(value);
2195 });
2196 self.sock.set_text(|text| &mut text.lib_ver, value);
2197 }
2198
2199 fn set_no_evict(&mut self, on: bool) {
2201 self.no_evict = on;
2202 self.sock.set_flag(clients::NO_EVICT, on);
2203 }
2204
2205 fn set_no_touch(&mut self, on: bool) {
2207 self.no_touch = on;
2208 self.sock.set_flag(clients::NO_TOUCH, on);
2209 }
2210}
2211
2212pub fn forget_session(server: &Server, session: &mut Session) {
2221 multi::release(server, session);
2222 pubsub::release(server, session);
2223}
2224
2225pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
2230 if args.is_empty() {
2233 return Flow::Continue;
2234 }
2235 let flow = resolved(server, session, lookup(args.name()), args, out);
2236 if flow != Flow::Hold {
2246 session.finished();
2247 }
2248 flow
2249}
2250
2251const CONTAINERS: [&str; 10] = [
2257 "backup", "client", "command", "config", "function", "object", "pubsub", "script", "xgroup",
2258 "xinfo",
2259];
2260
2261fn container_sub<'a>(spec: &Spec, args: &Args<'a>) -> Option<&'a [u8]> {
2267 (args.len() > 1 && CONTAINERS.contains(&spec.name)).then(|| args.get(1))
2268}
2269
2270const MAY_REPLICATE: [&str; 6] = ["eval", "evalsha", "fcall", "pfcount", "publish", "spublish"];
2276
2277fn may_replicate(spec: &Spec, session: &Session) -> bool {
2284 spec.flags.contains(&"write")
2285 || MAY_REPLICATE.contains(&spec.name)
2286 || (spec.name == "exec" && session.queued_writes())
2287}
2288
2289pub fn resolved(
2301 server: &Server,
2302 session: &mut Session,
2303 spec: Option<&'static Spec>,
2304 args: Args<'_>,
2305 out: &mut Out,
2306) -> Flow {
2307 if args.is_empty() {
2308 return Flow::Continue;
2309 }
2310 server.mine().stats.commands.bump();
2311
2312 let Some(spec) = spec else {
2319 multi::refuse(server, session, None, &args::unknown_command(args), out);
2320 return Flow::Continue;
2321 };
2322 if !arity_ok(spec, args.len()) {
2323 server.mine().cmdstats.at(spec).rejected.bump();
2324 multi::refuse(
2325 server,
2326 session,
2327 Some(spec),
2328 &args::wrong_arity(spec.name),
2329 out,
2330 );
2331 return Flow::Continue;
2332 }
2333 let argv = (0..args.len()).map(|i| args.get(i).len() as u64).sum();
2339 session.ran(
2340 table::index_of(spec),
2341 container_sub(spec, &args),
2342 argv,
2343 server.now_ms(),
2344 );
2345
2346 if session.in_multi()
2347 && let Some(e) = multi::refused_in_multi(spec)
2348 {
2349 server.mine().cmdstats.at(spec).rejected.bump();
2350 multi::refuse(server, session, Some(spec), &e, out);
2351 return Flow::Continue;
2352 }
2353
2354 if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
2364 server.mine().cmdstats.at(spec).rejected.bump();
2365 session.dirty_multi();
2366 out.error_line(b"OOM ", OOM);
2367 return Flow::Continue;
2368 }
2369
2370 if let Some(e) = pubsub::refused(session, spec, out) {
2378 server.mine().cmdstats.at(spec).rejected.bump();
2379 multi::refuse(server, session, Some(spec), &e, out);
2380 return Flow::Continue;
2381 }
2382
2383 if !session.running
2398 && let Some(all) = server.paused(server.now_ms())
2399 && (all || may_replicate(spec, session))
2400 {
2401 return Flow::Hold;
2402 }
2403
2404 if session.queues(spec.name) {
2409 return multi::queue(session, spec, args, out);
2410 }
2411
2412 server.mine().mark(match spec.group {
2419 "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
2420 | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
2421 1u64 << session.db
2422 }
2423 _ => ALL_DATABASES,
2424 });
2425
2426 let mark = out.len();
2427 let armed = notify::arm(server, session.db);
2438 misses::report(&server.dbs[session.db], session.db, spec, args);
2443 let reading = lookups::reading(misses::reading(spec, args));
2448 let done = if spec.flags.contains(&"blocking") {
2449 blocking::execute(server, session, spec, args, out)
2450 } else {
2451 match spec.group {
2452 "string" => {
2453 let db = session.db;
2454 strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2455 }
2456 "bitmap" => {
2460 let db = session.db;
2461 bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2462 }
2463 "hyperloglog" => {
2466 let db = session.db;
2467 hll::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2468 }
2469 "set" => {
2470 let db = session.db;
2471 sets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2472 }
2473 "hash" if spec.name == "himport" => {
2478 let db = session.db;
2479 himport::execute(&server.dbs[db], &mut session.sets, args, out)
2480 .map(|()| Flow::Continue)
2481 }
2482 "hash" => {
2487 let db = session.db;
2488 let changed = hashes::execute(&server.dbs[db], db, spec, args, out);
2489 changed.map(|changed| {
2490 indexing::changed(server, db, args.get(1), changed);
2491 Flow::Continue
2492 })
2493 }
2494 "list" => {
2495 let db = session.db;
2496 lists::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2497 }
2498 "zset" => {
2499 let db = session.db;
2500 zsets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2501 }
2502 "geo" => {
2506 let db = session.db;
2507 geo::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2508 }
2509 "array" => {
2510 let db = session.db;
2511 arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2512 }
2513 "graph" => {
2514 let db = session.db;
2515 graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2516 }
2517 "json" => {
2521 let db = session.db;
2522 json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2523 }
2524 "vector" => {
2525 let db = session.db;
2526 vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2527 }
2528 "bloom" => {
2529 let db = session.db;
2530 bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2531 }
2532 "cuckoo" => {
2533 let db = session.db;
2534 cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2535 }
2536 "cms" => {
2537 let db = session.db;
2538 cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2539 }
2540 "topk" => {
2541 let db = session.db;
2542 topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2543 }
2544 "tdigest" => {
2545 let db = session.db;
2546 tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2547 }
2548 "ts" => {
2549 let db = session.db;
2550 ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2551 }
2552 "stream" => {
2557 let db = session.db;
2558 let now = server.now_ms();
2559 streams::execute(&server.dbs[db], db, spec, args, now, out).map(|()| Flow::Continue)
2560 }
2561 "keyspace" if spec.name == "migrate" => {
2565 migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
2566 }
2567 "keyspace" => {
2573 let mut touched = indexing::Touched::new(server);
2574 let done =
2575 keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
2576 done.map(|()| {
2577 indexing::touched(server, &touched);
2578 Flow::Continue
2579 })
2580 }
2581 "search" if spec.name == "FT.SEARCH" => {
2588 search::find(server, session.db, args, out).map(|()| Flow::Continue)
2593 }
2594 "search" if spec.name == "FT.AGGREGATE" => {
2595 search::roll(server, session.db, args, out).map(|()| Flow::Continue)
2596 }
2597 "search" if spec.name == "FT.HYBRID" => {
2598 search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
2599 }
2600 "search" if spec.name == "FT.PROFILE" => {
2601 search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
2604 }
2605 "search" if spec.name.starts_with("FT.SUG") => {
2609 let db = session.db;
2610 suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2611 }
2612 "search"
2616 if matches!(
2617 spec.name,
2618 "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
2619 ) =>
2620 {
2621 let db = session.db;
2622 search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
2623 }
2624 "search" if spec.name == "FT.CURSOR" => {
2625 search::cursor::execute(server, args, out).map(|()| Flow::Continue)
2629 }
2630 "search" => {
2631 let db = session.db;
2632 let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
2633 made.map(|made| {
2634 match made {
2635 Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
2636 Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
2637 None => {}
2638 }
2639 Flow::Continue
2640 })
2641 }
2642 "scripting" => {
2643 scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
2644 }
2645 "transactions" => multi::execute(server, session, spec, args, out),
2646 "pubsub" => pubsub::execute(server, session, spec, args, out),
2650 _ => server::execute(server, session, spec, args, out),
2651 }
2652 };
2653 drop(reading);
2654 if let Err(e) = &done {
2662 misses::undo(spec, e);
2663 }
2664 notify::drain(server, armed);
2665
2666 let flow = match done {
2667 Ok(flow) => flow,
2668 Err(e) => {
2669 out.truncate(mark);
2670 write_error(out, &e);
2671 Flow::Continue
2672 }
2673 };
2674
2675 if server.watching() && spec.flags.contains(&"write") {
2680 multi::touched(server, session, spec, args);
2681 }
2682
2683 let row = server.mine().cmdstats.at(spec);
2694 row.calls.bump();
2695 if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
2696 row.failed.bump();
2697 }
2698 flow
2699}
2700
2701fn write_error(out: &mut Out, e: &Error) {
2711 let prefix: &[u8] = match e.code() {
2712 Code::WrongType => b"WRONGTYPE ",
2713 Code::Corrupt => b"INVALIDOBJ ",
2717 _ => b"ERR ",
2718 };
2719 out.error_line(prefix, e.message().as_bytes());
2720}
2721
2722#[cfg(test)]
2723mod tests {
2724 use super::*;
2725 use crate::proto::{Limits, Proto};
2726 use crate::request::Argv;
2727
2728 pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2733 let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2734 for p in parts {
2735 wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2736 wire.extend_from_slice(p);
2737 wire.extend_from_slice(b"\r\n");
2738 }
2739 wire
2740 }
2741
2742 struct Fixture {
2744 server: Server,
2745 session: Session,
2746 argv: Argv,
2747 out: Out,
2748 }
2749
2750 impl Fixture {
2751 fn new() -> Fixture {
2752 Fixture::on(Server::new())
2753 }
2754
2755 fn striped(width: usize) -> Fixture {
2757 Fixture::on(Server::with_width(width))
2758 }
2759
2760 fn on(server: Server) -> Fixture {
2761 Fixture {
2762 server,
2763 session: Session::new(7),
2764 argv: Argv::new(),
2765 out: Out::new(Proto::Resp2),
2766 }
2767 }
2768
2769 fn run(&mut self, parts: &[&[u8]]) -> String {
2771 self.flow(parts).1
2772 }
2773
2774 fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2780 let wire = encode(parts);
2781 self.argv.decode(&wire, &Limits::default()).unwrap();
2782 self.out.clear();
2783 execute(
2784 &self.server,
2785 &mut self.session,
2786 Args::new(&self.argv, &wire),
2787 &mut self.out,
2788 );
2789 self.out.as_slice().to_vec()
2790 }
2791
2792 fn advance(&mut self, ms: u64) {
2794 self.server.advance_clock_ms(ms);
2795 }
2796
2797 fn other(&mut self, parts: &[&[u8]]) -> String {
2802 self.other_in(self.session.db(), parts)
2803 }
2804
2805 fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
2807 let mut session = Session::new(8);
2808 session.db = db;
2809 let reply = self.by(&mut session, parts);
2810 forget_session(&self.server, &mut session);
2811 reply
2812 }
2813
2814 fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
2816 let wire = encode(parts);
2817 let mut argv = Argv::new();
2818 argv.decode(&wire, &Limits::default()).unwrap();
2819 let mut out = Out::new(Proto::Resp2);
2820 execute(&self.server, session, Args::new(&argv, &wire), &mut out);
2821 String::from_utf8_lossy(out.as_slice()).into_owned()
2822 }
2823
2824 fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2826 let wire = encode(parts);
2827 self.argv.decode(&wire, &Limits::default()).unwrap();
2828 self.out.clear();
2829 let flow = execute(
2830 &self.server,
2831 &mut self.session,
2832 Args::new(&self.argv, &wire),
2833 &mut self.out,
2834 );
2835 (
2836 flow,
2837 String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2838 )
2839 }
2840 }
2841
2842 #[test]
2843 fn multi_holds_commands_and_exec_runs_them() {
2844 let mut f = Fixture::new();
2845 assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
2846 assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
2847 assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
2848 assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
2850 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
2851 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
2852 }
2853
2854 #[test]
2863 fn the_second_exec_of_a_shape_does_not_grow_the_buffer() {
2864 let mut f = Fixture::new();
2865 for _ in 0..2 {
2866 f.run(&[b"MULTI"]);
2867 f.run(&[b"SET", b"k", b"1"]);
2868 f.run(&[b"INCR", b"k"]);
2869 f.run(&[b"EXEC"]);
2870 }
2871 let room = f.session.replay.room();
2872 assert!(room > 0, "the first transaction should have set the room");
2873 f.run(&[b"MULTI"]);
2874 f.run(&[b"SET", b"k", b"1"]);
2875 f.run(&[b"INCR", b"k"]);
2876 f.run(&[b"EXEC"]);
2877 assert_eq!(f.session.replay.room(), room);
2878 }
2879
2880 #[test]
2881 fn an_empty_transaction_answers_an_empty_array() {
2882 let mut f = Fixture::new();
2883 f.run(&[b"MULTI"]);
2884 assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
2885 }
2886
2887 #[test]
2888 fn exec_and_discard_want_a_transaction_to_be_open() {
2889 let mut f = Fixture::new();
2890 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2891 assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
2892 assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
2895 }
2896
2897 #[test]
2898 fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
2899 let mut f = Fixture::new();
2900 f.run(&[b"MULTI"]);
2901 assert_eq!(
2902 f.run(&[b"MULTI"]),
2903 "-ERR MULTI calls can not be nested\r\n",
2904 "nested MULTI is raised by the command and not by the funnel"
2905 );
2906 assert_eq!(
2907 f.run(&[b"WATCH", b"k"]),
2908 "-ERR WATCH inside MULTI is not allowed\r\n"
2909 );
2910 f.run(&[b"SET", b"k", b"1"]);
2911 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
2912 }
2913
2914 #[test]
2915 fn an_error_the_funnel_raises_kills_the_transaction() {
2916 for bad in [
2917 &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
2918 &[b"GET".as_slice()],
2919 ] {
2920 let mut f = Fixture::new();
2921 f.run(&[b"MULTI"]);
2922 assert!(f.run(bad).starts_with("-ERR "));
2923 assert_eq!(
2924 f.run(&[b"SET", b"k", b"1"]),
2925 "+QUEUED\r\n",
2926 "a dead transaction still answers QUEUED, which is Redis"
2927 );
2928 assert_eq!(
2929 f.run(&[b"EXEC"]),
2930 "-EXECABORT Transaction discarded because of previous errors.\r\n"
2931 );
2932 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2933 }
2934 }
2935
2936 #[test]
2937 fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
2938 let mut f = Fixture::new();
2939 f.run(&[b"MULTI"]);
2940 f.run(&[b"SET", b"k", b"1"]);
2941 assert_eq!(
2942 f.run(&[b"EXEC", b"x"]),
2943 "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
2944 );
2945 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2946 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2947 }
2948
2949 #[test]
2950 fn a_command_a_transaction_may_not_hold_kills_it() {
2951 let mut f = Fixture::new();
2952 f.run(&[b"MULTI"]);
2953 assert_eq!(
2954 f.run(&[b"SHUTDOWN", b"NOSAVE"]),
2955 "-ERR Command not allowed inside a transaction\r\n"
2956 );
2957 assert_eq!(
2958 f.run(&[b"EXEC"]),
2959 "-EXECABORT Transaction discarded because of previous errors.\r\n"
2960 );
2961 }
2962
2963 #[test]
2964 fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
2965 let mut f = Fixture::new();
2966 f.run(&[b"RPUSH", b"l", b"v"]);
2967 f.run(&[b"MULTI"]);
2968 f.run(&[b"INCR", b"l"]);
2969 f.run(&[b"SET", b"y", b"2"]);
2970 assert_eq!(
2971 f.run(&[b"EXEC"]),
2972 "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
2973 );
2974 assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
2975 }
2976
2977 #[test]
2978 fn discard_and_reset_both_throw_the_queue_away() {
2979 let mut f = Fixture::new();
2980 f.run(&[b"MULTI"]);
2981 f.run(&[b"SET", b"k", b"1"]);
2982 assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
2983 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2984 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2985
2986 f.run(&[b"MULTI"]);
2987 f.run(&[b"SET", b"k", b"1"]);
2988 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2989 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2990 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2991 }
2992
2993 #[test]
2994 fn select_is_queued_and_applied_when_exec_runs_it() {
2995 let mut f = Fixture::new();
2996 f.run(&[b"MULTI"]);
2997 assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
2998 f.run(&[b"SET", b"k", b"1"]);
2999 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
3000 assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
3001 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
3002 }
3003
3004 #[test]
3005 fn a_write_by_another_connection_fails_the_transaction() {
3006 let mut f = Fixture::new();
3007 f.run(&[b"SET", b"k", b"1"]);
3008 assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
3009 f.other(&[b"SET", b"k", b"2"]);
3010 f.run(&[b"MULTI"]);
3011 f.run(&[b"GET", b"k"]);
3012 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3013 }
3014
3015 #[test]
3016 fn a_write_that_puts_the_same_value_back_still_fails_it() {
3017 let mut f = Fixture::new();
3018 f.run(&[b"SET", b"k", b"1"]);
3019 f.run(&[b"WATCH", b"k"]);
3020 f.other(&[b"SET", b"k", b"1"]);
3021 f.run(&[b"MULTI"]);
3022 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3023 }
3024
3025 #[test]
3026 fn a_read_by_another_connection_does_not() {
3027 let mut f = Fixture::new();
3028 f.run(&[b"SET", b"k", b"1"]);
3029 f.run(&[b"WATCH", b"k"]);
3030 f.other(&[b"GET", b"k"]);
3031 f.other(&[b"STRLEN", b"k"]);
3032 f.run(&[b"MULTI"]);
3033 f.run(&[b"GET", b"k"]);
3034 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
3035 }
3036
3037 #[test]
3038 fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
3039 let mut f = Fixture::new();
3040 f.run(&[b"WATCH", b"k"]);
3041 f.other(&[b"DEL", b"k"]);
3042 f.run(&[b"MULTI"]);
3043 f.run(&[b"PING"]);
3044 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
3045 f.run(&[b"WATCH", b"k"]);
3047 f.other(&[b"SET", b"k", b"1"]);
3048 f.run(&[b"MULTI"]);
3049 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3050 }
3051
3052 #[test]
3053 fn a_watched_key_that_expires_fails_the_transaction() {
3054 let mut f = Fixture::new();
3055 f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
3056 f.run(&[b"WATCH", b"k"]);
3057 f.run(&[b"MULTI"]);
3058 f.advance(100);
3059 assert_eq!(
3060 f.run(&[b"EXEC"]),
3061 "*-1\r\n",
3062 "nothing wrote to the key, so only the liveness check can catch this"
3063 );
3064 }
3065
3066 #[test]
3067 fn every_way_a_transaction_ends_lets_go_of_the_watches() {
3068 for end in [
3069 &[b"EXEC".as_slice()] as &[&[u8]],
3070 &[b"DISCARD".as_slice()],
3071 &[b"UNWATCH".as_slice()],
3072 &[b"RESET".as_slice()],
3073 ] {
3074 let mut f = Fixture::new();
3075 f.run(&[b"SET", b"k", b"1"]);
3076 f.run(&[b"WATCH", b"k"]);
3077 if end[0] != b"UNWATCH" && end[0] != b"RESET" {
3078 f.run(&[b"MULTI"]);
3079 }
3080 f.run(end);
3081 assert!(!f.server.watching(), "{end:?} left a row behind");
3082 f.other(&[b"SET", b"k", b"2"]);
3084 f.run(&[b"MULTI"]);
3085 f.run(&[b"GET", b"k"]);
3086 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
3087 }
3088 }
3089
3090 #[test]
3091 fn a_connection_going_away_lets_go_of_its_watches() {
3092 let mut f = Fixture::new();
3093 f.run(&[b"SET", b"k", b"1"]);
3094 f.run(&[b"WATCH", b"k"]);
3095 assert!(f.server.watching());
3096 forget_session(&f.server, &mut f.session);
3097 assert!(!f.server.watching());
3098 }
3099
3100 #[test]
3101 fn watching_the_same_key_twice_is_one_watch() {
3102 let mut f = Fixture::new();
3103 f.run(&[b"SET", b"k", b"1"]);
3104 f.run(&[b"WATCH", b"k", b"k"]);
3105 f.run(&[b"UNWATCH"]);
3106 assert!(
3107 !f.server.watching(),
3108 "the row counts watchers, so a doubled watch would leave one behind"
3109 );
3110 }
3111
3112 #[test]
3113 fn two_connections_can_watch_the_same_key() {
3114 let mut f = Fixture::new();
3115 f.run(&[b"SET", b"k", b"1"]);
3116 f.run(&[b"WATCH", b"k"]);
3117 let mut second = Session::new(9);
3118 second.db = f.session.db();
3119 assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
3120 forget_session(&f.server, &mut second);
3122 assert!(f.server.watching());
3123 f.other(&[b"SET", b"k", b"2"]);
3124 f.run(&[b"MULTI"]);
3125 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3126 }
3127
3128 #[test]
3129 fn flushdb_fails_a_watch_on_a_key_that_was_there() {
3130 let mut f = Fixture::new();
3131 f.run(&[b"SET", b"k", b"1"]);
3132 f.run(&[b"WATCH", b"k"]);
3133 f.other(&[b"FLUSHDB"]);
3134 f.run(&[b"MULTI"]);
3135 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3136 }
3137
3138 #[test]
3139 fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
3140 let mut f = Fixture::new();
3141 f.run(&[b"WATCH", b"k"]);
3142 f.other(&[b"FLUSHDB"]);
3143 f.run(&[b"MULTI"]);
3144 f.run(&[b"PING"]);
3145 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
3146 }
3147
3148 #[test]
3149 fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
3150 let mut f = Fixture::new();
3151 f.run(&[b"SET", b"k", b"1"]);
3152 f.run(&[b"WATCH", b"k"]);
3153 let elsewhere = f.session.db() + 1;
3155 f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
3156 f.run(&[b"MULTI"]);
3157 f.run(&[b"GET", b"k"]);
3158 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
3159 }
3160
3161 #[test]
3162 fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
3163 let mut f = Fixture::new();
3164 f.run(&[b"RPUSH", b"src", b"1"]);
3165 f.run(&[b"WATCH", b"dst"]);
3166 f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
3167 f.run(&[b"MULTI"]);
3168 assert_eq!(
3169 f.run(&[b"EXEC"]),
3170 "*-1\r\n",
3171 "SORT is movablekeys, so every watched key in the database is asked"
3172 );
3173 }
3174
3175 #[test]
3176 fn a_server_nobody_is_watching_says_so() {
3177 let mut f = Fixture::new();
3178 assert!(!f.server.watching());
3179 f.run(&[b"SET", b"k", b"1"]);
3180 assert!(!f.server.watching());
3181 }
3182
3183 #[test]
3189 fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
3190 let mut f = Fixture::new();
3191 assert_eq!(
3192 f.run(&[b"SUBSCRIBE", b"a", b"b"]),
3193 "*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"
3194 );
3195 assert_eq!(
3196 f.run(&[b"PSUBSCRIBE", b"c*"]),
3197 "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
3198 );
3199 assert_eq!(
3200 f.run(&[b"SSUBSCRIBE", b"s"]),
3201 "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
3202 );
3203 assert_eq!(
3206 f.run(&[b"SUBSCRIBE", b"a"]),
3207 "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
3208 );
3209 }
3210
3211 #[test]
3214 fn unsubscribe_answers_for_names_it_was_not_holding_too() {
3215 let mut f = Fixture::new();
3216 f.run(&[b"SUBSCRIBE", b"a"]);
3217
3218 assert_eq!(
3221 f.run(&[b"UNSUBSCRIBE", b"zz"]),
3222 "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
3223 );
3224 f.run(&[b"SUBSCRIBE", b"b"]);
3226 f.run(&[b"PSUBSCRIBE", b"p*"]);
3227 assert_eq!(
3228 f.run(&[b"UNSUBSCRIBE"]),
3229 "*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"
3230 );
3231 assert_eq!(
3234 f.run(&[b"UNSUBSCRIBE"]),
3235 "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
3236 "the pattern is still held, so the count is one"
3237 );
3238 assert_eq!(
3239 f.run(&[b"SUNSUBSCRIBE"]),
3240 "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
3241 "shard channels are counted on their own"
3242 );
3243 }
3244
3245 #[test]
3250 fn the_subscribe_gate_does_not_reach_inside_exec() {
3251 let mut f = Fixture::new();
3252 f.run(&[b"SET", b"k", b"1"]);
3253 f.run(&[b"MULTI"]);
3254 assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
3255 assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
3256 assert_eq!(
3257 f.run(&[b"EXEC"]),
3258 "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
3259 );
3260 assert_eq!(
3263 f.run(&[b"GET", b"k"]),
3264 "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
3265 );
3266 }
3267
3268 #[test]
3271 fn exec_sent_by_a_subscriber_aborts_the_transaction() {
3272 let mut f = Fixture::new();
3273 f.run(&[b"MULTI"]);
3274 f.run(&[b"SET", b"k", b"1"]);
3275 f.run(&[b"SUBSCRIBE", b"z"]);
3276 f.run(&[b"EXEC"]);
3277 f.run(&[b"MULTI"]);
3278 assert_eq!(
3279 f.run(&[b"EXEC"]),
3280 "-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"
3281 );
3282 }
3283
3284 #[test]
3287 fn reset_lets_go_of_every_subscription() {
3288 let mut f = Fixture::new();
3289 f.run(&[b"SUBSCRIBE", b"a"]);
3290 f.run(&[b"PSUBSCRIBE", b"p*"]);
3291 f.run(&[b"SSUBSCRIBE", b"s"]);
3292 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
3293 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
3294 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
3295 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
3296 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3298 }
3299
3300 #[test]
3303 fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
3304 let mut f = Fixture::new();
3305 let mut sub = Session::new(9);
3306 f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
3307 f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
3308 f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
3309
3310 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
3311 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
3312 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
3313 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
3314 assert_eq!(
3315 f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
3316 "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
3317 );
3318 assert_eq!(
3319 f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
3320 "*2\r\n$1\r\na\r\n:1\r\n",
3321 "the shard channel and the channel share a name and not a count"
3322 );
3323 assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
3324
3325 forget_session(&f.server, &mut sub);
3326 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
3327 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
3328 }
3329
3330 #[test]
3333 fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
3334 let mut f = Fixture::new();
3335 assert_eq!(
3336 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
3337 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
3338 );
3339 assert_eq!(
3340 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
3341 "+OK\r\n"
3342 );
3343 assert_eq!(
3346 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
3347 "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
3348 );
3349 assert_eq!(
3350 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
3351 "+OK\r\n"
3352 );
3353 assert_eq!(
3354 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
3355 "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
3356 );
3357 }
3358
3359 #[test]
3360 fn a_letter_the_notification_setting_does_not_know_is_refused() {
3361 let mut f = Fixture::new();
3362 assert_eq!(
3363 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
3364 "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
3365 - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
3366 );
3367 assert_eq!(
3370 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
3371 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
3372 );
3373 }
3374
3375 #[test]
3381 fn pubsub_says_no_two_different_ways() {
3382 let mut f = Fixture::new();
3383 assert_eq!(
3384 f.run(&[b"PUBSUB"]),
3385 "-ERR wrong number of arguments for 'pubsub' command\r\n"
3386 );
3387 assert_eq!(
3388 f.run(&[b"PUBSUB", b"NOPE"]),
3389 "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
3390 );
3391 assert_eq!(
3392 f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
3393 "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
3394 );
3395 assert_eq!(
3396 f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
3397 "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
3398 );
3399 assert_eq!(
3400 f.run(&[b"PUBSUB", b"HELP", b"x"]),
3401 "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
3402 );
3403 }
3404
3405 #[test]
3408 fn publishing_to_nobody_answers_zero() {
3409 let mut f = Fixture::new();
3410 assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
3411 assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
3412 assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
3414 }
3415
3416 #[test]
3420 fn a_publish_counts_the_deliveries_and_not_the_clients() {
3421 let mut f = Fixture::new();
3422 let mut sub = Session::new(9);
3423 f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
3424 f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
3425 f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
3426 assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
3427 forget_session(&f.server, &mut sub);
3428 }
3429
3430 #[cfg_attr(miri, ignore = "megabytes through the arena")]
3443 #[test]
3444 fn rewriting_the_same_keys_does_not_grow_the_server() {
3445 let mut f = Fixture::new();
3446 let val = vec![b'v'; 1024];
3447 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3448
3449 for k in &keys {
3450 f.run(&[b"SET", k, &val]);
3451 }
3452 f.server.compact_step();
3453 let after_first = f.server.memory_bytes();
3454
3455 for _ in 0..500 {
3460 for k in &keys {
3461 f.run(&[b"SET", k, &val]);
3462 }
3463 f.server.compact_step();
3464 }
3465
3466 assert!(
3467 f.server.memory_bytes() <= after_first * 2,
3468 "held {} after five hundred passes against {after_first} after one",
3469 f.server.memory_bytes()
3470 );
3471 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3472 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3473 }
3474
3475 #[cfg_attr(miri, ignore = "megabytes through the arena")]
3492 #[test]
3493 fn a_database_nobody_started_on_is_still_collected() {
3494 let mut f = Fixture::new();
3495 assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
3496 let val = vec![b'v'; 1024];
3497 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3498
3499 for k in &keys {
3500 f.run(&[b"SET", k, &val]);
3501 }
3502 while f.server.compact_step().is_some() {}
3503 assert!(
3504 !f.server.mine().wanted(9),
3505 "database nine was drained and should not be asked again until it is written to"
3506 );
3507 let after_first = f.server.memory_bytes();
3508
3509 for _ in 0..500 {
3510 for k in &keys {
3511 f.run(&[b"SET", k, &val]);
3512 }
3513 f.server.compact_step();
3514 }
3515
3516 assert!(
3517 f.server.memory_bytes() <= after_first * 2,
3518 "held {} after five hundred passes against {after_first} after one",
3519 f.server.memory_bytes()
3520 );
3521 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3522 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3523 f.run(&[b"SELECT", b"0"]);
3525 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3526 }
3527
3528 #[test]
3529 fn a_command_goes_from_bytes_to_bytes() {
3530 let mut f = Fixture::new();
3531 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3532 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
3533 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3534 assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
3535 assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
3537 assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
3538 }
3539
3540 #[test]
3541 fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
3542 let mut f = Fixture::new();
3543 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
3544 assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
3547 assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
3548 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3549 assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
3551 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3552 }
3553
3554 #[test]
3555 fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
3556 let mut f = Fixture::new();
3557 f.run(&[b"SET", b"k", b"v"]);
3558 assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
3561 assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
3562 }
3563
3564 #[test]
3565 fn touch_counts_the_way_exists_counts() {
3566 let mut f = Fixture::new();
3567 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3568 assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
3569 assert_eq!(
3570 f.run(&[b"TOUCH", b"a", b"a"]),
3571 ":2\r\n",
3572 "twice counts twice"
3573 );
3574 assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
3575 assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
3576 }
3577
3578 #[test]
3579 fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
3580 let mut f = Fixture::new();
3581 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3582 f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
3583
3584 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
3585 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3586 assert_eq!(
3587 f.run(&[b"TTL", b"b"]),
3588 ":100\r\n",
3589 "the source's and not b's"
3590 );
3591 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3592 }
3593
3594 #[test]
3595 fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
3596 let mut f = Fixture::new();
3597 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
3598 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
3601 }
3602
3603 #[test]
3604 fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
3605 let mut f = Fixture::new();
3606 f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
3607
3608 assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
3609 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3610 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
3613 assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
3614 assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
3615 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
3616 }
3617
3618 #[test]
3619 fn renaming_a_set_does_not_touch_a_member() {
3620 let mut f = Fixture::new();
3621 for i in 0..300 {
3622 f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
3623 }
3624 let before = f.server.memory_bytes();
3625
3626 assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
3627 assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
3628 assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
3629 assert!(
3630 f.server.memory_bytes().abs_diff(before) < 256,
3631 "the members were copied: {} against {before}",
3632 f.server.memory_bytes()
3633 );
3634 }
3635
3636 #[test]
3637 fn a_copy_is_a_second_value_and_not_a_second_name() {
3638 let mut f = Fixture::new();
3639 f.run(&[b"SADD", b"s", b"m1", b"m2"]);
3640
3641 assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
3642 f.run(&[b"SADD", b"t", b"m3"]);
3643 assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
3644 assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
3645 }
3646
3647 #[test]
3657 fn every_type_can_be_copied() {
3658 let mut f = Fixture::new();
3659 f.run(&[b"SET", b"str", b"v1"]);
3660 f.run(&[b"SADD", b"set", b"m1"]);
3661 f.run(&[b"HSET", b"hash", b"f", b"v"]);
3662 f.run(&[b"RPUSH", b"list", b"a", b"b"]);
3663 f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
3664
3665 for name in [
3666 &b"str"[..],
3667 &b"set"[..],
3668 &b"hash"[..],
3669 &b"list"[..],
3670 &b"zset"[..],
3671 ] {
3672 let dst = [name, b":copy"].concat();
3673 assert_eq!(
3674 f.run(&[b"COPY", name, &dst]),
3675 ":1\r\n",
3676 "copying {}",
3677 String::from_utf8_lossy(name)
3678 );
3679 assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
3680 }
3681
3682 assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
3683 let mut want = String::from("*2\r\n");
3684 want.push_str("$1\r\na\r\n$1\r\nb\r\n");
3685 want
3686 });
3687 assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
3688
3689 f.run(&[b"RPUSH", b"list:copy", b"c"]);
3691 assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
3692 assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
3693 }
3694
3695 #[test]
3696 fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
3697 let mut f = Fixture::new();
3698 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3699 f.run(&[b"SET", b"b", b"v2"]);
3700
3701 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
3702 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3703 assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
3704 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3705 assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
3706 assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
3707 }
3708
3709 #[test]
3710 fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
3711 let mut f = Fixture::new();
3712 f.run(&[b"SET", b"a", b"v1"]);
3713
3714 assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
3717 f.run(&[b"SELECT", b"1"]);
3718 assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
3719 assert_eq!(
3720 f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
3721 ":0\r\n",
3722 "taken"
3723 );
3724 assert_eq!(
3725 f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
3726 ":1\r\n"
3727 );
3728 }
3729
3730 #[test]
3731 fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
3732 let mut f = Fixture::new();
3733 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3734 assert_eq!(
3735 f.run(&[b"SORT", b"l"]),
3736 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3737 );
3738 assert_eq!(
3740 f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
3741 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3742 );
3743 assert_eq!(
3744 f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
3745 "*1\r\n$1\r\n2\r\n"
3746 );
3747 }
3748
3749 #[test]
3750 fn sort_reads_a_key_per_element_for_by_and_for_get() {
3751 let mut f = Fixture::new();
3752 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
3753 f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
3754 assert_eq!(
3757 f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
3758 "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
3759 );
3760 }
3761
3762 #[test]
3763 fn sort_store_writes_a_list_and_answers_its_length() {
3764 let mut f = Fixture::new();
3765 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3766 assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
3767 assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
3768 assert_eq!(
3769 f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
3770 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3771 );
3772 assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
3775 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
3776 }
3777
3778 #[test]
3779 fn sort_ro_does_not_know_the_word_store() {
3780 let mut f = Fixture::new();
3781 f.run(&[b"RPUSH", b"l", b"2", b"1"]);
3782 assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
3783 assert_eq!(
3784 f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
3785 "-ERR syntax error\r\n"
3786 );
3787 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3788 }
3789
3790 #[test]
3791 fn sort_refuses_what_it_cannot_sort() {
3792 let mut f = Fixture::new();
3793 assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
3794 f.run(&[b"SET", b"s", b"x"]);
3795 assert_eq!(
3796 f.run(&[b"SORT", b"s"]),
3797 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
3798 );
3799 f.run(&[b"RPUSH", b"words", b"one", b"two"]);
3800 assert_eq!(
3801 f.run(&[b"SORT", b"words"]),
3802 "-ERR One or more scores can't be converted into double\r\n"
3803 );
3804 assert_eq!(
3805 f.run(&[b"SORT", b"words", b"ALPHA"]),
3806 "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
3807 );
3808 assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
3809 }
3810
3811 #[test]
3812 fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
3813 let mut f = Fixture::new();
3814 assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
3815 assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
3816 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3817 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3818 assert_eq!(
3819 f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
3820 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3821 );
3822 assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
3825 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3826 }
3827
3828 #[test]
3829 fn move_answers_zero_when_either_end_says_no() {
3830 let mut f = Fixture::new();
3831 assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
3832 assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
3833 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3834 assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
3835 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3836 assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
3839 assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
3840 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3841 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
3842 }
3843
3844 #[test]
3845 fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
3846 let mut f = Fixture::new();
3847 assert_eq!(
3848 f.run(&[b"MOVE", b"a", b"0"]),
3849 "-ERR source and destination objects are the same\r\n"
3850 );
3851 assert_eq!(
3852 f.run(&[b"MOVE", b"a", b"99"]),
3853 "-ERR DB index is out of range\r\n"
3854 );
3855 assert_eq!(
3856 f.run(&[b"MOVE", b"a", b"-1"]),
3857 "-ERR DB index is out of range\r\n"
3858 );
3859 assert_eq!(
3860 f.run(&[b"MOVE", b"a", b"x"]),
3861 "-ERR value is not an integer or out of range\r\n"
3862 );
3863 }
3864
3865 #[test]
3866 fn swapdb_swaps_what_two_connections_would_see() {
3867 let mut f = Fixture::new();
3868 assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
3869 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3870 assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
3871 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3872
3873 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3874 assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
3876 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3877 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3878 assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
3880 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3881 }
3882
3883 #[test]
3888 fn a_wide_server_puts_its_databases_on_its_own_clock() {
3889 let mut f = Fixture::striped(8);
3890 f.server.set_clock_ms(1_700_000_000_000);
3891 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
3892 assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
3893 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3894 f.server.set_clock_ms(1_700_000_050_000);
3895 assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
3896 }
3897
3898 #[test]
3903 fn swapdb_swaps_every_stripe_of_a_wide_database() {
3904 let mut f = Fixture::striped(4);
3905 for i in 0..16u32 {
3906 let key = format!("k{i}");
3907 assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
3908 }
3909 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3910 assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
3911 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3912
3913 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3914 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3915 assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
3916 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3917 assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
3918 for i in 0..16u32 {
3919 let key = format!("k{i}");
3920 assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
3921 }
3922 }
3923
3924 #[test]
3925 fn swapdb_says_which_index_it_could_not_read() {
3926 let mut f = Fixture::new();
3927 assert_eq!(
3928 f.run(&[b"SWAPDB", b"x", b"1"]),
3929 "-ERR invalid first DB index\r\n"
3930 );
3931 assert_eq!(
3932 f.run(&[b"SWAPDB", b"0", b"y"]),
3933 "-ERR invalid second DB index\r\n"
3934 );
3935 assert_eq!(
3939 f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
3940 "-ERR invalid first DB index\r\n"
3941 );
3942 assert_eq!(
3943 f.run(&[b"SWAPDB", b"0", b"99"]),
3944 "-ERR DB index is out of range\r\n"
3945 );
3946 assert_eq!(
3947 f.run(&[b"SWAPDB", b"-1", b"0"]),
3948 "-ERR DB index is out of range\r\n"
3949 );
3950 }
3951
3952 #[test]
3953 fn wait_answers_zero_replicas_without_waiting() {
3954 let mut f = Fixture::new();
3955 assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
3956 assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
3957 assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
3960 assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
3963 assert_eq!(
3964 f.run(&[b"WAIT", b"x", b"0"]),
3965 "-ERR value is not an integer or out of range\r\n"
3966 );
3967 assert_eq!(
3968 f.run(&[b"WAIT", b"0", b"-1"]),
3969 "-ERR timeout is negative\r\n"
3970 );
3971 assert_eq!(
3972 f.run(&[b"WAIT", b"0", b"1.5"]),
3973 "-ERR timeout is not an integer or out of range\r\n"
3974 );
3975 }
3976
3977 #[test]
3978 fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
3979 let mut f = Fixture::new();
3980 assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
3981 assert_eq!(
3982 f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
3983 "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
3984 );
3985 assert_eq!(
3986 f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
3987 "-ERR value is out of range, value must between 0 and 1\r\n"
3988 );
3989 assert_eq!(
3990 f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
3991 "-ERR value is out of range, must be positive\r\n"
3992 );
3993 assert_eq!(
3996 f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
3997 "-ERR timeout is negative\r\n"
3998 );
3999 }
4000
4001 fn payload(reply: &[u8]) -> Vec<u8> {
4005 let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
4006 reply[head + 2..reply.len() - 2].to_vec()
4007 }
4008
4009 #[test]
4010 fn a_value_survives_a_dump_and_a_restore() {
4011 let mut f = Fixture::new();
4012 f.run(&[b"SET", b"s", b"hello"]);
4013 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
4014 f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
4015 f.run(&[b"SADD", b"u", b"x", b"y"]);
4016 f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
4017 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
4018
4019 for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
4020 let mut copy = key.to_vec();
4021 copy.push(b'2');
4022 let bytes = payload(&f.raw(&[b"DUMP", key]));
4023 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
4024 assert_eq!(f.run(&[b"TYPE", ©]), f.run(&[b"TYPE", key]));
4025 }
4026
4027 assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
4028 assert_eq!(
4029 f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
4030 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4031 );
4032 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
4033 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
4034 assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
4035 assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
4036 assert_eq!(
4039 f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
4040 f.run(&[b"OBJECT", b"ENCODING", b"t"])
4041 );
4042 }
4043
4044 #[test]
4045 fn a_dumped_hash_keeps_its_field_deadlines() {
4046 let mut f = Fixture::new();
4047 f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
4048 assert_eq!(
4049 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
4050 "*1\r\n:1\r\n"
4051 );
4052 let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
4053 assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
4054 assert_eq!(
4055 f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
4056 "*2\r\n:-1\r\n:100\r\n"
4057 );
4058 }
4059
4060 #[test]
4061 fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
4062 let mut f = Fixture::new();
4063 f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
4064 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
4065 assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
4066 assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
4067 assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
4068 assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
4069 assert_eq!(
4072 f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
4073 "+OK\r\n"
4074 );
4075 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
4076 }
4077
4078 #[test]
4079 fn dump_answers_nothing_for_a_key_that_is_not_there() {
4080 let mut f = Fixture::new();
4081 assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
4082 f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
4083 f.advance(50);
4084 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
4085 }
4086
4087 #[test]
4088 fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
4089 let mut f = Fixture::new();
4090 f.run(&[b"SET", b"a", b"first"]);
4091 f.run(&[b"SET", b"b", b"second"]);
4092 let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
4093 assert_eq!(
4094 f.run(&[b"RESTORE", b"a", b"0", &bytes]),
4095 "-BUSYKEY Target key name already exists.\r\n"
4096 );
4097 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
4098 assert_eq!(
4099 f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
4100 "+OK\r\n"
4101 );
4102 assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
4103 }
4104
4105 #[test]
4109 fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
4110 let mut f = Fixture::new();
4111 f.run(&[b"SET", b"a", b"v"]);
4112 assert_eq!(
4113 f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
4114 "-BUSYKEY Target key name already exists.\r\n"
4115 );
4116 assert_eq!(
4119 f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
4120 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
4121 );
4122 }
4123
4124 #[test]
4125 fn restore_can_tell_a_bad_footer_from_bad_bytes() {
4126 let mut f = Fixture::new();
4127 f.run(&[b"SET", b"a", b"hello"]);
4128 let good = payload(&f.raw(&[b"DUMP", b"a"]));
4129
4130 let mut flipped = good.clone();
4131 flipped[2] ^= 0x40;
4132 assert_eq!(
4133 f.run(&[b"RESTORE", b"b", b"0", &flipped]),
4134 "-ERR DUMP payload version or checksum are wrong\r\n"
4135 );
4136 assert_eq!(
4137 f.run(&[b"RESTORE", b"b", b"0", b"short"]),
4138 "-ERR DUMP payload version or checksum are wrong\r\n"
4139 );
4140 let mut truncated = good[..1].to_vec();
4144 truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
4145 let crc = yo_common::crc::crc64(0, &truncated);
4146 truncated.extend_from_slice(&crc.to_le_bytes());
4147 assert_eq!(
4148 f.run(&[b"RESTORE", b"b", b"0", &truncated]),
4149 "-ERR Bad data format\r\n"
4150 );
4151 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
4152 }
4153
4154 #[test]
4155 fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
4156 let mut f = Fixture::new();
4157 f.run(&[b"SET", b"a", b"v"]);
4158 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
4159 assert_eq!(
4160 f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
4161 "-ERR Invalid TTL value, must be >= 0\r\n"
4162 );
4163 assert_eq!(
4164 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
4165 "-ERR Invalid IDLETIME value, must be >= 0\r\n"
4166 );
4167 assert_eq!(
4168 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
4169 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
4170 );
4171 assert_eq!(
4173 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
4174 "+OK\r\n"
4175 );
4176 assert_eq!(
4177 f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
4178 "+OK\r\n"
4179 );
4180 }
4181
4182 #[test]
4186 fn restore_takes_idletime_or_freq_and_not_both() {
4187 let mut f = Fixture::new();
4188 f.run(&[b"SET", b"a", b"v"]);
4189 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
4190 assert_eq!(
4191 f.run(&[
4192 b"RESTORE",
4193 b"b",
4194 b"0",
4195 &bytes,
4196 b"IDLETIME",
4197 b"1",
4198 b"FREQ",
4199 b"2"
4200 ]),
4201 "-ERR syntax error\r\n"
4202 );
4203 assert_eq!(
4204 f.run(&[
4205 b"RESTORE",
4206 b"b",
4207 b"0",
4208 &bytes,
4209 b"FREQ",
4210 b"2",
4211 b"IDLETIME",
4212 b"1"
4213 ]),
4214 "-ERR syntax error\r\n"
4215 );
4216 assert_eq!(
4217 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
4218 "-ERR syntax error\r\n"
4219 );
4220 assert_eq!(
4221 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
4222 "-ERR syntax error\r\n"
4223 );
4224 }
4225
4226 #[test]
4227 fn copy_checks_its_options_before_it_looks_for_anything() {
4228 let mut f = Fixture::new();
4229 assert_eq!(
4232 f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
4233 "-ERR DB index is out of range\r\n"
4234 );
4235 assert_eq!(
4236 f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
4237 "-ERR DB index is out of range\r\n"
4238 );
4239 assert_eq!(
4240 f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
4241 "-ERR value is not an integer or out of range\r\n"
4242 );
4243 assert_eq!(
4244 f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
4245 "-ERR syntax error\r\n"
4246 );
4247 assert_eq!(
4248 f.run(&[b"COPY", b"a", b"a"]),
4249 "-ERR source and destination objects are the same\r\n"
4250 );
4251 assert_eq!(
4253 f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
4254 ":0\r\n"
4255 );
4256 }
4257
4258 #[test]
4259 fn time_is_two_bulk_strings_and_moves() {
4260 let mut f = Fixture::new();
4261 let first = f.run(&[b"TIME"]);
4262 assert!(first.starts_with("*2\r\n$"), "got {first}");
4263 let parts: Vec<&str> = first.split("\r\n").collect();
4264 let secs: i64 = parts[2].parse().expect("seconds as decimal text");
4265 let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
4266 assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
4267 assert!((0..1_000_000).contains(µs), "got {micros}");
4268 assert_ne!(first, f.run(&[b"TIME"]));
4272 }
4273
4274 #[test]
4275 fn a_keyspace_scan_walks_every_key_once() {
4276 let n = if cfg!(miri) { 96 } else { 500 };
4280 let mut f = Fixture::new();
4281 for i in 0..n {
4282 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
4283 }
4284
4285 let mut seen: Vec<String> = Vec::new();
4286 let mut cursor = "0".to_owned();
4287 let mut calls = 0;
4288 loop {
4289 let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
4290 seen.extend(keys);
4291 cursor = next;
4292 calls += 1;
4293 assert!(calls < 10_000, "the cursor is not advancing");
4294 if cursor == "0" {
4295 break;
4296 }
4297 }
4298
4299 seen.sort();
4300 seen.dedup();
4301 assert_eq!(seen.len(), n, "every key once and only once");
4302 assert!(calls > 1, "{n} keys came back in one batch");
4305 }
4306
4307 #[test]
4308 fn a_scan_narrows_by_pattern_and_by_type() {
4309 let mut f = Fixture::new();
4310 f.run(&[b"SET", b"str", b"v"]);
4311 f.run(&[b"SADD", b"members", b"a"]);
4312 f.run(&[b"HSET", b"fields", b"f", b"v"]);
4313
4314 let all = |f: &mut Fixture, args: &[&[u8]]| {
4315 let mut out: Vec<String> = Vec::new();
4316 let mut cursor = "0".to_owned();
4317 loop {
4318 let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
4319 line.extend_from_slice(args);
4320 let (next, keys) = scan_reply(&f.run(&line));
4321 out.extend(keys);
4322 cursor = next;
4323 if cursor == "0" {
4324 break;
4325 }
4326 }
4327 out.sort();
4328 out
4329 };
4330
4331 assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
4332 assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
4333 assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
4334 assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
4336 assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
4338 assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
4339 assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
4341 }
4342
4343 #[test]
4344 fn a_scan_says_what_is_wrong_with_it() {
4345 let mut f = Fixture::new();
4346 assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
4347 assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
4348 assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
4349 assert_eq!(
4350 f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
4351 "-ERR syntax error\r\n"
4352 );
4353 assert_eq!(
4354 f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
4355 "-ERR value is not an integer or out of range\r\n"
4356 );
4357 assert_eq!(
4358 f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
4359 "-ERR syntax error\r\n"
4360 );
4361 assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
4366 }
4367
4368 #[test]
4369 fn keys_and_randomkey_look_at_the_whole_database() {
4370 let mut f = Fixture::new();
4371 assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
4372 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
4373
4374 for name in ["one", "two", "three"] {
4375 f.run(&[b"SET", name.as_bytes(), b"v"]);
4376 }
4377 assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
4378 assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
4379 assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
4380
4381 for _ in 0..50 {
4382 let got = f.run(&[b"RANDOMKEY"]);
4383 assert!(
4384 ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
4385 "got {got}"
4386 );
4387 }
4388 }
4389
4390 #[test]
4391 fn a_walk_does_not_answer_keys_that_have_expired() {
4392 let mut f = Fixture::new();
4393 f.run(&[b"SET", b"alive", b"v"]);
4394 f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
4395 f.server.advance_clock_ms(2);
4396 assert_eq!(
4397 f.run(&[b"DBSIZE"]),
4398 ":2\r\n",
4399 "nothing has collected it yet"
4400 );
4401
4402 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
4403 let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
4404 assert_eq!(keys, ["alive"]);
4405 for _ in 0..20 {
4406 assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
4407 }
4408 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4411 }
4412
4413 #[test]
4414 fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
4415 let mut f = Fixture::new();
4416 f.run(&[b"SET", b"k", b"v"]);
4417 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
4418 assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
4419
4420 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
4421 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4422 let ms = int(&f.run(&[b"PTTL", b"k"]));
4423 assert!((99_000..=100_000).contains(&ms), "got {ms}");
4424
4425 let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
4427 let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4428 assert_eq!(at, (at_ms + 500) / 1000);
4429 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
4430
4431 assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
4432 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
4433 assert_eq!(
4434 f.run(&[b"PERSIST", b"k"]),
4435 ":0\r\n",
4436 "nothing to take off the second time"
4437 );
4438 assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
4439 assert_eq!(
4440 f.run(&[b"GET", b"k"]),
4441 "$1\r\nv\r\n",
4442 "and the value went through all of that untouched"
4443 );
4444 }
4445
4446 #[test]
4447 fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
4448 let mut f = Fixture::new();
4449 f.run(&[b"SET", b"str", b"v"]);
4450 f.run(&[b"SADD", b"set", b"a", b"b"]);
4451 f.run(&[b"HSET", b"hash", b"f", b"v"]);
4452
4453 for key in [b"str".as_slice(), b"set", b"hash"] {
4454 assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
4455 assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
4456 }
4457 assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
4460 assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
4461 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
4462 }
4463
4464 #[test]
4465 fn a_deadline_that_has_already_gone_deletes_the_key_now() {
4466 let mut f = Fixture::new();
4467 for key in [b"a".as_slice(), b"b", b"c", b"d"] {
4468 f.run(&[b"SET", key, b"v"]);
4469 }
4470 assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
4474 assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
4475 assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
4476 assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
4477 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4478 assert_eq!(
4479 f.run(&[b"EXPIRE", b"a", b"100"]),
4480 ":0\r\n",
4481 "and the key really went, so there is nothing to put a deadline on"
4482 );
4483 }
4484
4485 #[test]
4486 fn the_four_conditions_decide_whether_the_deadline_moves() {
4487 let mut f = Fixture::new();
4488 f.run(&[b"SET", b"k", b"v"]);
4489
4490 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
4491 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
4492 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
4493 assert_eq!(
4494 f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
4495 ":1\r\n",
4496 "no deadline reads as infinitely far away, so LT passes where GT fails"
4497 );
4498
4499 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
4500 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
4501 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4502 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
4503 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
4504 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4505
4506 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
4509 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
4510 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
4511 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
4512 }
4513
4514 #[test]
4515 fn the_conditions_are_a_set_and_not_a_keyword() {
4516 let mut f = Fixture::new();
4517 f.run(&[b"SET", b"k", b"v"]);
4518
4519 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
4520 assert_eq!(
4521 f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
4522 ":0\r\n",
4523 "the same keyword twice means it once, and NX now has a deadline to fail on"
4524 );
4525
4526 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
4529 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4530 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
4531 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
4532 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4533 f.run(&[b"PERSIST", b"k"]);
4534 assert_eq!(
4535 f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
4536 ":0\r\n",
4537 "where LT on its own would have taken it"
4538 );
4539 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
4540 }
4541
4542 #[test]
4543 fn a_key_is_gone_once_its_moment_passes() {
4544 let mut f = Fixture::new();
4545 f.run(&[b"SET", b"k", b"v"]);
4546 f.run(&[b"EXPIRE", b"k", b"100"]);
4547
4548 let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4549 f.server.set_clock_ms(at as u64 + 1);
4550 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4551 assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
4552 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
4553 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4554 }
4555
4556 #[test]
4557 fn the_expiry_commands_refuse_what_a_real_server_refuses() {
4558 let mut f = Fixture::new();
4559 f.run(&[b"SET", b"k", b"v"]);
4560 for (bad, want) in [
4561 (
4562 &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
4563 "-ERR value is not an integer or out of range\r\n",
4564 ),
4565 (
4566 &[b"EXPIRE", b"k", b"100", b"MAYBE"],
4567 "-ERR Unsupported option MAYBE\r\n",
4568 ),
4569 (
4570 &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
4571 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4572 ),
4573 (
4574 &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
4575 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4576 ),
4577 (
4578 &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
4579 "-ERR GT and LT options at the same time are not compatible\r\n",
4580 ),
4581 (
4584 &[b"EXPIRE", b"k", b"9223372036854775807"],
4585 "-ERR invalid expire time in 'expire' command\r\n",
4586 ),
4587 (
4588 &[b"EXPIREAT", b"k", b"9223372036854775807"],
4589 "-ERR invalid expire time in 'expireat' command\r\n",
4590 ),
4591 (
4592 &[b"PEXPIRE", b"k", b"9223372036854775807"],
4593 "-ERR invalid expire time in 'pexpire' command\r\n",
4594 ),
4595 ] {
4596 assert_eq!(f.run(bad), want, "for {bad:?}");
4597 }
4598 assert_eq!(
4599 f.run(&[b"TTL", b"k"]),
4600 ":-1\r\n",
4601 "and none of those put a deadline on anything"
4602 );
4603
4604 assert_eq!(
4608 f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
4609 ":1\r\n"
4610 );
4611 assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
4612 }
4613
4614 #[test]
4615 fn flushing_empties_this_database_or_every_one_of_them() {
4616 let mut f = Fixture::new();
4617 f.run(&[b"SELECT", b"0"]);
4618 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4619 f.run(&[b"SELECT", b"1"]);
4620 f.run(&[b"SET", b"c", b"3"]);
4621 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4622 assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
4625 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4626 f.run(&[b"SELECT", b"0"]);
4628 assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
4629 assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
4630 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4631 f.run(&[b"SELECT", b"1"]);
4632 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4633 assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
4636 assert_eq!(
4637 f.run(&[b"FLUSHDB", b"sync", b"sync"]),
4638 "-ERR syntax error\r\n"
4639 );
4640 }
4641
4642 #[test]
4643 fn the_script_cache_and_the_library_set_answer_for_being_empty() {
4644 let mut f = Fixture::new();
4645 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4646 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
4647 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
4648 assert_eq!(
4651 f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
4652 "*2\r\n:0\r\n:0\r\n"
4653 );
4654 assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
4655 assert_eq!(
4656 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
4657 "*0\r\n"
4658 );
4659 assert_eq!(
4660 f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
4661 "-ERR Library not found\r\n"
4662 );
4663
4664 assert_eq!(
4667 f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
4668 "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
4669 );
4670 assert_eq!(
4671 f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
4672 "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
4673 );
4674 assert_eq!(
4679 f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
4680 "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
4681 );
4682 assert_eq!(
4683 f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
4684 "-ERR Unknown argument bogus\r\n"
4685 );
4686 assert_eq!(
4687 f.run(&[b"SCRIPT", b"EXISTS"]),
4688 "-ERR wrong number of arguments for 'script|exists' command\r\n"
4689 );
4690
4691 assert_eq!(
4692 f.run(&[b"FUNCTION", b"NOPE"]),
4693 "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
4694 );
4695 }
4696
4697 #[test]
4698 fn the_script_cache_holds_what_was_loaded_into_it() {
4699 let mut f = Fixture::new();
4700 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4703 assert_eq!(
4704 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4705 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4706 );
4707 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4708 assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
4709 assert_eq!(
4712 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4713 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4714 );
4715 assert!(
4716 f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
4717 .starts_with("-ERR Error compiling script"),
4718 );
4719
4720 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4721 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
4722 assert_eq!(
4723 f.run(&[b"EVALSHA", sha, b"0"]),
4724 "-NOSCRIPT No matching script. Please use EVAL.\r\n"
4725 );
4726
4727 assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
4730 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4731
4732 assert_eq!(
4735 f.run(&[b"SCRIPT", b"KILL"]),
4736 "-NOTBUSY No scripts in execution right now.\r\n"
4737 );
4738 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
4739 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
4740 assert_eq!(
4741 f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
4742 "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
4743 );
4744 }
4745
4746 #[test]
4747 fn eval_counts_its_keys_before_it_compiles_anything() {
4748 let mut f = Fixture::new();
4749 assert_eq!(
4750 f.run(&[b"EVAL", b"return 1"]),
4751 "-ERR wrong number of arguments for 'eval' command\r\n"
4752 );
4753 assert_eq!(
4754 f.run(&[b"EVAL", b"return 1", b"abc"]),
4755 "-ERR value is not an integer or out of range\r\n"
4756 );
4757 assert_eq!(
4758 f.run(&[b"EVAL", b"return 1", b"-1"]),
4759 "-ERR Number of keys can't be negative\r\n"
4760 );
4761 assert_eq!(
4762 f.run(&[b"EVAL", b"return 1", b"1"]),
4763 "-ERR Number of keys can't be greater than number of args\r\n"
4764 );
4765 assert_eq!(
4767 f.run(&[
4768 b"EVAL",
4769 b"return {KEYS[1],KEYS[2],ARGV[1]}",
4770 b"2",
4771 b"a",
4772 b"b",
4773 b"c"
4774 ]),
4775 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4776 );
4777 assert_eq!(
4778 f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
4779 ":0\r\n"
4780 );
4781 assert_eq!(
4782 f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
4783 ":2\r\n"
4784 );
4785 }
4786
4787 #[test]
4788 fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
4789 let mut f = Fixture::new();
4790 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4791
4792 assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
4795 assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
4796 assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
4797 assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
4798 assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
4799 assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
4800 assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
4801
4802 assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
4803 assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
4804 assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
4806 assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
4807 assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
4808 assert_eq!(eval(&mut f, b""), "$-1\r\n");
4809
4810 assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
4813 assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
4814 assert_eq!(
4815 eval(&mut f, b"return {1,'a',{2}}"),
4816 "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
4817 );
4818
4819 assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
4821 assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
4822 assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
4823 assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
4824 assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a b\r\n");
4827 assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
4830 assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
4831 }
4832
4833 #[test]
4834 fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
4835 let mut f = Fixture::new();
4836 assert_eq!(
4839 f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
4840 "$3\r\n3.5\r\n"
4841 );
4842 assert_eq!(
4843 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4844 "$3\r\n123\r\n"
4845 );
4846 assert_eq!(
4847 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4848 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4849 );
4850 assert_eq!(
4851 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4852 "*1\r\n$1\r\na\r\n"
4853 );
4854 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
4855
4856 f.out = Out::new(Proto::Resp3);
4857 assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
4858 assert_eq!(
4859 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4860 "(123\r\n"
4861 );
4862 assert_eq!(
4863 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4864 "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
4865 );
4866 assert_eq!(
4867 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4868 "~1\r\n$1\r\na\r\n"
4869 );
4870 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
4871 }
4872
4873 #[test]
4874 fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
4875 let mut f = Fixture::new();
4876 f.run(&[b"SET", b"s", b"hello"]);
4877 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4878 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4879
4880 assert_eq!(
4881 eval(&mut f, b"return type(redis.call('get','s'))"),
4882 "$6\r\nstring\r\n"
4883 );
4884 assert_eq!(
4885 eval(&mut f, b"return type(redis.call('llen','l'))"),
4886 "$6\r\nnumber\r\n"
4887 );
4888 assert_eq!(
4889 eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
4890 "$5\r\ntable\r\n"
4891 );
4892 assert_eq!(
4895 eval(&mut f, b"return redis.call('set','s','v')['ok']"),
4896 "$2\r\nOK\r\n"
4897 );
4898 assert_eq!(
4901 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4902 "$5\r\nfalse\r\n"
4903 );
4904 assert_eq!(
4905 eval(
4906 &mut f,
4907 b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
4908 ),
4909 "$3\r\nnil\r\n"
4910 );
4911 assert_eq!(
4913 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4914 "$5\r\nfalse\r\n"
4915 );
4916 }
4917
4918 #[test]
4919 fn an_error_from_a_script_names_the_line_it_came_from() {
4920 let mut f = Fixture::new();
4921 assert_eq!(
4924 f.run(&[b"EVAL", b"error('boom')", b"0"]),
4925 "-ERR user_script:1: boom script: \
4926 82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
4927 );
4928 assert_eq!(
4930 f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
4931 "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
4932 );
4933 assert!(
4935 f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
4936 .starts_with("-structured script: "),
4937 );
4938 assert_eq!(
4941 f.run(&[b"EVAL", b"return this is not lua", b"0"]),
4942 "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
4943 );
4944
4945 assert_eq!(
4948 f.run(&[
4949 b"EVAL",
4950 b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
4951 b"0"
4952 ]),
4953 "$8\r\nstring:z\r\n"
4954 );
4955 assert_eq!(
4956 f.run(&[
4957 b"EVAL",
4958 b"local a, b = pcall(function() error({a=1}) end) return type(b)",
4959 b"0"
4960 ]),
4961 "$5\r\ntable\r\n"
4962 );
4963 }
4964
4965 #[test]
4966 fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
4967 let mut f = Fixture::new();
4968 let sentence = |f: &mut Fixture, body: &[u8]| {
4969 let reply = f.run(&[b"EVAL", body, b"0"]);
4970 reply.split(" script: ").next().unwrap().to_owned()
4971 };
4972
4973 assert_eq!(
4974 sentence(&mut f, b"return redis.call()"),
4975 "-ERR Please specify at least one argument for this redis lib call"
4976 );
4977 assert_eq!(
4978 sentence(&mut f, b"return redis.call('get', {})"),
4979 "-ERR Lua redis lib command arguments must be strings or integers"
4980 );
4981 assert_eq!(
4982 sentence(&mut f, b"return redis.call('nosuchcmd')"),
4983 "-ERR Unknown Redis command called from script"
4984 );
4985 assert_eq!(
4986 sentence(&mut f, b"return redis.call('get')"),
4987 "-ERR Wrong number of args calling Redis command from script"
4988 );
4989 for name in [
4993 &b"return redis.call('multi')"[..],
4994 b"return redis.call('exec')",
4995 b"return redis.call('watch','k')",
4996 b"return redis.call('subscribe','c')",
4997 b"return redis.call('debug','jmap')",
4998 b"return redis.call('eval','return 1',0)",
4999 b"return redis.call('config','get','maxmemory')",
5000 ] {
5001 assert_eq!(
5002 sentence(&mut f, name),
5003 "-ERR This Redis command is not allowed from script",
5004 "for {}",
5005 String::from_utf8_lossy(name)
5006 );
5007 }
5008 assert!(
5011 f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
5012 .starts_with('*'),
5013 );
5014
5015 assert_eq!(
5018 f.run(&[
5019 b"EVAL",
5020 b"local x = redis.pcall('nosuchcmd') return x.err",
5021 b"0"
5022 ]),
5023 "$44\r\nERR Unknown Redis command called from script\r\n"
5024 );
5025 assert_eq!(
5027 f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
5028 "-ERR Unknown Redis command called from script\r\n"
5029 );
5030 }
5031
5032 #[test]
5033 fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
5034 let mut f = Fixture::new();
5035 f.run(&[b"SET", b"k", b"v"]);
5036 assert_eq!(
5037 f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
5038 "$1\r\nv\r\n"
5039 );
5040 assert!(
5041 f.run(&[
5042 b"EVAL_RO",
5043 b"return redis.call('set', KEYS[1], 'x')",
5044 b"1",
5045 b"k"
5046 ])
5047 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
5048 );
5049 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
5051 assert_eq!(
5052 f.run(&[
5053 b"EVAL",
5054 b"return redis.call('set', KEYS[1], 'x')",
5055 b"1",
5056 b"k"
5057 ]),
5058 "+OK\r\n"
5059 );
5060 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
5061
5062 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
5064 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
5065 assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
5066 }
5067
5068 #[test]
5069 fn a_script_cannot_leave_anything_behind_for_the_next_one() {
5070 let mut f = Fixture::new();
5071 for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
5074 let reply = f.run(&[b"EVAL", body, b"0"]);
5075 assert!(
5076 reply
5077 .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
5078 "{body:?} gave {reply}",
5079 );
5080 }
5081 for body in [
5084 &b"rawset(redis, 'call', 1)"[..],
5085 b"rawset(_G, 'zz', 1)",
5086 b"setmetatable(_G, {})",
5087 b"setmetatable(redis, {})",
5088 ] {
5089 let reply = f.run(&[b"EVAL", body, b"0"]);
5090 assert!(
5091 reply.starts_with("-ERR Attempt to modify a readonly table script: "),
5092 "{body:?} gave {reply}",
5093 );
5094 }
5095 assert!(
5098 f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
5099 .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
5100 );
5101 assert_eq!(
5104 f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
5105 "$3\r\nnil\r\n"
5106 );
5107
5108 assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
5111 assert_eq!(
5112 f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
5113 "$8\r\nfunction\r\n"
5114 );
5115 assert_eq!(
5116 f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
5117 "$8\r\nfunction\r\n"
5118 );
5119 }
5120
5121 #[test]
5122 fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
5123 let mut f = Fixture::new();
5124 assert_eq!(
5128 f.run(&[
5129 b"EVAL",
5130 b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
5131 b"0",
5132 ]),
5133 ":23\r\n"
5134 );
5135 assert_eq!(
5136 f.run(&[
5137 b"EVAL",
5138 b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
5139 table.sort(t) return table.concat(t, ' ')",
5140 b"0",
5141 ]),
5142 "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
5143 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
5144 acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
5145 set_repl setresp sha1hex status_reply\r\n"
5146 );
5147 assert_eq!(
5150 f.run(&[
5151 b"EVAL",
5152 b"local n = 0 for k, v in pairs(redis) do \
5153 if type(v) == 'function' then n = n + 1 end end return n",
5154 b"0",
5155 ]),
5156 ":12\r\n"
5157 );
5158 assert_eq!(
5160 f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
5161 "$6\r\nstring\r\n"
5162 );
5163 assert_eq!(
5164 f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
5165 "$8\r\nfunction\r\n"
5166 );
5167 assert_eq!(
5168 f.run(&[
5169 b"EVAL",
5170 b"return tostring(rawget(redis, 'nosuchfield'))",
5171 b"0",
5172 ]),
5173 "$3\r\nnil\r\n"
5174 );
5175 for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
5178 assert!(
5179 f.run(&[b"EVAL", body, b"0"])
5180 .contains("Attempt to modify a readonly table script: "),
5181 "{body:?}",
5182 );
5183 }
5184 assert_eq!(
5187 f.run(&[
5188 b"EVAL",
5189 b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
5190 b"0",
5191 ]),
5192 ":2\r\n"
5193 );
5194 assert_eq!(
5195 f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
5196 "$3\r\nnil\r\n"
5197 );
5198 assert_eq!(
5199 f.run(&[
5200 b"EVAL",
5201 b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
5202 return type(f)",
5203 b"0",
5204 ]),
5205 "$8\r\nfunction\r\n"
5206 );
5207 }
5208
5209 #[test]
5210 fn a_script_gets_the_bit_library_a_real_server_carries() {
5211 let mut f = Fixture::new();
5212 for (body, want) in [
5215 ("bit.tobit(1)", ":1\r\n"),
5216 ("bit.tobit(2^32 + 1)", ":1\r\n"),
5217 ("bit.tobit(2^31)", ":-2147483648\r\n"),
5218 ("bit.tobit(0xffffffff)", ":-1\r\n"),
5219 ("bit.tobit(1.5)", ":2\r\n"),
5221 ("bit.tobit(2.5)", ":2\r\n"),
5222 ("bit.bnot(0)", ":-1\r\n"),
5223 ("bit.band(0xff, 0x0f)", ":15\r\n"),
5224 ("bit.band(1, 2, 3)", ":0\r\n"),
5225 ("bit.bor(1, 2, 4)", ":7\r\n"),
5226 ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
5227 ("bit.lshift(1, 31)", ":-2147483648\r\n"),
5229 ("bit.lshift(1, 32)", ":1\r\n"),
5230 ("bit.lshift(1, 33)", ":2\r\n"),
5231 ("bit.rshift(-1, 1)", ":2147483647\r\n"),
5232 ("bit.arshift(-1, 1)", ":-1\r\n"),
5233 ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
5234 ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
5235 ("bit.bswap(0x12345678)", ":2018915346\r\n"),
5236 ("bit.tobit('0x10')", ":16\r\n"),
5239 ] {
5240 let script = format!("return {body}");
5241 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5242 }
5243 for (body, want) in [
5246 ("bit.tohex(1)", "00000001"),
5247 ("bit.tohex(-1)", "ffffffff"),
5248 ("bit.tohex(255, 2)", "ff"),
5249 ("bit.tohex(255, -8)", "000000FF"),
5250 ("bit.tohex(0x87654321, 4)", "4321"),
5251 ("bit.tohex(1, 0)", ""),
5252 ("bit.tohex(1, 9)", "00000001"),
5253 ] {
5254 let script = format!("return {body}");
5255 assert_eq!(
5256 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5257 format!("${}\r\n{want}\r\n", want.len()),
5258 "{body}",
5259 );
5260 }
5261 for (body, want) in [
5264 (
5265 "return bit.band()",
5266 "bad argument #1 to 'band' (number expected, got no value)",
5267 ),
5268 (
5269 "return bit.band('x')",
5270 "bad argument #1 to 'band' (number expected, got string)",
5271 ),
5272 (
5273 "return bit.tobit(true)",
5274 "bad argument #1 to 'tobit' (number expected, got boolean)",
5275 ),
5276 (
5277 "return bit.lshift(1)",
5278 "bad argument #2 to 'lshift' (number expected, got no value)",
5279 ),
5280 ] {
5281 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5282 assert!(
5283 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5284 "{body} gave {reply}",
5285 );
5286 }
5287 assert_eq!(
5290 f.run(&[
5291 b"EVAL",
5292 b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
5293 b"0",
5294 ]),
5295 "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
5296 );
5297 assert_eq!(
5299 f.run(&[
5300 b"EVAL",
5301 b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
5302 table.sort(t) return table.concat(t, ' ')",
5303 b"0",
5304 ]),
5305 "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
5306 );
5307 for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
5308 assert!(
5309 f.run(&[b"EVAL", body, b"0"])
5310 .contains("Attempt to modify a readonly table script: "),
5311 "{body:?}",
5312 );
5313 }
5314 }
5315
5316 #[test]
5317 fn a_script_gets_the_cjson_library_a_real_server_carries() {
5318 let mut f = Fixture::new();
5319 for (body, want) in [
5323 ("cjson.encode(nil)", "null"),
5324 ("cjson.encode(true)", "true"),
5325 ("cjson.encode(cjson.null)", "null"),
5326 ("cjson.encode(100)", "100"),
5327 ("cjson.encode(1/3)", "0.33333333333333"),
5328 ("cjson.encode(1e300)", "1e+300"),
5329 ("cjson.encode(2^53)", "9.007199254741e+15"),
5330 ("cjson.encode({})", "{}"),
5331 ("cjson.encode({1,2,3})", "[1,2,3]"),
5332 ("cjson.encode({a=1})", "{\"a\":1}"),
5333 ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
5334 ("cjson.encode({[0]=1})", "{\"0\":1}"),
5335 ("cjson.encode('a\\nb')", "\"a\\nb\""),
5336 ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
5338 ("cjson.encode('\\11')", "\"\\u000b\""),
5339 (
5342 "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
5343 "[1,[2,{\"a\":null}]]",
5344 ),
5345 ("cjson.encode(cjson.decode('[]'))", "{}"),
5348 ] {
5349 let script = format!("return {body}");
5350 assert_eq!(
5351 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5352 format!("${}\r\n{want}\r\n", want.len()),
5353 "{body}",
5354 );
5355 }
5356 for (body, want) in [
5359 ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
5360 ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
5361 ("cjson.decode('0x10')", ":16\r\n"),
5362 ("cjson.decode('+1')", ":1\r\n"),
5363 ("cjson.decode('01')", ":1\r\n"),
5364 ("cjson.decode(1) + 1", ":2\r\n"),
5365 ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
5367 ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
5368 ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
5369 ] {
5370 let script = format!("return {body}");
5371 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5372 }
5373 for (body, want) in [
5375 (
5376 "cjson.encode_number_precision(3) return cjson.encode(1/3)",
5377 "0.333",
5378 ),
5379 (
5380 "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
5381 "null",
5382 ),
5383 (
5384 "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
5385 "inf",
5386 ),
5387 (
5388 "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
5389 "{\"1\":1,\"100\":1}",
5390 ),
5391 (
5392 "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
5393 "[]",
5394 ),
5395 ("return tostring(cjson.encode_max_depth())", "1000"),
5396 ("return tostring(cjson.encode_keep_buffer(false))", "false"),
5397 ("return tostring(cjson.encode_sparse_array())", "false"),
5398 ("return tostring(cjson.encode_number_precision())", "14"),
5401 ] {
5402 assert_eq!(
5403 f.run(&[b"EVAL", body.as_bytes(), b"0"]),
5404 format!("${}\r\n{want}\r\n", want.len()),
5405 "{body}",
5406 );
5407 }
5408 for (body, want) in [
5410 (
5411 "return cjson.encode(1/0)",
5412 "Cannot serialise number: must not be NaN or Inf",
5413 ),
5414 (
5415 "return cjson.encode({[1]=1,[100]=1})",
5416 "Cannot serialise table: excessively sparse array",
5417 ),
5418 (
5419 "return cjson.encode({[true]=1})",
5420 "Cannot serialise boolean: table key must be a number or string",
5421 ),
5422 (
5423 "return cjson.encode(tostring)",
5424 "Cannot serialise function: type not supported",
5425 ),
5426 (
5427 "return cjson.encode()",
5428 "bad argument #1 to 'encode' (expected 1 argument)",
5429 ),
5430 (
5431 "return cjson.decode('[1,2')",
5432 "Expected comma or array end but found T_END at character 5",
5433 ),
5434 (
5435 "return cjson.decode('{\"a\" 1}')",
5436 "Expected colon but found T_NUMBER at character 6",
5437 ),
5438 (
5439 "return cjson.decode('tru')",
5440 "Expected value but found invalid token at character 1",
5441 ),
5442 (
5443 "return cjson.decode('[1] 2')",
5444 "Expected the end but found T_NUMBER at character 5",
5445 ),
5446 (
5447 "return cjson.encode_max_depth(0)",
5448 "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
5449 ),
5450 (
5451 "return cjson.encode_invalid_numbers('yes')",
5452 "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
5453 ),
5454 (
5455 "return cjson.encode_max_depth(1, 2)",
5456 "bad argument #2 to 'encode_max_depth' (found too many arguments)",
5457 ),
5458 ] {
5459 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5460 assert!(
5461 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5462 "{body} gave {reply}",
5463 );
5464 }
5465 assert_eq!(
5468 f.run(&[
5469 b"EVAL",
5470 b"local n = cjson.new() n.encode_number_precision(3) \
5471 return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
5472 b"0",
5473 ]),
5474 "$22\r\n0.33333333333333 0.333\r\n"
5475 );
5476 let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
5478 decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
5479 encode_max_depth encode_number_precision encode_sparse_array new null";
5480 assert_eq!(
5481 f.run(&[
5482 b"EVAL",
5483 b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
5484 table.sort(t) return table.concat(t, ' ')",
5485 b"0",
5486 ]),
5487 format!("${}\r\n{names}\r\n", names.len())
5488 );
5489 for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
5490 assert!(
5491 f.run(&[b"EVAL", body, b"0"])
5492 .contains("Attempt to modify a readonly table script: "),
5493 "{body:?}",
5494 );
5495 }
5496 }
5497
5498 #[test]
5499 fn a_script_gets_the_struct_library_a_real_server_carries() {
5500 let mut f = Fixture::new();
5501 for (body, want) in [
5504 ("#struct.pack('i4', 1)", ":4\r\n"),
5505 ("#struct.pack('l', 1)", ":8\r\n"),
5506 ("#struct.pack('d', 1)", ":8\r\n"),
5507 ("#struct.pack('f', 1)", ":4\r\n"),
5508 ("#struct.pack('s', 'abc')", ":4\r\n"),
5509 ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
5510 ("#struct.pack('x')", ":1\r\n"),
5511 ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
5512 ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
5513 ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
5514 ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
5517 ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
5518 ("#struct.pack('c0', 'abcd')", ":4\r\n"),
5521 ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
5522 ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
5523 ] {
5524 let script = format!("return {body}");
5525 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5526 }
5527 for (body, want) in [
5530 ("struct.size('i')", ":4\r\n"),
5531 ("struct.size('l')", ":8\r\n"),
5532 ("struct.size('T')", ":8\r\n"),
5533 ("struct.size('h')", ":2\r\n"),
5534 ("struct.size('c10')", ":10\r\n"),
5535 ("struct.size('ic')", ":5\r\n"),
5536 ("struct.size('!8ic')", ":5\r\n"),
5537 ("struct.size('!4i')", ":4\r\n"),
5538 ("struct.size('bd')", ":9\r\n"),
5541 ("struct.size('!bd')", ":16\r\n"),
5542 ("struct.size('A')", ":0\r\n"),
5543 ("struct.size('7')", ":0\r\n"),
5544 ] {
5545 let script = format!("return {body}");
5546 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5547 }
5548 for (body, want) in [
5551 ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
5552 ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
5553 ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
5554 ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
5555 ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
5556 (
5557 "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
5558 ":-70000\r\n",
5559 ),
5560 ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
5561 ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
5565 ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
5566 ] {
5567 let script = format!("return {body}");
5568 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5569 }
5570 for (body, want) in [
5571 ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
5572 ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
5573 ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
5574 ] {
5575 let script = format!("return {body}");
5576 assert_eq!(
5577 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5578 format!("${}\r\n{want}\r\n", want.len()),
5579 "{body}",
5580 );
5581 }
5582 for (body, want) in [
5585 (
5586 "return struct.pack()",
5587 "bad argument #1 to 'pack' (string expected, got no value)",
5588 ),
5589 (
5592 "return struct.pack('i4')",
5593 "bad argument #2 to 'pack' (number expected, got nil)",
5594 ),
5595 (
5598 "return struct.pack('c6', 'abc')",
5599 "bad argument #3 to 'pack' (string too short)",
5600 ),
5601 (
5602 "return struct.pack('A', 'x')",
5603 "bad argument #1 to 'pack' (invalid format option 'A')",
5604 ),
5605 (
5606 "return struct.pack('i33', 1)",
5607 "integral size 33 is larger than limit of 32",
5608 ),
5609 (
5610 "return struct.pack('!3i', 1)",
5611 "alignment 3 is not a power of 2",
5612 ),
5613 (
5614 "return struct.unpack()",
5615 "bad argument #1 to 'unpack' (string expected, got no value)",
5616 ),
5617 (
5618 "return struct.unpack('i4')",
5619 "bad argument #2 to 'unpack' (string expected, got no value)",
5620 ),
5621 (
5622 "return struct.unpack('i4', 'ab')",
5623 "bad argument #2 to 'unpack' (data string too short)",
5624 ),
5625 (
5626 "return struct.unpack('i1', 'abc', 0)",
5627 "bad argument #3 to 'unpack' (offset must be 1 or greater)",
5628 ),
5629 (
5630 "return struct.unpack('c0', 'abc')",
5631 "format 'c0' needs a previous size",
5632 ),
5633 (
5634 "return struct.unpack('s', 'abc')",
5635 "unfinished string in data",
5636 ),
5637 (
5638 "return struct.size()",
5639 "bad argument #1 to 'size' (string expected, got no value)",
5640 ),
5641 (
5642 "return struct.size('s')",
5643 "bad argument #1 to 'size' (option 's' has no fixed size)",
5644 ),
5645 (
5646 "return struct.size('c0')",
5647 "bad argument #1 to 'size' (option 'c0' has no fixed size)",
5648 ),
5649 ] {
5650 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5651 assert!(
5652 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5653 "{body} gave {reply}",
5654 );
5655 }
5656 let names = "pack size unpack";
5658 assert_eq!(
5659 f.run(&[
5660 b"EVAL",
5661 b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
5662 table.sort(t) return table.concat(t, ' ')",
5663 b"0",
5664 ]),
5665 format!("${}\r\n{names}\r\n", names.len())
5666 );
5667 for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
5668 assert!(
5669 f.run(&[b"EVAL", body, b"0"])
5670 .contains("Attempt to modify a readonly table script: "),
5671 "{body:?}",
5672 );
5673 }
5674 }
5675
5676 #[test]
5677 fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
5678 let mut f = Fixture::new();
5679 let hex = "local function hx(s) return (string.gsub(s, '.', \
5682 function(c) return string.format('%02x', string.byte(c)) end)) end ";
5683 for (body, want) in [
5684 ("cmsgpack.pack(nil)", "c0"),
5685 ("cmsgpack.pack(true)", "c3"),
5686 ("cmsgpack.pack(false)", "c2"),
5687 ("cmsgpack.pack(0)", "00"),
5688 ("cmsgpack.pack(127)", "7f"),
5689 ("cmsgpack.pack(128)", "cc80"),
5690 ("cmsgpack.pack(-1)", "ff"),
5691 ("cmsgpack.pack(-33)", "d0df"),
5692 ("cmsgpack.pack(65535)", "cdffff"),
5693 ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
5694 ("cmsgpack.pack(2^53)", "cf0020000000000000"),
5695 ("cmsgpack.pack(-2^63)", "d38000000000000000"),
5696 ("cmsgpack.pack(2^64)", "ca5f800000"),
5699 ("cmsgpack.pack(1.5)", "ca3fc00000"),
5700 ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
5701 ("cmsgpack.pack('abc')", "a3616263"),
5702 ("cmsgpack.pack('')", "a0"),
5703 ("cmsgpack.pack({})", "90"),
5704 ("cmsgpack.pack({1, 2})", "920102"),
5705 ("cmsgpack.pack({a = 1})", "81a16101"),
5706 ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
5707 (
5710 "(function() local t = {} local c = t \
5711 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
5712 "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
5713 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
5714 ),
5715 ] {
5716 let script = format!("{hex} return hx({body})");
5717 assert_eq!(
5718 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5719 format!("${}\r\n{want}\r\n", want.len()),
5720 "{body}",
5721 );
5722 }
5723 for (body, want) in [
5727 ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
5728 ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
5729 ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
5730 ("select('#', cmsgpack.unpack(''))", 0),
5731 ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
5732 ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5733 ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5734 ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
5735 ("select(1, cmsgpack.unpack_one('\\1'))", -1),
5736 ("select(1, cmsgpack.unpack_one('', 0))", -1),
5737 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
5738 ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
5739 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
5742 ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
5743 ] {
5744 let script = format!("return {body}");
5745 assert_eq!(
5746 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5747 format!(":{want}\r\n"),
5748 "{body}",
5749 );
5750 }
5751 for (body, want) in [
5752 ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
5753 ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
5754 ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
5755 (
5756 "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
5757 "inf",
5758 ),
5759 ("cmsgpack._NAME", "cmsgpack"),
5760 ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
5761 (
5762 "cmsgpack._COPYRIGHT",
5763 "Copyright (C) 2012, Salvatore Sanfilippo",
5764 ),
5765 (
5766 "cmsgpack._DESCRIPTION",
5767 "MessagePack C implementation for Lua",
5768 ),
5769 ] {
5770 let script = format!("return {body}");
5771 assert_eq!(
5772 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5773 format!("${}\r\n{want}\r\n", want.len()),
5774 "{body}",
5775 );
5776 }
5777 for (body, want) in [
5778 (
5781 "return cmsgpack.pack()",
5782 "bad argument #0 to 'pack' (MessagePack pack needs input.)",
5783 ),
5784 (
5785 "return cmsgpack.unpack()",
5786 "bad argument #1 to 'unpack' (string expected, got no value)",
5787 ),
5788 (
5789 "return cmsgpack.unpack(string.char(193))",
5790 "Bad data format in input.",
5791 ),
5792 (
5793 "return cmsgpack.unpack(string.char(204))",
5794 "Missing bytes in input.",
5795 ),
5796 (
5797 "return cmsgpack.unpack(string.char(146, 1))",
5798 "Missing bytes in input.",
5799 ),
5800 (
5801 "return cmsgpack.unpack_one('\\1', 5)",
5802 "Start offset 5 greater than input length 1.",
5803 ),
5804 (
5805 "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
5806 "Start offset 5 greater than input length 2.",
5807 ),
5808 (
5811 "return cmsgpack.unpack_one('\\1', -1)",
5812 "Invalid request to unpack with offset of -1 and limit of 1.",
5813 ),
5814 (
5815 "return cmsgpack.unpack_limit('\\1', -1, 0)",
5816 "Invalid request to unpack with offset of 0 and limit of 1.",
5817 ),
5818 ] {
5819 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5820 assert!(
5821 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5822 "{body} gave {reply}",
5823 );
5824 }
5825 let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
5827 assert_eq!(
5828 f.run(&[
5829 b"EVAL",
5830 b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
5831 table.sort(t) return table.concat(t, ' ')",
5832 b"0",
5833 ]),
5834 format!("${}\r\n{names}\r\n", names.len())
5835 );
5836 for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
5837 assert!(
5838 f.run(&[b"EVAL", body, b"0"])
5839 .contains("Attempt to modify a readonly table script: "),
5840 "{body:?}",
5841 );
5842 }
5843 assert_eq!(
5846 f.run(&[
5847 b"EVAL",
5848 b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
5849 b"0",
5850 ]),
5851 "$8\r\ncmsgpack\r\n"
5852 );
5853 }
5854
5855 const LIB: &[u8] = b"#!lua name=mylib\n\
5861 local counter = 0\n\
5862 redis.register_function{function_name = 'ping', description = 'says pong',\n\
5863 callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
5864 redis.register_function('count', function() counter = counter + 1 return counter end)\n\
5865 redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
5866 redis.register_function('setit', function(keys, args) \
5867 return redis.call('SET', keys[1], args[1]) end)\n\
5868 redis.register_function('raise', function() error('boom') end)\n";
5869
5870 const OTHER: &[u8] = b"#!lua name=other\n\
5872 redis.register_function('twice', function(keys, args) return 2 end)\n";
5873
5874 #[test]
5875 fn a_library_is_loaded_once_and_called_by_name_forever_after() {
5876 let mut f = Fixture::new();
5877 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5878 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5879 assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
5882 assert_eq!(
5886 f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
5887 "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5888 );
5889 assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
5890 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
5891 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5894 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
5895 assert_eq!(
5898 f.run(&[b"FCALL", b"raise", b"0"]),
5899 "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
5900 );
5901 assert_eq!(
5904 f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
5905 "-ERR Library not found\r\n"
5906 );
5907 assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
5908 assert_eq!(
5909 f.run(&[b"FCALL", b"ping", b"0"]),
5910 "-ERR Function not found\r\n"
5911 );
5912 }
5913
5914 #[test]
5915 fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
5916 let mut f = Fixture::new();
5917 for (code, want) in [
5918 (&b"return 1"[..], "ERR Missing library metadata"),
5919 (b"#!lua name=x", "ERR Invalid library metadata"),
5920 (b"#!\n", "ERR Library name was not given"),
5921 (b"#!lua\nx", "ERR Library name was not given"),
5922 (
5923 b"#!lua name=a name=b\nx",
5924 "ERR Invalid metadata value, name argument was given multiple times",
5925 ),
5926 (
5927 b"#!lua nome=a\nx",
5928 "ERR Invalid metadata value given: nome=a",
5929 ),
5930 (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
5931 (
5932 b"#!lua name=a-b\nx",
5933 "ERR Library names can only contain letters, numbers, or underscores(_) \
5934 and must be at least one character long",
5935 ),
5936 (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
5937 (
5938 b"#!lua name=c\nthis is not lua",
5939 "ERR Error compiling function: user_function:2: '=' expected near 'is'",
5940 ),
5941 (
5945 b"#!lua name=r\nerror('boom')",
5946 "ERR Error registering functions: ERR user_function:2: \
5947 Script attempted to access nonexistent global variable 'error'",
5948 ),
5949 (
5952 b"#!lua name=r\nredis.call('PING')",
5953 "ERR Error registering functions: ERR user_function:2: \
5954 Script attempted to access nonexistent global variable 'call'",
5955 ),
5956 (
5957 b"#!lua name=r\nx = 1",
5958 "ERR Error registering functions: ERR user_function:2: \
5959 Attempt to modify a readonly table",
5960 ),
5961 (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
5962 ] {
5963 assert_eq!(
5964 f.run(&[b"FUNCTION", b"LOAD", code]),
5965 format!("-{want}\r\n"),
5966 "{}",
5967 String::from_utf8_lossy(code),
5968 );
5969 }
5970 }
5971
5972 #[test]
5973 fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
5974 let mut f = Fixture::new();
5975 for (call, want) in [
5976 (
5977 &b"redis.register_function()"[..],
5978 "wrong number of arguments to redis.register_function",
5979 ),
5980 (
5981 b"redis.register_function('a', function() end, 1)",
5982 "wrong number of arguments to redis.register_function",
5983 ),
5984 (
5985 b"redis.register_function('a')",
5986 "calling redis.register_function with a single argument is only \
5987 applicable to Lua table (representing named arguments).",
5988 ),
5989 (
5990 b"redis.register_function({foo = 'a'})",
5991 "unknown argument given to redis.register_function",
5992 ),
5993 (
5994 b"redis.register_function({callback = function() end})",
5995 "redis.register_function must get a function name argument",
5996 ),
5997 (
5998 b"redis.register_function({function_name = 'a'})",
5999 "redis.register_function must get a callback argument",
6000 ),
6001 (
6002 b"redis.register_function({function_name = {}, callback = function() end})",
6003 "function_name argument given to redis.register_function must be a string",
6004 ),
6005 (
6006 b"redis.register_function({function_name = 'a', description = {}, \
6007 callback = function() end})",
6008 "description argument given to redis.register_function must be a string",
6009 ),
6010 (
6011 b"redis.register_function({function_name = 'a', callback = 1})",
6012 "callback argument given to redis.register_function must be a function",
6013 ),
6014 (
6015 b"redis.register_function({function_name = 'a', callback = function() end, \
6016 flags = 1})",
6017 "flags argument to redis.register_function must be a table \
6018 representing function flags",
6019 ),
6020 (
6021 b"redis.register_function({function_name = 'a', callback = function() end, \
6022 flags = {'zz'}})",
6023 "unknown flag given",
6024 ),
6025 (
6026 b"redis.register_function({}, function() end)",
6027 "first argument to redis.register_function must be a string",
6028 ),
6029 (
6030 b"redis.register_function('a', 1)",
6031 "second argument to redis.register_function must be a function",
6032 ),
6033 (
6034 b"redis.register_function('a-b', function() end)",
6035 "Library names can only contain letters, numbers, or underscores(_) \
6036 and must be at least one character long",
6037 ),
6038 (
6039 b"redis.register_function('d', function() end) \
6040 redis.register_function('d', function() end)",
6041 "Function already exists in the library",
6042 ),
6043 ] {
6044 let mut code = b"#!lua name=e\n".to_vec();
6045 code.extend_from_slice(call);
6046 assert_eq!(
6050 f.run(&[b"FUNCTION", b"LOAD", &code]),
6051 format!("-ERR Error registering functions: ERR {want}\r\n"),
6052 "{}",
6053 String::from_utf8_lossy(call),
6054 );
6055 }
6056 assert_eq!(
6059 f.run(&[
6060 b"FUNCTION",
6061 b"LOAD",
6062 b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
6063 ]),
6064 "$1\r\nn\r\n"
6065 );
6066 assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
6067 assert_eq!(
6070 f.run(&[
6071 b"FUNCTION",
6072 b"LOAD",
6073 b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
6074 redis.register_function('D', function() return 2 end)",
6075 ]),
6076 "$1\r\nc\r\n"
6077 );
6078 }
6079
6080 #[test]
6081 fn a_library_cannot_take_a_name_another_library_already_has() {
6082 let mut f = Fixture::new();
6083 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6084 assert_eq!(
6085 f.run(&[b"FUNCTION", b"LOAD", LIB]),
6086 "-ERR Library 'mylib' already exists\r\n"
6087 );
6088 assert_eq!(
6092 f.run(&[
6093 b"FUNCTION",
6094 b"LOAD",
6095 b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
6096 ]),
6097 "-ERR Function PING already exists\r\n"
6098 );
6099 assert_eq!(
6102 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
6103 "$5\r\nmylib\r\n"
6104 );
6105 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
6108 assert_eq!(
6109 f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
6110 "-ERR Unknown option given: NOPE\r\n"
6111 );
6112 assert_eq!(
6115 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
6116 "-ERR Missing library metadata\r\n"
6117 );
6118 assert_eq!(
6119 f.run(&[b"FUNCTION", b"LOAD"]),
6120 "-ERR wrong number of arguments for 'function|load' command\r\n"
6121 );
6122 }
6123
6124 #[test]
6125 fn fcall_checks_the_name_before_it_looks_at_anything_else() {
6126 let mut f = Fixture::new();
6127 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6128 for (args, want) in [
6129 (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
6130 (&[b"ping", b"x"], "ERR Bad number of keys provided"),
6131 (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
6132 (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
6133 (
6134 &[b"ping", b"99999999999999999999"],
6135 "ERR Bad number of keys provided",
6136 ),
6137 (
6138 &[b"ping", b"3", b"a"],
6139 "ERR Number of keys can't be greater than number of args",
6140 ),
6141 (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
6142 ] {
6143 let mut wire: Vec<&[u8]> = vec![b"FCALL"];
6144 wire.extend_from_slice(args);
6145 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
6146 }
6147 assert_eq!(
6150 f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
6151 "-ERR Can not execute a script with write flag using *_ro command.\r\n"
6152 );
6153 assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
6154 assert_eq!(
6155 f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
6156 "-ERR Function not found\r\n"
6157 );
6158 assert_eq!(
6161 f.run(&[
6162 b"FUNCTION",
6163 b"LOAD",
6164 b"#!lua name=w\nredis.register_function{function_name = 'w', \
6165 flags = {'no-writes'}, callback = function(keys) \
6166 return redis.call('SET', keys[1], 'x') end}",
6167 ]),
6168 "$1\r\nw\r\n"
6169 );
6170 assert!(
6171 f.run(&[b"FCALL", b"w", b"1", b"k"])
6172 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
6173 );
6174 }
6175
6176 #[test]
6177 fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
6178 let mut f = Fixture::new();
6179 let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
6182 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
6183 acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
6184 status_reply";
6185 let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
6186 gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
6187 rawequal rawget rawset redis select setmetatable string struct table \
6188 tonumber tostring type unpack xpcall";
6189 assert_eq!(
6190 f.run(&[
6191 b"FUNCTION",
6192 b"LOAD",
6193 b"#!lua name=g\n\
6194 local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
6195 table.sort(o) return table.concat(o, ' ') end\n\
6196 redis.register_function('names', function() return sorted(redis) end)\n\
6197 redis.register_function('globals', function() return sorted(_G) end)\n\
6198 redis.register_function('keysg', function() return KEYS[1] end)\n\
6199 redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
6200 redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
6201 redis.register_function('gwr', function() _G.pcall = 1 end)\n",
6202 ]),
6203 "$1\r\ng\r\n"
6204 );
6205 assert_eq!(
6206 f.run(&[b"FCALL", b"names", b"0"]),
6207 format!("${}\r\n{names}\r\n", names.len())
6208 );
6209 assert_eq!(
6210 f.run(&[b"FCALL", b"globals", b"0"]),
6211 format!("${}\r\n{globals}\r\n", globals.len())
6212 );
6213 assert!(
6216 f.run(&[b"FCALL", b"keysg", b"1", b"k"])
6217 .contains("nonexistent global variable 'KEYS'"),
6218 );
6219 assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
6222 for name in [&b"wr"[..], b"gwr"] {
6225 assert!(
6226 f.run(&[b"FCALL", name, b"0"])
6227 .contains("Attempt to modify a readonly table"),
6228 "{}",
6229 String::from_utf8_lossy(name),
6230 );
6231 }
6232 }
6233
6234 #[test]
6235 fn function_list_says_what_every_library_registered() {
6236 let mut f = Fixture::new();
6237 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6238 f.out = Out::new(Proto::Resp3);
6241 let listed = f.run(&[b"FUNCTION", b"LIST"]);
6242 assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
6243 assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
6244 assert!(listed.contains(
6245 "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
6246 $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
6247 ));
6248 assert!(listed.contains(
6251 "$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"
6252 ));
6253 assert!(!listed.contains("library_code"));
6254 assert!(
6255 f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
6256 .contains("library_code")
6257 );
6258 assert!(
6261 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
6262 .starts_with("*1\r\n")
6263 );
6264 assert_eq!(
6265 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
6266 "*0\r\n"
6267 );
6268 f.out = Out::new(Proto::Resp2);
6271 assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
6272 for (args, want) in [
6273 (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
6274 (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
6275 (
6276 &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
6277 "ERR Unknown argument LIBRARYNAME",
6278 ),
6279 (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
6280 ] {
6281 let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
6282 wire.extend_from_slice(args);
6283 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
6284 }
6285 }
6286
6287 #[test]
6288 fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
6289 let mut f = Fixture::new();
6290 f.out = Out::new(Proto::Resp3);
6291 assert_eq!(
6292 f.run(&[b"FUNCTION", b"STATS"]),
6293 "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
6294 %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
6295 );
6296 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6297 assert!(
6298 f.run(&[b"FUNCTION", b"STATS"])
6299 .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
6300 );
6301 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
6302 assert!(
6303 f.run(&[b"FUNCTION", b"STATS"])
6304 .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
6305 );
6306 }
6307
6308 #[test]
6309 fn every_function_subcommand_complains_about_its_own_arity() {
6310 let mut f = Fixture::new();
6311 for (args, want) in [
6312 (
6313 &[&b"STATS"[..], b"X"][..],
6314 "ERR wrong number of arguments for 'function|stats' command",
6315 ),
6316 (
6317 &[b"KILL", b"X"],
6318 "ERR wrong number of arguments for 'function|kill' command",
6319 ),
6320 (
6321 &[b"HELP", b"X"],
6322 "ERR wrong number of arguments for 'function|help' command",
6323 ),
6324 (
6325 &[b"DELETE"],
6326 "ERR wrong number of arguments for 'function|delete' command",
6327 ),
6328 (
6329 &[b"DELETE", b"a", b"b"],
6330 "ERR wrong number of arguments for 'function|delete' command",
6331 ),
6332 (
6333 &[b"DUMP", b"X"],
6334 "ERR wrong number of arguments for 'function|dump' command",
6335 ),
6336 (
6337 &[b"RESTORE"],
6338 "ERR wrong number of arguments for 'function|restore' command",
6339 ),
6340 (
6343 &[b"RESTORE", b"a", b"FLUSH", b"X"],
6344 "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
6345 Try FUNCTION HELP.",
6346 ),
6347 (
6348 &[b"RESTORE", b"a", b"ZZ"],
6349 "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
6350 or REPLACE.",
6351 ),
6352 (
6355 &[b"FLUSH", b"SYNC", b"X"],
6356 "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
6357 Try FUNCTION HELP.",
6358 ),
6359 (
6360 &[b"FLUSH", b"ZZ"],
6361 "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
6362 ),
6363 (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
6364 ] {
6365 let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
6366 wire.extend_from_slice(args);
6367 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
6368 }
6369 assert_eq!(
6370 f.run(&[b"FUNCTION"]),
6371 "-ERR wrong number of arguments for 'function' command\r\n"
6372 );
6373 assert_eq!(
6374 f.run(&[b"FUNCTION", b"KILL"]),
6375 "-NOTBUSY No scripts in execution right now.\r\n"
6376 );
6377 }
6378
6379 #[test]
6385 fn a_library_survives_a_dump_and_a_restore() {
6386 let mut f = Fixture::new();
6387 let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6388 assert_eq!(empty.len(), 10);
6389 assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
6390
6391 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6392 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6393 assert!(full.len() > empty.len());
6394
6395 assert_eq!(
6398 f.run(&[b"FUNCTION", b"RESTORE", &full]),
6399 "-ERR Library mylib already exists\r\n"
6400 );
6401 assert_eq!(
6402 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
6403 "+OK\r\n"
6404 );
6405 assert_eq!(
6406 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
6407 "+OK\r\n"
6408 );
6409 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6411
6412 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
6415 assert_eq!(
6416 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
6417 "+OK\r\n"
6418 );
6419 assert_eq!(
6420 f.run(&[b"FUNCTION", b"DELETE", b"other"]),
6421 "-ERR Library not found\r\n"
6422 );
6423 }
6424
6425 #[test]
6432 fn a_restore_that_fails_changes_nothing() {
6433 let mut f = Fixture::new();
6434 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6435 let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6436
6437 let reseal = |body: &[u8], version: u16| {
6441 let mut out = body.to_vec();
6442 out.extend_from_slice(&version.to_le_bytes());
6443 let crc = yo_common::crc::crc64(0, &out);
6444 out.extend_from_slice(&crc.to_le_bytes());
6445 out
6446 };
6447 let body = &good[..good.len() - 10];
6448
6449 let mut torn = good.clone();
6450 let n = torn.len();
6451 torn[n - 1] ^= 0xff;
6452 let future = reseal(body, 999);
6453 let mut pre_ga = body.to_vec();
6456 pre_ga[0] = 246;
6457 let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
6458 let mut other = body.to_vec();
6459 other[0] = 0;
6460 let other = reseal(&other, yo_kv::rdb::VERSION);
6461 let mut cut = body.to_vec();
6463 cut.truncate(body.len() - 1);
6464 let cut = reseal(&cut, yo_kv::rdb::VERSION);
6465
6466 for (bytes, want) in [
6467 (vec![], "ERR DUMP payload version or checksum are wrong"),
6468 (
6469 b"0123456789".to_vec(),
6470 "ERR DUMP payload version or checksum are wrong",
6471 ),
6472 (torn, "ERR DUMP payload version or checksum are wrong"),
6473 (future, "ERR DUMP payload version or checksum are wrong"),
6474 (pre_ga, "ERR Pre-GA function format not supported"),
6475 (other, "ERR given type is not a function"),
6476 (cut, "ERR Failed loading library payload"),
6477 ] {
6478 assert_eq!(
6479 f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
6480 format!("-{want}\r\n")
6481 );
6482 }
6483
6484 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6486 let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6487 assert_eq!(again, good);
6488 }
6489
6490 #[test]
6493 fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
6494 let mut f = Fixture::new();
6495 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6496 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6497 let clash =
6499 b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
6500 .as_slice();
6501 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
6502 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
6503 assert_eq!(
6504 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
6505 "-ERR Function ping already exists\r\n"
6506 );
6507 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
6509 }
6510
6511 #[test]
6512 fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
6513 let mut f = Fixture::new();
6514 assert_eq!(
6515 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
6516 "*1\r\n$1\r\nk\r\n"
6517 );
6518 assert_eq!(
6519 f.run(&[
6520 b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
6521 ]),
6522 "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
6523 );
6524 assert_eq!(
6527 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
6528 "*0\r\n"
6529 );
6530 assert_eq!(
6533 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
6534 "*0\r\n"
6535 );
6536 assert_eq!(
6537 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
6538 "*0\r\n"
6539 );
6540 assert_eq!(
6541 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
6542 "*0\r\n"
6543 );
6544 assert_eq!(
6546 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
6547 "-ERR Invalid number of arguments specified for command\r\n"
6548 );
6549 }
6550
6551 #[test]
6552 fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
6553 let mut f = Fixture::new();
6554 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
6555
6556 assert_eq!(
6557 eval(&mut f, b"return redis.sha1hex('')"),
6558 "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
6559 );
6560 assert_eq!(
6561 eval(&mut f, b"return redis.sha1hex('return 1')"),
6562 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
6563 );
6564 assert_eq!(
6567 eval(&mut f, b"return redis.error_reply('boom')"),
6568 "-ERR boom\r\n"
6569 );
6570 assert_eq!(
6571 eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
6572 "-WRONGTYPE nope\r\n"
6573 );
6574 assert_eq!(
6575 eval(&mut f, b"return redis.status_reply('fine')"),
6576 "+fine\r\n"
6577 );
6578 assert_eq!(
6581 eval(&mut f, b"return redis.error_reply(1)"),
6582 "-ERR wrong number or type of arguments\r\n"
6583 );
6584 assert_eq!(
6585 eval(&mut f, b"local x = redis.status_reply() return x.err"),
6586 "$37\r\nERR wrong number or type of arguments\r\n"
6587 );
6588
6589 assert_eq!(
6591 eval(
6592 &mut f,
6593 b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
6594 ),
6595 "$4\r\n0123\r\n"
6596 );
6597 assert_eq!(
6598 eval(
6599 &mut f,
6600 b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
6601 ),
6602 "$5\r\n01223\r\n"
6603 );
6604 assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
6606 assert_eq!(
6607 eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
6608 ":1\r\n"
6609 );
6610 assert_eq!(
6611 eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
6612 ":1\r\n"
6613 );
6614 assert_eq!(
6615 eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
6616 ":1\r\n"
6617 );
6618 assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
6620 assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
6621 assert!(
6622 eval(&mut f, b"redis.log('x', 'y')")
6623 .contains("First argument must be a number (log level)."),
6624 );
6625 assert!(
6626 eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
6627 .contains("Invalid command passed to redis.acl_check_cmd()"),
6628 );
6629 assert!(
6630 eval(&mut f, b"return redis.acl_check_cmd('get')")
6631 .contains("Wrong number of args for redis.acl_check_cmd()"),
6632 );
6633 }
6634
6635 #[test]
6636 fn a_counter_is_an_integer_and_not_a_string_of_digits() {
6637 let mut f = Fixture::new();
6638 assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
6639 assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
6640 assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
6641 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
6644 assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
6645 f.run(&[b"SET", b"k", b"hello"]);
6648 assert_eq!(
6649 f.run(&[b"INCR", b"k"]),
6650 "-ERR value is not an integer or out of range\r\n"
6651 );
6652 assert_eq!(
6653 f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
6654 "-ERR increment would produce NaN or Infinity\r\n"
6655 );
6656 }
6657
6658 #[test]
6663 fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
6664 let mut f = Fixture::new();
6665 assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
6666 assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
6669 assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
6670 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
6671 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
6672 assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
6673 assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
6674 assert_eq!(
6675 f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
6676 "*2\r\n:1\r\n:0\r\n",
6677 "a refused increment reports the value it left alone and applied nothing"
6678 );
6679 assert_eq!(
6680 f.run(&[
6681 b"INCREX",
6682 b"n",
6683 b"BYINT",
6684 b"5",
6685 b"UBOUND",
6686 b"3",
6687 b"SATURATE"
6688 ]),
6689 "*2\r\n:3\r\n:2\r\n"
6690 );
6691 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
6692 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
6693 }
6694
6695 #[test]
6696 fn the_same_answers_come_out_in_resp3_spelling() {
6697 let mut f = Fixture::new();
6698 assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
6699 assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
6700 assert_eq!(
6703 f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
6704 "*2\r\n,1.5\r\n,1.5\r\n"
6705 );
6706 assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
6707 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6710 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
6711 }
6712
6713 #[test]
6714 fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
6715 let mut f = Fixture::new();
6716 let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
6717 assert_eq!(flow, Flow::Continue);
6718 assert_eq!(
6719 reply,
6720 "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
6721 );
6722 let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
6725 assert_eq!(reply.matches("\r\n").count(), 1);
6726 }
6727
6728 #[test]
6729 fn arity_is_checked_before_the_command_is() {
6730 let mut f = Fixture::new();
6731 assert_eq!(
6732 f.run(&[b"GET"]),
6733 "-ERR wrong number of arguments for 'get' command\r\n"
6734 );
6735 assert_eq!(
6736 f.run(&[b"MSET", b"k"]),
6737 "-ERR wrong number of arguments for 'mset' command\r\n"
6738 );
6739 assert_eq!(
6743 f.run(&[b"PING", b"a", b"b"]),
6744 "-ERR wrong number of arguments for 'ping' command\r\n"
6745 );
6746 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
6747 assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
6748 assert_eq!(
6750 f.run(&[b"DELEX", b"k", b"IFEQ"]),
6751 "-ERR wrong number of arguments for 'delex' command\r\n"
6752 );
6753 }
6754
6755 #[test]
6759 fn the_option_combinations_are_the_ones_a_real_server_accepts() {
6760 let mut f = Fixture::new();
6761 let syntax = "-ERR syntax error\r\n";
6762 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
6763 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
6764 assert_eq!(
6765 f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
6766 syntax
6767 );
6768 assert_eq!(
6769 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
6770 syntax
6771 );
6772 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
6773 assert_eq!(
6775 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
6776 "+OK\r\n"
6777 );
6778 assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
6779 assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
6780 assert_eq!(
6782 f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
6783 syntax
6784 );
6785 assert_eq!(
6786 f.run(&[b"INCREX", b"n", b"ENX"]),
6787 "-ERR ENX flag requires an expiration\r\n"
6788 );
6789 assert_eq!(
6790 f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
6791 "-ERR UBOUND is not an integer or out of range\r\n"
6792 );
6793 assert_eq!(
6794 f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
6795 "-ERR LBOUND can't be greater than UBOUND\r\n"
6796 );
6797 assert_eq!(
6798 f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
6799 "-ERR If you want both the length and indexes, please just use IDX.\r\n"
6800 );
6801 }
6802
6803 #[test]
6807 fn the_expiry_rules_are_redis_own() {
6808 let mut f = Fixture::new();
6809 let bad = "-ERR invalid expire time in 'set' command\r\n";
6810 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
6811 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
6812 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
6813 assert_eq!(
6814 f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
6815 bad
6816 );
6817 assert_eq!(
6818 f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
6819 "-ERR value is not an integer or out of range\r\n"
6820 );
6821 assert_eq!(
6822 f.run(&[b"SETEX", b"k", b"0", b"v"]),
6823 "-ERR invalid expire time in 'setex' command\r\n"
6824 );
6825 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
6826 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
6827 assert_eq!(
6828 f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
6829 "-ERR syntax error\r\n",
6830 "the option list is still checked before the key is looked up"
6831 );
6832 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6834 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
6835 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6836 }
6837
6838 #[test]
6839 fn mset_takes_its_pairs_from_the_read_buffer() {
6840 let mut f = Fixture::new();
6841 assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
6842 assert_eq!(
6843 f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
6844 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
6845 );
6846 assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
6847 assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
6848 assert_eq!(
6849 f.run(&[b"MSETEX", b"2", b"e", b"5"]),
6850 "-ERR wrong number of key-value pairs\r\n"
6851 );
6852 assert_eq!(
6853 f.run(&[b"MSETEX", b"0", b"e", b"5"]),
6854 "-ERR invalid numkeys value\r\n"
6855 );
6856 assert_eq!(
6857 f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
6858 "-ERR invalid numkeys value\r\n"
6859 );
6860 }
6861
6862 #[test]
6863 fn lcs_answers_the_length_the_string_and_the_runs() {
6864 let mut f = Fixture::new();
6865 f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
6866 assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
6867 assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
6868 assert_eq!(
6869 f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
6870 "*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"
6871 );
6872 assert_eq!(
6875 f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
6876 "$6\r\nmytext\r\n"
6877 );
6878 }
6879
6880 #[test]
6881 fn select_moves_the_connection_and_the_databases_stay_apart() {
6882 let mut f = Fixture::new();
6883 f.run(&[b"SET", b"k", b"zero"]);
6884 assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
6885 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6886 f.run(&[b"SET", b"k", b"four"]);
6887 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
6888 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6889 assert_eq!(
6890 f.run(&[b"SELECT", b"99"]),
6891 "-ERR DB index is out of range\r\n"
6892 );
6893 assert_eq!(
6894 f.run(&[b"SELECT", b"-1"]),
6895 "-ERR DB index is out of range\r\n"
6896 );
6897 assert_eq!(
6898 f.run(&[b"SELECT", b"abc"]),
6899 "-ERR value is not an integer or out of range\r\n"
6900 );
6901 f.run(&[b"SELECT", b"4"]);
6903 f.run(&[b"RESET"]);
6904 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6905 }
6906
6907 #[test]
6908 fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
6909 let mut f = Fixture::new();
6910 let reply = f.run(&[b"HELLO"]);
6911 assert!(reply.starts_with("*14\r\n"), "{reply}");
6912 assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
6913 assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
6914 assert!(
6915 reply.contains(":7\r\n"),
6916 "the connection id is in there: {reply}"
6917 );
6918 assert_eq!(
6919 f.run(&[b"HELLO", b"4"]),
6920 "-NOPROTO unsupported protocol version\r\n"
6921 );
6922 assert_eq!(
6923 f.run(&[b"HELLO", b"abc"]),
6924 "-ERR Protocol version is not an integer or out of range\r\n"
6925 );
6926 assert_eq!(
6927 f.run(&[b"HELLO", b"3", b"SETNAME"]),
6928 "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
6929 );
6930 assert!(
6931 f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
6932 .starts_with("%7\r\n")
6933 );
6934 assert_eq!(f.session.name(), b"bob");
6935 f.run(&[b"RESET"]);
6936 assert_eq!(f.session.name(), b"");
6937 }
6938
6939 #[test]
6940 fn command_describes_this_server_in_the_shape_a_driver_reads() {
6941 let mut f = Fixture::new();
6942 let count = format!(":{}\r\n", COMMANDS.len());
6943 assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
6944 let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
6945 assert_eq!(
6946 info,
6947 "*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\
6948 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
6949 );
6950 assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
6952 assert_eq!(
6953 f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
6954 "*1\r\n$8\r\ngetrange\r\n"
6955 );
6956 assert_eq!(
6957 f.run(&[b"COMMAND", b"NOPE"]),
6958 "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
6959 );
6960 }
6961
6962 #[test]
6966 fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
6967 let mut f = Fixture::new();
6968 assert_eq!(
6969 f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
6970 "*1\r\n$1\r\nk\r\n"
6971 );
6972 assert_eq!(
6973 f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
6974 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6975 );
6976 assert_eq!(
6977 f.run(&[
6978 b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
6979 ]),
6980 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6981 );
6982 assert_eq!(
6983 f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
6984 "-ERR The command has no key arguments\r\n"
6985 );
6986 assert_eq!(
6987 f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
6988 "-ERR Invalid number of arguments specified for command\r\n"
6989 );
6990 }
6991
6992 #[test]
6993 fn config_answers_what_it_can_and_refuses_what_it_cannot() {
6994 let mut f = Fixture::new();
6995 assert_eq!(
6996 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6997 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
6998 );
6999 let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
7002 assert!(both.starts_with("*6\r\n"), "{both}");
7003 assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
7004 assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
7005 assert_eq!(
7006 f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
7007 "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
7008 );
7009 assert_eq!(
7010 f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
7011 "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
7012 );
7013 assert_eq!(
7014 f.run(&[b"CONFIG", b"GET"]),
7015 "-ERR wrong number of arguments for 'config|get' command\r\n"
7016 );
7017 assert_eq!(
7021 f.run(&[b"CONFIG", b"SET", b"appendonly"]),
7022 "-ERR wrong number of arguments for 'config|set' command\r\n"
7023 );
7024 assert_eq!(
7025 f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
7026 "-ERR syntax error\r\n"
7027 );
7028 assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
7029 assert_eq!(
7030 f.run(&[b"CONFIG", b"REWRITE"]),
7031 "-ERR The server is running without a config file\r\n"
7032 );
7033 }
7034
7035 #[test]
7036 fn the_eviction_policy_reads_back_what_was_written_to_it() {
7037 let mut f = Fixture::new();
7038 assert_eq!(
7039 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
7040 "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
7041 );
7042 assert_eq!(
7043 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
7044 "+OK\r\n",
7045 "the name is matched without regard to case, like every other one"
7046 );
7047 assert_eq!(
7048 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
7049 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
7050 );
7051 assert!(
7053 f.run(&[b"INFO", b"memory"])
7054 .contains("maxmemory_policy:allkeys-lfu"),
7055 "INFO and CONFIG disagree about the policy"
7056 );
7057 assert_eq!(
7061 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
7062 "-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"
7063 );
7064 assert_eq!(
7067 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
7068 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
7069 );
7070 f.run(&[
7071 b"CONFIG",
7072 b"SET",
7073 b"hash-max-listpack-entries",
7074 b"7",
7075 b"maxmemory-policy",
7076 b"nonsense",
7077 ]);
7078 assert_eq!(
7079 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7080 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
7081 );
7082 }
7083
7084 #[test]
7085 fn the_three_eviction_numbers_read_back_too() {
7086 let mut f = Fixture::new();
7087 for (name, default, set) in [
7088 ("maxmemory-samples", "5", "12"),
7089 ("lfu-log-factor", "10", "3"),
7090 ("lfu-decay-time", "1", "60"),
7091 ] {
7092 let get = || {
7093 format!(
7094 "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
7095 name.len(),
7096 default.len()
7097 )
7098 };
7099 assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
7100 assert_eq!(
7101 f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
7102 "+OK\r\n"
7103 );
7104 assert_eq!(
7105 f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
7106 format!(
7107 "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
7108 name.len(),
7109 set.len()
7110 )
7111 );
7112 assert_eq!(
7115 f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
7116 format!(
7117 "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
7118 )
7119 );
7120 }
7121 }
7122
7123 #[test]
7124 fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
7125 let mut f = Fixture::new();
7126 assert_eq!(
7127 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
7128 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
7129 "no limit is the default"
7130 );
7131 for (typed, bytes) in [
7134 (&b"1024"[..], "1024"),
7135 (b"1k", "1000"),
7136 (b"1kb", "1024"),
7137 (b"1M", "1000000"),
7138 (b"1Mb", "1048576"),
7139 (b"1gb", "1073741824"),
7140 (b"100mb", "104857600"),
7141 ] {
7142 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
7143 assert_eq!(
7144 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
7145 format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
7146 "set {}",
7147 String::from_utf8_lossy(typed)
7148 );
7149 }
7150 assert!(
7151 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
7152 "the report agrees with the setting"
7153 );
7154
7155 for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
7158 assert_eq!(
7159 f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
7160 "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
7161 "refused {}",
7162 String::from_utf8_lossy(bad)
7163 );
7164 }
7165 assert!(
7166 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
7167 "and the refusal left the old one alone"
7168 );
7169 }
7170
7171 #[test]
7172 fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
7173 let mut f = Fixture::new();
7174 f.run(&[b"SET", b"here", b"already"]);
7175 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
7179 assert_eq!(
7180 f.run(&[b"SET", b"k", b"v"]),
7181 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
7182 );
7183 assert_eq!(
7184 f.run(&[b"LPUSH", b"l", b"v"]),
7185 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
7186 );
7187 assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
7189 assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
7190 assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
7191
7192 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
7194 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
7195 }
7196
7197 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
7201 #[test]
7202 fn an_allkeys_policy_makes_room_instead_of_refusing() {
7203 let mut f = Fixture::new();
7204 let val = vec![b'v'; 256];
7205 for i in 0..24000u32 {
7206 let k = format!("key:{i:08}");
7207 f.run(&[b"SET", k.as_bytes(), &val]);
7208 }
7209 let full = f.server.memory_bytes();
7210 assert!(
7211 full > 3 * 1024 * 1024,
7212 "the arena is several segments: {full}"
7213 );
7214
7215 let limit = full - 2 * 1024 * 1024;
7219 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
7220 f.run(&[
7221 b"CONFIG",
7222 b"SET",
7223 b"maxmemory",
7224 limit.to_string().as_bytes(),
7225 ]);
7226
7227 for i in 0..2000u32 {
7231 let k = format!("new:{i:08}");
7232 assert_eq!(
7233 f.run(&[b"SET", k.as_bytes(), &val]),
7234 "+OK\r\n",
7235 "write {i} was refused"
7236 );
7237 f.server.refresh_memory();
7238 if f.server.memory_bytes() <= limit {
7239 break;
7240 }
7241 }
7242 assert!(
7243 f.server.memory_bytes() <= limit,
7244 "it never got under: {} against {limit}",
7245 f.server.memory_bytes()
7246 );
7247 let info = f.run(&[b"INFO", b"stats"]);
7248 assert!(!info.contains("evicted_keys:0"), "{info}");
7249 assert!(
7250 f.run(&[b"DBSIZE"]) != ":0\r\n",
7251 "and it did not empty the database to get there"
7252 );
7253 }
7254
7255 #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
7266 #[test]
7267 fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
7268 let mut f = Fixture::new();
7275 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
7276 let big = vec![b'v'; 200];
7277
7278 for i in 0..400u32 {
7279 let n = i.to_string();
7280 let n = n.as_bytes();
7281 f.run(&[b"SADD", b"s", n]);
7282 f.run(&[b"SADD", b"s2", &big]);
7283 f.run(&[b"HSET", b"h", n, &big]);
7284 f.run(&[b"RPUSH", b"l", &big]);
7285 f.run(&[b"ZADD", b"z", n, n]);
7286 f.run(&[b"ARSET", b"a", n, &big]);
7287 if i % 7 == 0 {
7288 f.run(&[b"SREM", b"s", n]);
7289 f.run(&[b"HDEL", b"h", n]);
7290 f.run(&[b"LPOP", b"l"]);
7291 f.run(&[b"ZREM", b"z", n]);
7292 f.run(&[b"ARDEL", b"a", n]);
7293 }
7294 if i % 53 == 0 {
7295 f.run(&[b"DEL", b"s2"]);
7298 }
7299 assert_eq!(
7300 f.server.settled_memory(),
7301 f.server.memory_bytes(),
7302 "after round {i}"
7303 );
7304 }
7305
7306 assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
7309 assert!(
7310 f.server.memory_bytes() > 512 * 1024,
7311 "{}",
7312 f.server.memory_bytes()
7313 );
7314
7315 f.run(&[b"FLUSHALL"]);
7317 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
7318 }
7319
7320 #[test]
7321 fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
7322 let mut f = Fixture::new();
7327 for i in 0..200u32 {
7328 let n = i.to_string();
7329 f.run(&[b"SADD", b"s", n.as_bytes()]);
7330 f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
7331 }
7332 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
7333 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
7334
7335 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
7336 for i in 200..400u32 {
7337 let n = i.to_string();
7338 f.run(&[b"SADD", b"s", n.as_bytes()]);
7339 }
7340 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
7341 assert_eq!(
7342 f.server.settled_memory(),
7343 f.server.memory_bytes(),
7344 "the writes it was not watching are in the number it started from"
7345 );
7346 }
7347
7348 #[test]
7349 fn evicted_keys_and_expired_keys_are_different_numbers() {
7350 let mut f = Fixture::new();
7351 f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
7354 f.server.advance_clock_ms(20);
7355 f.run(&[b"GET", b"gone"]);
7356 let info = f.run(&[b"INFO", b"stats"]);
7357 assert!(info.contains("expired_keys:1"), "{info}");
7358 assert!(info.contains("evicted_keys:0"), "{info}");
7359 }
7360
7361 #[test]
7362 fn the_two_counters_count_the_reads_and_nothing_else() {
7363 let mut f = Fixture::new();
7364 f.run(&[b"SET", b"k", b"v"]);
7365 f.run(&[b"GET", b"k"]);
7366 f.run(&[b"GET", b"nope"]);
7367 f.run(&[b"EXISTS", b"k", b"nope"]);
7368 let info = f.run(&[b"INFO", b"stats"]);
7371 assert!(info.contains("keyspace_hits:2"), "{info}");
7372 assert!(info.contains("keyspace_misses:2"), "{info}");
7373
7374 f.run(&[b"CONFIG", b"RESETSTAT"]);
7375 let info = f.run(&[b"INFO", b"stats"]);
7376 assert!(info.contains("keyspace_hits:0"), "{info}");
7377 assert!(info.contains("keyspace_misses:0"), "{info}");
7378 }
7379
7380 #[test]
7383 fn a_read_that_visits_its_key_twice_is_counted_once() {
7384 let mut f = Fixture::new();
7385 f.run(&[b"ZADD", b"z", b"1", b"m"]);
7386 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]);
7387 f.run(&[b"ZMSCORE", b"z", b"m", b"gone", b"also gone"]);
7388 f.run(&[b"OBJECT", b"ENCODING", b"z"]);
7389 f.run(&[b"DUMP", b"z"]);
7390 let info = f.run(&[b"INFO", b"stats"]);
7391 assert!(info.contains("keyspace_hits:4"), "{info}");
7392 assert!(info.contains("keyspace_misses:0"), "{info}");
7395 }
7396
7397 #[test]
7400 fn the_key_a_read_writes_afterwards_is_not_counted() {
7401 let mut f = Fixture::new();
7402 f.run(&[b"SET", b"s", b"v"]);
7403 f.run(&[b"COPY", b"s", b"dst"]);
7404 f.run(&[b"GETEX", b"s", b"EX", b"100"]);
7405 f.run(&[b"BITOP", b"AND", b"into", b"s", b"nope"]);
7406 let info = f.run(&[b"INFO", b"stats"]);
7407 assert!(info.contains("keyspace_hits:3"), "{info}");
7411 assert!(info.contains("keyspace_misses:1"), "{info}");
7412 }
7413
7414 #[test]
7415 fn the_object_subcommands_follow_the_policy() {
7416 let mut f = Fixture::new();
7417 f.run(&[b"SET", b"s", b"v"]);
7418 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
7422 assert!(
7423 f.run(&[b"OBJECT", b"FREQ", b"s"])
7424 .starts_with("-ERR An LFU maxmemory policy is not selected"),
7425 );
7426
7427 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
7428 assert!(
7429 f.run(&[b"OBJECT", b"IDLETIME", b"s"])
7430 .starts_with("-ERR An LFU maxmemory policy is selected"),
7431 );
7432 assert!(
7437 f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
7438 "FREQ should answer under an LFU policy"
7439 );
7440 }
7441
7442 #[test]
7443 fn object_says_which_rung_of_the_ladder_a_key_is_on() {
7444 let mut f = Fixture::new();
7445 f.run(&[b"SET", b"s", b"hello"]);
7446 f.run(&[b"SET", b"n", b"123"]);
7447 f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
7448 f.run(&[b"SADD", b"ss", b"a", b"b"]);
7449 f.run(&[b"HSET", b"h", b"f", b"v"]);
7450 for (key, want) in [
7451 (b"s".as_slice(), "embstr"),
7452 (b"n", "int"),
7453 (b"si", "intset"),
7454 (b"ss", "listpack"),
7455 (b"h", "listpack"),
7456 ] {
7457 let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
7458 assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
7459 }
7460
7461 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
7464 assert_eq!(
7465 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7466 "$10\r\nlistpackex\r\n"
7467 );
7468
7469 assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
7470 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
7471 assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
7472 }
7473
7474 #[test]
7475 fn object_answers_nil_for_a_key_that_is_not_there() {
7476 let mut f = Fixture::new();
7477 for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
7478 assert_eq!(
7479 f.run(&[b"OBJECT", sub, b"nokey"]),
7480 "$-1\r\n",
7481 "a nil and not an error, which is what 8.10.1 does"
7482 );
7483 }
7484 f.run(&[b"SET", b"s", b"v"]);
7487 assert!(
7488 f.run(&[b"OBJECT", b"FREQ", b"s"])
7489 .starts_with("-ERR An LFU maxmemory policy is not"),
7490 );
7491 assert_eq!(
7492 f.run(&[b"OBJECT", b"NOPE", b"s"]),
7493 "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
7494 );
7495 assert_eq!(
7496 f.run(&[b"OBJECT", b"ENCODING"]),
7497 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7498 );
7499 assert_eq!(
7500 f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
7501 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7502 );
7503 assert_eq!(
7504 f.run(&[b"OBJECT"]),
7505 "-ERR wrong number of arguments for 'object' command\r\n"
7506 );
7507 }
7508
7509 #[test]
7510 fn config_moves_the_ladder_and_object_encoding_agrees() {
7511 let mut f = Fixture::new();
7512 assert_eq!(
7513 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7514 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7515 "512 and not the 128 everyone remembers, which is what 8.10.1 says"
7516 );
7517 assert_eq!(
7520 f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
7521 "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
7522 );
7523 assert!(
7524 f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
7525 .starts_with("*8\r\n")
7526 );
7527 assert!(
7528 f.run(&[b"CONFIG", b"GET", b"set-max-*"])
7529 .starts_with("*6\r\n")
7530 );
7531
7532 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
7533 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
7534
7535 assert_eq!(
7536 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
7537 "+OK\r\n",
7538 "written under the old name and read back under the new one"
7539 );
7540 assert_eq!(
7541 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7542 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
7543 );
7544 assert_eq!(
7545 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7546 "$8\r\nlistpack\r\n",
7547 "the hash that already exists is left exactly where it was"
7548 );
7549 f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
7550 assert_eq!(
7551 f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
7552 "$9\r\nhashtable\r\n",
7553 "and the next one built goes straight to a table"
7554 );
7555
7556 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
7558 f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
7559 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
7560 f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
7561 f.run(&[b"SADD", b"s2", b"abcdefgh"]);
7562 assert_eq!(
7563 f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
7564 "$9\r\nhashtable\r\n"
7565 );
7566 }
7567
7568 #[test]
7569 fn config_set_takes_all_of_the_ladder_or_none_of_it() {
7570 let mut f = Fixture::new();
7571 assert_eq!(
7572 f.run(&[
7573 b"CONFIG",
7574 b"SET",
7575 b"hash-max-listpack-entries",
7576 b"7",
7577 b"set-max-listpack-entries",
7578 b"abc"
7579 ]),
7580 "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
7581 );
7582 assert_eq!(
7583 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7584 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7585 "the pair in front of the bad one did not go in"
7586 );
7587 assert_eq!(
7590 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
7591 "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
7592 );
7593 assert_eq!(
7594 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
7595 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
7596 );
7597 assert_eq!(
7600 f.run(&[
7601 b"CONFIG",
7602 b"SET",
7603 b"set-max-intset-entries",
7604 b"99999999999999999999"
7605 ]),
7606 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
7607 );
7608 assert_eq!(
7609 f.run(&[
7610 b"CONFIG",
7611 b"SET",
7612 b"set-max-intset-entries",
7613 b"9223372036854775807"
7614 ]),
7615 "+OK\r\n"
7616 );
7617 }
7618
7619 #[test]
7620 fn a_setting_moved_on_one_database_moved_on_all_of_them() {
7621 let mut f = Fixture::new();
7622 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
7623 f.run(&[b"SELECT", b"3"]);
7624 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7625 assert_eq!(
7626 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7627 "$9\r\nhashtable\r\n",
7628 "these are one server wide number in Redis, whatever a Keyspace carries"
7629 );
7630 }
7631
7632 #[test]
7633 fn info_reports_the_numbers_it_can_stand_behind() {
7634 let mut f = Fixture::new();
7635 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
7636 let all = f.run(&[b"INFO"]);
7637 assert!(all.contains("redis_version:8.8.0"), "{all}");
7638 assert!(
7639 all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
7640 "{all}"
7641 );
7642 assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
7643 assert!(all.contains("role:master"), "{all}");
7644 let clients = f.run(&[b"INFO", b"clients"]);
7646 assert!(clients.contains("connected_clients:0"), "{clients}");
7647 assert!(!clients.contains("redis_version"), "{clients}");
7648 assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
7649 }
7650
7651 #[test]
7658 fn commandstats_is_asked_for_and_replication_is_not() {
7659 let mut f = Fixture::new();
7660 for arg in ["", "all", "default", "everything"] {
7661 let info = if arg.is_empty() {
7662 f.run(&[b"INFO"])
7663 } else {
7664 f.run(&[b"INFO", arg.as_bytes()])
7665 };
7666 assert!(info.contains("redis_version"), "{arg}: {info}");
7667 assert!(info.contains("used_cpu_user"), "{arg}: {info}");
7668 assert!(info.contains("used_memory"), "{arg}: {info}");
7669 assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
7670 let asked = arg == "all" || arg == "everything";
7671 assert_eq!(
7672 info.contains("rejected_calls"),
7673 asked,
7674 "{arg} should{} carry the command counters: {info}",
7675 if asked { "" } else { " not" }
7676 );
7677 }
7678
7679 let cpu = f.run(&[b"INFO", b"cpu"]);
7680 assert!(cpu.contains("used_cpu_user"), "{cpu}");
7681 assert!(!cpu.contains("used_memory"), "{cpu}");
7682
7683 let stats = f.run(&[b"INFO", b"commandSTATS"]);
7686 assert!(!stats.contains("used_memory"), "{stats}");
7687 assert!(stats.contains("rejected_calls"), "{stats}");
7688
7689 let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
7691 assert!(pair.contains("used_cpu_user"), "{pair}");
7692 assert!(!pair.contains("master_repl_offset"), "{pair}");
7693
7694 let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
7695 assert!(with_all.contains("used_memory"), "{with_all}");
7696 assert!(with_all.contains("master_repl_offset"), "{with_all}");
7697 assert!(with_all.contains("rejected_calls"), "{with_all}");
7698 assert_eq!(
7700 with_all.matches("used_cpu_user_children").count(),
7701 1,
7702 "{with_all}"
7703 );
7704
7705 let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
7706 assert!(with_default.contains("used_memory"), "{with_default}");
7707 assert!(
7708 with_default.contains("master_repl_offset"),
7709 "{with_default}"
7710 );
7711 assert!(!with_default.contains("rejected_calls"), "{with_default}");
7712 assert_eq!(
7713 with_default.matches("used_cpu_user_children").count(),
7714 1,
7715 "{with_default}"
7716 );
7717 }
7718
7719 #[test]
7728 fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
7729 let mut f = Fixture::new();
7730 let info = f.run(&[b"INFO", b"memory"]);
7731 for field in [
7732 "total_system_memory:",
7733 "mem_cgroup_limit:",
7734 "mem_limit:",
7735 "mem_budget:",
7736 ] {
7737 assert!(info.contains(field), "no {field} in {info}");
7738 }
7739
7740 let field = |name: &str| -> u64 {
7741 info.lines()
7742 .find_map(|l| l.strip_prefix(name))
7743 .unwrap_or_else(|| panic!("no {name} in {info}"))
7744 .trim()
7745 .parse()
7746 .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
7747 };
7748 let limit = field("mem_limit:");
7749 assert_eq!(field("mem_budget:"), limit / 4, "{info}");
7750 if limit != 0 {
7753 let host = field("total_system_memory:");
7754 let cgroup = field("mem_cgroup_limit:");
7755 assert!(
7756 limit == host || limit == cgroup,
7757 "the limit came from neither number: {info}"
7758 );
7759 }
7760 }
7761
7762 #[test]
7771 fn a_command_counts_what_it_did_separately_from_what_it_refused() {
7772 let mut f = Fixture::new();
7773 f.run(&[b"SET", b"k", b"v"]);
7774 f.run(&[b"SET", b"k", b"w"]);
7775 f.run(&[b"LPUSH", b"k", b"x"]);
7777 f.run(&[b"LPUSH", b"k"]);
7779
7780 let stats = f.run(&[b"INFO", b"commandstats"]);
7781 assert!(
7782 stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
7783 "{stats}"
7784 );
7785 assert!(
7786 stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
7787 "{stats}"
7788 );
7789 assert!(
7790 !stats.contains("cmdstat_zadd"),
7791 "a command nobody has sent has no row: {stats}"
7792 );
7793 }
7794
7795 #[test]
7799 fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
7800 let (dead, live) = if cfg!(miri) {
7807 (375, 125)
7808 } else {
7809 (3_000, 1_000)
7810 };
7811 let mut f = Fixture::new();
7812 for i in 0..dead {
7813 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7814 }
7815 for i in 0..live {
7816 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7817 }
7818 let all = format!(":{}\r\n", dead + live);
7819 assert_eq!(f.run(&[b"DBSIZE"]), all);
7820 f.advance(100);
7821 assert_eq!(
7822 f.run(&[b"DBSIZE"]),
7823 all,
7824 "DBSIZE counts records and nothing has read past the dead ones yet"
7825 );
7826
7827 let rest = format!(":{live}\r\n");
7829 let mut spent = 0;
7830 for _ in 0..2_000 {
7831 spent += f.server.expire_step(4096);
7832 if f.run(&[b"DBSIZE"]) == rest {
7833 break;
7834 }
7835 }
7836 assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
7837 assert!(
7838 f.run(&[b"INFO", b"stats"])
7839 .contains(&format!("expired_keys:{dead}"))
7840 );
7841 for i in 0..live {
7842 assert_eq!(
7843 f.run(&[b"GET", format!("k{i}").as_bytes()]),
7844 "$1\r\nv\r\n",
7845 "it took a key that had no deadline"
7846 );
7847 }
7848 }
7849
7850 #[test]
7851 fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
7852 let n = if cfg!(miri) { 200 } else { 2_000 };
7856 let mut f = Fixture::new();
7857 for i in 0..n {
7858 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7859 }
7860 assert_eq!(f.server.expire_step(4096), 0);
7861 f.run(&[b"SELECT", b"3"]);
7863 f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
7864 f.advance(100);
7865 for _ in 0..64 {
7866 f.server.expire_step(4096);
7867 }
7868 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
7869 f.run(&[b"SELECT", b"0"]);
7870 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
7871 assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
7872 }
7873
7874 #[test]
7877 fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
7878 let mut f = Fixture::new();
7879 for i in 0..500u32 {
7880 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7881 }
7882 f.advance(100);
7883 let at = f.server.striped(0).now_ms();
7884 f.server.set_clock_ms(at);
7885 assert!(f.server.expire_slice(8) > 0, "the first one works");
7889 for _ in 0..1_000 {
7890 assert_eq!(
7891 f.server.expire_slice(8),
7892 0,
7893 "the millisecond has not moved and neither should this"
7894 );
7895 }
7896 assert!(
7897 f.server.striped(0).expires() > 400,
7898 "there is plenty left to take"
7899 );
7900 f.server.set_clock_ms(at + 1);
7901 assert!(f.server.expire_slice(8) > 0, "and then it goes again");
7902 }
7903
7904 #[test]
7907 fn info_keyspace_counts_the_keys_that_have_a_deadline() {
7908 let mut f = Fixture::new();
7909 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
7910 assert!(
7911 f.run(&[b"INFO", b"keyspace"])
7912 .contains("db0:keys=3,expires=0"),
7913 "none of them has one yet"
7914 );
7915 f.run(&[b"EXPIRE", b"a", b"1000"]);
7916 f.run(&[b"EXPIRE", b"b", b"1000"]);
7917 let two = f.run(&[b"INFO", b"keyspace"]);
7918 assert!(two.contains("db0:keys=3,expires=2"), "{two}");
7919 f.run(&[b"PERSIST", b"a"]);
7920 f.run(&[b"DEL", b"b"]);
7921 let none = f.run(&[b"INFO", b"keyspace"]);
7922 assert!(none.contains("db0:keys=2,expires=0"), "{none}");
7923
7924 f.run(&[b"SELECT", b"1"]);
7926 f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
7927 let both = f.run(&[b"INFO", b"keyspace"]);
7928 assert!(both.contains("db0:keys=2,expires=0"), "{both}");
7929 assert!(both.contains("db1:keys=1,expires=1"), "{both}");
7930 }
7931
7932 #[cfg(unix)]
7939 #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
7940 #[test]
7941 fn info_cpu_reports_processor_time_that_was_really_measured() {
7942 let mut f = Fixture::new();
7943 let cpu = f.run(&[b"INFO", b"cpu"]);
7944 assert!(cpu.contains("# CPU"), "{cpu}");
7945 assert!(cpu.contains("used_cpu_user:"), "{cpu}");
7947 assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
7948 assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
7949 assert!(!cpu.contains("redis_version"), "{cpu}");
7950
7951 let before = used_cpu_user(&cpu);
7955 let mut n = 0u64;
7956 let mut rounds = 0;
7957 while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
7958 for i in 0..1_000_000u64 {
7959 n = n.wrapping_add(i.wrapping_mul(i));
7960 }
7961 rounds += 1;
7962 assert!(rounds < 1_000, "cpu time never moved, n is {n}");
7966 }
7967 }
7968
7969 #[cfg(unix)]
7971 fn used_cpu_user(info: &str) -> f64 {
7972 info.lines()
7973 .find_map(|l| l.strip_prefix("used_cpu_user:"))
7974 .expect("no used_cpu_user in the reply")
7975 .trim()
7976 .parse()
7977 .expect("used_cpu_user is not a number")
7978 }
7979
7980 #[test]
7986 fn a_command_that_fails_leaves_nothing_half_written() {
7987 let mut f = Fixture::new();
7988 let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
7989 assert_eq!(reply, "-ERR offset is out of range\r\n");
7990 assert!(!reply.contains(':'), "no integer went out in front of it");
7991 }
7992
7993 #[test]
7994 fn quit_answers_first_and_closes_after() {
7995 let mut f = Fixture::new();
7996 let (flow, reply) = f.flow(&[b"QUIT"]);
7997 assert_eq!(reply, "+OK\r\n");
7998 assert_eq!(flow, Flow::Close);
7999 }
8000
8001 #[test]
8008 fn shutdown_writes_nothing_and_sets_the_flag() {
8009 let mut f = Fixture::new();
8010 assert!(!f.server.stopping(), "nobody has asked yet");
8011
8012 let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
8013 assert_eq!(reply, "");
8014 assert_eq!(flow, Flow::Close);
8015 assert!(f.server.stopping());
8016 }
8017
8018 #[test]
8026 fn shutdown_takes_the_flags_redis_takes() {
8027 for flags in [
8028 &[b"NOSAVE".as_slice()][..],
8029 &[b"SAVE"],
8030 &[b"NOW"],
8031 &[b"FORCE"],
8032 &[b"nosave"],
8033 &[b"NOW", b"NOW"],
8034 &[b"SAVE", b"SAVE"],
8035 &[b"NOSAVE", b"NOW", b"FORCE"],
8036 ] {
8037 let mut f = Fixture::new();
8038 let mut parts = vec![b"SHUTDOWN".as_slice()];
8039 parts.extend_from_slice(flags);
8040 let (flow, reply) = f.flow(&parts);
8041 assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
8042 assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
8043 assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
8044 }
8045
8046 for flags in [
8047 &[b"BOGUS".as_slice()][..],
8048 &[b"SAVE", b"NOSAVE"],
8049 &[b"NOSAVE", b"SAVE"],
8050 &[b"ABORT", b"NOW"],
8051 &[b"NOSAVE", b"ABORT"],
8052 &[b"NOW", b"FORCE", b"ABORT"],
8053 ] {
8054 let mut f = Fixture::new();
8055 let mut parts = vec![b"SHUTDOWN".as_slice()];
8056 parts.extend_from_slice(flags);
8057 assert_eq!(
8058 f.run(&parts),
8059 "-ERR syntax error\r\n",
8060 "SHUTDOWN {flags:?} was accepted"
8061 );
8062 assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
8063 }
8064 }
8065
8066 #[test]
8074 fn shutdown_abort_never_has_anything_to_abort() {
8075 let mut f = Fixture::new();
8076 for parts in [
8077 &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
8078 &[b"SHUTDOWN", b"ABORT", b"ABORT"],
8079 ] {
8080 assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
8081 assert!(!f.server.stopping(), "an abort stopped the server");
8082 }
8083 }
8084
8085 struct Backups {
8093 f: Fixture,
8094 dir: PathBuf,
8095 }
8096
8097 impl Backups {
8098 fn new(name: &str) -> Backups {
8099 let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
8100 let _ = std::fs::remove_dir_all(&dir);
8101 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
8102 let mut f = Fixture::new();
8103 f.server.set_dir(dir.clone());
8104 Backups { f, dir }
8105 }
8106
8107 fn run(&mut self, parts: &[&[u8]]) -> String {
8108 self.f.run(parts)
8109 }
8110
8111 fn files(&self) -> Vec<String> {
8113 let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
8114 Ok(entries) => entries
8115 .filter_map(|e| e.ok())
8116 .map(|e| e.file_name().to_string_lossy().into_owned())
8117 .collect(),
8118 Err(_) => Vec::new(),
8119 };
8120 names.sort();
8121 names
8122 }
8123
8124 fn read(&self, name: &str) -> Vec<u8> {
8125 std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
8126 }
8127 }
8128
8129 impl Drop for Backups {
8130 fn drop(&mut self) {
8131 let _ = std::fs::remove_dir_all(&self.dir);
8132 }
8133 }
8134
8135 #[test]
8138 fn backup_walks_the_states_the_reference_walks() {
8139 let mut b = Backups::new("states");
8140 let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
8141
8142 assert!(status(&mut b).contains("idle"));
8143 assert!(b.files().is_empty(), "an idle server has written a backup");
8144
8145 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
8146 assert!(status(&mut b).contains("incrementing"));
8147 assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
8148
8149 assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
8150 assert!(status(&mut b).contains("sealed"));
8151 assert_eq!(
8152 b.files(),
8153 [
8154 "appendonly.aof.1.base.rdb",
8155 "appendonly.aof.1.incr.aof",
8156 "appendonly.aof.manifest",
8157 ]
8158 );
8159
8160 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
8161 assert!(status(&mut b).contains("idle"));
8162 assert!(b.files().is_empty(), "cleanup left something behind");
8163 }
8164
8165 #[test]
8167 fn backup_refuses_the_moves_the_reference_refuses() {
8168 let mut b = Backups::new("refusals");
8169
8170 assert_eq!(
8171 b.run(&[b"BACKUP", b"SEAL"]),
8172 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
8173 );
8174 assert_eq!(
8175 b.run(&[b"BACKUP", b"ABORT"]),
8176 "-ERR No backup in progress\r\n"
8177 );
8178 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
8181
8182 b.run(&[b"BACKUP", b"START"]);
8183 assert_eq!(
8184 b.run(&[b"BACKUP", b"START"]),
8185 "-ERR A backup is already in progress, ABORT it first\r\n"
8186 );
8187 assert_eq!(
8188 b.run(&[b"BACKUP", b"CLEANUP"]),
8189 "-ERR Backup is in progress\r\n"
8190 );
8191
8192 b.run(&[b"BACKUP", b"SEAL"]);
8193 assert_eq!(
8194 b.run(&[b"BACKUP", b"START"]),
8195 "-ERR A sealed backup exists, CLEANUP it first\r\n"
8196 );
8197 assert_eq!(
8198 b.run(&[b"BACKUP", b"SEAL"]),
8199 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
8200 );
8201 assert_eq!(
8202 b.run(&[b"BACKUP", b"ABORT"]),
8203 "-ERR No backup in progress\r\n"
8204 );
8205 }
8206
8207 #[test]
8214 fn backup_abort_removes_the_file_and_says_who_did_it() {
8215 let mut b = Backups::new("abort");
8216 b.run(&[b"BACKUP", b"START"]);
8217 assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
8218
8219 let status = b.run(&[b"BACKUP", b"STATUS"]);
8220 assert!(status.contains("failed"), "{status}");
8221 assert!(status.contains("aborted by user"), "{status}");
8222 assert!(b.files().is_empty(), "abort left the base file behind");
8223 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
8224
8225 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
8227 assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
8228 let status = b.run(&[b"BACKUP", b"STATUS"]);
8229 assert!(status.contains("incrementing"), "{status}");
8230 assert!(!status.contains("aborted"), "the old error was kept");
8231 }
8232
8233 #[test]
8235 fn backup_list_names_the_files_that_are_pinned_so_far() {
8236 let mut b = Backups::new("list");
8237 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
8238
8239 b.run(&[b"BACKUP", b"START"]);
8240 let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
8241 let base = base.to_string_lossy().into_owned();
8242 assert_eq!(
8243 b.run(&[b"BACKUP", b"LIST"]),
8244 format!("*1\r\n${}\r\n{base}\r\n", base.len())
8245 );
8246
8247 b.run(&[b"BACKUP", b"SEAL"]);
8248 let listed = b.run(&[b"BACKUP", b"LIST"]);
8249 assert!(listed.starts_with("*3\r\n"), "{listed}");
8250 let names: Vec<&str> = listed
8253 .lines()
8254 .filter(|l| l.starts_with('/') || l.contains(":\\"))
8255 .collect();
8256 assert_eq!(names.len(), 3, "{listed}");
8257 assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
8258 assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
8259 assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
8260 }
8261
8262 #[test]
8270 fn a_backup_holds_the_dataset_as_it_was_at_start() {
8271 let mut b = Backups::new("contents");
8272 b.run(&[b"SET", b"bk", b"v1"]);
8273 b.run(&[b"BACKUP", b"START"]);
8274 b.run(&[b"SET", b"bk", b"v2"]);
8275 b.run(&[b"BACKUP", b"SEAL"]);
8276
8277 let base = b.read("appendonly.aof.1.base.rdb");
8278 assert!(base.starts_with(b"REDIS"), "not an RDB file");
8279 assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
8280 assert!(
8281 !base.windows(2).any(|w| w == b"v2"),
8282 "the base file moved on after START"
8283 );
8284 let at = base
8289 .windows(8)
8290 .position(|w| w == b"aof-base")
8291 .expect("no aof-base aux field");
8292 assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
8293
8294 assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
8295 assert_eq!(
8296 String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
8297 "file appendonly.aof.1.base.rdb seq 1 type b\n\
8298 file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
8299 );
8300 }
8301
8302 #[test]
8305 fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
8306 let mut b = Backups::new("status");
8307 b.f.server.set_clock_ms(1_700_000_000_000);
8308
8309 assert_eq!(
8310 b.run(&[b"BACKUP", b"STATUS"]),
8311 "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
8312 $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
8313 );
8314
8315 b.f.out = Out::new(Proto::Resp3);
8316 b.run(&[b"BACKUP", b"START"]);
8317 assert_eq!(
8318 b.run(&[b"BACKUP", b"STATUS"]),
8319 "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
8320 $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
8321 );
8322
8323 b.run(&[b"BACKUP", b"SEAL"]);
8324 let sealed = b.run(&[b"BACKUP", b"STATUS"]);
8325 assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
8326 }
8327
8328 #[test]
8331 fn a_sealed_backup_is_swept_away_after_the_timeout() {
8332 let mut b = Backups::new("ttl");
8333 b.f.server.set_clock_ms(1_000_000);
8334 assert_eq!(
8335 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
8336 "+OK\r\n"
8337 );
8338 b.run(&[b"BACKUP", b"START"]);
8339 b.run(&[b"BACKUP", b"SEAL"]);
8340
8341 b.f.server.set_clock_ms(1_000_000 + 59_000);
8343 b.f.server.backup_expire();
8344 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
8345 assert_eq!(b.files().len(), 3);
8346
8347 b.f.server.set_clock_ms(1_000_000 + 60_000);
8348 b.f.server.backup_expire();
8349 let status = b.run(&[b"BACKUP", b"STATUS"]);
8350 assert!(status.contains("idle"), "{status}");
8351 assert!(b.files().is_empty(), "the timeout left the files behind");
8352
8353 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
8355 b.run(&[b"BACKUP", b"START"]);
8356 b.run(&[b"BACKUP", b"SEAL"]);
8357 b.f.server.set_clock_ms(9_000_000_000);
8358 b.f.server.backup_expire();
8359 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
8360 }
8361
8362 #[test]
8365 fn the_backup_settings_behave_the_way_the_reference_does() {
8366 let mut b = Backups::new("config");
8367 let dir = b.dir.to_string_lossy().into_owned();
8368
8369 assert_eq!(
8370 b.run(&[b"CONFIG", b"GET", b"dir"]),
8371 format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
8372 );
8373 assert_eq!(
8374 b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
8375 "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
8376 );
8377 assert_eq!(
8378 b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
8379 "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
8380 );
8381
8382 assert_eq!(
8385 b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
8386 "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
8387 );
8388 assert_eq!(
8389 b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
8390 "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
8391 );
8392 assert!(
8393 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
8394 .contains("argument couldn't be parsed into an integer")
8395 );
8396 assert!(
8397 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
8398 .contains("argument must be between 0 and 9223372036854775807 inclusive")
8399 );
8400 }
8401
8402 #[test]
8404 fn backup_help_is_the_text_the_reference_sends() {
8405 let mut f = Fixture::new();
8406 let help = f.run(&[b"BACKUP", b"HELP"]);
8407 assert!(help.starts_with("*17\r\n"), "{help}");
8408 assert!(
8409 help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
8410 );
8411 assert!(help.contains("+ Start a new backup into the configured 'backupdirname'.\r\n"));
8412 assert!(help.contains("+ Freeze the current backup (BASE + INCR + manifest).\r\n"));
8413 assert!(help.contains("+ Return this help.\r\n+HELP\r\n+ Print this help.\r\n"));
8414 }
8415
8416 #[test]
8424 fn backup_refuses_what_it_cannot_read() {
8425 let mut f = Fixture::new();
8426 assert_eq!(
8427 f.run(&[b"BACKUP"]),
8428 "-ERR wrong number of arguments for 'backup' command\r\n"
8429 );
8430 assert_eq!(
8431 f.run(&[b"BACKUP", b"START", b"x"]),
8432 "-ERR wrong number of arguments for 'backup' command\r\n"
8433 );
8434 assert_eq!(
8435 f.run(&[b"BACKUP", b"NOPE"]),
8436 "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
8437 );
8438 }
8439
8440 #[test]
8441 fn the_command_counter_counts_every_command_including_the_bad_ones() {
8442 let mut f = Fixture::new();
8443 f.run(&[b"PING"]);
8444 f.run(&[b"NOPE"]);
8445 f.run(&[b"GET"]);
8446 assert_eq!(f.server.totals().commands, 3);
8447 }
8448
8449 #[test]
8450 fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
8451 let mut server = Server::new();
8452 server.set_threads(2);
8453 server.mine().turn.store(0, Relaxed);
8456 server.locals[1].mark(1 << 9);
8457 server.collect_marks();
8458 assert!(server.mine().wanted(9));
8459 assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
8461 }
8462
8463 #[test]
8464 fn what_two_threads_counted_is_added_up_when_info_asks() {
8465 let mut server = Server::new();
8466 server.set_threads(2);
8467 let ping = lookup(b"PING").expect("PING is a command");
8471 for (at, calls) in [(0, 2), (1, 3)] {
8472 let counters = &server.locals[at];
8473 for _ in 0..calls {
8474 counters.stats.commands.bump();
8475 counters.cmdstats.at(ping).calls.bump();
8476 }
8477 counters.stats.opened();
8478 }
8479 assert_eq!(server.totals().commands, 5);
8480 assert_eq!(server.totals().clients, 2);
8481 assert_eq!(server.totals().connections, 2);
8482 let rows: Vec<_> = server.command_stats().collect();
8483 assert_eq!(rows.len(), 1);
8484 assert_eq!(rows[0].0, "ping");
8485 assert_eq!(rows[0].1.calls, 5);
8486 server.reset_stats();
8489 assert_eq!(server.totals().commands, 0);
8490 assert_eq!(server.totals().connections, 0);
8491 assert_eq!(server.totals().clients, 2);
8492 }
8493
8494 #[test]
8495 fn the_parked_count_says_what_the_waiter_list_says() {
8496 let mut f = Fixture::new();
8497 assert_eq!(f.server.parked(), 0);
8498 for client in 1..=3u64 {
8499 f.session = Session::new(client);
8500 assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
8501 }
8502 assert_eq!(f.server.parked(), 3);
8503 assert_eq!(f.server.waiters().len(), 3);
8504
8505 f.server.forget_waiters(2);
8509 assert_eq!(f.server.parked(), f.server.waiters().len());
8510 f.server.forget_waiters(1);
8511 assert_eq!(f.server.parked(), f.server.waiters().len());
8512 f.run(&[b"RPUSH", b"q", b"v"]);
8513 let mut out = Out::new(Proto::Resp2);
8514 assert!(f.server.serve_waiter(3, 0, &mut out));
8515 f.server.forget_waiters(3);
8516 assert_eq!(f.server.parked(), 0);
8517 assert!(f.server.waiters().is_empty());
8518 }
8519
8520 #[test]
8521 fn a_set_goes_from_bytes_to_bytes() {
8522 let mut f = Fixture::new();
8523 assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
8524 assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
8525 assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
8526 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
8527 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
8528 assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
8529 assert_eq!(
8530 f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
8531 "*3\r\n:1\r\n:0\r\n:1\r\n"
8532 );
8533 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
8534 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8535 }
8536
8537 #[test]
8538 fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
8539 let mut f = Fixture::new();
8540 assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
8541 assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
8542 assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
8543 assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
8544 assert_eq!(
8545 f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
8546 "*2\r\n:0\r\n:0\r\n"
8547 );
8548 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
8549 }
8550
8551 #[test]
8552 fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
8553 let mut f = Fixture::new();
8557 f.run(&[b"SADD", b"s", b"one"]);
8558 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
8559
8560 f.run(&[b"HELLO", b"3"]);
8561 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
8562 }
8563
8564 #[test]
8565 fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
8566 let mut f = Fixture::new();
8569 f.run(&[b"SADD", b"s", b"42"]);
8570 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
8571 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
8572 assert_eq!(
8573 f.run(&[b"SISMEMBER", b"s", b"042"]),
8574 ":0\r\n",
8575 "the member is the bytes and not the number they parse to"
8576 );
8577 }
8578
8579 #[test]
8580 fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
8581 let mut f = Fixture::new();
8582 f.run(&[b"SET", b"str", b"v"]);
8583 f.run(&[b"SADD", b"set", b"a"]);
8584
8585 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8586 assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
8587 assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
8588 assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
8589 assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
8590 assert_eq!(f.run(&[b"GET", b"set"]), wrong);
8591 assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
8592 assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
8593 assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
8594
8595 assert_eq!(
8598 f.run(&[b"MGET", b"str", b"set", b"nope"]),
8599 "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
8600 );
8601 assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
8603 assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
8604 }
8605
8606 #[test]
8607 fn a_wrongtype_leaves_nothing_half_written() {
8608 let mut f = Fixture::new();
8612 f.run(&[b"SET", b"k", b"v"]);
8613 let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
8614 assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
8615 assert!(!reply.contains('*'), "an array header went out in front");
8616 }
8617
8618 #[test]
8619 fn emptying_a_set_takes_the_key_with_it() {
8620 let mut f = Fixture::new();
8621 f.run(&[b"SADD", b"s", b"a", b"b"]);
8622 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
8623 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
8624 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8625 assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
8626 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8627 }
8628
8629 fn split_scan(reply: &str) -> (String, Vec<String>) {
8635 let mut lines = reply.split("\r\n");
8636 assert_eq!(lines.next(), Some("*2"), "got {reply}");
8637 lines.next().expect("the cursor header");
8638 let cursor = lines.next().expect("the cursor").to_owned();
8639 let header = lines.next().expect("the member header");
8640 let n: usize = header[1..].parse().expect("a member count");
8641 let mut members = Vec::with_capacity(n);
8642 for _ in 0..n {
8643 lines.next().expect("a member header");
8644 members.push(lines.next().expect("a member").to_owned());
8645 }
8646 (cursor, members)
8647 }
8648
8649 #[test]
8650 fn popping_takes_a_member_off_the_set_and_hands_it_back() {
8651 let mut f = Fixture::new();
8652 f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
8653
8654 let one = f.run(&[b"SPOP", b"s"]);
8655 assert!(
8656 ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
8657 "got {one}"
8658 );
8659 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8660
8661 let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
8663 assert!(rest.starts_with("*3\r\n"), "got {rest}");
8664 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8665 assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
8667 assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
8668 }
8669
8670 #[test]
8671 fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
8672 let mut f = Fixture::new();
8677 f.run(&[b"HELLO", b"3"]);
8678 f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
8679
8680 assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
8681 assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
8683
8684 f.run(&[b"SADD", b"one", b"z"]);
8688 assert_eq!(
8689 f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
8690 "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
8691 );
8692 }
8693
8694 #[test]
8695 fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
8696 let mut f = Fixture::new();
8697 f.run(&[b"SADD", b"s", b"only"]);
8698 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8699 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8700 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
8701
8702 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
8703 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
8706 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
8707 assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
8709 }
8710
8711 #[test]
8712 fn a_pop_count_that_is_not_a_positive_number_says_so() {
8713 let mut f = Fixture::new();
8714 f.run(&[b"SADD", b"s", b"a"]);
8715 let bad = "-ERR value is out of range, must be positive\r\n";
8716 assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
8717 assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
8718 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
8719 assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
8721 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
8722 }
8723
8724 #[test]
8725 fn a_scan_walks_a_set_of_any_size_exactly_once() {
8726 let mut f = Fixture::new();
8727 let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
8728 let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
8729 .into_iter()
8730 .chain(members.iter().map(Vec::as_slice))
8731 .collect();
8732 f.run(&args);
8733
8734 let mut seen = Vec::new();
8735 let mut cursor = "0".to_owned();
8736 loop {
8737 let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
8738 let (next, got) = split_scan(&reply);
8739 seen.extend(got);
8740 cursor = next;
8741 if cursor == "0" {
8742 break;
8743 }
8744 }
8745 seen.sort();
8746 seen.dedup();
8747 assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
8748
8749 f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
8752 let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
8753 assert_eq!(cursor, "0");
8754 assert_eq!(got.len(), 3);
8755 assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
8757 }
8758
8759 #[test]
8760 fn a_scan_takes_match_and_count_and_refuses_anything_else() {
8761 let mut f = Fixture::new();
8762 f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
8763
8764 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
8765 let mut got = got;
8766 got.sort();
8767 assert_eq!(got, ["aa", "ab"]);
8768
8769 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
8772 let mut got = got;
8773 got.sort();
8774 assert_eq!(got, ["12", "13"]);
8775
8776 assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
8777 assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
8778 assert_eq!(
8779 f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
8780 "-ERR syntax error\r\n"
8781 );
8782 assert_eq!(
8785 f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
8786 "-ERR syntax error\r\n"
8787 );
8788 }
8789
8790 #[test]
8791 fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
8792 let mut f = Fixture::new();
8793 f.run(&[b"SADD", b"src", b"a", b"b"]);
8794 f.run(&[b"SADD", b"dst", b"c"]);
8795
8796 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
8797 assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
8798 assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
8799 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
8801 assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
8802
8803 assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
8806 assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
8807 assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
8808 }
8809
8810 #[test]
8811 fn moving_checks_the_types_in_the_order_redis_checks_them() {
8812 let mut f = Fixture::new();
8816 f.run(&[b"SET", b"str", b"v"]);
8817 f.run(&[b"SADD", b"set", b"a"]);
8818
8819 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8820 assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
8821 assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
8822 assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
8823 assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
8824 assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
8825 assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
8826 assert_eq!(
8827 f.run(&[b"SISMEMBER", b"set", b"a"]),
8828 ":1\r\n",
8829 "and none of that moved anything"
8830 );
8831 }
8832
8833 #[test]
8834 fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8835 let mut f = Fixture::new();
8838 f.run(&[b"SADD", b"s", b"a"]);
8839 for bad in [
8840 &[b"SSCAN".as_slice(), b"s", b"abc"][..],
8841 &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
8842 &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
8843 ] {
8844 let reply = f.run(bad);
8845 assert!(reply.starts_with("-ERR"), "got {reply}");
8846 assert!(!reply.contains('*'), "an array header went out in front");
8847 }
8848 }
8849
8850 #[test]
8851 fn a_hash_writes_reads_and_deletes_its_fields() {
8852 let mut f = Fixture::new();
8853 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
8854 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
8855 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8856 assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
8857 assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
8858 assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
8859 assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
8860 assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
8861 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
8862 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
8863
8864 assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
8867
8868 assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
8869 assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
8870 assert_eq!(
8871 f.run(&[b"EXISTS", b"h"]),
8872 ":0\r\n",
8873 "and losing the last field lost the key"
8874 );
8875 }
8876
8877 #[test]
8878 fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
8879 let mut f = Fixture::new();
8880 f.run(&[b"HSET", b"h", b"a", b"1"]);
8881 assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
8882 assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
8883 assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
8884 assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
8885 assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
8886
8887 f.run(&[b"HELLO", b"3"]);
8888 assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
8889 assert_eq!(
8890 f.run(&[b"HGETALL", b"nokey"]),
8891 "%0\r\n",
8892 "a missing key is the empty hash and never a nil"
8893 );
8894 assert_eq!(
8895 f.run(&[b"HKEYS", b"h"]),
8896 "*1\r\n$1\r\na\r\n",
8897 "and the two that answer one side stay arrays"
8898 );
8899 }
8900
8901 #[test]
8902 fn hmget_answers_once_per_field_and_hmset_answers_ok() {
8903 let mut f = Fixture::new();
8904 assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
8905 assert_eq!(
8906 f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
8907 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
8908 "the reply is positional, so b is a nil and not a gap"
8909 );
8910 assert_eq!(
8911 f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
8912 "*2\r\n$-1\r\n$-1\r\n",
8913 "and a missing key is all nils rather than an empty array"
8914 );
8915
8916 assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
8917 assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
8918 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8919 }
8920
8921 #[test]
8922 fn a_hash_counts_up_and_says_so_when_it_cannot() {
8923 let mut f = Fixture::new();
8924 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
8925 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
8926 assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
8927 assert_eq!(
8928 f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
8929 "$4\r\n10.5\r\n",
8930 "a bulk string and not a double, on both protocols"
8931 );
8932
8933 f.run(&[b"HSET", b"h", b"s", b"words"]);
8934 let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
8935 assert!(
8936 bad.starts_with("-ERR hash value is not an integer"),
8937 "{bad}"
8938 );
8939 let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
8940 assert!(
8941 bad.starts_with("-ERR value is not an integer"),
8942 "a bad argument is not yet a hash value, {bad}"
8943 );
8944 assert_eq!(
8945 f.run(&[b"HGET", b"h", b"s"]),
8946 "$5\r\nwords\r\n",
8947 "and neither of them wrote anything"
8948 );
8949 }
8950
8951 #[test]
8952 fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
8953 let fields = if cfg!(miri) { 96 } else { 500 };
8958 let mut f = Fixture::new();
8959 for i in 0..fields {
8960 let field = format!("field-{i}");
8961 let value = format!("value-{i}");
8962 f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
8963 }
8964
8965 let mut seen: Vec<String> = Vec::new();
8966 let mut cursor = "0".to_owned();
8967 loop {
8968 let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
8969 let (next, items) = scan_reply(&reply);
8970 assert_eq!(items.len() % 2, 0, "a pair went out half written");
8971 for pair in items.chunks(2) {
8972 assert_eq!(
8973 pair[0].strip_prefix("field-"),
8974 pair[1].strip_prefix("value-"),
8975 "a field came back with someone else's value"
8976 );
8977 seen.push(pair[0].clone());
8978 }
8979 cursor = next;
8980 if cursor == "0" {
8981 break;
8982 }
8983 }
8984 seen.sort();
8985 seen.dedup();
8986 assert_eq!(seen.len(), fields, "every field once and only once");
8987
8988 let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
8989 assert!(
8990 items.iter().all(|s| s.starts_with("field-")),
8991 "NOVALUES still sent the values"
8992 );
8993
8994 let last = fields - 1;
8995 let (_, one) = scan_reply(&f.run(&[
8996 b"HSCAN",
8997 b"h",
8998 b"0",
8999 b"MATCH",
9000 format!("field-{last}").as_bytes(),
9001 b"COUNT",
9002 b"1000",
9003 ]));
9004 assert_eq!(
9005 one,
9006 [format!("field-{last}"), format!("value-{last}")],
9007 "MATCH is on the field"
9008 );
9009 }
9010
9011 #[test]
9012 fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
9013 let mut f = Fixture::new();
9014 f.run(&[b"HSET", b"h", b"a", b"1"]);
9015 assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
9016 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
9017 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
9018 assert_eq!(
9019 f.run(&[b"HRANDFIELD", b"h", b"3"]),
9020 "*1\r\n$1\r\na\r\n",
9021 "a positive count is capped at the size of the hash"
9022 );
9023 assert_eq!(
9024 f.run(&[b"HRANDFIELD", b"h", b"-3"]),
9025 "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
9026 "and a negative one repeats itself"
9027 );
9028 assert_eq!(
9029 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
9030 "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
9031 "flat on RESP2"
9032 );
9033
9034 f.run(&[b"HELLO", b"3"]);
9035 assert_eq!(
9036 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
9037 "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
9038 "and nested on RESP3, but still an array and never a map"
9039 );
9040 }
9041
9042 #[test]
9043 fn every_hash_command_says_wrongtype_and_writes_nothing() {
9044 let mut f = Fixture::new();
9045 f.run(&[b"SET", b"str", b"v"]);
9046 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9047
9048 for cmd in [
9049 &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
9050 &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
9051 &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
9052 &[b"HGET".as_slice(), b"str", b"f"][..],
9053 &[b"HMGET".as_slice(), b"str", b"f"][..],
9054 &[b"HDEL".as_slice(), b"str", b"f"][..],
9055 &[b"HLEN".as_slice(), b"str"][..],
9056 &[b"HEXISTS".as_slice(), b"str", b"f"][..],
9057 &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
9058 &[b"HGETALL".as_slice(), b"str"][..],
9059 &[b"HKEYS".as_slice(), b"str"][..],
9060 &[b"HVALS".as_slice(), b"str"][..],
9061 &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
9062 &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
9063 &[b"HRANDFIELD".as_slice(), b"str"][..],
9064 &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
9065 &[b"HSCAN".as_slice(), b"str", b"0"][..],
9066 ] {
9067 let reply = f.run(cmd);
9068 assert_eq!(reply, wrong, "{:?}", cmd[0]);
9069 }
9070 assert_eq!(
9071 f.run(&[b"GET", b"str"]),
9072 "$1\r\nv\r\n",
9073 "and none of them touched the value"
9074 );
9075 }
9076
9077 #[test]
9078 fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
9079 let mut f = Fixture::new();
9080 f.run(&[b"HSET", b"h", b"f", b"v"]);
9081 for bad in [
9082 &[b"HSCAN".as_slice(), b"h", b"abc"][..],
9083 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
9084 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
9085 &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
9086 ] {
9087 let reply = f.run(bad);
9088 assert!(reply.starts_with("-ERR"), "got {reply}");
9089 assert!(!reply.contains('*'), "an array header went out in front");
9090 }
9091 }
9092
9093 #[test]
9094 fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
9095 let mut f = Fixture::new();
9096 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9097 assert_eq!(
9098 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
9099 "*1\r\n:1\r\n"
9100 );
9101 assert_eq!(
9102 f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
9103 "*3\r\n:100\r\n:-1\r\n:-2\r\n",
9104 "one answer per field, and the two sentinels are TTL's own"
9105 );
9106
9107 let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
9110 assert!((99_000..=100_000).contains(&ms), "got {ms}");
9111 let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
9112 let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
9113 assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
9114 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
9115
9116 assert_eq!(
9117 f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
9118 "*3\r\n:1\r\n:-1\r\n:-2\r\n",
9119 "one for the deadline taken off, and it does not say what it was"
9120 );
9121 assert_eq!(
9122 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9123 "*1\r\n:-1\r\n"
9124 );
9125 assert_eq!(
9126 f.run(&[b"HGET", b"h", b"a"]),
9127 "$1\r\n1\r\n",
9128 "and the field is still there with the value it had"
9129 );
9130 }
9131
9132 #[test]
9133 fn a_deadline_that_has_already_gone_deletes_the_field_now() {
9134 let mut f = Fixture::new();
9135 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9136 assert_eq!(
9137 f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
9138 "*1\r\n:2\r\n",
9139 "two, and not one, because nothing was stored"
9140 );
9141 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
9142 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
9143
9144 assert_eq!(
9145 f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
9146 "*1\r\n:2\r\n"
9147 );
9148 assert_eq!(
9149 f.run(&[b"EXISTS", b"h"]),
9150 ":0\r\n",
9151 "and the last field going took the key with it"
9152 );
9153
9154 f.run(&[b"HSET", b"h", b"a", b"1"]);
9157 assert_eq!(
9158 f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
9159 "*1\r\n:2\r\n"
9160 );
9161 }
9162
9163 #[test]
9164 fn a_field_is_gone_once_its_moment_passes() {
9165 let mut f = Fixture::new();
9166 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9167 assert_eq!(
9168 f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
9169 "*1\r\n:1\r\n"
9170 );
9171 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
9172
9173 f.server.advance_clock_ms(60);
9177 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
9178 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
9179 assert_eq!(
9180 f.run(&[b"HGETALL", b"h"]),
9181 "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
9182 "and the walks do not hand back a field that has expired"
9183 );
9184 }
9185
9186 #[test]
9187 fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
9188 let mut f = Fixture::new();
9189 for cmd in [
9190 &[
9191 b"HEXPIRE".as_slice(),
9192 b"nokey",
9193 b"100",
9194 b"FIELDS",
9195 b"2",
9196 b"a",
9197 b"b",
9198 ][..],
9199 &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
9200 &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
9201 &[
9202 b"HEXPIRETIME".as_slice(),
9203 b"nokey",
9204 b"FIELDS",
9205 b"2",
9206 b"a",
9207 b"b",
9208 ][..],
9209 &[
9210 b"HPERSIST".as_slice(),
9211 b"nokey",
9212 b"FIELDS",
9213 b"2",
9214 b"a",
9215 b"b",
9216 ][..],
9217 ] {
9218 assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
9219 }
9220 }
9221
9222 #[test]
9223 fn writing_a_field_clears_the_deadline_that_was_on_it() {
9224 let mut f = Fixture::new();
9225 f.run(&[b"HSET", b"h", b"a", b"1"]);
9226 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
9227 f.run(&[b"HSET", b"h", b"a", b"2"]);
9228 assert_eq!(
9229 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9230 "*1\r\n:-1\r\n",
9231 "Redis has done this since 7.4, and it is why HGETEX exists"
9232 );
9233 }
9234
9235 #[test]
9236 fn the_four_conditions_reach_the_store_the_way_they_were_written() {
9237 let mut f = Fixture::new();
9238 f.run(&[b"HSET", b"h", b"a", b"1"]);
9239 assert_eq!(
9240 f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
9241 "*1\r\n:0\r\n",
9242 "XX on a field with no deadline changes nothing"
9243 );
9244 assert_eq!(
9245 f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
9246 "*1\r\n:1\r\n"
9247 );
9248 assert_eq!(
9249 f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
9250 "*1\r\n:0\r\n",
9251 "and NX will not move one that is already there"
9252 );
9253 assert_eq!(
9254 f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
9255 "*1\r\n:0\r\n"
9256 );
9257 assert_eq!(
9258 f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
9259 "*1\r\n:1\r\n"
9260 );
9261 assert_eq!(
9262 f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
9263 "*1\r\n:1\r\n"
9264 );
9265 assert_eq!(
9266 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9267 "*1\r\n:50\r\n"
9268 );
9269 }
9270
9271 #[test]
9272 fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
9273 let mut f = Fixture::new();
9274 f.run(&[b"HSET", b"h", b"a", b"1"]);
9275 for (bad, want) in [
9276 (
9277 &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
9278 "-ERR invalid expire time, must be >= 0",
9279 ),
9280 (
9281 &[
9282 b"HEXPIRE".as_slice(),
9283 b"h",
9284 b"9999999999999999",
9285 b"FIELDS",
9286 b"1",
9287 b"a",
9288 ][..],
9289 "-ERR invalid expire time in 'hexpire' command",
9290 ),
9291 (
9292 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
9293 "-ERR wrong number of arguments for 'hexpire' command",
9294 ),
9295 (
9296 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
9297 "-ERR Parameter `numFields` should be greater than 0",
9298 ),
9299 (
9300 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
9301 "-ERR wrong number of arguments",
9302 ),
9303 (
9304 &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
9305 "-ERR wrong number of arguments",
9306 ),
9307 ] {
9308 let reply = f.run(bad);
9309 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
9310 assert!(!reply.contains('*'), "an array header went out in front");
9311 }
9312 assert_eq!(
9313 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9314 "*1\r\n:-1\r\n",
9315 "and not one of them put a deadline on anything"
9316 );
9317 }
9318
9319 #[test]
9320 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
9321 let mut f = Fixture::new();
9322 f.run(&[b"SET", b"str", b"v"]);
9323 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9324
9325 for cmd in [
9326 &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
9327 &[
9328 b"HPEXPIRE".as_slice(),
9329 b"str",
9330 b"100",
9331 b"FIELDS",
9332 b"1",
9333 b"f",
9334 ][..],
9335 &[
9336 b"HEXPIREAT".as_slice(),
9337 b"str",
9338 b"9999999999",
9339 b"FIELDS",
9340 b"1",
9341 b"f",
9342 ][..],
9343 &[
9344 b"HPEXPIREAT".as_slice(),
9345 b"str",
9346 b"9999999999999",
9347 b"FIELDS",
9348 b"1",
9349 b"f",
9350 ][..],
9351 &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9352 &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9353 &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9354 &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9355 &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9356 ] {
9357 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9358 }
9359 assert_eq!(
9360 f.run(&[b"GET", b"str"]),
9361 "$1\r\nv\r\n",
9362 "and none of them touched the value"
9363 );
9364 }
9365
9366 #[test]
9367 fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
9368 let mut f = Fixture::new();
9369 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9370 assert_eq!(
9371 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
9372 "*2\r\n$1\r\n1\r\n$-1\r\n",
9373 "positional, so the field that was not there is a nil in its place"
9374 );
9375 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
9376 assert_eq!(
9377 f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
9378 "*1\r\n$-1\r\n"
9379 );
9380 assert_eq!(
9381 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
9382 "*1\r\n$1\r\n2\r\n"
9383 );
9384 assert_eq!(
9385 f.run(&[b"EXISTS", b"h"]),
9386 ":0\r\n",
9387 "and the last field took the key"
9388 );
9389 }
9390
9391 #[test]
9392 fn hgetex_reads_and_moves_the_deadline_in_one_command() {
9393 let mut f = Fixture::new();
9394 f.run(&[b"HSET", b"h", b"a", b"1"]);
9395 assert_eq!(
9396 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
9397 "*1\r\n$1\r\n1\r\n"
9398 );
9399 assert_eq!(
9400 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9401 "*1\r\n:-1\r\n",
9402 "no option means leave it alone, which is the one place this is not GETEX"
9403 );
9404
9405 f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
9406 assert_eq!(
9407 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9408 "*1\r\n:100\r\n"
9409 );
9410 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
9411 assert_eq!(
9412 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9413 "*1\r\n:100\r\n",
9414 "and a plain read really does leave it alone"
9415 );
9416 assert_eq!(
9417 f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
9418 "*1\r\n$1\r\n1\r\n"
9419 );
9420 assert_eq!(
9421 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9422 "*1\r\n:-1\r\n"
9423 );
9424
9425 assert_eq!(
9426 f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
9427 "*1\r\n$1\r\n1\r\n",
9428 "the value goes out before the deadline that has already gone is applied"
9429 );
9430 assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
9431 assert_eq!(
9432 f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
9433 "*1\r\n$-1\r\n"
9434 );
9435 }
9436
9437 #[test]
9438 fn hsetex_writes_all_of_it_or_none_of_it() {
9439 let mut f = Fixture::new();
9440 assert_eq!(
9441 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
9442 ":1\r\n"
9443 );
9444 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
9445 assert_eq!(
9446 f.run(&[
9447 b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
9448 ]),
9449 ":0\r\n",
9450 "FNX wants every field named to be missing"
9451 );
9452 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
9453 assert_eq!(
9454 f.run(&[b"HEXISTS", b"h", b"new"]),
9455 ":0\r\n",
9456 "and none of the list was written"
9457 );
9458 assert_eq!(
9459 f.run(&[
9460 b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
9461 ]),
9462 ":0\r\n",
9463 "and FXX wants every one of them to be there"
9464 );
9465 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
9466 assert_eq!(
9467 f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
9468 ":1\r\n"
9469 );
9470 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
9471
9472 assert_eq!(
9473 f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
9474 ":0\r\n"
9475 );
9476 assert_eq!(
9477 f.run(&[b"EXISTS", b"gone"]),
9478 ":0\r\n",
9479 "a key with no fields cannot meet FXX and is not created trying"
9480 );
9481 }
9482
9483 #[test]
9484 fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
9485 let mut f = Fixture::new();
9486 f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
9487 assert_eq!(
9488 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9489 "*1\r\n:100\r\n"
9490 );
9491
9492 f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
9493 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
9494 assert_eq!(
9495 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9496 "*1\r\n:100\r\n",
9497 "KEEPTTL put back what the write cleared"
9498 );
9499
9500 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
9501 assert_eq!(
9502 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9503 "*1\r\n:-1\r\n",
9504 "and without it a write clears the deadline the way HSET does"
9505 );
9506
9507 assert_eq!(
9510 f.run(&[
9511 b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
9512 ]),
9513 ":1\r\n"
9514 );
9515 assert_eq!(
9516 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9517 "*1\r\n:100\r\n"
9518 );
9519
9520 assert_eq!(
9521 f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
9522 ":1\r\n",
9523 "written, and not the separate code the HEXPIRE family has for this"
9524 );
9525 assert_eq!(
9526 f.run(&[b"EXISTS", b"h"]),
9527 ":0\r\n",
9528 "and storing it and then removing it emptied the hash"
9529 );
9530 }
9531
9532 #[test]
9533 fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
9534 let mut f = Fixture::new();
9535 f.run(&[b"HSET", b"h", b"a", b"1"]);
9536 for (bad, want) in [
9537 (
9539 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9540 "-ERR Number of fields must be a positive integer",
9541 ),
9542 (
9543 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9544 "-ERR The `numfields` parameter must match the number of arguments",
9545 ),
9546 (
9547 &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9548 "-ERR Mandatory argument FIELDS is missing or not at the right position",
9549 ),
9550 (
9552 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9553 "-ERR invalid number of fields",
9554 ),
9555 (
9556 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9557 "-ERR wrong number of arguments",
9558 ),
9559 (
9560 &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9561 "-ERR unknown argument: FIELD",
9562 ),
9563 (
9564 &[
9565 b"HGETEX".as_slice(),
9566 b"h",
9567 b"KEEPTTL",
9568 b"FIELDS",
9569 b"1",
9570 b"a",
9571 ][..],
9572 "-ERR unknown argument: KEEPTTL",
9573 ),
9574 (
9575 &[
9576 b"HGETEX".as_slice(),
9577 b"h",
9578 b"EX",
9579 b"100",
9580 b"PERSIST",
9581 b"FIELDS",
9582 b"1",
9583 b"a",
9584 ][..],
9585 "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
9586 ),
9587 (
9588 &[
9589 b"HSETEX".as_slice(),
9590 b"h",
9591 b"EX",
9592 b"1",
9593 b"KEEPTTL",
9594 b"FIELDS",
9595 b"1",
9596 b"a",
9597 b"1",
9598 ][..],
9599 "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
9600 ),
9601 (
9602 &[
9603 b"HSETEX".as_slice(),
9604 b"h",
9605 b"FNX",
9606 b"FXX",
9607 b"FIELDS",
9608 b"1",
9609 b"a",
9610 b"1",
9611 ][..],
9612 "-ERR Only one of FXX or FNX arguments can be specified",
9613 ),
9614 (
9615 &[
9616 b"HSETEX".as_slice(),
9617 b"h",
9618 b"FIELDS",
9619 b"2",
9620 b"a",
9621 b"1",
9622 b"b",
9623 ][..],
9624 "-ERR wrong number of arguments",
9625 ),
9626 (
9627 &[
9628 b"HGETEX".as_slice(),
9629 b"h",
9630 b"EX",
9631 b"-1",
9632 b"FIELDS",
9633 b"1",
9634 b"a",
9635 ][..],
9636 "-ERR invalid expire time, must be >= 0",
9637 ),
9638 (
9639 &[
9640 b"HGETEX".as_slice(),
9641 b"h",
9642 b"PXAT",
9643 b"99999999999999",
9644 b"FIELDS",
9645 b"1",
9646 b"a",
9647 ][..],
9648 "-ERR invalid expire time in 'hgetex' command",
9649 ),
9650 (
9651 &[
9652 b"HSETEX".as_slice(),
9653 b"h",
9654 b"EX",
9655 b"abc",
9656 b"FIELDS",
9657 b"1",
9658 b"a",
9659 b"1",
9660 ][..],
9661 "-ERR value is not an integer or out of range",
9662 ),
9663 ] {
9664 let reply = f.run(bad);
9665 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
9666 assert!(!reply.contains('*'), "an array header went out in front");
9667 }
9668 assert_eq!(
9669 f.run(&[b"HGET", b"h", b"a"]),
9670 "$1\r\n1\r\n",
9671 "and not one of them wrote anything"
9672 );
9673 assert_eq!(
9674 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9675 "*1\r\n:-1\r\n"
9676 );
9677 }
9678
9679 #[test]
9680 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
9681 let mut f = Fixture::new();
9682 f.run(&[b"SET", b"str", b"v"]);
9683 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9684 for cmd in [
9685 &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9686 &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9687 &[
9688 b"HGETEX".as_slice(),
9689 b"str",
9690 b"EX",
9691 b"100",
9692 b"FIELDS",
9693 b"1",
9694 b"f",
9695 ][..],
9696 &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
9697 ] {
9698 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9699 }
9700 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
9701 }
9702
9703 #[test]
9711 fn himport_writes_declared_values_into_sorted_fields() {
9712 let mut f = Fixture::new();
9713 assert_eq!(
9714 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
9715 "+OK\r\n"
9716 );
9717 assert_eq!(
9718 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
9719 "+OK\r\n"
9720 );
9721 assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
9722 assert_eq!(
9723 f.run(&[b"HGETALL", b"k"]),
9724 bulks(&["a", "3", "b", "1", "aa", "2"])
9725 );
9726 }
9727
9728 #[test]
9731 fn himport_set_replaces_the_whole_key() {
9732 let mut f = Fixture::new();
9733 f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
9734 f.run(&[b"EXPIRE", b"k", b"100"]);
9735 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9736 assert_eq!(
9737 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9738 "+OK\r\n"
9739 );
9740 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9741 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
9742 }
9743
9744 #[test]
9747 fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
9748 let mut f = Fixture::new();
9749 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
9750 f.run(&[b"SELECT", b"1"]);
9751 assert_eq!(
9752 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9753 "+OK\r\n"
9754 );
9755 f.run(&[b"SELECT", b"0"]);
9756 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
9757 assert_eq!(
9758 f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
9759 "-ERR no such fieldset\r\n"
9760 );
9761 }
9762
9763 #[test]
9770 fn himport_complains_in_the_order_a_real_server_does() {
9771 let mut f = Fixture::new();
9772 f.run(&[b"SET", b"str", b"v"]);
9773 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9774 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9775 assert_eq!(
9776 f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
9777 wrong,
9778 "the type beats a missing fieldset"
9779 );
9780 assert_eq!(
9781 f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
9782 wrong,
9783 "and it beats a value count that does not fit"
9784 );
9785 assert_eq!(
9786 f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
9787 "-ERR no such fieldset\r\n"
9788 );
9789 for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
9791 let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
9792 line.extend_from_slice(values);
9793 assert_eq!(
9794 f.run(&line),
9795 "-ERR value count does not match fieldset field count\r\n",
9796 "{} values into two fields",
9797 values.len()
9798 );
9799 }
9800 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9801 }
9802
9803 #[test]
9805 fn himport_checks_each_subcommand_count_under_its_own_name() {
9806 let mut f = Fixture::new();
9807 assert_eq!(
9808 f.run(&[b"HIMPORT"]),
9809 "-ERR wrong number of arguments for 'himport' command\r\n"
9810 );
9811 for (rest, name) in [
9812 (&["PREPARE"][..], "prepare"),
9813 (&["PREPARE", "fs"][..], "prepare"),
9814 (&["SET"][..], "set"),
9815 (&["SET", "k"][..], "set"),
9816 (&["SET", "k", "fs"][..], "set"),
9817 (&["DISCARD"][..], "discard"),
9818 (&["DISCARD", "a", "b"][..], "discard"),
9819 (&["DISCARDALL", "x"][..], "discardall"),
9820 ] {
9821 let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
9822 line.extend(rest.iter().map(|a| a.as_bytes()));
9823 assert_eq!(
9824 f.run(&line),
9825 format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
9826 "HIMPORT {}",
9827 rest.join(" ")
9828 );
9829 }
9830 assert_eq!(
9831 f.run(&[b"HIMPORT", b"NOPE", b"x"]),
9832 "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
9833 );
9834 }
9835
9836 #[test]
9839 fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
9840 let mut f = Fixture::new();
9841 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9842 assert_eq!(
9843 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
9844 "-ERR duplicate field name in fieldset\r\n"
9845 );
9846 assert_eq!(
9847 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9848 "+OK\r\n"
9849 );
9850 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9851 }
9852
9853 #[test]
9856 fn himport_prepare_replaces_and_the_discards_count() {
9857 let mut f = Fixture::new();
9858 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9859 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
9860 assert_eq!(
9861 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9862 "+OK\r\n"
9863 );
9864 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
9865
9866 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
9867 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
9868 f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
9869 f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
9870 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
9871 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
9872 }
9873
9874 fn int(reply: &str) -> i64 {
9880 let body = reply
9881 .strip_prefix(':')
9882 .and_then(|s| s.strip_suffix("\r\n"))
9883 .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
9884 body.parse().expect("an integer")
9885 }
9886
9887 fn int_reply(reply: &str) -> i64 {
9888 let body = reply
9889 .strip_prefix("*1\r\n:")
9890 .and_then(|s| s.strip_suffix("\r\n"))
9891 .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
9892 body.parse().expect("an integer")
9893 }
9894
9895 fn scan_reply(reply: &str) -> (String, Vec<String>) {
9897 let mut lines = reply.split("\r\n");
9898 assert_eq!(lines.next(), Some("*2"), "got {reply}");
9899 lines.next().expect("the cursor header");
9900 let cursor = lines.next().expect("a cursor").to_owned();
9901 let header = lines.next().expect("an item count");
9902 let n: usize = header[1..].parse().expect("a count");
9903 let mut items = Vec::with_capacity(n);
9904 for _ in 0..n {
9905 lines.next().expect("an item header");
9906 items.push(lines.next().expect("an item").to_owned());
9907 }
9908 (cursor, items)
9909 }
9910
9911 fn sorted(reply: &str) -> Vec<String> {
9914 let mut lines = reply.split("\r\n");
9915 let header = lines.next().expect("a header");
9916 assert!(
9917 header.starts_with('*') || header.starts_with('~'),
9918 "got {reply}"
9919 );
9920 let n: usize = header[1..].parse().expect("a member count");
9921 let mut got = Vec::with_capacity(n);
9922 for _ in 0..n {
9923 lines.next().expect("a member header");
9924 got.push(lines.next().expect("a member").to_owned());
9925 }
9926 got.sort();
9927 got
9928 }
9929
9930 #[test]
9931 fn the_algebra_answers_what_the_sets_share_and_do_not() {
9932 let mut f = Fixture::new();
9933 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9934 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9935 f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
9936
9937 assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
9938 assert_eq!(
9939 sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
9940 ["1", "2", "3", "4", "5"]
9941 );
9942 assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
9943 assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
9944
9945 assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
9948 assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
9949 assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
9950 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
9951 }
9952
9953 #[test]
9954 fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
9955 let mut f = Fixture::new();
9956 f.run(&[b"SADD", b"a", b"x"]);
9957 assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
9958 assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
9959 assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
9960
9961 f.run(&[b"HELLO", b"3"]);
9962 assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
9963 assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
9964 assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
9965 assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
9966 }
9967
9968 #[test]
9969 fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
9970 let mut f = Fixture::new();
9971 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9972 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9973
9974 assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
9975 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
9976 assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
9977 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
9978 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
9979 assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
9980
9981 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
9984 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
9985 assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
9986 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
9987
9988 f.run(&[b"SET", b"str", b"v"]);
9991 assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
9992 assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
9993 }
9994
9995 #[test]
9996 fn sintercard_counts_without_building_and_stops_at_a_limit() {
9997 let mut f = Fixture::new();
9998 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9999 f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
10000
10001 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
10002 assert_eq!(
10003 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
10004 ":2\r\n"
10005 );
10006 assert_eq!(
10007 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
10008 ":3\r\n",
10009 "a limit of zero is no limit"
10010 );
10011 assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
10012 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
10013
10014 assert_eq!(
10016 f.run(&[b"SINTERCARD", b"0", b"a"]),
10017 "-ERR numkeys should be greater than 0\r\n"
10018 );
10019 assert_eq!(
10020 f.run(&[b"SINTERCARD", b"abc", b"a"]),
10021 "-ERR numkeys should be greater than 0\r\n"
10022 );
10023 assert_eq!(
10024 f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
10025 "-ERR Number of keys can't be greater than number of args\r\n"
10026 );
10027 assert_eq!(
10028 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
10029 "-ERR LIMIT can't be negative\r\n"
10030 );
10031 assert_eq!(
10032 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
10033 "-ERR syntax error\r\n"
10034 );
10035 f.run(&[b"SADD", b"LIMIT", b"2"]);
10037 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
10038 }
10039
10040 #[test]
10043 fn sunioncard_and_sdiffcard_count_without_building() {
10044 let mut f = Fixture::new();
10045 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
10046 f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
10047
10048 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
10049 assert_eq!(
10050 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
10051 ":2\r\n"
10052 );
10053 assert_eq!(
10054 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
10055 ":6\r\n",
10056 "a limit of zero is no limit"
10057 );
10058 assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
10059 assert_eq!(
10060 f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
10061 ":4\r\n",
10062 "a missing key adds nothing to a union"
10063 );
10064
10065 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
10066 assert_eq!(
10067 f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
10068 ":1\r\n"
10069 );
10070 assert_eq!(
10071 f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
10072 ":2\r\n",
10073 "a difference is not symmetric"
10074 );
10075 assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
10076 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
10077 assert_eq!(
10078 f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
10079 ":0\r\n",
10080 "nothing taken away from nothing"
10081 );
10082
10083 for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
10086 assert_eq!(
10087 f.run(&[name, b"0", b"a"]),
10088 "-ERR numkeys should be greater than 0\r\n"
10089 );
10090 assert_eq!(
10091 f.run(&[name, b"abc", b"a"]),
10092 "-ERR numkeys should be greater than 0\r\n"
10093 );
10094 assert_eq!(
10095 f.run(&[name, b"-1", b"a"]),
10096 "-ERR numkeys should be greater than 0\r\n"
10097 );
10098 assert_eq!(
10099 f.run(&[name, b"3", b"a", b"b"]),
10100 "-ERR Number of keys can't be greater than number of args\r\n"
10101 );
10102 assert_eq!(
10103 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
10104 "-ERR LIMIT can't be negative\r\n"
10105 );
10106 assert_eq!(
10107 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
10108 "-ERR LIMIT can't be negative\r\n",
10109 "a LIMIT that is not a number gets the negative message too"
10110 );
10111 assert_eq!(
10112 f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
10113 "-ERR syntax error\r\n"
10114 );
10115 assert_eq!(
10116 f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
10117 "-ERR syntax error\r\n"
10118 );
10119 assert_eq!(
10120 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
10121 "-ERR syntax error\r\n"
10122 );
10123 }
10124
10125 f.run(&[b"SADD", b"LIMIT", b"2"]);
10127 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
10128 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
10129 }
10130
10131 #[test]
10132 fn the_algebra_answers_wrongtype_before_it_writes_anything() {
10133 let mut f = Fixture::new();
10134 f.run(&[b"SADD", b"a", b"1"]);
10135 f.run(&[b"SADD", b"d", b"old"]);
10136 f.run(&[b"SET", b"str", b"v"]);
10137
10138 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10139 for bad in [
10140 &[b"SINTER".as_slice(), b"a", b"str"][..],
10141 &[b"SUNION".as_slice(), b"str"][..],
10142 &[b"SDIFF".as_slice(), b"a", b"str"][..],
10143 &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
10144 &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
10145 &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
10146 &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
10147 ] {
10148 let reply = f.run(bad);
10149 assert_eq!(reply, wrong, "for {:?}", bad[0]);
10150 }
10151 assert_eq!(
10152 f.run(&[b"SMEMBERS", b"d"]),
10153 "*1\r\n$3\r\nold\r\n",
10154 "and the destination was left alone every time"
10155 );
10156 }
10157
10158 #[cfg_attr(miri, ignore = "the volume is the claim")]
10170 #[test]
10171 fn churning_sets_does_not_grow_the_server() {
10172 let mut f = Fixture::new();
10173 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
10174 let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
10175 .chain(std::iter::once(&b"s"[..]))
10176 .chain(members.iter().map(Vec::as_slice))
10177 .collect();
10178
10179 f.run(&args);
10180 f.run(&[b"DEL", b"s"]);
10181 f.server.compact_step();
10182 let after_first = f.server.memory_bytes();
10183
10184 for _ in 0..200 {
10185 f.run(&args);
10186 f.run(&[b"DEL", b"s"]);
10187 f.server.compact_step();
10188 }
10189 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
10190 assert!(
10191 f.server.memory_bytes() <= after_first * 2,
10192 "held {} after two hundred passes against {after_first} after one",
10193 f.server.memory_bytes()
10194 );
10195 }
10196
10197 #[test]
10205 fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
10206 let mut f = Fixture::new();
10207 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
10208 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
10209 assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
10210 assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
10211 assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
10212 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
10213
10214 assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
10216 assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
10217 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
10218
10219 f.run(&[b"SET", b"num", b"12345"]);
10220 assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
10221 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
10222 assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
10223 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
10224 assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
10225 }
10226
10227 #[test]
10232 fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
10233 let mut f = Fixture::new();
10234 f.run(&[b"SET", b"mykey", b"foobar"]);
10235 assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
10236 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
10237 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
10238 assert_eq!(
10239 f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
10240 ":6\r\n"
10241 );
10242 assert_eq!(
10243 f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
10244 ":25\r\n"
10245 );
10246 assert_eq!(
10247 f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
10248 ":17\r\n"
10249 );
10250 assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
10251
10252 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
10255
10256 assert_eq!(
10258 f.run(&[b"BITCOUNT", b"mykey", b"0"]),
10259 "-ERR syntax error\r\n"
10260 );
10261 assert_eq!(
10262 f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
10263 "-ERR syntax error\r\n"
10264 );
10265 }
10266
10267 #[test]
10273 fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
10274 let mut f = Fixture::new();
10275 f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
10276 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
10277 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
10278 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
10279 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
10280 assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
10281
10282 f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
10283 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
10284 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
10285 assert_eq!(
10286 f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
10287 ":8\r\n"
10288 );
10289
10290 assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
10293 assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
10294 }
10295
10296 #[test]
10298 fn the_eight_combinations_write_what_a_real_server_writes() {
10299 let mut f = Fixture::new();
10300 f.run(&[b"SET", b"a", b"abc"]);
10301 f.run(&[b"SET", b"b", b"abd"]);
10302 let cases: &[(&[u8], &str)] = &[
10303 (b"AND", "ab`"),
10304 (b"OR", "abg"),
10305 (b"XOR", "\u{0}\u{0}\u{7}"),
10306 (b"DIFF", "\u{0}\u{0}\u{3}"),
10307 (b"DIFF1", "\u{0}\u{0}\u{4}"),
10308 (b"ANDOR", "ab`"),
10309 (b"ONE", "\u{0}\u{0}\u{7}"),
10310 ];
10311 for (op, want) in cases {
10312 assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
10313 assert_eq!(
10314 f.run(&[b"GET", b"d"]),
10315 format!("$3\r\n{want}\r\n"),
10316 "{op:?}"
10317 );
10318 }
10319 assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
10321 assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
10322
10323 assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
10326 assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
10327
10328 f.run(&[b"SET", b"dest", b"x"]);
10331 assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
10332 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
10333 }
10334
10335 #[test]
10337 fn bitop_names_the_operation_in_its_own_complaints() {
10338 let mut f = Fixture::new();
10339 f.run(&[b"SET", b"a", b"abc"]);
10340 assert_eq!(
10341 f.run(&[b"BITOP", b"nope", b"d", b"a"]),
10342 "-ERR syntax error\r\n"
10343 );
10344 assert_eq!(
10345 f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
10346 "-ERR BITOP NOT must be called with a single source key.\r\n"
10347 );
10348 for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
10349 assert_eq!(
10350 f.run(&[b"BITOP", op, b"d", b"a"]),
10351 format!(
10352 "-ERR BITOP {} must be called with at least two source keys.\r\n",
10353 String::from_utf8_lossy(op)
10354 )
10355 );
10356 }
10357 f.run(&[b"LPUSH", b"l", b"x"]);
10358 assert_eq!(
10359 f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
10360 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10361 );
10362 }
10363
10364 #[test]
10366 fn bitfield_reads_and_writes_packed_fields() {
10367 let mut f = Fixture::new();
10368 assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
10369 assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
10370
10371 assert_eq!(
10372 f.run(&[
10373 b"BITFIELD",
10374 b"bf",
10375 b"INCRBY",
10376 b"u2",
10377 b"100",
10378 b"1",
10379 b"GET",
10380 b"u4",
10381 b"0"
10382 ]),
10383 "*2\r\n:1\r\n:0\r\n"
10384 );
10385 assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
10388
10389 assert_eq!(
10391 f.run(&[
10392 b"BITFIELD",
10393 b"bf",
10394 b"SET",
10395 b"u8",
10396 b"#0",
10397 b"255",
10398 b"GET",
10399 b"u8",
10400 b"#0"
10401 ]),
10402 "*2\r\n:0\r\n:255\r\n"
10403 );
10404
10405 assert_eq!(
10406 f.run(&[
10407 b"BITFIELD",
10408 b"bf",
10409 b"OVERFLOW",
10410 b"SAT",
10411 b"INCRBY",
10412 b"i8",
10413 b"0",
10414 b"120",
10415 b"INCRBY",
10416 b"i8",
10417 b"0",
10418 b"120"
10419 ]),
10420 "*2\r\n:119\r\n:127\r\n"
10421 );
10422 assert_eq!(
10423 f.run(&[
10424 b"BITFIELD",
10425 b"bf2",
10426 b"OVERFLOW",
10427 b"FAIL",
10428 b"INCRBY",
10429 b"u2",
10430 b"0",
10431 b"5"
10432 ]),
10433 "*1\r\n$-1\r\n"
10434 );
10435 assert_eq!(
10436 f.run(&[
10437 b"BITFIELD",
10438 b"bf3",
10439 b"OVERFLOW",
10440 b"WRAP",
10441 b"INCRBY",
10442 b"u2",
10443 b"0",
10444 b"5"
10445 ]),
10446 "*1\r\n:1\r\n"
10447 );
10448 assert_eq!(
10449 f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
10450 "*1\r\n:4611686018427387904\r\n"
10451 );
10452 }
10453
10454 #[test]
10460 fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
10461 let mut f = Fixture::new();
10462 let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
10463 assert_eq!(
10464 f.run(&[
10465 b"BITFIELD",
10466 b"bad",
10467 b"SET",
10468 b"u8",
10469 b"0",
10470 b"1",
10471 b"GET",
10472 b"u99",
10473 b"0"
10474 ]),
10475 bad_type
10476 );
10477 assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
10478 assert_eq!(
10479 f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
10480 bad_type
10481 );
10482 assert_eq!(
10483 f.run(&[b"BITFIELD", b"bad", b"GET"]),
10484 "-ERR syntax error\r\n"
10485 );
10486 assert_eq!(
10487 f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
10488 "-ERR syntax error\r\n"
10489 );
10490 assert_eq!(
10491 f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
10492 "-ERR syntax error\r\n"
10493 );
10494 assert_eq!(
10495 f.run(&[
10496 b"BITFIELD",
10497 b"bad",
10498 b"OVERFLOW",
10499 b"NOPE",
10500 b"GET",
10501 b"u8",
10502 b"0"
10503 ]),
10504 "-ERR Invalid OVERFLOW type specified\r\n"
10505 );
10506 assert_eq!(
10507 f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
10508 "-ERR value is not an integer or out of range\r\n"
10509 );
10510 for at in [&b"#-1"[..], b"abc"] {
10511 assert_eq!(
10512 f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
10513 "-ERR bit offset is not an integer or out of range\r\n"
10514 );
10515 }
10516 }
10517
10518 #[test]
10520 fn bitfield_ro_answers_gets_and_refuses_the_rest() {
10521 let mut f = Fixture::new();
10522 f.run(&[b"SET", b"n", b"123"]);
10523 assert_eq!(
10524 f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
10525 "*1\r\n:49\r\n"
10526 );
10527 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
10529
10530 assert_eq!(
10532 f.run(&[
10533 b"BITFIELD_RO",
10534 b"n",
10535 b"OVERFLOW",
10536 b"SAT",
10537 b"GET",
10538 b"u8",
10539 b"0"
10540 ]),
10541 "*1\r\n:49\r\n"
10542 );
10543 for sub in [&b"SET"[..], b"INCRBY"] {
10544 assert_eq!(
10545 f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
10546 "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
10547 );
10548 }
10549
10550 assert_eq!(
10551 f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
10552 "*1\r\n:0\r\n"
10553 );
10554 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10555 }
10556
10557 #[test]
10559 fn an_offset_off_the_end_of_the_world_is_refused() {
10560 let mut f = Fixture::new();
10561 let bad = "-ERR bit offset is not an integer or out of range\r\n";
10562 for arg in [&b"abc"[..], b"-1", b"4294967296"] {
10563 assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
10564 assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
10565 }
10566 for arg in [&b"2"[..], b"-1"] {
10567 assert_eq!(
10568 f.run(&[b"BITPOS", b"k", arg]),
10569 "-ERR The bit argument must be 1 or 0.\r\n"
10570 );
10571 }
10572 assert_eq!(
10573 f.run(&[b"BITPOS", b"k", b"abc"]),
10574 "-ERR value is not an integer or out of range\r\n"
10575 );
10576 assert_eq!(
10577 f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
10578 "-ERR value is not an integer or out of range\r\n"
10579 );
10580 let bad_bit = "-ERR bit is not an integer or out of range\r\n";
10581 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
10582 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
10583 }
10584
10585 #[test]
10587 fn every_bitmap_command_says_wrongtype() {
10588 let mut f = Fixture::new();
10589 f.run(&[b"LPUSH", b"l", b"x"]);
10590 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10591 let cases: &[&[&[u8]]] = &[
10592 &[b"SETBIT", b"l", b"0", b"1"],
10593 &[b"GETBIT", b"l", b"0"],
10594 &[b"BITCOUNT", b"l"],
10595 &[b"BITPOS", b"l", b"1"],
10596 &[b"BITOP", b"AND", b"d", b"l"],
10597 &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
10598 &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
10599 ];
10600 for case in cases {
10601 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
10602 }
10603 }
10604
10605 #[test]
10608 fn a_sketch_is_added_to_and_counted() {
10609 let mut f = Fixture::new();
10610 assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
10612 assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
10613 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
10614 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
10615 assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
10618 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
10619
10620 assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
10621 assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
10622 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10623 }
10624
10625 #[test]
10626 fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
10627 let mut f = Fixture::new();
10628 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10629 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";
10631 let mut reply = b"$27\r\n".to_vec();
10632 reply.extend_from_slice(want);
10633 reply.extend_from_slice(b"\r\n");
10634 assert_eq!(f.raw(&[b"GET", b"h"]), reply);
10635 }
10636
10637 #[test]
10638 fn counting_several_keys_counts_their_union() {
10639 let mut f = Fixture::new();
10640 f.run(&[b"PFADD", b"a", b"x", b"y"]);
10641 f.run(&[b"PFADD", b"b", b"y", b"z"]);
10642 assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
10643 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
10644 assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
10647 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
10648 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10649 }
10650
10651 #[test]
10652 fn a_merge_keeps_what_the_destination_had() {
10653 let mut f = Fixture::new();
10654 f.run(&[b"PFADD", b"a", b"x", b"y"]);
10655 f.run(&[b"PFADD", b"b", b"z"]);
10656 assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
10657 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
10658 f.run(&[b"PFADD", b"c", b"w"]);
10660 assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
10661 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
10662 assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
10665 assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
10666 }
10667
10668 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
10677 #[test]
10678 fn the_debug_forms_answer_four_different_shapes() {
10679 let mut f = Fixture::new();
10680 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10681 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
10682 assert_eq!(
10683 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10684 "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
10685 );
10686 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
10687 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
10688 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
10689 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
10690 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10691 assert_eq!(
10693 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10694 "-ERR HLL encoding is not sparse\r\n"
10695 );
10696
10697 let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
10699 assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
10700 assert_eq!(reply.matches(":0\r\n").count(), 16381);
10701 assert_eq!(reply.matches(":1\r\n").count(), 2);
10702 assert_eq!(reply.matches(":2\r\n").count(), 1);
10703
10704 assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
10705 }
10706
10707 #[test]
10708 fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
10709 let mut f = Fixture::new();
10710 f.run(&[b"SET", b"plain", b"not a sketch"]);
10711 let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
10712 assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
10713 assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
10714 assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
10715 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
10716
10717 f.run(&[b"RPUSH", b"l", b"x"]);
10720 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10721 assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
10722 assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
10723 assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
10724 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
10725 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
10726 }
10727
10728 #[test]
10729 fn pfdebug_has_its_own_complaints() {
10730 let mut f = Fixture::new();
10731 f.run(&[b"PFADD", b"h", b"a"]);
10732 assert_eq!(
10735 f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
10736 "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
10737 );
10738 let gone = "-ERR The specified key does not exist\r\n";
10740 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
10741 assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
10742 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
10743 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
10744 assert_eq!(
10745 f.run(&[b"PFDEBUG"]),
10746 "-ERR wrong number of arguments for 'pfdebug' command\r\n"
10747 );
10748 assert_eq!(
10749 f.run(&[b"PFSELFTEST", b"x"]),
10750 "-ERR wrong number of arguments for 'pfselftest' command\r\n"
10751 );
10752 }
10753
10754 #[test]
10755 fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
10756 let mut f = Fixture::new();
10757 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10758 let reply = f.raw(&[b"GET", b"h"]);
10761 let short = reply[5..reply.len() - 3].to_vec();
10762 f.run(&[b"SET", b"h", &short]);
10763 assert_eq!(
10764 f.run(&[b"PFCOUNT", b"h"]),
10765 "-INVALIDOBJ Corrupted HLL object detected\r\n"
10766 );
10767 }
10768
10769 #[test]
10770 fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
10771 let mut f = Fixture::new();
10772 f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
10775 if cfg!(miri) {
10782 f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
10783 f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
10784 } else {
10785 for i in 0..10_000u32 {
10786 let ele = format!("e{i}");
10787 f.run(&[b"PFADD", b"big", ele.as_bytes()]);
10788 }
10789 }
10790 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
10791 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
10792
10793 for key in [&b"small"[..], b"big"] {
10794 let mut copy = key.to_vec();
10795 copy.push(b'2');
10796 let bytes = payload(&f.raw(&[b"DUMP", key]));
10797 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
10798 assert_eq!(f.raw(&[b"GET", ©]), f.raw(&[b"GET", key]));
10801 assert_eq!(
10802 f.run(&[b"PFDEBUG", b"ENCODING", ©]),
10803 f.run(&[b"PFDEBUG", b"ENCODING", key])
10804 );
10805 assert_eq!(f.run(&[b"PFCOUNT", ©]), f.run(&[b"PFCOUNT", key]));
10806 }
10807 assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
10808 assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
10809 }
10810
10811 fn bulk(s: &str) -> String {
10815 format!("${}\r\n{s}\r\n", s.len())
10816 }
10817
10818 fn bulks(parts: &[&str]) -> String {
10821 let mut s = format!("*{}\r\n", parts.len());
10822 for p in parts {
10823 s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
10824 }
10825 s
10826 }
10827
10828 #[test]
10829 fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
10830 let mut f = Fixture::new();
10831 assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
10835 assert_eq!(
10836 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10837 bulks(&["c", "b", "a"])
10838 );
10839 assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
10840 assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
10841 assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
10842 assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
10843 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
10844 assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
10845 }
10846
10847 #[test]
10848 fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
10849 let mut f = Fixture::new();
10850 assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
10851 assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
10852 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10853 f.run(&[b"RPUSH", b"k", b"a"]);
10854 assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
10855 assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
10856 assert_eq!(
10857 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10858 bulks(&["z", "a", "y"])
10859 );
10860 }
10861
10862 #[test]
10865 fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
10866 let mut f = Fixture::new();
10867 assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
10868 assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
10869 assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
10870 assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
10871 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10872 assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
10875 assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
10876 assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
10878 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10879 }
10880
10881 #[test]
10882 fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
10883 let mut f = Fixture::new();
10884 f.run(&[b"RPUSH", b"k", b"a"]);
10885 let range = "-ERR value is out of range, must be positive\r\n";
10886 assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
10887 assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
10888 assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
10889 assert_eq!(
10892 f.run(&[b"LPOP", b"k", b"1", b"2"]),
10893 "-ERR wrong number of arguments for 'lpop' command\r\n"
10894 );
10895 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10896 }
10897
10898 #[test]
10899 fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
10900 let mut f = Fixture::new();
10901 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10902 assert_eq!(
10903 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10904 bulks(&["a", "b", "c"])
10905 );
10906 assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
10907 assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
10908 assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
10909 assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
10910 assert_eq!(
10911 f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
10912 bulks(&["a", "b", "c"])
10913 );
10914 assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
10917 assert_eq!(
10918 f.run(&[b"LRANGE", b"k", b"a", b"b"]),
10919 "-ERR value is not an integer or out of range\r\n"
10920 );
10921 }
10922
10923 #[test]
10924 fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
10925 let mut f = Fixture::new();
10926 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10927 assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
10928 assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
10929 assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
10930 assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
10931 assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
10932 assert_eq!(
10933 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10934 bulks(&["a", "b", "z"])
10935 );
10936 assert_eq!(
10939 f.run(&[b"LSET", b"k", b"99", b"z"]),
10940 "-ERR index out of range\r\n"
10941 );
10942 assert_eq!(
10943 f.run(&[b"LSET", b"nope", b"0", b"z"]),
10944 "-ERR no such key\r\n"
10945 );
10946 }
10947
10948 #[test]
10949 fn linsert_says_three_things_with_one_signed_number() {
10950 let mut f = Fixture::new();
10951 assert_eq!(
10954 f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
10955 ":0\r\n"
10956 );
10957 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10958 assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
10959 assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
10960 assert_eq!(
10961 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10962 bulks(&["X", "a", "b", "Y"])
10963 );
10964 assert_eq!(
10965 f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
10966 ":-1\r\n"
10967 );
10968 assert_eq!(
10969 f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
10970 "-ERR syntax error\r\n"
10971 );
10972 }
10973
10974 #[test]
10975 fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
10976 let mut f = Fixture::new();
10977 f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
10978 assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
10979 assert_eq!(
10980 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10981 bulks(&["b", "c", "a"])
10982 );
10983 assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
10984 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10985 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
10986 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
10987 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10988 assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
10989 }
10990
10991 #[test]
10992 fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
10993 let mut f = Fixture::new();
10994 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
10995 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
10996 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10997 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
11000 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
11001 assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
11002 }
11003
11004 #[test]
11005 fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
11006 let mut f = Fixture::new();
11007 f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
11008 assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
11009 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
11010 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
11011 assert_eq!(
11012 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
11013 "*2\r\n:0\r\n:3\r\n"
11014 );
11015 assert_eq!(
11016 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
11017 "*3\r\n:6\r\n:3\r\n:0\r\n"
11018 );
11019 assert_eq!(
11022 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
11023 "*1\r\n:0\r\n"
11024 );
11025 assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
11028 assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
11029 assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
11030 assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
11031 }
11032
11033 #[test]
11034 fn lpos_words_its_three_mistakes_the_way_redis_does() {
11035 let mut f = Fixture::new();
11036 f.run(&[b"RPUSH", b"p", b"a"]);
11037 assert_eq!(
11040 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
11041 "-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"
11042 );
11043 assert_eq!(
11044 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
11045 "-ERR COUNT can't be negative\r\n"
11046 );
11047 assert_eq!(
11048 f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
11049 "-ERR MAXLEN can't be negative\r\n"
11050 );
11051 assert_eq!(
11052 f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
11053 "-ERR syntax error\r\n"
11054 );
11055 assert_eq!(
11056 f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
11057 "-ERR syntax error\r\n"
11058 );
11059 }
11060
11061 #[test]
11062 fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
11063 let mut f = Fixture::new();
11064 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
11065 assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
11066 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
11067 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
11068 assert_eq!(
11069 f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
11070 "$1\r\na\r\n"
11071 );
11072 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
11073 f.run(&[b"DEL", b"r"]);
11076 f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
11077 assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
11078 assert_eq!(
11079 f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
11080 bulks(&["3", "1", "2"])
11081 );
11082 assert_eq!(
11083 f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
11084 "$-1\r\n"
11085 );
11086 assert_eq!(
11087 f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
11088 "-ERR syntax error\r\n"
11089 );
11090 }
11091
11092 #[test]
11093 fn a_move_checks_the_destination_before_it_takes_anything() {
11094 let mut f = Fixture::new();
11095 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
11096 f.run(&[b"SET", b"str", b"v"]);
11097 assert_eq!(
11098 f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
11099 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
11100 );
11101 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
11103 }
11104
11105 #[test]
11106 fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
11107 for (from, to, order, want) in [
11111 ("LEFT", "RIGHT", "OBO", ["a", "b"]),
11112 ("LEFT", "RIGHT", "BULK", ["a", "b"]),
11113 ("LEFT", "LEFT", "OBO", ["b", "a"]),
11114 ("LEFT", "LEFT", "BULK", ["a", "b"]),
11115 ("RIGHT", "LEFT", "OBO", ["d", "e"]),
11116 ("RIGHT", "LEFT", "BULK", ["d", "e"]),
11117 ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
11118 ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
11119 ] {
11120 let mut f = Fixture::new();
11121 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
11122 let how = format!("{from} {to} {order}");
11123 let reply = f.run(&[
11124 b"LMOVEM",
11125 b"s",
11126 b"d",
11127 from.as_bytes(),
11128 to.as_bytes(),
11129 b"COUNT",
11130 b"2",
11131 order.as_bytes(),
11132 ]);
11133 assert_eq!(reply, bulks(&want), "the reply for {how}");
11134 assert_eq!(
11135 f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
11136 bulks(&want),
11137 "the destination for {how}"
11138 );
11139 }
11140 }
11141
11142 #[test]
11143 fn a_block_move_of_one_needs_no_count_at_all() {
11144 let mut f = Fixture::new();
11145 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
11146 assert_eq!(
11147 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
11148 bulks(&["a"])
11149 );
11150 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
11151 assert_eq!(
11154 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
11155 "-ERR syntax error\r\n"
11156 );
11157 assert_eq!(
11158 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
11159 "-ERR syntax error\r\n"
11160 );
11161 }
11162
11163 #[test]
11164 fn a_block_move_with_exactly_takes_all_of_them_or_none() {
11165 let mut f = Fixture::new();
11166 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
11167 assert_eq!(
11171 f.run(&[
11172 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
11173 ]),
11174 "*-1\r\n"
11175 );
11176 assert_eq!(
11177 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
11178 bulks(&["a", "b", "c"])
11179 );
11180 assert_eq!(
11182 f.run(&[
11183 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
11184 ]),
11185 bulks(&["a", "b", "c"])
11186 );
11187 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
11188 assert_eq!(
11189 f.run(&[
11190 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
11191 ]),
11192 "*-1\r\n"
11193 );
11194 }
11195
11196 #[test]
11197 fn a_block_move_onto_itself_rotates_by_the_count() {
11198 let mut f = Fixture::new();
11199 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
11200 assert_eq!(
11201 f.run(&[
11202 b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
11203 ]),
11204 bulks(&["a", "b"])
11205 );
11206 assert_eq!(
11207 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
11208 bulks(&["c", "a", "b"])
11209 );
11210 }
11211
11212 #[test]
11213 fn a_block_move_reads_the_count_before_the_ordering_word() {
11214 let mut f = Fixture::new();
11215 f.run(&[b"RPUSH", b"s", b"a", b"b"]);
11216 f.run(&[b"SET", b"str", b"v"]);
11217 let count = "-ERR count should be greater than 0\r\n";
11218 assert_eq!(
11219 f.run(&[
11220 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
11221 ]),
11222 count
11223 );
11224 assert_eq!(
11225 f.run(&[
11226 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
11227 ]),
11228 count
11229 );
11230 assert_eq!(
11231 f.run(&[
11232 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
11233 ]),
11234 "-ERR syntax error\r\n"
11235 );
11236 assert_eq!(
11237 f.run(&[
11238 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
11239 ]),
11240 "-ERR syntax error\r\n"
11241 );
11242 assert_eq!(
11245 f.run(&[
11246 b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
11247 ]),
11248 count
11249 );
11250 assert_eq!(
11251 f.run(&[
11252 b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
11253 ]),
11254 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
11255 );
11256 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
11257 }
11258
11259 #[test]
11260 fn lmpop_answers_from_the_first_key_that_has_anything() {
11261 let mut f = Fixture::new();
11262 f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
11263 assert_eq!(
11266 f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
11267 "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
11268 );
11269 assert_eq!(
11270 f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
11271 "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
11272 );
11273 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
11274 assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
11277 }
11278
11279 #[test]
11280 fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
11281 let mut f = Fixture::new();
11282 f.run(&[b"RPUSH", b"k", b"a"]);
11283 assert_eq!(
11284 f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
11285 "-ERR numkeys should be greater than 0\r\n"
11286 );
11287 assert_eq!(
11288 f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
11289 "-ERR numkeys should be greater than 0\r\n"
11290 );
11291 assert_eq!(
11292 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
11293 "-ERR count should be greater than 0\r\n"
11294 );
11295 assert_eq!(
11298 f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
11299 "-ERR syntax error\r\n"
11300 );
11301 assert_eq!(
11302 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
11303 "-ERR syntax error\r\n"
11304 );
11305 assert_eq!(
11306 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
11307 "-ERR syntax error\r\n"
11308 );
11309 assert_eq!(
11310 f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
11311 "-ERR syntax error\r\n"
11312 );
11313 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
11314 }
11315
11316 #[test]
11317 fn every_list_command_says_wrongtype_and_writes_nothing() {
11318 let mut f = Fixture::new();
11319 f.run(&[b"SET", b"str", b"v"]);
11320 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11321 for cmd in [
11322 &[b"LPUSH".as_slice(), b"str", b"a"][..],
11323 &[b"RPUSH", b"str", b"a"],
11324 &[b"LPUSHX", b"str", b"a"],
11325 &[b"RPUSHX", b"str", b"a"],
11326 &[b"LPOP", b"str"],
11327 &[b"LPOP", b"str", b"2"],
11328 &[b"RPOP", b"str"],
11329 &[b"LLEN", b"str"],
11330 &[b"LRANGE", b"str", b"0", b"-1"],
11331 &[b"LINDEX", b"str", b"0"],
11332 &[b"LSET", b"str", b"0", b"a"],
11333 &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
11334 &[b"LREM", b"str", b"0", b"a"],
11335 &[b"LTRIM", b"str", b"0", b"-1"],
11336 &[b"LPOS", b"str", b"a"],
11337 &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
11338 &[b"RPOPLPUSH", b"str", b"d"],
11339 &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
11340 &[b"LMPOP", b"1", b"str", b"LEFT"],
11341 ] {
11342 assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
11343 }
11344 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
11345 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11346 }
11347
11348 #[test]
11352 fn a_timeout_has_three_ways_of_being_wrong() {
11353 let mut f = Fixture::new();
11354 let not_float = "-ERR timeout is not a float or out of range\r\n";
11355 let range = "-ERR timeout is out of range\r\n";
11356 for (bad, want) in [
11357 (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
11358 (&[b"BLPOP", b"k", b"nan"], not_float),
11359 (&[b"BLPOP", b"k", b""], not_float),
11360 (&[b"BLPOP", b"k", b" 1"], not_float),
11363 (&[b"BLPOP", b"k", b"1 "], not_float),
11364 (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
11365 (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
11366 (&[b"BLPOP", b"k", b"1e400"], range),
11369 (&[b"BLPOP", b"k", b"inf"], range),
11370 (&[b"BLPOP", b"k", b"9999999999999999"], range),
11371 (&[b"BRPOP", b"k", b"abc"], not_float),
11372 (
11373 &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
11374 not_float,
11375 ),
11376 (
11377 &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
11378 "-ERR timeout is negative\r\n",
11379 ),
11380 (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
11381 ] {
11382 assert_eq!(f.run(bad), want, "for {bad:?}");
11383 }
11384 }
11385
11386 #[test]
11389 fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
11390 let mut f = Fixture::new();
11391 for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
11392 let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
11393 assert_eq!(flow, Flow::Block, "for {timeout:?}");
11394 assert!(out.is_empty(), "for {timeout:?}");
11395 }
11396 let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
11400 assert_eq!(flow, Flow::Block);
11401 assert!(out.is_empty());
11402 }
11403
11404 #[test]
11405 fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
11406 let mut f = Fixture::new();
11407 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
11408
11409 assert_eq!(
11412 f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
11413 (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
11414 );
11415 assert_eq!(
11416 f.run(&[b"BRPOP", b"L", b"0"]),
11417 "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
11418 );
11419 assert_eq!(
11420 f.run(&[
11421 b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
11422 ]),
11423 "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11424 );
11425 assert_eq!(
11426 f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
11427 "$1\r\nd\r\n"
11428 );
11429 assert_eq!(
11430 f.run(&[b"EXISTS", b"L"]),
11431 ":0\r\n",
11432 "and the key went with it"
11433 );
11434 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
11435 f.run(&[b"RPUSH", b"D", b"x"]);
11438 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
11439 assert_eq!(
11440 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
11441 "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
11442 );
11443 }
11444
11445 #[test]
11446 fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
11447 let mut f = Fixture::new();
11448 f.run(&[b"RPUSH", b"k", b"a"]);
11449 for (bad, want) in [
11450 (
11451 &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
11452 "-ERR numkeys should be greater than 0\r\n",
11453 ),
11454 (
11455 &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
11456 "-ERR numkeys should be greater than 0\r\n",
11457 ),
11458 (
11461 &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
11462 "-ERR syntax error\r\n",
11463 ),
11464 (
11465 &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
11466 "-ERR syntax error\r\n",
11467 ),
11468 (
11469 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
11470 "-ERR syntax error\r\n",
11471 ),
11472 (
11473 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
11474 "-ERR syntax error\r\n",
11475 ),
11476 (
11479 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
11480 "-ERR count should be greater than 0\r\n",
11481 ),
11482 (
11483 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
11484 "-ERR count should be greater than 0\r\n",
11485 ),
11486 ] {
11487 assert_eq!(f.run(bad), want, "for {bad:?}");
11488 }
11489 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
11490 }
11491
11492 #[test]
11493 fn a_blocking_move_reads_its_directions_before_its_timeout() {
11494 let mut f = Fixture::new();
11495 assert_eq!(
11498 f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
11499 "-ERR syntax error\r\n"
11500 );
11501 assert_eq!(
11502 f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
11503 "-ERR syntax error\r\n"
11504 );
11505 }
11506
11507 #[test]
11511 fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
11512 let mut f = Fixture::new();
11513 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
11514 assert_eq!(
11515 f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
11516 (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
11517 );
11518 assert_eq!(
11519 f.run(&[
11520 b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
11521 ]),
11522 bulks(&["e", "d"])
11523 );
11524 assert_eq!(
11525 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
11526 bulks(&["a", "e", "d"])
11527 );
11528 assert_eq!(
11530 f.run(&[
11531 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
11532 ]),
11533 bulks(&["b", "c"])
11534 );
11535 assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
11536 }
11537
11538 #[test]
11542 fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
11543 let mut f = Fixture::new();
11544 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11545 assert_eq!(
11547 f.flow(&[
11548 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
11549 ]),
11550 (Flow::Continue, bulks(&["a", "b"]))
11551 );
11552
11553 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11554 assert_eq!(
11557 f.flow(&[
11558 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
11559 ])
11560 .0,
11561 Flow::Block
11562 );
11563 assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
11564 }
11565
11566 #[test]
11567 fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
11568 let mut f = Fixture::new();
11569 let syntax = "-ERR syntax error\r\n";
11570 assert_eq!(
11572 f.run(&[
11573 b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
11574 ]),
11575 syntax
11576 );
11577 assert_eq!(
11579 f.run(&[
11580 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
11581 ]),
11582 "-ERR timeout is not a float or out of range\r\n"
11583 );
11584 assert_eq!(
11585 f.run(&[
11586 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
11587 ]),
11588 "-ERR timeout is negative\r\n"
11589 );
11590 assert_eq!(
11592 f.run(&[
11593 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
11594 ]),
11595 "-ERR count should be greater than 0\r\n"
11596 );
11597 assert_eq!(
11598 f.run(&[
11599 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
11600 ]),
11601 syntax
11602 );
11603 assert_eq!(
11606 f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
11607 syntax
11608 );
11609 assert_eq!(
11610 f.run(&[
11611 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
11612 ]),
11613 syntax
11614 );
11615 }
11616
11617 #[test]
11620 fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
11621 let mut f = Fixture::new();
11622 f.run(&[b"SET", b"S", b"v"]);
11623 f.run(&[b"RPUSH", b"D", b"x"]);
11624 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11625
11626 assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
11627 assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
11630 assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
11631 assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
11632 assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
11633 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
11636 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
11637 assert_eq!(
11638 f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
11639 wrong
11640 );
11641 assert_eq!(
11642 f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
11643 wrong
11644 );
11645
11646 assert_eq!(
11650 f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11651 .0,
11652 Flow::Block
11653 );
11654 assert_eq!(
11658 f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11659 .0,
11660 Flow::Block
11661 );
11662 f.run(&[b"RPUSH", b"E", b"1", b"2"]);
11663 assert_eq!(
11664 f.flow(&[
11665 b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
11666 ])
11667 .0,
11668 Flow::Block
11669 );
11670 }
11671
11672 #[cfg_attr(miri, ignore = "the volume is the claim")]
11677 #[test]
11678 fn churning_lists_does_not_grow_the_server() {
11679 let mut f = Fixture::new();
11680 let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
11681 let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
11682 .into_iter()
11683 .chain(vals.iter().map(Vec::as_slice))
11684 .collect();
11685
11686 f.run(&args);
11687 f.run(&[b"DEL", b"k"]);
11688 f.server.compact_step();
11689 let after_first = f.server.memory_bytes();
11690
11691 for _ in 0..200 {
11692 f.run(&args);
11693 f.run(&[b"LTRIM", b"k", b"1", b"0"]);
11694 f.server.compact_step();
11695 }
11696 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11697 assert!(
11698 f.server.memory_bytes() <= after_first * 2,
11699 "held {} after two hundred passes against {after_first} after one",
11700 f.server.memory_bytes()
11701 );
11702 }
11703
11704 #[test]
11707 fn a_sorted_set_takes_scores_and_gives_them_back() {
11708 let mut f = Fixture::new();
11709 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
11710 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
11711 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
11712 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
11713 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
11714 assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
11715 assert_eq!(
11716 f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
11717 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
11718 );
11719 assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
11720 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
11721 assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
11723 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11724 }
11725
11726 #[test]
11727 fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
11728 let mut f = Fixture::new();
11729 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
11730 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
11731 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
11732 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
11733
11734 f.out = Out::new(Proto::Resp3);
11735 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
11736 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
11737 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
11738 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
11739 }
11740
11741 #[test]
11742 fn the_zadd_options_gate_what_gets_written() {
11743 let mut f = Fixture::new();
11744 f.run(&[b"ZADD", b"z", b"5", b"a"]);
11745 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
11747 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
11748 assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
11749 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
11750 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
11752 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
11753 assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
11754 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
11756 assert_eq!(
11757 f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
11758 ":2\r\n"
11759 );
11760 }
11761
11762 #[test]
11763 fn zadd_incr_answers_a_score_or_nothing_at_all() {
11764 let mut f = Fixture::new();
11765 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
11766 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
11767 assert_eq!(
11770 f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
11771 "$-1\r\n"
11772 );
11773 assert_eq!(
11774 f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
11775 "$-1\r\n"
11776 );
11777 assert_eq!(
11778 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
11779 "$-1\r\n"
11780 );
11781 assert_eq!(
11782 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
11783 "$1\r\n8\r\n"
11784 );
11785 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
11786 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
11787 }
11788
11789 #[test]
11790 fn the_two_infinities_will_not_be_added_together() {
11791 let mut f = Fixture::new();
11792 f.run(&[b"ZADD", b"z", b"inf", b"m"]);
11793 let nan = "-ERR resulting score is not a number (NaN)\r\n";
11794 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
11795 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
11796 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
11797 assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
11799 }
11800
11801 #[test]
11802 fn zadd_says_its_mistakes_the_way_redis_says_them() {
11803 let mut f = Fixture::new();
11804 assert_eq!(
11807 f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
11808 "-ERR syntax error\r\n"
11809 );
11810 assert_eq!(
11811 f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
11812 "-ERR XX and NX options at the same time are not compatible\r\n"
11813 );
11814 let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
11815 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
11816 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
11817 assert_eq!(
11818 f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
11819 "-ERR INCR option supports a single increment-element pair\r\n"
11820 );
11821 assert_eq!(
11823 f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
11824 "-ERR syntax error\r\n"
11825 );
11826 assert_eq!(
11828 f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
11829 "-ERR value is not a valid float\r\n"
11830 );
11831 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11832 }
11833
11834 #[test]
11835 fn a_rank_says_where_a_member_sits_from_either_end() {
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 assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
11839 assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
11840 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
11841 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
11842 assert_eq!(
11844 f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
11845 "*2\r\n:1\r\n$1\r\n2\r\n"
11846 );
11847 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
11848 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
11849 assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
11850 assert_eq!(
11853 f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
11854 "-ERR syntax error\r\n"
11855 );
11856 assert_eq!(
11857 f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
11858 "-ERR wrong number of arguments for 'zrevrank' command\r\n"
11859 );
11860 }
11861
11862 #[test]
11863 fn the_two_counts_read_their_two_kinds_of_bound() {
11864 let mut f = Fixture::new();
11865 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11866 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
11867 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
11868 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
11869 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
11870 assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
11871 assert_eq!(
11872 f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
11873 "-ERR min or max is not a float\r\n"
11874 );
11875
11876 f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
11877 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
11878 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
11879 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
11880 assert_eq!(
11883 f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
11884 "-ERR min or max not valid string range item\r\n"
11885 );
11886 }
11887
11888 #[test]
11894 fn one_range_command_selects_by_rank_or_score_or_name() {
11895 let mut f = Fixture::new();
11896 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11897 assert_eq!(
11898 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11899 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11900 );
11901 assert_eq!(
11902 f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
11903 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11904 );
11905 assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
11906 assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
11907 assert_eq!(
11910 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
11911 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11912 );
11913 assert_eq!(
11914 f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
11915 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11916 );
11917 assert_eq!(
11920 f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
11921 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11922 );
11923 assert_eq!(
11924 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
11925 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11926 );
11927 assert_eq!(
11928 f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
11929 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11930 );
11931 }
11932
11933 #[test]
11936 fn the_older_range_spellings_name_their_high_end_first() {
11937 let mut f = Fixture::new();
11938 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11939 assert_eq!(
11940 f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
11941 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11942 );
11943 assert_eq!(
11944 f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
11945 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11946 );
11947 assert_eq!(
11948 f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
11949 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11950 );
11951 assert_eq!(
11952 f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
11953 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11954 );
11955 assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
11959 assert_eq!(
11960 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
11961 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
11962 );
11963 assert_eq!(
11964 f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
11965 "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
11966 );
11967 for cmd in [
11970 &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
11971 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
11972 &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
11973 ] {
11974 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
11975 }
11976 }
11977
11978 #[test]
11981 fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
11982 let mut f = Fixture::new();
11983 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11984 assert_eq!(
11985 f.run(&[
11986 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
11987 ]),
11988 "*1\r\n$1\r\nb\r\n"
11989 );
11990 assert_eq!(
11992 f.run(&[
11993 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
11994 ]),
11995 "*0\r\n"
11996 );
11997 assert_eq!(
11998 f.run(&[
11999 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
12000 ]),
12001 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
12002 );
12003 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";
12005 assert_eq!(
12006 f.run(&[
12007 b"ZRANGEBYSCORE",
12008 b"z",
12009 b"1",
12010 b"3",
12011 b"WITHSCORES",
12012 b"LIMIT",
12013 b"0",
12014 b"2"
12015 ]),
12016 both
12017 );
12018 assert_eq!(
12019 f.run(&[
12020 b"ZRANGEBYSCORE",
12021 b"z",
12022 b"1",
12023 b"3",
12024 b"LIMIT",
12025 b"0",
12026 b"2",
12027 b"WITHSCORES"
12028 ]),
12029 both
12030 );
12031 let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
12034 assert_eq!(
12035 f.run(&[
12036 b"ZREVRANGE",
12037 b"z",
12038 b"0",
12039 b"-1",
12040 b"WITHSCORES",
12041 b"LIMIT",
12042 b"0",
12043 b"1"
12044 ]),
12045 needs_by
12046 );
12047 assert_eq!(
12048 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
12049 needs_by
12050 );
12051 let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
12052 assert_eq!(
12053 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
12054 not_bylex
12055 );
12056 assert_eq!(
12057 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
12058 not_bylex
12059 );
12060 for cmd in [
12063 &[
12064 b"ZRANGE".as_slice(),
12065 b"z",
12066 b"0",
12067 b"-1",
12068 b"BYSCORE",
12069 b"BYLEX",
12070 ][..],
12071 &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
12072 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
12073 ] {
12074 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
12075 }
12076 assert_eq!(
12077 f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
12078 "-ERR min or max is not a float\r\n"
12079 );
12080 assert_eq!(
12081 f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
12082 "-ERR min or max not valid string range item\r\n"
12083 );
12084 assert_eq!(
12085 f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
12086 "-ERR value is not an integer or out of range\r\n"
12087 );
12088 }
12089
12090 #[test]
12093 fn withscores_nests_on_resp3_and_flattens_on_resp2() {
12094 let mut f = Fixture::new();
12095 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12096 assert_eq!(
12097 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
12098 "*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"
12099 );
12100 f.out = Out::new(Proto::Resp3);
12101 assert_eq!(
12102 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
12103 "*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"
12104 );
12105 assert_eq!(
12106 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
12107 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
12108 );
12109 }
12110
12111 #[test]
12113 fn a_range_store_writes_the_window_into_another_key() {
12114 let mut f = Fixture::new();
12115 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12116 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
12117 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
12120 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
12121 assert_eq!(
12122 f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
12123 ":2\r\n"
12124 );
12125 assert_eq!(
12126 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
12127 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
12128 );
12129 assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
12132 assert_eq!(
12133 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
12134 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
12135 );
12136 assert_eq!(
12139 f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
12140 "-ERR syntax error\r\n"
12141 );
12142 }
12143
12144 #[test]
12147 fn the_three_removals_share_their_window_with_the_reads() {
12148 let mut f = Fixture::new();
12149 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12150 assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
12151 assert_eq!(
12152 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
12153 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
12154 );
12155 assert_eq!(
12156 f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
12157 ":1\r\n"
12158 );
12159 assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
12160 assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
12162 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12163 assert_eq!(
12164 f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
12165 ":0\r\n"
12166 );
12167 assert_eq!(
12168 f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
12169 "-ERR value is not an integer or out of range\r\n"
12170 );
12171 }
12172
12173 #[test]
12175 fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
12176 let mut f = Fixture::new();
12177 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12178 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
12179 assert_eq!(
12180 f.run(&[b"ZUNION", b"2", b"z", b"y"]),
12181 "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
12182 );
12183 assert_eq!(
12186 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
12187 "*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"
12188 );
12189 assert_eq!(
12190 f.run(&[
12191 b"ZUNION",
12192 b"2",
12193 b"z",
12194 b"y",
12195 b"WEIGHTS",
12196 b"2",
12197 b"3",
12198 b"WITHSCORES"
12199 ]),
12200 "*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"
12201 );
12202 assert_eq!(
12203 f.run(&[
12204 b"ZUNION",
12205 b"2",
12206 b"z",
12207 b"y",
12208 b"AGGREGATE",
12209 b"MIN",
12210 b"WITHSCORES"
12211 ]),
12212 "*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"
12213 );
12214 assert_eq!(
12215 f.run(&[
12216 b"ZUNION",
12217 b"2",
12218 b"z",
12219 b"y",
12220 b"AGGREGATE",
12221 b"MAX",
12222 b"WITHSCORES"
12223 ]),
12224 "*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"
12225 );
12226 assert_eq!(
12227 f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
12228 "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
12229 );
12230 assert_eq!(
12231 f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
12232 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
12233 );
12234 assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
12235 f.run(&[b"SADD", b"p", b"a", b"d"]);
12238 assert_eq!(
12239 f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
12240 "*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"
12241 );
12242 for cmd in [
12245 &[
12246 b"ZDIFF".as_slice(),
12247 b"2",
12248 b"z",
12249 b"y",
12250 b"WEIGHTS",
12251 b"1",
12252 b"1",
12253 ][..],
12254 &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
12255 ] {
12256 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
12257 }
12258 }
12259
12260 #[test]
12262 fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
12263 let mut f = Fixture::new();
12264 f.run(&[b"ZADD", b"z", b"1", b"a"]);
12265 f.run(&[b"ZADD", b"y", b"2", b"b"]);
12266 assert_eq!(
12268 f.run(&[b"ZUNION", b"0", b"z"]),
12269 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
12270 );
12271 assert_eq!(
12272 f.run(&[b"ZUNION", b"-1", b"z"]),
12273 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
12274 );
12275 assert_eq!(
12276 f.run(&[b"ZINTERCARD", b"0", b"z"]),
12277 "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
12278 );
12279 assert_eq!(
12282 f.run(&[b"ZUNION", b"3", b"z", b"y"]),
12283 "-ERR syntax error\r\n"
12284 );
12285 assert_eq!(
12286 f.run(&[b"ZUNION", b"x", b"z"]),
12287 "-ERR value is not an integer or out of range\r\n"
12288 );
12289 assert_eq!(
12292 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
12293 "-ERR syntax error\r\n"
12294 );
12295 assert_eq!(
12296 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
12297 "-ERR weight value is not a float\r\n"
12298 );
12299 assert_eq!(
12300 f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
12301 "-ERR syntax error\r\n"
12302 );
12303 }
12304
12305 #[test]
12307 fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
12308 let mut f = Fixture::new();
12309 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12310 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
12311 assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
12312 assert_eq!(
12313 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
12314 "*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"
12315 );
12316 assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
12317 assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
12318 assert_eq!(
12321 f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
12322 ":0\r\n"
12323 );
12324 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
12325 assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
12327 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
12328 for cmd in [
12329 &[
12330 b"ZUNIONSTORE".as_slice(),
12331 b"d",
12332 b"2",
12333 b"z",
12334 b"y",
12335 b"WITHSCORES",
12336 ][..],
12337 &[
12338 b"ZDIFFSTORE",
12339 b"d",
12340 b"2",
12341 b"z",
12342 b"y",
12343 b"WEIGHTS",
12344 b"1",
12345 b"1",
12346 ],
12347 ] {
12348 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
12349 }
12350 }
12351
12352 #[test]
12354 fn intercard_counts_and_stops_at_its_limit() {
12355 let mut f = Fixture::new();
12356 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12357 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
12358 assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
12359 assert_eq!(
12361 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
12362 ":2\r\n"
12363 );
12364 assert_eq!(
12365 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
12366 ":1\r\n"
12367 );
12368 let bad = "-ERR LIMIT can't be negative\r\n";
12371 assert_eq!(
12372 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
12373 bad
12374 );
12375 assert_eq!(
12376 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
12377 bad
12378 );
12379 for cmd in [
12380 &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
12381 &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
12382 &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
12383 ] {
12384 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
12385 }
12386 }
12387
12388 #[test]
12390 fn a_draw_answers_one_member_or_an_array_of_them() {
12391 let mut f = Fixture::new();
12392 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12393 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
12396 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
12397 assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
12398 assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
12399 let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
12402 assert!(all.starts_with("*3\r\n"), "{all}");
12403 for m in ["a", "b", "c"] {
12404 assert!(all.contains(m), "{all}");
12405 }
12406 assert!(
12409 f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
12410 "five draws with replacement"
12411 );
12412 assert!(
12413 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
12414 .starts_with("*4\r\n"),
12415 "two pairs, flat on RESP2"
12416 );
12417 f.out = Out::new(Proto::Resp3);
12418 let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
12419 assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
12420 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
12421 f.out = Out::new(Proto::Resp2);
12422 assert_eq!(
12423 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
12424 "-ERR syntax error\r\n"
12425 );
12426 assert_eq!(
12427 f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
12428 "-ERR value is not an integer or out of range\r\n"
12429 );
12430 }
12431
12432 #[test]
12434 fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
12435 let mut f = Fixture::new();
12436 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12437 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";
12438 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
12439 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
12440 assert_eq!(
12441 f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
12442 "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
12443 );
12444 assert_eq!(
12445 f.run(&[b"ZSCAN", b"nokey", b"0"]),
12446 "*2\r\n$1\r\n0\r\n*0\r\n"
12447 );
12448 f.out = Out::new(Proto::Resp3);
12451 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
12452 f.out = Out::new(Proto::Resp2);
12453 assert_eq!(
12454 f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
12455 "-ERR NOVALUES option can only be used in HSCAN\r\n"
12456 );
12457 assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
12458 assert_eq!(
12459 f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
12460 "-ERR syntax error\r\n"
12461 );
12462 }
12463
12464 #[test]
12466 fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
12467 let mut f = Fixture::new();
12468 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12469 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
12471 assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
12472 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12473 assert_eq!(
12475 f.run(&[b"ZPOPMIN", b"z", b"2"]),
12476 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
12477 );
12478 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
12481 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
12482 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
12483 assert_eq!(
12485 f.run(&[b"ZPOPMIN", b"z", b"9"]),
12486 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
12487 );
12488 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12489
12490 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
12491 f.out = Out::new(Proto::Resp3);
12492 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
12493 assert_eq!(
12494 f.run(&[b"ZPOPMIN", b"z", b"1"]),
12495 "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
12496 );
12497 f.out = Out::new(Proto::Resp2);
12498 let bad = "-ERR value is out of range, must be positive\r\n";
12501 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
12502 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
12503 assert_eq!(
12504 f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
12505 "-ERR syntax error\r\n"
12506 );
12507 }
12508
12509 #[test]
12511 fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
12512 let mut f = Fixture::new();
12513 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12514 assert_eq!(
12515 f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
12516 "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
12517 );
12518 assert_eq!(
12521 f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
12522 "*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"
12523 );
12524 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
12526 f.out = Out::new(Proto::Resp3);
12527 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
12528 f.out = Out::new(Proto::Resp2);
12529 let numkeys = "-ERR numkeys should be greater than 0\r\n";
12530 for bad in [
12531 &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
12532 &[b"ZMPOP", b"-1", b"z", b"MIN"],
12533 &[b"ZMPOP", b"x", b"z", b"MIN"],
12534 ] {
12535 assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
12536 }
12537 let count = "-ERR count should be greater than 0\r\n";
12538 for bad in [
12539 &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
12540 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
12541 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
12542 ] {
12543 assert_eq!(f.run(bad), count, "{:?}", bad[5]);
12544 }
12545 let syntax = "-ERR syntax error\r\n";
12546 for bad in [
12547 &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
12550 &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
12551 &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
12552 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
12553 ] {
12554 assert_eq!(f.run(bad), syntax, "{bad:?}");
12555 }
12556 }
12557
12558 #[test]
12561 fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
12562 let mut f = Fixture::new();
12563 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12564 assert_eq!(
12565 f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
12566 (
12567 Flow::Continue,
12568 "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
12569 )
12570 );
12571 assert_eq!(
12572 f.run(&[b"BZPOPMAX", b"z", b"0"]),
12573 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
12574 );
12575 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12576 assert_eq!(
12577 f.run(&[
12578 b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
12579 ]),
12580 "*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"
12581 );
12582 f.out = Out::new(Proto::Resp3);
12583 assert_eq!(
12584 f.run(&[b"BZPOPMIN", b"z", b"0"]),
12585 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
12586 );
12587 f.out = Out::new(Proto::Resp2);
12588 assert_eq!(
12590 f.flow(&[b"BZPOPMIN", b"z", b"0"]),
12591 (Flow::Block, String::new())
12592 );
12593 assert_eq!(
12594 f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
12595 (Flow::Block, String::new())
12596 );
12597 assert_eq!(
12600 f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
12601 "-ERR timeout is not a float or out of range\r\n"
12602 );
12603 assert_eq!(
12604 f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
12605 "-ERR numkeys should be greater than 0\r\n"
12606 );
12607 assert_eq!(
12608 f.run(&[b"BZPOPMIN", b"z", b"-1"]),
12609 "-ERR timeout is negative\r\n"
12610 );
12611 }
12612
12613 #[test]
12617 fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
12618 let mut f = Fixture::new();
12619 assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
12620 assert_eq!(f.server.parked(), 1);
12621 f.run(&[b"SET", b"z", b"v"]);
12624 let mut out = Out::new(Proto::Resp2);
12625 assert!(!f.server.serve_waiter(7, 0, &mut out));
12626 assert!(out.as_slice().is_empty());
12627 f.run(&[b"DEL", b"z"]);
12628 f.run(&[b"ZADD", b"z", b"5", b"m"]);
12629 assert!(f.server.serve_waiter(7, 0, &mut out));
12630 assert_eq!(
12631 core::str::from_utf8(out.as_slice()).expect("ascii"),
12632 "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
12633 );
12634 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12637 }
12638
12639 #[test]
12640 fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
12641 let mut f = Fixture::new();
12642 f.run(&[b"SET", b"s", b"v"]);
12643 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12644 for cmd in [
12645 &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
12646 &[b"ZINCRBY", b"s", b"1", b"a"],
12647 &[b"ZCARD", b"s"],
12648 &[b"ZSCORE", b"s", b"a"],
12649 &[b"ZMSCORE", b"s", b"a"],
12650 &[b"ZREM", b"s", b"a"],
12651 &[b"ZRANK", b"s", b"a"],
12652 &[b"ZREVRANK", b"s", b"a"],
12653 &[b"ZCOUNT", b"s", b"1", b"2"],
12654 &[b"ZLEXCOUNT", b"s", b"-", b"+"],
12655 &[b"ZRANGE", b"s", b"0", b"-1"],
12656 &[b"ZREVRANGE", b"s", b"0", b"-1"],
12657 &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
12658 &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
12659 &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
12660 &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
12661 &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
12662 &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
12663 &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
12664 &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
12665 &[b"ZUNION", b"1", b"s"],
12666 &[b"ZINTER", b"1", b"s"],
12667 &[b"ZDIFF", b"1", b"s"],
12668 &[b"ZUNIONSTORE", b"d", b"1", b"s"],
12669 &[b"ZINTERSTORE", b"d", b"1", b"s"],
12670 &[b"ZDIFFSTORE", b"d", b"1", b"s"],
12671 &[b"ZINTERCARD", b"1", b"s"],
12672 &[b"ZRANDMEMBER", b"s"],
12673 &[b"ZSCAN", b"s", b"0"],
12674 &[b"ZPOPMIN", b"s"],
12675 &[b"ZPOPMAX", b"s", b"2"],
12676 &[b"ZMPOP", b"1", b"s", b"MIN"],
12677 &[b"BZPOPMIN", b"s", b"0"],
12678 &[b"BZPOPMAX", b"s", b"0"],
12679 &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
12680 ] {
12681 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12682 }
12683 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
12684 }
12685
12686 #[cfg_attr(miri, ignore = "the volume is the claim")]
12691 #[test]
12692 fn churning_sorted_sets_does_not_grow_the_server() {
12693 let mut f = Fixture::new();
12694 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
12695 let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
12696 let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
12697 for i in 0..200 {
12698 args.push(&scores[i]);
12699 args.push(&members[i]);
12700 }
12701
12702 f.run(&args);
12703 f.run(&[b"DEL", b"z"]);
12704 f.server.compact_step();
12705 let after_first = f.server.memory_bytes();
12706
12707 for _ in 0..200 {
12708 f.run(&args);
12709 f.run(&[b"DEL", b"z"]);
12710 f.server.compact_step();
12711 }
12712 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
12713 assert!(
12714 f.server.memory_bytes() <= after_first * 2,
12715 "held {} after two hundred passes against {after_first} after one",
12716 f.server.memory_bytes()
12717 );
12718 }
12719
12720 fn sicily(f: &mut Fixture) {
12728 f.run(&[
12729 b"GEOADD",
12730 b"Sicily",
12731 b"13.361389",
12732 b"38.115556",
12733 b"Palermo",
12734 b"15.087269",
12735 b"37.502669",
12736 b"Catania",
12737 ]);
12738 f.run(&[
12739 b"GEOADD",
12740 b"Sicily",
12741 b"13.583333",
12742 b"37.316667",
12743 b"Agrigento",
12744 ]);
12745 }
12746
12747 #[test]
12748 fn places_go_in_as_scores_and_come_back_as_positions() {
12749 let mut f = Fixture::new();
12750 assert_eq!(
12751 f.run(&[
12752 b"GEOADD",
12753 b"Sicily",
12754 b"13.361389",
12755 b"38.115556",
12756 b"Palermo",
12757 b"15.087269",
12758 b"37.502669",
12759 b"Catania"
12760 ]),
12761 ":2\r\n"
12762 );
12763 assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
12767 assert_eq!(
12768 f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
12769 "$16\r\n3479099956230698\r\n"
12770 );
12771 assert_eq!(
12772 f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
12773 "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
12774 );
12775 assert_eq!(
12776 f.run(&[
12777 b"GEOHASH",
12778 b"Sicily",
12779 b"Palermo",
12780 b"Catania",
12781 b"NonExisting"
12782 ]),
12783 "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
12784 );
12785 assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
12789 assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
12790 }
12791
12792 #[test]
12793 fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
12794 let mut f = Fixture::new();
12795 sicily(&mut f);
12796 assert_eq!(
12797 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
12798 "$11\r\n166274.1516\r\n"
12799 );
12800 assert_eq!(
12801 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
12802 "$8\r\n166.2742\r\n"
12803 );
12804 assert_eq!(
12805 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
12806 "$8\r\n103.3182\r\n"
12807 );
12808 assert_eq!(
12812 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
12813 "$-1\r\n"
12814 );
12815 assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
12816 assert_eq!(
12817 f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
12818 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
12819 );
12820 assert_eq!(
12821 f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
12822 "-ERR syntax error\r\n"
12823 );
12824 }
12825
12826 #[test]
12827 fn a_search_finds_what_is_inside_it_nearest_first() {
12828 let mut f = Fixture::new();
12829 sicily(&mut f);
12830 let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
12831 assert_eq!(
12832 f.run(&[
12833 b"GEOSEARCH",
12834 b"Sicily",
12835 b"FROMLONLAT",
12836 b"15",
12837 b"37",
12838 b"BYRADIUS",
12839 b"200",
12840 b"km",
12841 b"ASC"
12842 ]),
12843 all
12844 );
12845 assert_eq!(
12848 f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
12849 all
12850 );
12851 assert_eq!(
12852 f.run(&[
12853 b"GEORADIUS_RO",
12854 b"Sicily",
12855 b"15",
12856 b"37",
12857 b"200",
12858 b"km",
12859 b"ASC"
12860 ]),
12861 all
12862 );
12863 assert_eq!(
12866 f.run(&[
12867 b"GEORADIUS",
12868 b"Sicily",
12869 b"15",
12870 b"37",
12871 b"200",
12872 b"km",
12873 b"DESC",
12874 b"COUNT",
12875 b"1"
12876 ]),
12877 "*1\r\n$7\r\nPalermo\r\n"
12878 );
12879 assert_eq!(
12880 f.run(&[
12881 b"GEORADIUS",
12882 b"Sicily",
12883 b"15",
12884 b"37",
12885 b"200",
12886 b"km",
12887 b"COUNT",
12888 b"1"
12889 ]),
12890 "*1\r\n$7\r\nCatania\r\n"
12891 );
12892 let empty = "*0\r\n";
12895 assert_eq!(
12896 f.run(&[
12897 b"GEOSEARCH",
12898 b"Sicily",
12899 b"FROMLONLAT",
12900 b"15",
12901 b"37",
12902 b"BYRADIUS",
12903 b"1",
12904 b"km"
12905 ]),
12906 empty
12907 );
12908 assert_eq!(
12909 f.run(&[
12910 b"GEOSEARCH",
12911 b"nokey",
12912 b"FROMLONLAT",
12913 b"15",
12914 b"37",
12915 b"BYRADIUS",
12916 b"1",
12917 b"km"
12918 ]),
12919 empty
12920 );
12921 assert_eq!(
12922 f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
12923 empty
12924 );
12925 }
12926
12927 #[test]
12928 fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
12929 let mut f = Fixture::new();
12930 sicily(&mut f);
12931 assert_eq!(
12932 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
12933 "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12934 );
12935 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";
12938 assert_eq!(
12939 f.run(&[
12940 b"GEORADIUSBYMEMBER_RO",
12941 b"Sicily",
12942 b"Agrigento",
12943 b"100",
12944 b"km",
12945 b"WITHDIST"
12946 ]),
12947 with_dist
12948 );
12949 assert_eq!(
12950 f.run(&[
12951 b"GEOSEARCH",
12952 b"Sicily",
12953 b"FROMMEMBER",
12954 b"Agrigento",
12955 b"BYRADIUS",
12956 b"100",
12957 b"km",
12958 b"ASC",
12959 b"WITHDIST"
12960 ]),
12961 with_dist
12962 );
12963 assert_eq!(
12964 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
12965 "-ERR could not decode requested zset member\r\n"
12966 );
12967 }
12968
12969 #[test]
12970 fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
12971 let mut f = Fixture::new();
12972 sicily(&mut f);
12973 assert_eq!(
12977 f.run(&[
12978 b"GEOSEARCH",
12979 b"Sicily",
12980 b"FROMLONLAT",
12981 b"15",
12982 b"37",
12983 b"BYBOX",
12984 b"400",
12985 b"400",
12986 b"km",
12987 b"ASC",
12988 b"WITHCOORD",
12989 b"WITHDIST",
12990 b"WITHHASH"
12991 ]),
12992 "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
12993 $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
12994 *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
12995 $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
12996 *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
12997 $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
12998 );
12999 }
13000
13001 #[test]
13002 fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
13003 let mut f = Fixture::new();
13004 sicily(&mut f);
13005 let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
13006 $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
13007 $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
13008 assert_eq!(
13009 f.run(&[
13010 b"GEOSEARCHSTORE",
13011 b"dst",
13012 b"Sicily",
13013 b"FROMLONLAT",
13014 b"15",
13015 b"37",
13016 b"BYRADIUS",
13017 b"200",
13018 b"km",
13019 b"ASC"
13020 ]),
13021 ":3\r\n"
13022 );
13023 assert_eq!(
13024 f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
13025 hashes
13026 );
13027 assert_eq!(
13030 f.run(&[
13031 b"GEORADIUS",
13032 b"Sicily",
13033 b"15",
13034 b"37",
13035 b"200",
13036 b"km",
13037 b"STORE",
13038 b"dst3"
13039 ]),
13040 ":3\r\n"
13041 );
13042 assert_eq!(
13043 f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
13044 hashes
13045 );
13046 assert_eq!(
13055 f.run(&[
13056 b"GEOSEARCHSTORE",
13057 b"dst2",
13058 b"Sicily",
13059 b"FROMLONLAT",
13060 b"15",
13061 b"37",
13062 b"BYRADIUS",
13063 b"200",
13064 b"km",
13065 b"ASC",
13066 b"STOREDIST"
13067 ]),
13068 ":3\r\n"
13069 );
13070 for (member, want) in [
13071 ("Catania", 56.441_257_870_158_19),
13072 ("Agrigento", 130.423_487_067_147_14),
13073 ("Palermo", 190.442_429_847_757_92),
13074 ] {
13075 let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
13076 let got: f64 = reply
13077 .trim_start_matches(|c: char| c != '\n')
13078 .trim()
13079 .parse()
13080 .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
13081 assert!(
13082 (got - want).abs() < 1e-9,
13083 "{member} scored {got} not {want}"
13084 );
13085 }
13086 assert_eq!(
13089 f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
13090 "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
13091 );
13092 assert_eq!(
13096 f.run(&[
13097 b"GEOSEARCHSTORE",
13098 b"dst",
13099 b"nokey",
13100 b"FROMLONLAT",
13101 b"15",
13102 b"37",
13103 b"BYRADIUS",
13104 b"200",
13105 b"km"
13106 ]),
13107 ":0\r\n"
13108 );
13109 assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
13110 }
13111
13112 #[test]
13113 fn the_gates_on_geoadd_are_the_ones_zadd_has() {
13114 let mut f = Fixture::new();
13115 sicily(&mut f);
13116 assert_eq!(
13119 f.run(&[
13120 b"GEOADD",
13121 b"Sicily",
13122 b"XX",
13123 b"CH",
13124 b"13.361389",
13125 b"38.115556",
13126 b"Palermo"
13127 ]),
13128 ":0\r\n"
13129 );
13130 assert_eq!(
13131 f.run(&[
13132 b"GEOADD",
13133 b"Sicily",
13134 b"NX",
13135 b"13.361389",
13136 b"38.9",
13137 b"Palermo"
13138 ]),
13139 ":0\r\n"
13140 );
13141 assert_eq!(
13142 f.run(&[
13143 b"GEOADD",
13144 b"Sicily",
13145 b"CH",
13146 b"13.361389",
13147 b"38.9",
13148 b"Palermo"
13149 ]),
13150 ":1\r\n"
13151 );
13152 assert_eq!(
13155 f.run(&[
13156 b"GEOADD",
13157 b"new",
13158 b"13.361389",
13159 b"38.115556",
13160 b"here",
13161 b"181",
13162 b"38",
13163 b"there"
13164 ]),
13165 "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
13166 );
13167 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
13168 assert_eq!(
13169 f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
13170 "-ERR value is not a valid float\r\n"
13171 );
13172 assert_eq!(
13175 f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
13176 "-ERR syntax error\r\n"
13177 );
13178 assert_eq!(
13179 f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
13180 "-ERR syntax error\r\n"
13181 );
13182 assert_eq!(
13183 f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
13184 "-ERR syntax error\r\n"
13185 );
13186 assert_eq!(
13187 f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
13188 "-ERR wrong number of arguments for 'geoadd' command\r\n"
13189 );
13190 }
13191
13192 #[test]
13195 fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
13196 let mut f = Fixture::new();
13197 sicily(&mut f);
13198 let cases: &[(&[&[u8]], &str)] = &[
13199 (
13200 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
13201 "-ERR need numeric radius\r\n",
13202 ),
13203 (
13204 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
13205 "-ERR radius cannot be negative\r\n",
13206 ),
13207 (
13208 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
13209 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
13210 ),
13211 (
13212 &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
13213 "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
13214 ),
13215 (
13216 &[
13217 b"GEOSEARCH",
13218 b"Sicily",
13219 b"FROMLONLAT",
13220 b"15",
13221 b"37",
13222 b"BYBOX",
13223 b"x",
13224 b"1",
13225 b"km",
13226 ],
13227 "-ERR need numeric width\r\n",
13228 ),
13229 (
13230 &[
13231 b"GEOSEARCH",
13232 b"Sicily",
13233 b"FROMLONLAT",
13234 b"15",
13235 b"37",
13236 b"BYBOX",
13237 b"1",
13238 b"y",
13239 b"km",
13240 ],
13241 "-ERR need numeric height\r\n",
13242 ),
13243 (
13244 &[
13245 b"GEOSEARCH",
13246 b"Sicily",
13247 b"FROMLONLAT",
13248 b"15",
13249 b"37",
13250 b"BYBOX",
13251 b"-1",
13252 b"1",
13253 b"km",
13254 ],
13255 "-ERR height or width cannot be negative\r\n",
13256 ),
13257 (
13258 &[
13259 b"GEOSEARCH",
13260 b"Sicily",
13261 b"FROMLONLAT",
13262 b"15",
13263 b"37",
13264 b"BYRADIUS",
13265 b"1",
13266 b"km",
13267 b"ANY",
13268 ],
13269 "-ERR the ANY argument requires COUNT argument\r\n",
13270 ),
13271 (
13272 &[
13273 b"GEOSEARCH",
13274 b"Sicily",
13275 b"FROMLONLAT",
13276 b"15",
13277 b"37",
13278 b"BYRADIUS",
13279 b"1",
13280 b"km",
13281 b"COUNT",
13282 b"0",
13283 ],
13284 "-ERR COUNT must be > 0\r\n",
13285 ),
13286 (
13287 &[
13288 b"GEOSEARCH",
13289 b"Sicily",
13290 b"BYRADIUS",
13291 b"1",
13292 b"km",
13293 b"BYBOX",
13294 b"1",
13295 b"1",
13296 b"km",
13297 ],
13298 "-ERR syntax error\r\n",
13299 ),
13300 (
13301 &[
13302 b"GEOSEARCH",
13303 b"Sicily",
13304 b"FROMMEMBER",
13305 b"Palermo",
13306 b"FROMLONLAT",
13307 b"1",
13308 b"2",
13309 b"BYRADIUS",
13310 b"1",
13311 b"km",
13312 ],
13313 "-ERR syntax error\r\n",
13314 ),
13315 (
13318 &[
13319 b"geosearch",
13320 b"Sicily",
13321 b"BYRADIUS",
13322 b"1",
13323 b"km",
13324 b"ASC",
13325 b"WITHDIST",
13326 ],
13327 "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
13328 ),
13329 (
13330 &[
13331 b"GEOSEARCH",
13332 b"Sicily",
13333 b"FROMLONLAT",
13334 b"15",
13335 b"37",
13336 b"ASC",
13337 b"WITHDIST",
13338 ],
13339 "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
13340 ),
13341 (
13344 &[
13345 b"GEOSEARCHSTORE",
13346 b"d",
13347 b"Sicily",
13348 b"FROMLONLAT",
13349 b"15",
13350 b"37",
13351 b"BYRADIUS",
13352 b"1",
13353 b"km",
13354 b"WITHCOORD",
13355 ],
13356 "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
13357 ),
13358 (
13359 &[
13360 b"GEORADIUS",
13361 b"Sicily",
13362 b"15",
13363 b"37",
13364 b"1",
13365 b"km",
13366 b"WITHDIST",
13367 b"STORE",
13368 b"d",
13369 ],
13370 "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
13371 ),
13372 (
13375 &[
13376 b"GEORADIUS_RO",
13377 b"Sicily",
13378 b"15",
13379 b"37",
13380 b"1",
13381 b"km",
13382 b"STORE",
13383 b"d",
13384 ],
13385 "-ERR syntax error\r\n",
13386 ),
13387 (
13388 &[
13389 b"GEOSEARCH",
13390 b"Sicily",
13391 b"FROMLONLAT",
13392 b"15",
13393 b"37",
13394 b"BYRADIUS",
13395 b"1",
13396 b"km",
13397 b"STOREDIST",
13398 ],
13399 "-ERR syntax error\r\n",
13400 ),
13401 ];
13402 for (parts, want) in cases {
13403 assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
13404 }
13405 }
13406
13407 #[test]
13410 fn every_geo_command_says_wrongtype() {
13411 let mut f = Fixture::new();
13412 f.run(&[b"SET", b"s", b"v"]);
13413 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13414 let cases: &[&[&[u8]]] = &[
13415 &[b"GEOADD", b"s", b"13", b"38", b"m"],
13416 &[b"GEOPOS", b"s", b"m"],
13417 &[b"GEOHASH", b"s", b"m"],
13418 &[b"GEODIST", b"s", b"a", b"b"],
13419 &[
13420 b"GEOSEARCH",
13421 b"s",
13422 b"FROMLONLAT",
13423 b"15",
13424 b"37",
13425 b"BYRADIUS",
13426 b"1",
13427 b"km",
13428 ],
13429 &[
13430 b"GEOSEARCHSTORE",
13431 b"d",
13432 b"s",
13433 b"FROMLONLAT",
13434 b"15",
13435 b"37",
13436 b"BYRADIUS",
13437 b"1",
13438 b"km",
13439 ],
13440 &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
13441 &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
13442 &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
13443 &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
13444 ];
13445 for case in cases {
13446 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
13447 }
13448 assert_eq!(
13451 f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
13452 wrong
13453 );
13454 }
13455
13456 #[test]
13459 fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
13460 let mut f = Fixture::new();
13461 assert_eq!(
13464 f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
13465 ":3\r\n"
13466 );
13467 assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
13468 assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
13469 assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
13470 assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
13472 assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
13473 assert_eq!(
13474 f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
13475 "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
13476 );
13477 assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
13479 assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
13480 }
13481
13482 #[test]
13485 fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
13486 let mut f = Fixture::new();
13487 assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
13488 assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
13489 f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
13490 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13491 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13492 assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
13494 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13495 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
13496
13497 f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
13501 assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
13502 assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
13503 assert_eq!(
13506 f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
13507 "-ERR array index overflow\r\n"
13508 );
13509 assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
13510 }
13511
13512 #[test]
13515 fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
13516 let mut f = Fixture::new();
13517 f.run(&[b"ARSET", b"a", b"1", b"x"]);
13518 assert_eq!(
13519 f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
13520 "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
13521 );
13522 assert_eq!(
13525 f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
13526 "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
13527 );
13528 assert_eq!(
13530 f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
13531 "*2\r\n$-1\r\n$-1\r\n"
13532 );
13533 assert_eq!(
13537 f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
13538 "-ERR range exceeds maximum of 1000000 items\r\n"
13539 );
13540 }
13541
13542 #[test]
13545 fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
13546 let mut f = Fixture::new();
13547 assert_eq!(
13548 f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
13549 "-ERR invalid array index\r\n"
13550 );
13551 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13552 f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
13553 assert_eq!(
13554 f.run(&[b"ARDEL", b"a", b"0", b"01"]),
13555 "-ERR invalid array index\r\n"
13556 );
13557 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
13558 assert_eq!(
13561 f.run(&[b"ARGET", b"a", b"-1"]),
13562 "-ERR invalid array index\r\n"
13563 );
13564 assert_eq!(
13567 f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
13568 "-ERR wrong number of arguments for 'armset' command\r\n"
13569 );
13570 assert_eq!(
13571 f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
13572 "-ERR wrong number of arguments for 'ardelrange' command\r\n"
13573 );
13574 }
13575
13576 #[test]
13577 fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
13578 let mut f = Fixture::new();
13579 f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
13580 assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
13581 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13582 assert_eq!(
13585 f.run(&[
13586 b"ARDELRANGE",
13587 b"a",
13588 b"100",
13589 b"200",
13590 b"0",
13591 b"18446744073709551614"
13592 ]),
13593 ":2\r\n"
13594 );
13595 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13596 assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
13597 assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
13598 }
13599
13600 #[test]
13603 fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
13604 let mut f = Fixture::new();
13605 let long = vec![b'v'; 200];
13606 f.run(&[
13607 b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
13608 b"short", b"5", &long, b"6", b"-0",
13609 ]);
13610 assert_eq!(
13614 f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
13615 format!(
13616 "*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",
13617 String::from_utf8_lossy(&long)
13618 )
13619 );
13620 }
13621
13622 #[test]
13623 fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
13624 let mut f = Fixture::new();
13625 f.run(&[b"ARSET", b"a", b"0", b"x"]);
13626 assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
13627 assert_eq!(
13628 f.run(&[b"OBJECT", b"ENCODING", b"a"]),
13629 "$12\r\nsliced-array\r\n"
13630 );
13631 assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
13633 assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
13634 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
13635 assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
13636 assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
13637 assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
13638 }
13639
13640 #[test]
13641 fn every_array_command_refuses_a_key_holding_something_else() {
13642 let mut f = Fixture::new();
13643 f.run(&[b"SET", b"s", b"v"]);
13644 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13645 for cmd in [
13646 &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
13647 &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
13648 &[b"ARGET".as_ref(), b"s", b"0"][..],
13649 &[b"ARMGET".as_ref(), b"s", b"0"][..],
13650 &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
13651 &[b"ARLEN".as_ref(), b"s"][..],
13652 &[b"ARCOUNT".as_ref(), b"s"][..],
13653 &[b"ARDEL".as_ref(), b"s", b"0"][..],
13654 &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
13655 &[b"ARINSERT".as_ref(), b"s", b"x"][..],
13656 &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
13657 &[b"ARNEXT".as_ref(), b"s"][..],
13658 &[b"ARSEEK".as_ref(), b"s", b"1"][..],
13659 &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
13660 &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
13661 &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
13662 &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
13663 &[b"ARINFO".as_ref(), b"s"][..],
13664 ] {
13665 assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
13666 }
13667 }
13668
13669 #[test]
13673 fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
13674 let mut f = Fixture::new();
13675 f.run(&[b"SET", b"s", b"v"]);
13676 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13677 let bad = "-ERR invalid array index\r\n";
13678 assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
13679 assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
13680 assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
13681 assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
13682 assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
13683 assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
13684 f.run(&[b"ARSET", b"a", b"0", b"x"]);
13686 assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
13687 assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
13688 }
13689
13690 #[test]
13691 fn an_append_follows_a_cursor_the_client_can_move() {
13692 let mut f = Fixture::new();
13693 assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
13694 assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
13695 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
13696 assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
13697 assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
13698
13699 assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
13702 assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
13703 assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
13704 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
13705 assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
13706 assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
13707 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
13708
13709 assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
13712 assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
13713 assert_eq!(
13714 f.run(&[b"ARINSERT", b"a", b"x"]),
13715 "-ERR insert index overflow\r\n"
13716 );
13717 assert_eq!(
13718 f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
13719 "-ERR invalid array index\r\n"
13720 );
13721 }
13722
13723 #[test]
13724 fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
13725 let mut f = Fixture::new();
13726 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
13727 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
13728 assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
13729 assert_eq!(
13730 f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
13731 "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
13732 );
13733 assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
13736 assert_eq!(
13737 f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
13738 "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
13739 );
13740 assert_eq!(
13743 f.run(&[b"ARRING", b"r", b"0", b"x"]),
13744 "-ERR size must be positive\r\n"
13745 );
13746 assert_eq!(
13747 f.run(&[b"ARRING", b"r", b"big", b"x"]),
13748 "-ERR invalid size\r\n"
13749 );
13750 }
13751
13752 #[test]
13753 fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
13754 let mut f = Fixture::new();
13755 assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
13756 f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
13757 assert_eq!(
13758 f.run(&[b"ARLASTITEMS", b"r", b"3"]),
13759 "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
13760 );
13761 assert_eq!(
13762 f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
13763 "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
13764 );
13765 assert_eq!(
13766 f.run(&[b"ARLASTITEMS", b"r", b"99"]),
13767 "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
13768 "more than there is gets what there is"
13769 );
13770 assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
13773 assert_eq!(
13774 f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
13775 "-ERR syntax error\r\n"
13776 );
13777 assert_eq!(
13778 f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
13779 "-ERR invalid COUNT\r\n"
13780 );
13781
13782 f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
13785 assert_eq!(
13786 f.run(&[b"ARLASTITEMS", b"h", b"5"]),
13787 "*2\r\n$-1\r\n$1\r\nz\r\n"
13788 );
13789 }
13790
13791 #[test]
13792 fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
13793 let mut f = Fixture::new();
13794 assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
13795 f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
13796 assert_eq!(
13799 f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
13800 "*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"
13801 );
13802 assert_eq!(
13803 f.run(&[
13804 b"ARSCAN",
13805 b"a",
13806 b"18446744073709551614",
13807 b"0",
13808 b"LIMIT",
13809 b"1"
13810 ]),
13811 "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
13812 );
13813 assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
13814 assert_eq!(
13815 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
13816 "-ERR LIMIT must be positive\r\n"
13817 );
13818 assert_eq!(
13819 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
13820 "-ERR syntax error\r\n"
13821 );
13822 assert_eq!(
13823 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
13824 "-ERR wrong number of arguments for 'arscan' command\r\n"
13825 );
13826 }
13827
13828 #[test]
13829 fn a_grep_answers_the_indexes_whose_elements_match() {
13830 let mut f = Fixture::new();
13831 assert_eq!(
13832 f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
13833 "*0\r\n"
13834 );
13835 f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
13836
13837 assert_eq!(
13840 f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
13841 "*3\r\n:0\r\n:1\r\n:2\r\n"
13842 );
13843 assert_eq!(
13844 f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
13845 "*3\r\n:2\r\n:1\r\n:0\r\n"
13846 );
13847 assert_eq!(
13848 f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
13849 "*2\r\n:1\r\n:2\r\n"
13850 );
13851
13852 assert_eq!(
13855 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
13856 "*1\r\n:0\r\n"
13857 );
13858 assert_eq!(
13859 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
13860 "*2\r\n:0\r\n:3\r\n"
13861 );
13862 assert_eq!(
13863 f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
13864 "*1\r\n:2\r\n"
13865 );
13866 assert_eq!(
13867 f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
13868 "*2\r\n:1\r\n:2\r\n"
13869 );
13870
13871 let both: &[&[u8]] = &[
13874 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
13875 ];
13876 assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
13877 assert_eq!(
13878 f.run(&[
13879 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
13880 ]),
13881 "*0\r\n"
13882 );
13883 assert_eq!(
13884 f.run(&[
13885 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
13886 ]),
13887 "*2\r\n:0\r\n:1\r\n"
13888 );
13889
13890 assert_eq!(
13893 f.run(&[
13894 b"ARGREP",
13895 b"a",
13896 b"-",
13897 b"+",
13898 b"MATCH",
13899 b"a",
13900 b"WITHVALUES",
13901 b"LIMIT",
13902 b"2"
13903 ]),
13904 "*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"
13905 );
13906 assert_eq!(
13907 f.run(&[
13908 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
13909 ]),
13910 "*1\r\n:3\r\n"
13911 );
13912 }
13913
13914 #[test]
13916 fn a_grep_reports_a_broken_command_the_way_redis_does() {
13917 let mut f = Fixture::new();
13918 f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
13919 let syntax = "-ERR syntax error\r\n";
13920
13921 assert_eq!(
13924 f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
13925 "-ERR invalid array index\r\n"
13926 );
13927 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
13928 assert_eq!(
13930 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
13931 syntax
13932 );
13933 assert_eq!(
13934 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
13935 syntax
13936 );
13937 assert_eq!(
13938 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
13939 syntax,
13940 "a command with no predicate in it at all"
13941 );
13942 assert_eq!(
13943 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
13944 "-ERR LIMIT must be positive\r\n"
13945 );
13946 assert_eq!(
13947 f.run(&[
13948 b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
13949 ]),
13950 "-ERR value is not an integer or out of range\r\n"
13951 );
13952 assert_eq!(
13953 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
13954 "-ERR regular expression is empty\r\n"
13955 );
13956 assert_eq!(
13957 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
13958 "-ERR invalid regular expression: Missing ')'\r\n"
13959 );
13960 assert_eq!(
13961 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
13962 "-ERR regular expression backreferences are not supported\r\n"
13963 );
13964 let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
13967 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
13968 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
13969 }
13970
13971 #[test]
13972 fn an_op_reduces_a_range_to_one_number() {
13973 let mut f = Fixture::new();
13974 f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
13975 assert_eq!(
13976 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
13977 "$4\r\n-0.5\r\n"
13978 );
13979 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
13980 assert_eq!(
13981 f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
13982 "$3\r\n2.5\r\n"
13983 );
13984 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
13985 assert_eq!(
13986 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
13987 ":1\r\n"
13988 );
13989 f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
13992 assert_eq!(
13993 f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
13994 "$19\r\n0.30000000000000004\r\n"
13995 );
13996 assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
13997 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
13998
13999 f.run(&[b"ARSET", b"w", b"0", b"word"]);
14002 assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
14003 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
14004 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
14005
14006 assert_eq!(
14007 f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
14008 "-ERR unknown operation\r\n"
14009 );
14010 assert_eq!(
14011 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
14012 "-ERR MATCH requires a value argument\r\n"
14013 );
14014 assert_eq!(
14015 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
14016 "-ERR wrong number of arguments for 'arop' command\r\n"
14017 );
14018 }
14019
14020 #[test]
14021 fn the_info_is_a_map_and_a_missing_key_is_an_error() {
14022 let mut f = Fixture::new();
14023 assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
14024 f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
14025 let short = f.run(&[b"ARINFO", b"a"]);
14026 assert!(
14027 short.starts_with("*14\r\n"),
14028 "seven pairs on RESP2: {short}"
14029 );
14030 assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
14031 assert!(
14032 short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
14033 "{short}"
14034 );
14035 assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
14036 let full = f.run(&[b"ARINFO", b"a", b"full"]);
14037 assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
14038 assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
14041 assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
14042 assert!(
14043 full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
14044 "{full}"
14045 );
14046 assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
14047
14048 let mut g = Fixture::new();
14050 g.run(&[b"HELLO", b"3"]);
14051 g.run(&[b"ARINSERT", b"a", b"x"]);
14052 let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
14053 assert!(map.starts_with("%12\r\n"), "{map}");
14054 assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
14055 assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
14056 }
14057
14058 #[test]
14059 fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
14060 let mut f = Fixture::new();
14061 for (score, want) in [
14064 ("3", "3"),
14065 ("3.5", "3.5"),
14066 ("0.3", "0.3"),
14067 ("1e30", "1e+30"),
14068 ("1e19", "1e+19"),
14069 ("1e-7", "1e-7"),
14070 ("0.000001", "0.000001"),
14071 ("4611686018427387904", "4611686018427387904"),
14072 ("-0", "-0"),
14073 ] {
14074 f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
14075 assert_eq!(
14076 f.run(&[b"ZSCORE", b"z", b"m"]),
14077 format!("${}\r\n{want}\r\n", want.len()),
14078 "score {score}"
14079 );
14080 }
14081
14082 let mut g = Fixture::new();
14085 g.run(&[b"HELLO", b"3"]);
14086 g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
14087 assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
14088 assert_eq!(
14093 g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
14094 "$31\r\n1000000000000000000000000000000\r\n"
14095 );
14096 assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
14097 assert_eq!(
14098 g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
14099 "$20\r\n10000000000000000000\r\n"
14100 );
14101 }
14102
14103 #[test]
14106 fn a_node_comes_back_with_the_fields_it_went_in_with() {
14107 let mut f = Fixture::new();
14108 assert_eq!(
14109 f.run(&[
14110 b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
14111 ]),
14112 ":1\r\n"
14113 );
14114 assert_eq!(
14120 f.run(&[b"G.NGET", b"social", b"ada"]),
14121 "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
14122 );
14123 assert_eq!(
14126 f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
14127 ":0\r\n"
14128 );
14129 assert_eq!(
14130 f.run(&[b"G.NGET", b"social", b"ada"]),
14131 "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
14132 );
14133 assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
14136 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
14137 assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
14138 assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
14139
14140 assert_eq!(
14143 f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
14144 "-ERR syntax error\r\n"
14145 );
14146 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
14147
14148 let mut g = Fixture::new();
14150 g.run(&[b"HELLO", b"3"]);
14151 g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
14152 assert_eq!(
14153 g.run(&[b"G.NGET", b"social", b"ada"]),
14154 "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
14155 );
14156 }
14157
14158 #[test]
14159 fn an_edge_creates_the_ends_it_needs() {
14160 let mut f = Fixture::new();
14161 assert_eq!(
14162 f.run(&[
14163 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
14164 ]),
14165 ":1\r\n"
14166 );
14167 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
14169 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
14170 assert_eq!(
14171 f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
14172 "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
14173 );
14174 assert_eq!(
14175 f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
14176 "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
14177 );
14178 assert_eq!(
14181 f.run(&[
14182 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
14183 ]),
14184 ":0\r\n"
14185 );
14186 assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
14187 assert_eq!(
14189 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
14190 ":1\r\n"
14191 );
14192 assert_eq!(
14193 f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
14194 ":1\r\n"
14195 );
14196
14197 assert_eq!(
14198 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
14199 ":1\r\n"
14200 );
14201 assert_eq!(
14202 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
14203 ":0\r\n"
14204 );
14205 assert_eq!(
14208 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
14209 ":0\r\n"
14210 );
14211 assert_eq!(
14212 f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
14213 ":0\r\n"
14214 );
14215 assert_eq!(
14216 f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
14217 ":0\r\n"
14218 );
14219 }
14220
14221 #[test]
14224 fn a_hop_answers_a_cursor_and_a_page() {
14225 let mut f = Fixture::new();
14226 for i in 0..25u32 {
14227 let dst = format!("n{i}");
14228 f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
14229 }
14230 let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
14232 assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
14233
14234 let mut seen = 0;
14235 let mut cursor = String::from("0");
14236 loop {
14237 let page = f.run(&[
14238 b"G.OUT",
14239 b"social",
14240 b"hub",
14241 b"FOLLOWS",
14242 b"COUNT",
14243 b"7",
14244 b"CURSOR",
14245 cursor.as_bytes(),
14246 ]);
14247 let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
14248 cursor = head
14249 .rsplit("\r\n")
14250 .next()
14251 .expect("the cursor line")
14252 .to_string();
14253 seen += rest
14254 .split_once("\r\n")
14255 .expect("the page length")
14256 .0
14257 .parse::<usize>()
14258 .expect("a length");
14259 if cursor == "0" {
14260 break;
14261 }
14262 }
14263 assert_eq!(seen, 25, "every neighbour once across the pages");
14264
14265 assert_eq!(
14268 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
14269 "*2\r\n$1\r\n0\r\n*0\r\n"
14270 );
14271 assert_eq!(
14272 f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
14273 "*2\r\n$1\r\n0\r\n*0\r\n"
14274 );
14275 assert_eq!(
14276 f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
14277 "*2\r\n$1\r\n0\r\n*0\r\n"
14278 );
14279 assert_eq!(
14280 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
14281 "-ERR COUNT must be a positive integer\r\n"
14282 );
14283 assert_eq!(
14284 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
14285 "-ERR syntax error\r\n"
14286 );
14287 }
14288
14289 #[test]
14290 fn a_degree_counts_one_way_or_both() {
14291 let mut f = Fixture::new();
14292 f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
14293 f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
14294 f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
14295 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
14296 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
14297 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
14298 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
14299 assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
14300 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
14301 assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
14302 assert_eq!(
14303 f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
14304 "-ERR syntax error\r\n"
14305 );
14306 }
14307
14308 #[test]
14311 fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
14312 let mut f = Fixture::new();
14313 for (src, dst) in [
14314 ("ada", "grace"),
14315 ("ada", "alan"),
14316 ("grace", "edsger"),
14317 ("alan", "edsger"),
14318 ("edsger", "barbara"),
14319 ] {
14320 f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
14321 }
14322 assert_eq!(
14325 f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
14326 "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
14327 );
14328 assert_eq!(
14329 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
14330 "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
14331 );
14332 let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
14333 assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
14334 assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
14335 assert_eq!(
14337 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
14338 "*1\r\n$5\r\ngrace\r\n"
14339 );
14340 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
14342 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
14343 assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
14344 assert_eq!(
14345 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
14346 "-ERR DEPTH must be a positive integer\r\n"
14347 );
14348 assert_eq!(
14349 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
14350 "-ERR syntax error\r\n"
14351 );
14352 }
14353
14354 #[test]
14357 fn a_path_is_the_shortest_one_and_goes_over_any_label() {
14358 let mut f = Fixture::new();
14359 for i in 0..6u32 {
14362 let src = format!("n{i}");
14363 let dst = format!("n{}", i + 1);
14364 f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
14365 }
14366 assert_eq!(
14367 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
14368 "*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"
14369 );
14370 f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
14371 assert_eq!(
14372 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
14373 "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
14374 );
14375 assert_eq!(
14378 f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
14379 "*1\r\n$2\r\nn2\r\n"
14380 );
14381 assert_eq!(
14382 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
14383 "*0\r\n"
14384 );
14385 assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
14387 f.run(&[b"G.NADD", b"road", b"island"]);
14390 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
14391 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
14392 assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
14393 assert_eq!(
14394 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
14395 "-ERR syntax error\r\n"
14396 );
14397 }
14398
14399 #[test]
14403 fn the_keyspace_sees_a_graph_key_like_any_other() {
14404 let mut f = Fixture::new();
14405 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
14406 assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
14407 assert_eq!(
14408 f.run(&[b"OBJECT", b"ENCODING", b"social"]),
14409 "$9\r\nadjacency\r\n"
14410 );
14411 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
14412 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
14413 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
14414 let held = f.server.memory_bytes();
14418 for i in 0..200u32 {
14419 let dst = format!("n{i}");
14420 f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
14421 }
14422 assert!(
14423 f.server.memory_bytes() > held,
14424 "two hundred edges cost something: {held} then {}",
14425 f.server.memory_bytes()
14426 );
14427 f.run(&[b"DEL", b"big"]);
14428
14429 assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
14432 assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
14433 assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
14434 assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
14435 assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
14436 f.run(&[b"SELECT", b"1"]);
14437 assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
14438
14439 assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
14440 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14441 f.run(&[b"G.NADD", b"g", b"n"]);
14442 assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
14443 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14444 }
14445
14446 #[test]
14449 fn a_graph_cannot_be_copied_or_dumped() {
14450 let mut f = Fixture::new();
14451 f.run(&[b"G.NADD", b"social", b"ada"]);
14452 assert_eq!(
14453 f.run(&[b"COPY", b"social", b"other"]),
14454 "-ERR COPY is not supported for a graph\r\n"
14455 );
14456 assert_eq!(
14457 f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
14458 "-ERR COPY is not supported for a graph\r\n"
14459 );
14460 assert_eq!(
14461 f.run(&[b"DUMP", b"social"]),
14462 "-ERR DUMP is not supported for a graph\r\n"
14463 );
14464 assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
14466 }
14467
14468 #[test]
14471 fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
14472 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14473 let mut f = Fixture::new();
14474 f.run(&[b"G.NADD", b"social", b"ada"]);
14475 assert_eq!(f.run(&[b"GET", b"social"]), wrong);
14476 assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
14477 assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
14478
14479 f.run(&[b"SET", b"str", b"v"]);
14480 for cmd in [
14481 vec![b"G.NADD".as_ref(), b"str", b"n"],
14482 vec![b"G.NGET".as_ref(), b"str", b"n"],
14483 vec![b"G.NDEL".as_ref(), b"str", b"n"],
14484 vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
14485 vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
14486 vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
14487 vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
14488 vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
14489 vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
14490 vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
14491 ] {
14492 assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
14493 }
14494 }
14495
14496 #[test]
14499 fn a_graph_goes_when_its_last_node_does() {
14500 let mut f = Fixture::new();
14501 f.run(&[
14502 b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
14503 ]);
14504 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
14505 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
14507 assert_eq!(
14508 f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
14509 ":0\r\n"
14510 );
14511 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
14512 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
14513
14514 assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
14515 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
14516 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14517 assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
14518
14519 f.run(&[b"G.NADD", b"social", b"first"]);
14522 f.run(&[b"G.NADD", b"social", b"second"]);
14523 f.run(&[b"G.NDEL", b"social", b"first"]);
14524 f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
14525 assert_eq!(
14526 f.run(&[b"G.OUT", b"social", b"third", b"F"]),
14527 "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
14528 );
14529 }
14530
14531 #[test]
14536 fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
14537 let mut f = Fixture::new();
14538 let doc = br#"{"a":1,"b":{"c":true}}"#;
14539 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
14540 assert_eq!(
14543 f.run(&[b"JSON.GET", b"doc"]),
14544 bulk(r#"{"a":1,"b":{"c":true}}"#)
14545 );
14546 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
14547 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
14548 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
14549 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
14552 assert_eq!(
14553 f.run(&[b"JSON.GET", b"doc", b".nope"]),
14554 "-ERR Path does not exist\r\n"
14555 );
14556 assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
14557 assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
14560 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
14561 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
14562 assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
14563 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14564 }
14565
14566 #[test]
14572 fn the_two_json_errors_that_carry_no_prefix() {
14573 let mut f = Fixture::new();
14574 f.run(&[b"SET", b"plain", b"x"]);
14575 let wrong = "-Existing key has wrong Redis type\r\n";
14576 assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
14577 assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
14578 assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
14579 assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
14580 assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
14581
14582 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
14583 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
14587 assert_eq!(
14588 f.run(&[b"JSON.GET", b"doc"]),
14589 bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
14590 );
14591 assert_eq!(
14592 f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
14593 "-Err wrong static path\r\n"
14594 );
14595 }
14596
14597 #[test]
14599 fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
14600 let mut f = Fixture::new();
14601 assert_eq!(
14603 f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
14604 "-ERR new objects must be created at the root\r\n"
14605 );
14606 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14607 assert_eq!(
14610 f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
14611 "-ERR new objects must be created at the root\r\n"
14612 );
14613 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
14614 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
14615
14616 f.run(&[
14617 b"JSON.SET",
14618 b"doc",
14619 b"$",
14620 br#"{"o":{},"arr":[1,2],"s":"x"}"#,
14621 ]);
14622 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
14624 assert_eq!(
14627 f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
14628 "$-1\r\n"
14629 );
14630 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
14631 assert_eq!(
14633 f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
14634 "-ERR array index out of range\r\n"
14635 );
14636 assert_eq!(
14637 f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
14638 "-ERR array index out of range\r\n"
14639 );
14640 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
14641 assert_eq!(
14644 f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
14645 "$-1\r\n"
14646 );
14647 assert_eq!(
14648 f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
14649 "$-1\r\n"
14650 );
14651 assert_eq!(
14652 f.run(&[b"JSON.GET", b"doc"]),
14653 bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
14654 );
14655 assert!(
14659 f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
14660 .starts_with("-this is not the start of a value")
14661 );
14662 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
14663 }
14664
14665 #[test]
14668 fn the_json_commands_that_do_not_answer_text() {
14669 let mut f = Fixture::new();
14670 let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
14671 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14672
14673 assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
14674 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
14675 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
14676 assert_eq!(
14677 f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
14678 format!("*1\r\n{}", bulk("integer"))
14679 );
14680 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
14683 assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
14684
14685 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
14688 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
14689 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
14692 assert_eq!(
14693 f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
14694 "-ERR Path does not exist or not a bool\r\n"
14695 );
14696 assert_eq!(
14697 f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
14698 "-ERR Path does not exist or not a bool\r\n"
14699 );
14700 assert_eq!(
14701 f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
14702 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14703 );
14704
14705 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
14708 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
14709 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
14710 assert_eq!(
14711 f.run(&[b"JSON.GET", b"doc"]),
14712 bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
14713 );
14714
14715 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
14718 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
14719 assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
14724 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
14725 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14726 assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
14727 assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
14728 assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
14729 assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
14730 assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
14731 }
14732
14733 #[test]
14740 fn json_get_lays_out_the_wrapper_it_builds() {
14741 let mut f = Fixture::new();
14742 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
14743
14744 assert_eq!(
14745 f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
14746 bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
14747 );
14748 assert_eq!(
14750 f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
14751 bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
14752 );
14753 let fmt: &[&[u8]] = &[b"INDENT", b" ", b"NEWLINE", b"\n", b"SPACE", b" "];
14754 let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
14755 one.extend_from_slice(fmt);
14756 one.push(b"$.b");
14757 assert_eq!(
14758 f.run(&one),
14759 bulk("[\n [\n 1,\n {\n \"c\": 2\n }\n ]\n]")
14760 );
14761 let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
14762 two.extend_from_slice(fmt);
14763 two.push(b"$.a");
14764 two.push(b"$.nope");
14765 assert_eq!(
14766 f.run(&two),
14767 bulk("{\n \"$.a\": [\n 1\n ],\n \"$.nope\": []\n}")
14768 );
14769 let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
14772 root.push(b".a");
14773 assert_eq!(f.run(&root), bulk("1"));
14774 }
14775
14776 #[test]
14779 fn json_mget_answers_once_per_key_whatever_is_under_them() {
14780 let mut f = Fixture::new();
14781 f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
14782 f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
14783 f.run(&[b"SET", b"plain", b"x"]);
14784 assert_eq!(
14785 f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
14786 format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
14787 );
14788 assert_eq!(
14791 f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
14792 format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
14793 );
14794 assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
14797 }
14798
14799 #[test]
14807 fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
14808 let mut f = Fixture::new();
14809 let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
14810 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14811
14812 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
14813 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
14814 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
14815 assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
14816 assert_eq!(
14817 f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
14818 format!("*2\r\n{}{}", bulk("x"), bulk("y"))
14819 );
14820 assert_eq!(
14823 f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
14824 "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
14825 );
14826
14827 assert_eq!(
14830 f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
14831 "-ERR Path does not exist\r\n"
14832 );
14833 assert_eq!(
14834 f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
14835 "-ERR Path does not exist\r\n"
14836 );
14837 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
14838 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
14841 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
14844
14845 assert_eq!(
14849 f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
14850 "-ERR Path does not exist or not an array\r\n"
14851 );
14852 assert_eq!(
14853 f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
14854 "-ERR Path does not exist or not an object\r\n"
14855 );
14856 assert_eq!(
14857 f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
14858 "-WRONGTYPE wrong type of path value - expected object\r\n"
14859 );
14860 assert_eq!(
14861 f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
14862 "-WRONGTYPE wrong type of path value - expected string\r\n"
14863 );
14864
14865 assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
14868 assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
14869 assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
14870 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
14871 assert_eq!(
14872 f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
14873 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14874 );
14875 assert_eq!(
14877 f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
14878 "-ERR Path does not exist or not an object\r\n"
14879 );
14880 }
14881
14882 #[test]
14888 fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
14889 let mut f = Fixture::new();
14890 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
14891
14892 assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
14893 assert_eq!(
14894 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
14895 "*1\r\n:6\r\n"
14896 );
14897 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
14898
14899 assert_eq!(
14902 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
14903 ":7\r\n"
14904 );
14905 assert_eq!(
14906 f.run(&[b"JSON.GET", b"doc", b".a"]),
14907 bulk("[1,2,3,4,5,0,6]")
14908 );
14909 assert_eq!(
14910 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
14911 ":8\r\n"
14912 );
14913 assert_eq!(
14915 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
14916 "-ERR index out of bounds\r\n"
14917 );
14918 assert_eq!(
14919 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
14920 "-ERR index out of bounds\r\n"
14921 );
14922
14923 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
14926 assert_eq!(
14927 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
14928 ":3\r\n"
14929 );
14930 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
14931 assert_eq!(
14932 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
14933 ":2\r\n"
14934 );
14935 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
14936 assert_eq!(
14937 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
14938 ":0\r\n"
14939 );
14940 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14941
14942 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
14945 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
14946 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
14947 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
14948 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
14949
14950 for call in [
14953 &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
14954 &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
14955 &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
14956 &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
14957 ] {
14958 for path in [&b".n"[..], &b".nope"[..]] {
14959 let args: Vec<&[u8]> = call
14960 .iter()
14961 .map(|a| if *a == b"PATH" { path } else { *a })
14962 .collect();
14963 assert_eq!(
14964 f.run(&args),
14965 "-ERR Path does not exist or not an array\r\n",
14966 "{} {}",
14967 String::from_utf8_lossy(call[0]),
14968 String::from_utf8_lossy(path)
14969 );
14970 }
14971 }
14972
14973 assert_eq!(
14976 f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
14977 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14978 );
14979 assert_eq!(
14980 f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
14981 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14982 );
14983
14984 assert!(
14989 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
14990 .starts_with("-this is not the start of a value")
14991 );
14992 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14993 }
14994
14995 #[test]
15003 fn json_arrinsert_is_all_or_nothing_across_the_matches() {
15004 let mut f = Fixture::new();
15005 let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
15006 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15007 assert_eq!(
15008 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
15009 "-ERR index out of bounds\r\n"
15010 );
15011 assert_eq!(
15012 f.run(&[b"JSON.GET", b"doc"]),
15013 bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
15014 );
15015 assert_eq!(
15017 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
15018 "*3\r\n:4\r\n:3\r\n:2\r\n"
15019 );
15020 assert_eq!(
15021 f.run(&[b"JSON.GET", b"doc"]),
15022 bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
15023 );
15024 }
15025
15026 #[test]
15034 fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
15035 let mut f = Fixture::new();
15036 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
15037
15038 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
15039 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
15040 assert_eq!(
15041 f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
15042 "*1\r\n:1\r\n"
15043 );
15044
15045 assert_eq!(
15048 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
15049 ":3\r\n"
15050 );
15051 assert_eq!(
15054 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
15055 ":-1\r\n"
15056 );
15057
15058 for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
15061 assert_eq!(
15062 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
15063 ":3\r\n",
15064 "{}",
15065 String::from_utf8_lossy(start)
15066 );
15067 }
15068 assert_eq!(
15069 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
15070 ":0\r\n"
15071 );
15072 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
15075 assert_eq!(
15076 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
15077 ":-1\r\n"
15078 );
15079
15080 f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
15084 assert_eq!(
15085 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
15086 ":0\r\n"
15087 );
15088 assert_eq!(
15089 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
15090 ":1\r\n"
15091 );
15092 assert_eq!(
15093 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
15094 ":-1\r\n"
15095 );
15096
15097 assert_eq!(
15101 f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
15102 "-ERR Path does not exist\r\n"
15103 );
15104 assert_eq!(
15105 f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
15106 "-WRONGTYPE wrong type of path value - expected array\r\n"
15107 );
15108 assert_eq!(
15109 f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
15110 "-ERR Path does not exist\r\n"
15111 );
15112 assert_eq!(
15113 f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
15114 "-ERR Path does not exist\r\n"
15115 );
15116 }
15117
15118 #[test]
15121 fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
15122 let mut f = Fixture::new();
15123 let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
15124 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15125
15126 assert_eq!(
15129 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
15130 bulk("9").as_str()
15131 );
15132 assert_eq!(
15134 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
15135 bulk("[11]").as_str()
15136 );
15137 assert_eq!(
15140 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
15141 bulk("13.0").as_str()
15142 );
15143 assert_eq!(
15144 f.run(&[b"JSON.TYPE", b"doc", b".i"]),
15145 bulk("number").as_str()
15146 );
15147 assert_eq!(
15148 f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
15149 bulk("3.0").as_str()
15150 );
15151 assert_eq!(
15152 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
15153 bulk("-8").as_str()
15154 );
15155 f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
15158 assert_eq!(
15159 f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
15160 bulk("1.224744871391589").as_str()
15161 );
15162 assert_eq!(
15163 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
15164 "-ERR result is not a number\r\n"
15165 );
15166 f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
15170 assert_eq!(
15171 f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
15172 "-ERR numeric overflow\r\n"
15173 );
15174 f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
15175 assert_eq!(
15176 f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
15177 "-ERR numeric overflow\r\n"
15178 );
15179 f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
15181 assert_eq!(
15182 f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
15183 "-ERR result is not a number\r\n"
15184 );
15185
15186 assert_eq!(
15190 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
15191 bulk("[null]").as_str()
15192 );
15193 assert_eq!(
15194 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
15195 bulk("[]").as_str()
15196 );
15197 assert_eq!(
15198 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
15199 "-ERR Path does not exist or does not contains a number\r\n"
15200 );
15201 assert_eq!(
15202 f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
15203 "-ERR Path does not exist or does not contains a number\r\n"
15204 );
15205 assert_eq!(
15208 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
15209 "-bad input number\r\n"
15210 );
15211 assert_eq!(
15212 f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
15213 "-ERR could not perform this operation on a key that doesn't exist\r\n"
15214 );
15215 assert_eq!(
15216 f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
15217 "-ERR could not perform this operation on a key that doesn't exist\r\n"
15218 );
15219 }
15220
15221 #[test]
15224 fn json_strappend_reads_its_shape_off_the_argument_count() {
15225 let mut f = Fixture::new();
15226 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
15227
15228 assert_eq!(
15229 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
15230 ":3\r\n"
15231 );
15232 assert_eq!(
15233 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
15234 "*1\r\n:4\r\n"
15235 );
15236 assert_eq!(
15239 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
15240 ":6\r\n"
15241 );
15242 f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
15245 assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
15246 assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
15247
15248 assert_eq!(
15252 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
15253 "-WRONGTYPE wrong type of path value - expected string\r\n"
15254 );
15255 assert_eq!(
15256 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
15257 "*1\r\n$-1\r\n"
15258 );
15259 assert_eq!(
15260 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
15261 "-ERR Path does not exist or not a string\r\n"
15262 );
15263 assert_eq!(
15264 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
15265 "*0\r\n"
15266 );
15267 assert_eq!(
15268 f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
15269 "-ERR could not perform this operation on a key that doesn't exist\r\n"
15270 );
15271 }
15272
15273 #[test]
15276 fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
15277 let mut f = Fixture::new();
15278 let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
15281
15282 f.run(&[b"JSON.SET", b"doc", b"$", three]);
15283 assert_eq!(
15284 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
15285 ":4\r\n"
15286 );
15287 f.run(&[b"JSON.SET", b"doc", b"$", three]);
15288 assert_eq!(
15289 f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
15290 ":2\r\n"
15291 );
15292 f.run(&[b"JSON.SET", b"doc", b"$", three]);
15293 assert_eq!(
15294 f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
15295 ":1\r\n"
15296 );
15297 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
15298 assert_eq!(
15299 f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
15300 bulk("1").as_str()
15301 );
15302 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
15303 assert_eq!(
15304 f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
15305 bulk("13").as_str()
15306 );
15307 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
15308 assert_eq!(
15309 f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
15310 ":4\r\n"
15311 );
15312 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
15313 assert_eq!(
15314 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
15315 bulk("false").as_str()
15316 );
15317 assert_eq!(
15320 f.run(&[b"JSON.GET", b"doc", b".a"]),
15321 bulk("[false,true,false]").as_str()
15322 );
15323
15324 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
15327 assert_eq!(
15328 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
15329 ":3\r\n"
15330 );
15331 assert_eq!(
15332 f.run(&[b"JSON.GET", b"doc", b".a"]),
15333 bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
15334 );
15335 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
15338 assert_eq!(
15339 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
15340 "-ERR Path does not exist or not an array\r\n"
15341 );
15342 assert_eq!(
15343 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
15344 "-ERR Path does not exist or not a bool\r\n"
15345 );
15346 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
15350 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
15351 assert_eq!(
15352 f.run(&[b"JSON.GET", b"doc", b".a"]),
15353 bulk("[[],[2]]").as_str()
15354 );
15355 }
15356
15357 #[test]
15360 fn a_write_reaches_a_match_that_sits_inside_another_match() {
15361 let mut f = Fixture::new();
15362 let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
15363
15364 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
15365 assert_eq!(
15366 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
15367 "*3\r\n:3\r\n:2\r\n:3\r\n"
15368 );
15369 assert_eq!(
15370 f.run(&[b"JSON.GET", b"doc", b"$"]),
15371 bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
15372 );
15373
15374 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
15377 assert_eq!(
15378 f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
15379 "*3\r\n:1\r\n:1\r\n:1\r\n"
15380 );
15381 assert_eq!(
15382 f.run(&[b"JSON.GET", b"doc", b"$"]),
15383 bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
15384 );
15385
15386 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
15389 assert_eq!(
15390 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
15391 bulk("[null,8,8]").as_str()
15392 );
15393 }
15394
15395 #[test]
15398 fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
15399 let mut f = Fixture::new();
15400 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
15401
15402 assert_eq!(
15405 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
15406 bulk("[null]").as_str()
15407 );
15408 assert_eq!(
15409 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
15410 bulk("[null]").as_str()
15411 );
15412 assert_eq!(
15413 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
15414 bulk("[]").as_str()
15415 );
15416 assert_eq!(
15417 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
15418 "-ERR Path does not exist or does not contains a number\r\n"
15419 );
15420 assert_eq!(
15422 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
15423 "-bad input number\r\n"
15424 );
15425
15426 assert_eq!(
15428 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
15429 "*1\r\n$-1\r\n"
15430 );
15431 assert_eq!(
15432 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
15433 "-ERR Path does not exist or not a string\r\n"
15434 );
15435 assert_eq!(
15436 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
15437 "-WRONGTYPE wrong type of path value - expected string\r\n"
15438 );
15439
15440 assert_eq!(
15442 f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
15443 "-ERR could not perform this operation on a key that doesn't exist\r\n"
15444 );
15445 assert_eq!(
15446 f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
15447 "-ERR could not perform this operation on a key that doesn't exist\r\n"
15448 );
15449 }
15450
15451 #[test]
15454 fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
15455 let mut f = Fixture::new();
15456
15457 assert_eq!(
15461 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
15462 "+OK\r\n"
15463 );
15464 assert_eq!(
15465 f.run(&[b"JSON.GET", b"doc", b"$"]),
15466 bulk(r#"[{"x":null,"y":1}]"#).as_str()
15467 );
15468
15469 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
15472 assert_eq!(
15473 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
15474 "+OK\r\n"
15475 );
15476 assert_eq!(
15477 f.run(&[b"JSON.GET", b"doc", b"$"]),
15478 bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
15479 );
15480
15481 assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
15483 assert_eq!(
15484 f.run(&[b"JSON.GET", b"doc", b"$"]),
15485 bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
15486 );
15487
15488 assert_eq!(
15492 f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
15493 "+OK\r\n"
15494 );
15495 assert_eq!(
15496 f.run(&[b"JSON.GET", b"doc", b"$"]),
15497 bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
15498 );
15499
15500 assert_eq!(
15504 f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
15505 "+OK\r\n"
15506 );
15507 assert_eq!(
15508 f.run(&[b"JSON.GET", b"doc", b"$.new"]),
15509 bulk(r#"[{"z":null}]"#).as_str()
15510 );
15511 assert_eq!(
15512 f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
15513 "$-1\r\n"
15514 );
15515 assert_eq!(
15516 f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
15517 "-Err wrong static path\r\n"
15518 );
15519
15520 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
15522 assert_eq!(
15523 f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
15524 "+OK\r\n"
15525 );
15526 assert_eq!(
15527 f.run(&[b"JSON.GET", b"doc", b"$"]),
15528 bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
15529 );
15530
15531 assert_eq!(
15533 f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
15534 "-ERR syntax error\r\n"
15535 );
15536 assert_eq!(
15537 f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
15538 "-ERR new objects must be created at the root\r\n"
15539 );
15540 f.run(&[b"SET", b"str", b"x"]);
15541 assert_eq!(
15542 f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
15543 "-Existing key has wrong Redis type\r\n"
15544 );
15545 }
15546
15547 #[test]
15550 fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
15551 let mut f = Fixture::new();
15552 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15553 assert_eq!(
15554 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
15555 "+OK\r\n"
15556 );
15557 assert_eq!(
15561 f.run(&[b"JSON.GET", b"doc", b"$"]),
15562 bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
15563 );
15564
15565 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15568 assert_eq!(
15569 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
15570 "+OK\r\n"
15571 );
15572 assert_eq!(
15573 f.run(&[b"JSON.GET", b"doc", b"$"]),
15574 bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
15575 );
15576 }
15577
15578 #[test]
15581 fn a_filter_path_reads_and_writes_the_members_it_keeps() {
15582 let mut f = Fixture::new();
15583 let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
15584 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15585
15586 assert_eq!(
15587 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
15588 bulk(r#"["a","c"]"#).as_str()
15589 );
15590 assert_eq!(
15593 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
15594 bulk(r#"["a","c"]"#).as_str()
15595 );
15596 assert_eq!(
15598 f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
15599 bulk(r#""a""#).as_str()
15600 );
15601 assert_eq!(
15602 f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
15603 "*1\r\n$6\r\nobject\r\n"
15604 );
15605
15606 assert_eq!(
15610 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
15611 bulk("[9,10]").as_str()
15612 );
15613 assert_eq!(
15614 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
15615 "+OK\r\n"
15616 );
15617 assert_eq!(
15618 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
15619 "-Err wrong static path\r\n"
15620 );
15621 assert_eq!(
15622 f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
15623 ":2\r\n"
15624 );
15625 assert_eq!(
15626 f.run(&[b"JSON.GET", b"doc", b"$"]),
15627 bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
15628 );
15629
15630 assert!(
15633 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
15634 .starts_with("-ERR")
15635 );
15636 assert!(
15637 f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
15638 .starts_with("-ERR")
15639 );
15640 }
15641
15642 #[test]
15645 fn a_filter_takes_the_membership_operators_and_the_methods_too() {
15646 let mut f = Fixture::new();
15647 let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
15648 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15649
15650 for (path, want) in [
15651 (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
15652 (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
15653 (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
15654 (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
15655 (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
15656 (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
15657 (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
15658 (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
15659 (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
15660 (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
15661 (b"$.box[?(@.n~)].t", "[]"),
15662 (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
15663 (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
15664 (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
15665 (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
15666 ] {
15667 assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
15668 }
15669
15670 assert_eq!(
15673 f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
15674 "+OK\r\n"
15675 );
15676 assert_eq!(
15677 f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
15678 bulk(r#"["b"]"#).as_str()
15679 );
15680 }
15681
15682 #[test]
15685 fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
15686 let mut f = Fixture::new();
15687 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
15688 assert_eq!(
15689 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15690 "+OK\r\n"
15691 );
15692 assert_eq!(
15693 f.run(&[b"JSON.GET", b"doc", b"$"]),
15694 bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
15695 );
15696 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
15699 assert_eq!(
15700 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15701 "+OK\r\n"
15702 );
15703 assert_eq!(
15704 f.run(&[b"JSON.GET", b"doc", b"$"]),
15705 bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
15706 );
15707 }
15708
15709 #[test]
15712 fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
15713 let mut f = Fixture::new();
15714 assert_eq!(
15715 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
15716 "+OK\r\n"
15717 );
15718 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
15719 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
15720
15721 assert_eq!(
15723 f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
15724 "+OK\r\n"
15725 );
15726 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
15727
15728 f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
15733 assert_eq!(
15734 f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
15735 "$-1\r\n"
15736 );
15737 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
15738 assert_eq!(
15739 f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
15740 "$-1\r\n"
15741 );
15742 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15743
15744 f.run(&[b"SET", b"str", b"x"]);
15749 assert_eq!(
15750 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
15751 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
15752 );
15753 assert_eq!(
15754 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
15755 "-Existing key has wrong Redis type\r\n"
15756 );
15757 assert_eq!(
15758 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
15759 "-ERR new objects must be created at the root\r\n"
15760 );
15761 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
15762
15763 assert_eq!(
15768 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
15769 "-Err wrong static path\r\n"
15770 );
15771 assert_eq!(
15772 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
15773 "-ERR array index out of range\r\n"
15774 );
15775 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15776
15777 f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
15781 assert_eq!(
15782 f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
15783 "+OK\r\n"
15784 );
15785 assert_eq!(
15786 f.run(&[b"JSON.GET", b"c", b"$"]),
15787 bulk(r#"[{"n":3}]"#).as_str()
15788 );
15789
15790 assert_eq!(
15793 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
15794 "-ERR wrong number of arguments for 'json.mset' command\r\n"
15795 );
15796 }
15797
15798 #[test]
15801 fn json_resp_answers_the_document_as_resp_types() {
15802 let mut f = Fixture::new();
15803 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
15804 assert_eq!(
15805 f.run(&[b"JSON.RESP", b"doc"]),
15806 "*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"
15807 );
15808 assert_eq!(
15810 f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
15811 "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
15812 );
15813
15814 f.run(&[
15815 b"JSON.SET",
15816 b"doc",
15817 b"$",
15818 br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
15819 ]);
15820 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
15821 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
15822 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
15825 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
15826 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
15827
15828 assert_eq!(
15831 f.run(&[b"JSON.RESP", b"doc", b".nope"]),
15832 "-ERR Path does not exist\r\n"
15833 );
15834 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
15835 assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
15836 assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
15837 }
15838
15839 #[test]
15843 fn json_debug_answers_a_byte_count_and_its_own_help() {
15844 let mut f = Fixture::new();
15845 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
15846 let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
15847 assert!(one.starts_with(':'), "{one}");
15848 assert_eq!(
15849 f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
15850 format!("*1\r\n{one}")
15851 );
15852 let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
15853 assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
15854
15855 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
15859 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
15860 assert_eq!(
15861 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
15862 "-ERR Path does not exist\r\n"
15863 );
15864 assert_eq!(
15865 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
15866 "*0\r\n"
15867 );
15868
15869 assert_eq!(
15870 f.run(&[b"JSON.DEBUG", b"HELP"]),
15871 "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
15872 $34\r\nHELP - this message\r\n"
15873 );
15874 assert_eq!(
15875 f.run(&[b"JSON.DEBUG", b"NOPE"]),
15876 "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
15877 );
15878 assert_eq!(
15879 f.run(&[b"JSON.DEBUG", b"MEMORY"]),
15880 "-ERR wrong number of arguments for 'json.debug' command\r\n"
15881 );
15882 }
15883
15884 #[test]
15889 fn the_first_vadd_decides_how_wide_the_set_is() {
15890 let mut f = Fixture::new();
15891 assert_eq!(
15892 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
15893 ":1\r\n"
15894 );
15895 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
15896 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15897 assert_eq!(
15900 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
15901 ":0\r\n"
15902 );
15903 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15904 assert_eq!(
15907 f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
15908 "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
15909 );
15910 assert_eq!(
15913 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
15914 ":1\r\n"
15915 );
15916 assert_eq!(
15917 f.run(&[b"VEMB", b"v", b"nowhere"]),
15918 "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
15919 );
15920 assert_eq!(
15924 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
15925 "-ERR asked quantization mismatch with existing vector set\r\n"
15926 );
15927 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
15930 assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
15931 assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
15932 }
15933
15934 #[test]
15937 fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
15938 let mut f = Fixture::new();
15939 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
15940 assert_eq!(
15946 f.run(&[b"VEMB", b"v", b"a"]),
15947 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
15948 );
15949 f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
15951 assert_eq!(
15952 f.run(&[b"VEMB", b"n", b"a"]),
15953 "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
15954 );
15955 f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
15958 assert_eq!(
15959 f.run(&[b"VEMB", b"b", b"a"]),
15960 "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
15961 );
15962 assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
15963 assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
15964
15965 let mut f = Fixture::new();
15969 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
15970 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
15971 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
15972 assert_eq!(
15973 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
15974 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
15975 $8\r\nopposite\r\n$1\r\n0\r\n"
15976 );
15977 assert_eq!(
15980 f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
15981 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15982 );
15983 assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
15986 assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
15987 assert_eq!(
15990 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
15991 "*1\r\n$6\r\nacross\r\n"
15992 );
15993 assert_eq!(
15994 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
15995 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15996 );
15997 assert_eq!(
16001 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
16002 "*1\r\n$6\r\nacross\r\n"
16003 );
16004
16005 let mut g = Fixture::new();
16008 g.run(&[b"HELLO", b"3"]);
16009 g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16010 assert_eq!(
16011 g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
16012 "%1\r\n$4\r\neast\r\n,1\r\n"
16013 );
16014 }
16015
16016 #[test]
16018 fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
16019 let mut f = Fixture::new();
16020 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16021 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
16022 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
16023 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
16024 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
16027 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
16028 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
16030 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
16031 assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
16034 assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
16035 assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
16036 assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
16039 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
16040 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
16041 assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
16042
16043 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
16045 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
16046 assert_eq!(
16047 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
16048 "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
16049 );
16050 }
16051
16052 #[test]
16055 fn vrem_takes_the_attribute_with_it() {
16056 let mut f = Fixture::new();
16057 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16058 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
16059 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
16060 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
16061 assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
16062 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
16065
16066 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16069 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
16070 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
16071 f.run(&[b"VREM", b"v", b"east"]);
16072 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
16073 assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
16074 }
16075
16076 #[test]
16079 fn vinfo_says_partition_first() {
16080 let mut f = Fixture::new();
16081 f.run(&[
16082 b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
16083 ]);
16084 f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
16085 let info = f.run(&[b"VINFO", b"v"]);
16086 assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
16087 assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
16090 assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
16091 assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
16092 assert!(
16095 info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
16096 "{info}"
16097 );
16098 let mut f = Fixture::new();
16099 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
16100 assert!(
16101 f.run(&[b"VINFO", b"v"])
16102 .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
16103 );
16104 assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
16105 }
16106
16107 fn named() -> Fixture {
16109 let mut f = Fixture::new();
16110 for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
16111 .iter()
16112 .enumerate()
16113 {
16114 let x = (i + 1).to_string();
16115 f.run(&[
16116 b"VADD",
16117 b"r",
16118 b"VALUES",
16119 b"2",
16120 x.as_bytes(),
16121 b"1",
16122 name.as_bytes(),
16123 ]);
16124 }
16125 f
16126 }
16127
16128 #[test]
16131 fn vrange_walks_the_names_and_not_the_vectors() {
16132 let mut f = named();
16133 assert_eq!(
16134 f.run(&[b"VRANGE", b"r", b"-", b"+"]),
16135 "*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"
16136 );
16137 assert_eq!(
16138 f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
16139 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
16140 "the high end is a name and not a prefix, so delta is past it"
16141 );
16142 assert_eq!(
16143 f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
16144 "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
16145 );
16146 assert_eq!(
16147 f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
16148 "*1\r\n$4\r\nbeta\r\n"
16149 );
16150 assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
16151 f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
16154 assert_eq!(
16155 f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
16156 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
16157 );
16158 assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
16159 }
16160
16161 #[test]
16164 fn a_vrange_count_of_zero_asks_for_nothing() {
16165 let mut f = named();
16166 assert_eq!(
16167 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
16168 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
16169 );
16170 assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
16171 assert!(
16172 f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
16173 .starts_with("*5\r\n"),
16174 "a negative count is no limit at all"
16175 );
16176 }
16177
16178 #[test]
16181 fn vrange_says_which_end_it_could_not_read() {
16182 let mut f = named();
16183 assert_eq!(
16184 f.run(&[b"VRANGE", b"r", b"x", b"y"]),
16185 "-ERR invalid start range format\r\n"
16186 );
16187 assert_eq!(
16188 f.run(&[b"VRANGE", b"r", b"+", b"x"]),
16189 "-ERR invalid end range format\r\n",
16190 "the high end is spelled wrong, which is worth saying before the \
16191 low end being on the wrong side"
16192 );
16193 assert_eq!(
16194 f.run(&[b"VRANGE", b"r", b"+", b"-"]),
16195 "-ERR '-' can only be used as first argument, '+' only as second\r\n"
16196 );
16197 assert_eq!(
16200 f.run(&[b"VRANGE", b"r", b"[", b"+"]),
16201 "-ERR invalid start range format\r\n"
16202 );
16203 assert_eq!(
16204 f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
16205 "-ERR invalid COUNT value\r\n"
16206 );
16207 assert_eq!(
16208 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
16209 "-ERR wrong number of arguments for 'VRANGE' command\r\n"
16210 );
16211 f.run(&[b"SET", b"s", b"x"]);
16212 assert!(
16213 f.run(&[b"VRANGE", b"s", b"-", b"+"])
16214 .starts_with("-WRONGTYPE")
16215 );
16216 }
16217
16218 #[test]
16221 fn reduce_is_refused_and_not_ignored() {
16222 let mut f = Fixture::new();
16223 let reduce = f.run(&[
16224 b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
16225 ]);
16226 assert!(
16227 reduce.starts_with("-ERR REDUCE is not supported."),
16228 "{reduce}"
16229 );
16230 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
16231 }
16232
16233 #[test]
16236 fn vsim_filter_reads_the_attributes() {
16237 let mut f = Fixture::new();
16238 for (name, x, y, attr) in [
16239 ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
16240 ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
16241 ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
16242 ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
16243 ] {
16244 f.run(&[
16245 b"VADD",
16246 b"v",
16247 b"VALUES",
16248 b"2",
16249 x.as_bytes(),
16250 y.as_bytes(),
16251 name.as_bytes(),
16252 b"SETATTR",
16253 attr.as_bytes(),
16254 ]);
16255 }
16256 assert_eq!(
16259 f.run(&[
16260 b"VSIM",
16261 b"v",
16262 b"VALUES",
16263 b"2",
16264 b"9",
16265 b"1",
16266 b"COUNT",
16267 b"2",
16268 b"FILTER",
16269 b".lang == \"en\"",
16270 ]),
16271 "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
16272 );
16273 assert_eq!(
16276 f.run(&[
16277 b"VSIM",
16278 b"v",
16279 b"VALUES",
16280 b"2",
16281 b"9",
16282 b"1",
16283 b"FILTER",
16284 b".lang == 'en' and .year > 1980",
16285 ]),
16286 "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
16287 );
16288 assert_eq!(
16290 f.run(&[
16291 b"VSIM",
16292 b"v",
16293 b"VALUES",
16294 b"2",
16295 b"9",
16296 b"1",
16297 b"FILTER",
16298 b".lang in ['fr', 'de']",
16299 ]),
16300 "*1\r\n$1\r\nb\r\n"
16301 );
16302 assert_eq!(
16303 f.run(&[
16304 b"VSIM",
16305 b"v",
16306 b"VALUES",
16307 b"2",
16308 b"9",
16309 b"1",
16310 b"FILTER",
16311 b".rating > 3"
16312 ]),
16313 "*0\r\n"
16314 );
16315 assert_eq!(
16318 f.run(&[
16319 b"VSIM",
16320 b"v",
16321 b"VALUES",
16322 b"2",
16323 b"9",
16324 b"1",
16325 b"TRUTH",
16326 b"FILTER",
16327 b".year < 1980",
16328 ]),
16329 "*1\r\n$1\r\nc\r\n"
16330 );
16331 f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
16334 assert_eq!(
16335 f.run(&[
16336 b"VSIM",
16337 b"v",
16338 b"VALUES",
16339 b"2",
16340 b"9",
16341 b"1",
16342 b"COUNT",
16343 b"1",
16344 b"FILTER",
16345 b".lang == \"en\"",
16346 ]),
16347 "*1\r\n$1\r\nb\r\n"
16348 );
16349 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
16352 assert_eq!(
16353 f.run(&[
16354 b"VSIM",
16355 b"v",
16356 b"VALUES",
16357 b"2",
16358 b"9",
16359 b"1",
16360 b"COUNT",
16361 b"1",
16362 b"FILTER",
16363 b".lang == \"en\"",
16364 ]),
16365 "*1\r\n$1\r\nb\r\n"
16366 );
16367
16368 let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
16371 assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
16372 assert_eq!(
16373 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
16374 "-ERR invalid FILTER expression\r\n"
16375 );
16376 assert_eq!(
16379 f.run(&[
16380 b"VSIM",
16381 b"v",
16382 b"VALUES",
16383 b"2",
16384 b"9",
16385 b"1",
16386 b"COUNT",
16387 b"1",
16388 b"FILTER-EF",
16389 b"500",
16390 b"FILTER",
16391 b".lang == 'en'",
16392 ]),
16393 "*1\r\n$1\r\nb\r\n"
16394 );
16395 assert_eq!(
16396 f.run(&[
16397 b"VSIM",
16398 b"v",
16399 b"VALUES",
16400 b"2",
16401 b"9",
16402 b"1",
16403 b"COUNT",
16404 b"1",
16405 b"FILTER-EF",
16406 b"0"
16407 ]),
16408 "*1\r\n$1\r\nb\r\n"
16409 );
16410 assert_eq!(
16411 f.run(&[
16412 b"VSIM",
16413 b"v",
16414 b"VALUES",
16415 b"2",
16416 b"9",
16417 b"1",
16418 b"FILTER-EF",
16419 b"lots"
16420 ]),
16421 "-ERR EF must be a positive integer\r\n"
16422 );
16423 }
16424
16425 #[test]
16428 fn the_keyspace_sees_a_vector_set_key_like_any_other() {
16429 let mut f = Fixture::new();
16430 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16431 assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
16432 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
16433 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
16434 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
16435 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
16436 assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
16437 assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
16438 assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
16439 assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
16440 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
16441
16442 f.run(&[b"SET", b"s", b"1"]);
16444 assert_eq!(
16445 f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
16446 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16447 );
16448 assert_eq!(
16449 f.run(&[b"VCARD", b"s"]),
16450 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16451 );
16452 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16453 assert_eq!(
16454 f.run(&[b"GET", b"v"]),
16455 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16456 );
16457 f.run(&[b"G.NADD", b"social", b"ada"]);
16461 assert_eq!(
16462 f.run(&[b"VCARD", b"social"]),
16463 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16464 );
16465 assert_eq!(
16466 f.run(&[b"G.NGET", b"v", b"ada"]),
16467 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16468 );
16469 }
16470
16471 #[test]
16474 fn vrandmember_has_the_two_shapes_srandmember_has() {
16475 let mut f = Fixture::new();
16476 for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
16477 let x = (i + 1).to_string();
16478 f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
16479 }
16480 let one = f.run(&[b"VRANDMEMBER", b"v"]);
16482 assert!(one.starts_with("$1\r\n"), "{one}");
16483 let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
16485 assert!(all.starts_with("*3\r\n"), "{all}");
16486 for name in ["a", "b", "c"] {
16487 assert!(all.contains(name), "{all} is missing {name}");
16488 }
16489 all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
16490 assert!(all.starts_with("*2\r\n"), "{all}");
16491 let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
16493 assert!(many.starts_with("*5\r\n"), "{many}");
16494 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
16496 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
16497 }
16498
16499 #[test]
16502 fn vlinks_reports_one_layer_of_partition_neighbours() {
16503 let mut f = Fixture::new();
16504 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16505 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
16506 assert_eq!(
16509 f.run(&[b"VLINKS", b"v", b"east"]),
16510 "*1\r\n*1\r\n$5\r\nnorth\r\n"
16511 );
16512 assert_eq!(
16513 f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
16514 "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
16515 );
16516 assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
16517 assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
16518 }
16519
16520 #[test]
16523 fn fp32_and_values_are_the_same_vector() {
16524 let mut f = Fixture::new();
16525 let mut blob = Vec::new();
16526 for x in [3.0f32, 4.0] {
16527 blob.extend_from_slice(&x.to_le_bytes());
16528 }
16529 assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
16530 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
16531 assert_eq!(
16532 f.run(&[b"VEMB", b"v", b"a"]),
16533 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
16534 );
16535 assert_eq!(
16541 f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
16542 "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
16543 );
16544 assert_eq!(
16546 f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
16547 "-ERR invalid vector specification\r\n"
16548 );
16549 assert_eq!(
16551 f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
16552 "-ERR syntax error\r\n"
16553 );
16554 assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
16555 }
16556
16557 #[test]
16562 fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
16563 let mut f = Fixture::new();
16564 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
16565 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
16566 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
16567 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
16568 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
16569 assert_eq!(
16572 f.run(&[b"BF.INFO", b"b"]),
16573 "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16574 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16575 +Expansion rate\r\n:2\r\n"
16576 );
16577 assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
16578 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
16579 assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
16582 assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
16583 }
16584
16585 #[test]
16591 fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
16592 let mut f = Fixture::new();
16593 f.run(&[b"SET", b"s", b"text"]);
16594 assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
16595 assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16596 for cmd in [
16597 vec![&b"BF.ADD"[..], b"s", b"x"],
16598 vec![&b"BF.MADD"[..], b"s", b"x"],
16599 vec![&b"BF.CARD"[..], b"s"],
16600 vec![&b"BF.INFO"[..], b"s"],
16601 vec![&b"BF.DEBUG"[..], b"s"],
16602 vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
16603 ] {
16604 let name = String::from_utf8_lossy(cmd[0]).into_owned();
16605 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16606 }
16607 assert_eq!(
16610 f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
16611 "-ERR bad error rate\r\n"
16612 );
16613 assert!(
16614 f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
16615 .starts_with("-WRONGTYPE")
16616 );
16617 }
16618
16619 #[test]
16622 fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
16623 let mut f = Fixture::new();
16624 assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
16625 for i in 0..10u32 {
16626 assert_eq!(
16627 f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
16628 ":1\r\n"
16629 );
16630 }
16631 assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
16632 assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
16633 assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
16634 assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
16637 assert_eq!(
16638 f.run(&[b"BF.DEBUG", b"g"]),
16639 "*3\r\n$7\r\nsize:11\r\n\
16640 $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
16641 $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
16642 );
16643
16644 assert_eq!(
16646 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
16647 "+OK\r\n"
16648 );
16649 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
16650 assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
16651 assert_eq!(
16652 f.run(&[b"BF.ADD", b"n", b"c"]),
16653 "-ERR non scaling filter is full\r\n"
16654 );
16655 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
16658 assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
16661 f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
16662 assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
16663 assert_eq!(
16666 f.run(&[
16667 b"BF.RESERVE",
16668 b"q",
16669 b"0.01",
16670 b"2",
16671 b"NONSCALING",
16672 b"EXPANSION",
16673 b"2"
16674 ]),
16675 "-Nonscaling filters cannot expand\r\n"
16676 );
16677 }
16678
16679 #[test]
16682 fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
16683 let mut f = Fixture::new();
16684 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
16685 assert_eq!(
16686 f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
16687 "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
16688 );
16689 assert_eq!(
16690 f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
16691 "*2\r\n:1\r\n:0\r\n"
16692 );
16693 }
16694
16695 #[test]
16698 fn insert_is_a_reserve_and_a_madd_with_different_errors() {
16699 let mut f = Fixture::new();
16700 assert_eq!(
16701 f.run(&[
16702 b"BF.INSERT",
16703 b"i",
16704 b"CAPACITY",
16705 b"50",
16706 b"ERROR",
16707 b"0.001",
16708 b"ITEMS",
16709 b"a",
16710 b"b"
16711 ]),
16712 "*2\r\n:1\r\n:1\r\n"
16713 );
16714 assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
16715 assert_eq!(
16717 f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16718 "-ERR not found\r\n"
16719 );
16720 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16721 assert_eq!(
16724 f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16725 "-Bad capacity\r\n"
16726 );
16727 assert_eq!(
16728 f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
16729 "-Bad error rate\r\n"
16730 );
16731 assert_eq!(
16732 f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
16733 "-Bad expansion\r\n"
16734 );
16735 assert_eq!(
16740 f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
16741 "*1\r\n:1\r\n"
16742 );
16743 assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
16744 assert_eq!(
16748 f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
16749 "*1\r\n:1\r\n"
16750 );
16751 assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
16752 assert_eq!(
16753 f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
16754 "*1\r\n:1\r\n"
16755 );
16756 assert_eq!(
16757 f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
16758 "-ERR not found\r\n"
16759 );
16760 assert_eq!(
16762 f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16763 "-Unknown argument received\r\n"
16764 );
16765 assert_eq!(
16767 f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16768 "*1\r\n:1\r\n"
16769 );
16770 assert!(
16772 f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
16773 .contains("wrong number of arguments")
16774 );
16775 }
16776
16777 #[test]
16780 fn a_dump_replays_into_a_filter_that_answers_the_same() {
16781 let mut f = Fixture::new();
16782 f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
16783 for i in 0..25u32 {
16784 f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
16785 }
16786 assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
16787
16788 let mut iter = b"0".to_vec();
16791 let mut chunks = 0;
16792 loop {
16793 let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
16794 let text = String::from_utf8_lossy(&raw).into_owned();
16795 let next = text
16796 .split("\r\n")
16797 .nth(1)
16798 .and_then(|n| n.strip_prefix(':'))
16799 .expect("a two element reply of an iterator and a chunk")
16800 .to_owned();
16801 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16802 let data = &body[body
16803 .windows(2)
16804 .position(|w| w == b"\r\n")
16805 .expect("a length line")
16806 + 2..body.len() - 2];
16807 if next == "0" {
16808 assert!(data.is_empty(), "the last chunk is empty");
16809 break;
16810 }
16811 let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
16812 assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
16813 iter = next.into_bytes();
16814 chunks += 1;
16815 }
16816 assert_eq!(chunks, 3, "a header and one chunk per link");
16817
16818 assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
16819 assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
16820 for i in 0..25u32 {
16821 assert_eq!(
16822 f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
16823 ":1\r\n"
16824 );
16825 }
16826
16827 assert_eq!(
16830 f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
16831 "-ERR received bad data\r\n"
16832 );
16833 assert_eq!(
16834 f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
16835 "-ERR received bad data\r\n"
16836 );
16837 assert_eq!(
16839 f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
16840 "-ERR invalid offset - no link found\r\n"
16841 );
16842 assert_eq!(
16843 f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
16844 "-ERR Second argument must be numeric\r\n"
16845 );
16846 assert_eq!(
16849 f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
16850 "-Second argument must be numeric\r\n"
16851 );
16852 }
16853
16854 #[test]
16857 fn reserve_reads_its_numbers_the_way_string2ll_does() {
16858 let mut f = Fixture::new();
16859 for (args, want) in [
16860 (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
16861 (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
16862 (
16863 vec![&b"0"[..], b"10"],
16864 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16865 ),
16866 (
16867 vec![&b"1"[..], b"10"],
16868 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16869 ),
16870 (
16871 vec![&b"inf"[..], b"10"],
16872 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16873 ),
16874 (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
16875 (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
16876 (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
16877 (
16878 vec![&b"0.01"[..], b"0"],
16879 "-ERR capacity must be in the range [1, 1073741824]\r\n",
16880 ),
16881 (
16882 vec![&b"0.01"[..], b"1073741825"],
16883 "-ERR capacity must be in the range [1, 1073741824]\r\n",
16884 ),
16885 ] {
16886 let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
16887 cmd.extend(args.iter().copied());
16888 assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
16889 }
16890 assert_eq!(
16891 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
16892 "-ERR no expansion\r\n"
16893 );
16894 assert_eq!(
16895 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
16896 "-ERR bad expansion\r\n"
16897 );
16898 assert_eq!(
16899 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
16900 "-ERR expansion must be in the range [0, 32768]\r\n"
16901 );
16902 assert_eq!(
16904 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
16905 "+OK\r\n"
16906 );
16907 assert_eq!(
16908 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
16909 "-ERR item exists\r\n"
16910 );
16911 assert_eq!(
16912 f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
16913 "-Invalid information value\r\n"
16914 );
16915 assert!(
16916 f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
16917 .contains("wrong number of arguments")
16918 );
16919 }
16920
16921 #[test]
16923 fn the_bloom_family_answers_in_resp3_spelling_too() {
16924 let mut f = Fixture::new();
16925 f.out.set_proto(Proto::Resp3);
16926 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
16927 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
16928 assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
16929 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
16930 assert_eq!(
16931 f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
16932 "*2\r\n#t\r\n#f\r\n"
16933 );
16934 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
16936 assert_eq!(
16937 f.run(&[b"BF.INFO", b"b"]),
16938 "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16939 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
16940 +Expansion rate\r\n:2\r\n"
16941 );
16942 assert_eq!(
16945 f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
16946 "%1\r\n+Capacity\r\n:100\r\n"
16947 );
16948 }
16949
16950 fn cf_header(
16959 items: u64,
16960 buckets: u64,
16961 deletes: u64,
16962 filters: u64,
16963 geometry: [u16; 3],
16964 ) -> Vec<u8> {
16965 let mut out = Vec::with_capacity(38);
16966 for n in [items, buckets, deletes, filters] {
16967 out.extend_from_slice(&n.to_le_bytes());
16968 }
16969 for n in geometry {
16970 out.extend_from_slice(&n.to_le_bytes());
16971 }
16972 out
16973 }
16974
16975 #[test]
16979 fn cf_add_makes_the_filter_and_counts_the_copies() {
16980 let mut f = Fixture::new();
16981 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16982 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16983 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
16984 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
16987 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
16988 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
16989 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
16990 assert_eq!(
16991 f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
16992 "*2\r\n:1\r\n:0\r\n"
16993 );
16994 assert_eq!(
16997 f.run(&[b"CF.INFO", b"d"]),
16998 "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16999 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
17000 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
17001 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
17002 );
17003 assert_eq!(
17004 f.run(&[b"CF.DEBUG", b"d"]),
17005 "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
17006 max_iterations:20 expansion:1\r\n"
17007 );
17008 assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
17009 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
17010
17011 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
17013 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
17014 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
17015 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
17016 assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
17017
17018 assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
17021 assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
17022 assert_eq!(
17023 f.run(&[b"CF.COMPACT", b"gone"]),
17024 "-Cuckoo filter was not found\r\n"
17025 );
17026 assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
17027 assert!(
17031 f.run(&[b"CF.COMPACT", b"a", b"b"])
17032 .contains("wrong number of arguments")
17033 );
17034 }
17035
17036 #[test]
17039 fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
17040 let mut f = Fixture::new();
17041 f.run(&[b"SET", b"s", b"text"]);
17042 assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
17043 assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
17044 assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
17045 assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
17049 assert_eq!(
17050 f.run(&[b"CF.COMPACT", b"s"]),
17051 "-Cuckoo filter was not found\r\n"
17052 );
17053 for cmd in [
17054 vec![&b"CF.ADD"[..], b"s", b"x"],
17055 vec![&b"CF.ADDNX"[..], b"s", b"x"],
17056 vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
17057 vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
17058 vec![&b"CF.INFO"[..], b"s"],
17059 vec![&b"CF.DEBUG"[..], b"s"],
17060 vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
17061 vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
17062 vec![&b"CF.RESERVE"[..], b"s", b"64"],
17063 ] {
17064 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17065 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
17066 }
17067 }
17068
17069 #[test]
17072 fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
17073 let mut f = Fixture::new();
17074 assert_eq!(
17075 f.run(&[
17076 b"CF.RESERVE",
17077 b"r",
17078 b"64",
17079 b"BUCKETSIZE",
17080 b"1",
17081 b"MAXITERATIONS",
17082 b"7",
17083 b"EXPANSION",
17084 b"4"
17085 ]),
17086 "+OK\r\n"
17087 );
17088 assert_eq!(
17089 f.run(&[b"CF.DEBUG", b"r"]),
17090 "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
17091 max_iterations:7 expansion:4\r\n"
17092 );
17093 assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
17094
17095 assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
17096 assert_eq!(
17097 f.run(&[b"CF.RESERVE", b"q", b"1"]),
17098 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
17099 );
17100 assert_eq!(
17103 f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
17104 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
17105 );
17106 assert_eq!(
17107 f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
17108 "+OK\r\n"
17109 );
17110
17111 assert_eq!(
17116 f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
17117 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
17118 );
17119 assert_eq!(
17120 f.run(&[
17121 b"CF.RESERVE",
17122 b"q2",
17123 b"64",
17124 b"EXPANSION",
17125 b"xx",
17126 b"BUCKETSIZE",
17127 b"0"
17128 ]),
17129 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
17130 );
17131 assert_eq!(
17132 f.run(&[
17133 b"CF.RESERVE",
17134 b"q2",
17135 b"64",
17136 b"MAXITERATIONS",
17137 b"0",
17138 b"BUCKETSIZE",
17139 b"0"
17140 ]),
17141 "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
17142 );
17143 assert_eq!(
17147 f.run(&[
17148 b"CF.RESERVE",
17149 b"q3",
17150 b"64",
17151 b"BUCKETSIZE",
17152 b"1",
17153 b"BUCKETSIZE",
17154 b"0"
17155 ]),
17156 "+OK\r\n"
17157 );
17158 assert_eq!(
17161 f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
17162 "+OK\r\n"
17163 );
17164 assert_eq!(
17165 f.run(&[b"CF.DEBUG", b"q4"]),
17166 "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
17167 max_iterations:20 expansion:1\r\n"
17168 );
17169 assert!(
17172 f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
17173 .contains("wrong number of arguments")
17174 );
17175 }
17176
17177 #[test]
17180 fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
17181 let mut f = Fixture::new();
17182 assert_eq!(
17183 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
17184 "*2\r\n:1\r\n:1\r\n"
17185 );
17186 assert_eq!(
17187 f.run(&[b"CF.DEBUG", b"i"]),
17188 "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
17189 max_iterations:20 expansion:1\r\n"
17190 );
17191 assert_eq!(
17194 f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
17195 "*2\r\n:0\r\n:1\r\n"
17196 );
17197 assert_eq!(
17198 f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
17199 "-ERR not found\r\n"
17200 );
17201 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
17202
17203 assert_eq!(
17204 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
17205 "-Bad capacity\r\n"
17206 );
17207 assert_eq!(
17210 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
17211 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
17212 );
17213 assert_eq!(
17217 f.run(&[
17218 b"CF.INSERT",
17219 b"i",
17220 b"CAPACITY",
17221 b"8",
17222 b"CAPACITY",
17223 b"2",
17224 b"ITEMS",
17225 b"a"
17226 ]),
17227 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
17228 );
17229 assert_eq!(
17233 f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
17234 "*1\r\n:1\r\n"
17235 );
17236 assert_eq!(
17237 f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
17238 "*1\r\n:1\r\n"
17239 );
17240 assert_eq!(
17241 f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
17242 "-Unknown argument received\r\n"
17243 );
17244 assert_eq!(
17246 f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
17247 "*1\r\n:1\r\n"
17248 );
17249 assert!(
17251 f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
17252 .contains("wrong number of arguments")
17253 );
17254 assert!(
17255 f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
17256 .contains("wrong number of arguments")
17257 );
17258 }
17259
17260 #[test]
17263 fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
17264 let mut f = Fixture::new();
17265 f.run(&[
17266 b"CF.RESERVE",
17267 b"s",
17268 b"4",
17269 b"BUCKETSIZE",
17270 b"1",
17271 b"EXPANSION",
17272 b"0",
17273 ]);
17274 for i in 0..4u32 {
17275 assert_eq!(
17276 f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
17277 ":1\r\n"
17278 );
17279 }
17280 assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
17281 assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
17282 assert_eq!(
17285 f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
17286 "*2\r\n:-1\r\n:-1\r\n"
17287 );
17288 assert_eq!(
17289 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
17290 "*2\r\n:0\r\n:-1\r\n"
17291 );
17292
17293 let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
17299 assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
17300 assert_eq!(
17301 f.run(&[b"CF.ADD", b"g", b"q"]),
17302 "-Maximum expansions reached\r\n"
17303 );
17304 assert_eq!(
17305 f.run(&[b"CF.INFO", b"g"]),
17306 "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
17307 +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
17308 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
17309 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
17310 );
17311 }
17312
17313 #[test]
17317 fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
17318 let mut f = Fixture::new();
17319 f.run(&[
17320 b"CF.RESERVE",
17321 b"src",
17322 b"8",
17323 b"BUCKETSIZE",
17324 b"2",
17325 b"EXPANSION",
17326 b"2",
17327 ]);
17328 for i in 0..40u32 {
17329 f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
17330 }
17331 let mut pos = b"0".to_vec();
17335 let mut chunks = 0;
17336 loop {
17337 let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
17338 let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
17339 let next = head
17340 .split("\r\n")
17341 .nth(1)
17342 .and_then(|n| n.strip_prefix(':'))
17343 .expect("a two element reply of a position and a chunk")
17344 .to_owned();
17345 if next == "0" {
17346 assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
17347 break;
17348 }
17349 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
17350 let at = body
17351 .windows(2)
17352 .position(|w| w == b"\r\n")
17353 .expect("a length line")
17354 + 2;
17355 let data = &body[at..body.len() - 2];
17356 assert_eq!(
17357 f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
17358 "+OK\r\n",
17359 "loading chunk {chunks}"
17360 );
17361 pos = next.into_bytes();
17362 chunks += 1;
17363 }
17364 assert!(chunks >= 2, "a header and at least one chunk");
17365
17366 assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
17367 assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
17368 for i in 0..40u32 {
17369 assert_eq!(
17370 f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
17371 ":1\r\n"
17372 );
17373 }
17374
17375 f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
17378 assert_eq!(
17379 f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
17380 "*2\r\n:0\r\n$-1\r\n"
17381 );
17382
17383 assert_eq!(
17387 f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
17388 "-Invalid position\r\n"
17389 );
17390 assert_eq!(
17391 f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
17392 "-Invalid position\r\n"
17393 );
17394 assert_eq!(
17395 f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
17396 "-Invalid position\r\n"
17397 );
17398 assert_eq!(
17399 f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
17400 "-Couldn't load chunk!\r\n"
17401 );
17402 let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
17404 assert_eq!(
17405 f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
17406 "-ERR item exists\r\n"
17407 );
17408 assert_eq!(
17412 f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
17413 "-Invalid header\r\n"
17414 );
17415 for (why, bad) in [
17416 ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
17417 ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
17418 (
17419 "a bucket count that is not a power of two",
17420 cf_header(0, 3, 0, 1, [2, 20, 1]),
17421 ),
17422 ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
17423 ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
17424 (
17425 "a growth nobody could reach",
17426 cf_header(0, 8, 0, 1, [2, 20, 32769]),
17427 ),
17428 (
17429 "a chain that cannot grow and did",
17430 cf_header(0, 8, 0, 2, [2, 20, 0]),
17431 ),
17432 (
17435 "a filter count that wraps",
17436 cf_header(0, 8, 0, 65_536, [2, 20, 1]),
17437 ),
17438 ] {
17439 assert_eq!(
17440 f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
17441 "-Couldn't create filter!\r\n",
17442 "{why}"
17443 );
17444 }
17445 }
17446
17447 #[test]
17450 fn the_cuckoo_family_answers_in_resp3_spelling_too() {
17451 let mut f = Fixture::new();
17452 f.out.set_proto(Proto::Resp3);
17453 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
17454 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
17455 assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
17456 assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
17457 assert_eq!(
17458 f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
17459 "*2\r\n#t\r\n#f\r\n"
17460 );
17461 assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
17462 assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
17463 assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
17465 assert_eq!(
17466 f.run(&[b"CF.INFO", b"c"]),
17467 "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
17468 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
17469 +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
17470 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
17471 );
17472
17473 f.run(&[
17478 b"CF.RESERVE",
17479 b"s",
17480 b"4",
17481 b"BUCKETSIZE",
17482 b"1",
17483 b"EXPANSION",
17484 b"0",
17485 ]);
17486 assert_eq!(
17487 f.run(&[
17488 b"CF.INSERT",
17489 b"s",
17490 b"ITEMS",
17491 b"a",
17492 b"b",
17493 b"c",
17494 b"d",
17495 b"e",
17496 b"f"
17497 ]),
17498 "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
17499 );
17500 assert_eq!(
17501 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
17502 "*2\r\n:0\r\n:-1\r\n"
17503 );
17504 assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
17505 assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
17508 }
17509
17510 #[test]
17515 fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
17516 let mut f = Fixture::new();
17517 assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
17518 assert_eq!(
17519 f.run(&[b"CMS.INFO", b"d"]),
17520 "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
17521 );
17522 assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
17523 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
17524 assert_eq!(
17527 f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
17528 "+OK\r\n"
17529 );
17530 assert_eq!(
17531 f.run(&[b"CMS.INFO", b"p"]),
17532 "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
17533 );
17534 assert_eq!(
17537 f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
17538 "-CMS: key already exists\r\n"
17539 );
17540 assert_eq!(
17541 f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
17542 "-CMS: invalid width\r\n"
17543 );
17544 assert_eq!(
17545 f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
17546 "-CMS: invalid depth\r\n"
17547 );
17548 assert_eq!(
17549 f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
17550 "-CMS: invalid overestimation value\r\n"
17551 );
17552 assert_eq!(
17553 f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
17554 "-CMS: invalid prob value\r\n"
17555 );
17556 assert_eq!(
17560 f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
17561 "-CMS: invalid init arguments\r\n"
17562 );
17563 assert_eq!(
17567 f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
17568 "-CMS: Insufficient memory to create the key\r\n"
17569 );
17570 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17571 }
17572
17573 #[test]
17576 fn increments_are_parsed_whole_and_the_counters_saturate() {
17577 let mut f = Fixture::new();
17578 f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
17579 assert_eq!(
17580 f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
17581 "*2\r\n:3\r\n:4\r\n"
17582 );
17583 assert_eq!(
17586 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
17587 "*2\r\n:4\r\n:5\r\n"
17588 );
17589 assert_eq!(
17591 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
17592 "-CMS: Cannot parse number\r\n"
17593 );
17594 assert_eq!(
17595 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
17596 "-CMS: Number cannot be negative\r\n"
17597 );
17598 assert_eq!(
17599 f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
17600 "*2\r\n:5\r\n:4\r\n"
17601 );
17602 f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
17605 assert_eq!(
17606 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
17607 "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
17608 );
17609 assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
17610 f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
17613 f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
17614 f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
17615 assert_eq!(
17616 f.run(&[b"CMS.INFO", b"w"]),
17617 "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
17618 );
17619 assert!(
17622 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
17623 .contains("wrong number of arguments")
17624 );
17625 assert_eq!(
17626 f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
17627 "-CMS: key does not exist\r\n"
17628 );
17629 assert_eq!(
17630 f.run(&[b"CMS.QUERY", b"nope", b"a"]),
17631 "-CMS: key does not exist\r\n"
17632 );
17633 }
17634
17635 #[test]
17638 fn a_merge_lands_whole_or_not_at_all() {
17639 let mut f = Fixture::new();
17640 for name in [&b"m1"[..], b"m2", b"dst"] {
17641 f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
17642 }
17643 f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
17644 f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
17645 assert_eq!(
17646 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17647 "+OK\r\n"
17648 );
17649 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17650 assert_eq!(
17653 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17654 "+OK\r\n"
17655 );
17656 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17657 assert_eq!(
17658 f.run(&[
17659 b"CMS.MERGE",
17660 b"dst",
17661 b"2",
17662 b"m1",
17663 b"m2",
17664 b"WEIGHTS",
17665 b"2",
17666 b"3"
17667 ]),
17668 "+OK\r\n"
17669 );
17670 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17671 assert_eq!(
17674 f.run(&[
17675 b"CMS.MERGE",
17676 b"dst",
17677 b"1",
17678 b"m1",
17679 b"WEIGHTS",
17680 b"4611686018427387904"
17681 ]),
17682 "-CMS: MERGE overflow\r\n"
17683 );
17684 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17685 f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
17688 assert_eq!(
17689 f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
17690 "-CMS: key does not exist\r\n"
17691 );
17692 assert_eq!(
17693 f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
17694 "-CMS: Number of keys must be positive\r\n"
17695 );
17696 assert_eq!(
17697 f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
17698 "-CMS: wrong number of keys\r\n"
17699 );
17700 assert_eq!(
17701 f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
17702 "-CMS: wrong number of keys/weights\r\n"
17703 );
17704 assert_eq!(
17705 f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
17706 "-CMS: width/depth is not equal\r\n"
17707 );
17708 assert_eq!(
17709 f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
17710 "-CMS: key does not exist\r\n"
17711 );
17712 }
17713
17714 #[test]
17717 fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17718 let mut f = Fixture::new();
17719 f.run(&[b"SET", b"s", b"text"]);
17720 for cmd in [
17721 vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
17722 vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
17723 vec![&b"CMS.QUERY"[..], b"s", b"a"],
17724 vec![&b"CMS.INFO"[..], b"s"],
17725 vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
17726 ] {
17727 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17728 let reply = f.run(&cmd);
17729 assert!(
17732 reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
17733 "{name}: {reply}"
17734 );
17735 }
17736 f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
17737 assert_eq!(
17741 f.run(&[b"COPY", b"c", b"c2"]),
17742 "-ERR not supported for this module key\r\n"
17743 );
17744 assert_eq!(
17745 f.run(&[b"DUMP", b"c"]),
17746 "-ERR DUMP is not supported for this module key\r\n"
17747 );
17748 f.run(&[b"G.NADD", b"g", b"a"]);
17750 assert_eq!(
17751 f.run(&[b"COPY", b"g", b"g2"]),
17752 "-ERR COPY is not supported for a graph\r\n"
17753 );
17754 assert_eq!(
17755 f.run(&[b"DUMP", b"g"]),
17756 "-ERR DUMP is not supported for a graph\r\n"
17757 );
17758 assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
17761 assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
17762 assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
17763 assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
17764 assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
17765 }
17766
17767 #[test]
17772 fn a_reserve_takes_three_arguments_or_six() {
17773 let mut f = Fixture::new();
17774 assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
17775 assert_eq!(
17776 f.run(&[b"TOPK.INFO", b"t"]),
17777 "*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"
17778 );
17779 for cmd in [
17782 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
17783 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
17784 ] {
17785 assert!(f.run(&cmd).contains("wrong number of arguments"));
17786 }
17787 assert_eq!(
17788 f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
17789 "+OK\r\n"
17790 );
17791 assert_eq!(
17794 f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
17795 "-TopK: key already exists\r\n"
17796 );
17797 assert_eq!(
17798 f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
17799 "-TopK: invalid k\r\n"
17800 );
17801 assert_eq!(
17802 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
17803 "-TopK: invalid width\r\n"
17804 );
17805 assert_eq!(
17806 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
17807 "-TopK: invalid depth\r\n"
17808 );
17809 assert_eq!(
17811 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
17812 "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
17813 );
17814 assert_eq!(
17815 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
17816 "+OK\r\n"
17817 );
17818 assert_eq!(
17820 f.run(&[
17821 b"TOPK.RESERVE",
17822 b"w",
17823 b"1",
17824 b"4294967295",
17825 b"4294967295",
17826 b"0.9"
17827 ]),
17828 "-ERR Insufficient memory to create topk data structure\r\n"
17829 );
17830 }
17831
17832 #[test]
17834 fn the_kept_set_is_what_query_and_list_answer_from() {
17835 let mut f = Fixture::new();
17836 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
17837 assert_eq!(
17840 f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
17841 "*2\r\n$-1\r\n$-1\r\n"
17842 );
17843 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
17844 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
17847 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
17848 assert_eq!(
17849 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
17850 "*3\r\n:1\r\n:0\r\n:1\r\n"
17851 );
17852 assert_eq!(
17854 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17855 "*3\r\n:11\r\n:1\r\n:6\r\n"
17856 );
17857 assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
17858 assert_eq!(
17859 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
17860 "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
17861 );
17862 assert_eq!(
17865 f.run(&[b"TOPK.LIST", b"t", b"w"]),
17866 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17867 );
17868 assert_eq!(
17869 f.run(&[b"TOPK.LIST", b"t", b""]),
17870 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17871 );
17872 assert_eq!(
17873 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
17874 "-WITHCOUNT keyword expected\r\n"
17875 );
17876 assert_eq!(
17879 f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
17880 "-WITHCOUNT keyword expected\r\n"
17881 );
17882 assert_eq!(
17883 f.run(&[b"TOPK.LIST", b"missing"]),
17884 "-TopK: key does not exist\r\n"
17885 );
17886 f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
17888 assert_eq!(
17889 f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
17890 "*1\r\n$-1\r\n"
17891 );
17892 assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
17893 assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
17894 }
17895
17896 #[test]
17899 fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
17900 let mut f = Fixture::new();
17901 f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
17902 assert_eq!(
17906 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
17907 format!(
17908 "*2\r\n$-1\r\n-{}\r\n",
17909 "TopK: increment must be an integer greater or equal to 0 and smaller or equal to 100,000"
17910 )
17911 );
17912 assert_eq!(
17913 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17914 "*3\r\n:3\r\n:0\r\n:0\r\n"
17915 );
17916 assert_eq!(
17918 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
17919 "*1\r\n$-1\r\n"
17920 );
17921 assert!(
17922 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
17923 .contains("smaller or equal to 100,000")
17924 );
17925 assert!(
17927 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
17928 .contains("wrong number of arguments")
17929 );
17930 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
17931 }
17932
17933 #[test]
17935 fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
17936 let mut f = Fixture::new();
17937 f.run(&[b"HELLO", b"3"]);
17938 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
17939 f.run(&[b"TOPK.ADD", b"t", b"a"]);
17940 assert_eq!(
17941 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
17942 "*2\r\n#t\r\n#f\r\n"
17943 );
17944 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
17946 assert_eq!(
17947 f.run(&[b"TOPK.INFO", b"t"]),
17948 "%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"
17949 );
17950 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
17951 }
17952
17953 #[test]
17956 fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17957 let mut f = Fixture::new();
17958 f.run(&[b"SET", b"s", b"text"]);
17959 for cmd in [
17960 vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
17961 vec![&b"TOPK.ADD"[..], b"s", b"a"],
17962 vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
17963 vec![&b"TOPK.QUERY"[..], b"s", b"a"],
17964 vec![&b"TOPK.COUNT"[..], b"s", b"a"],
17965 vec![&b"TOPK.LIST"[..], b"s"],
17966 vec![&b"TOPK.INFO"[..], b"s"],
17967 ] {
17968 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17969 let reply = f.run(&cmd);
17970 assert!(
17971 reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
17972 "{name}: {reply}"
17973 );
17974 }
17975 f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
17976 assert_eq!(
17977 f.run(&[b"COPY", b"t", b"t2"]),
17978 "-ERR not supported for this module key\r\n"
17979 );
17980 assert_eq!(
17981 f.run(&[b"DUMP", b"t"]),
17982 "-ERR DUMP is not supported for this module key\r\n"
17983 );
17984 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17985 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17986 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17987 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
17988 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17989 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17990 assert_eq!(
17993 f.run(&[b"TOPK.INFO", b"t3"]),
17994 "-TopK: key does not exist\r\n"
17995 );
17996 }
17997
17998 #[test]
18003 fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
18004 let mut f = Fixture::new();
18005 assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
18006 assert_eq!(
18008 f.run(&[b"TDIGEST.INFO", b"t"]),
18009 "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
18010 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
18011 +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
18012 );
18013 assert_eq!(
18014 f.run(&[b"TDIGEST.CREATE", b"t"]),
18015 "-ERR T-Digest: key already exists\r\n"
18016 );
18017 assert!(
18019 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
18020 .contains("wrong number of arguments")
18021 );
18022 assert_eq!(
18023 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
18024 "+OK\r\n"
18025 );
18026 assert_eq!(
18027 f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
18028 "+OK\r\n"
18029 );
18030 assert_eq!(
18034 f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
18035 "-ERR T-Digest: error parsing compression parameter\r\n"
18036 );
18037 assert_eq!(
18038 f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
18039 "-ERR T-Digest: wrong keyword\r\n"
18040 );
18041 assert_eq!(
18042 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
18043 "-ERR T-Digest: error parsing compression parameter\r\n"
18044 );
18045 assert_eq!(
18046 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
18047 "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
18048 );
18049 assert_eq!(
18052 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
18053 "-ERR T-Digest: allocation failed\r\n"
18054 );
18055 assert_eq!(
18057 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
18058 "-ERR T-Digest: allocation failed\r\n"
18059 );
18060 assert_eq!(
18063 f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
18064 "-ERR T-Digest: key already exists\r\n"
18065 );
18066 }
18067
18068 #[test]
18071 fn the_quantile_family_answers_what_the_module_answers() {
18072 let mut f = Fixture::new();
18073 f.run(&[b"TDIGEST.CREATE", b"s"]);
18074 assert_eq!(
18075 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
18076 "+OK\r\n"
18077 );
18078 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
18079 assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
18080 assert_eq!(
18082 f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
18083 "*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"
18084 );
18085 assert_eq!(
18086 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
18087 "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
18088 );
18089 assert_eq!(
18092 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
18093 "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
18094 );
18095 assert_eq!(
18096 f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
18097 "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
18098 );
18099 assert_eq!(
18100 f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
18101 "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
18102 );
18103 assert_eq!(
18104 f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
18105 "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
18106 );
18107 assert_eq!(
18108 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
18109 "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
18110 );
18111 assert_eq!(
18112 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
18113 "$3\r\n2.5\r\n"
18114 );
18115 assert_eq!(
18116 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
18117 "$3\r\n2.5\r\n"
18118 );
18119 assert_eq!(
18121 f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
18122 "-ERR T-Digest: quantile should be in [0,1]\r\n"
18123 );
18124 assert_eq!(
18125 f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
18126 "-ERR T-Digest: error parsing quantile\r\n"
18127 );
18128 assert_eq!(
18129 f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
18130 "-ERR T-Digest: error parsing cdf\r\n"
18131 );
18132 assert_eq!(
18133 f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
18134 "-ERR T-Digest: error parsing value\r\n"
18135 );
18136 assert_eq!(
18137 f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
18138 "-ERR T-Digest: rank needs to be non negative\r\n"
18139 );
18140 assert_eq!(
18141 f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
18142 "-ERR T-Digest: error parsing rank\r\n"
18143 );
18144 assert_eq!(
18147 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
18148 "-ERR T-Digest: error parsing low_cut_percentile\r\n"
18149 );
18150 assert_eq!(
18151 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
18152 "-ERR T-Digest: error parsing high_cut_percentile\r\n"
18153 );
18154 assert_eq!(
18155 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
18156 "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
18157 );
18158 assert_eq!(
18159 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
18160 "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
18161 );
18162 }
18163
18164 #[test]
18167 fn an_empty_digest_has_an_answer_for_everything() {
18168 let mut f = Fixture::new();
18169 f.run(&[b"TDIGEST.CREATE", b"e"]);
18170 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
18171 assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
18172 assert_eq!(
18173 f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
18174 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
18175 );
18176 assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
18177 assert_eq!(
18178 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
18179 "$3\r\nnan\r\n"
18180 );
18181 assert_eq!(
18184 f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
18185 "*2\r\n:-2\r\n:-2\r\n"
18186 );
18187 assert_eq!(
18188 f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
18189 "*2\r\n:-2\r\n:-2\r\n"
18190 );
18191 assert_eq!(
18192 f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
18193 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
18194 );
18195 f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
18197 assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
18198 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
18199 f.run(&[b"TDIGEST.CREATE", b"e2"]);
18202 assert_eq!(
18203 f.run(&[b"TDIGEST.INFO", b"e"]),
18204 f.run(&[b"TDIGEST.INFO", b"e2"])
18205 );
18206 }
18207
18208 #[test]
18211 fn a_sample_is_read_the_way_redis_reads_a_double() {
18212 let mut f = Fixture::new();
18213 f.run(&[b"TDIGEST.CREATE", b"a"]);
18214 for bad in [
18217 &b"nan"[..],
18218 b"1e400",
18219 b"-1e400",
18220 b"1e309",
18221 b"1e-400",
18222 b"",
18223 b" 1",
18224 b"1 ",
18225 b"1e",
18226 b"--1",
18227 ] {
18228 assert_eq!(
18229 f.run(&[b"TDIGEST.ADD", b"a", bad]),
18230 "-ERR T-Digest: error parsing val parameter\r\n",
18231 "{}",
18232 String::from_utf8_lossy(bad)
18233 );
18234 }
18235 for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
18238 assert_eq!(
18239 f.run(&[b"TDIGEST.ADD", b"a", word]),
18240 "-ERR T-Digest: val parameter needs to be a finite number\r\n",
18241 "{}",
18242 String::from_utf8_lossy(word)
18243 );
18244 }
18245 for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
18248 assert_eq!(
18249 f.run(&[b"TDIGEST.ADD", b"a", good]),
18250 "+OK\r\n",
18251 "{}",
18252 String::from_utf8_lossy(good)
18253 );
18254 }
18255 assert!(
18257 f.run(&[b"TDIGEST.INFO", b"a"])
18258 .contains("Observations\r\n:6\r\n")
18259 );
18260 assert_eq!(
18263 f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
18264 "-ERR T-Digest: error parsing val parameter\r\n"
18265 );
18266 assert!(
18267 f.run(&[b"TDIGEST.INFO", b"a"])
18268 .contains("Observations\r\n:6\r\n")
18269 );
18270 }
18271
18272 #[test]
18275 fn a_merge_sweeps_the_destination_between_its_inputs() {
18276 let mut f = Fixture::new();
18277 f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
18278 f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
18279 f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
18280 f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
18281 assert_eq!(
18282 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
18283 "+OK\r\n"
18284 );
18285 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
18290 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
18291 assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
18292 assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
18293 assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
18294 assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
18295 assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
18296 assert!(
18299 f.run(&[b"TDIGEST.INFO", b"m1"])
18300 .contains("Merged nodes\r\n:3\r\n")
18301 );
18302 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
18305 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
18306 assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
18307 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
18308 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
18311 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
18312 assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
18313 assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
18314 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
18316 assert!(
18317 f.run(&[b"TDIGEST.INFO", b"d"])
18318 .contains("Compression\r\n:500\r\n")
18319 );
18320 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
18322 assert!(
18323 f.run(&[b"TDIGEST.INFO", b"d"])
18324 .contains("Observations\r\n:12\r\n")
18325 );
18326 assert_eq!(
18328 f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
18329 "-ERR T-Digest: error parsing numkeys\r\n"
18330 );
18331 assert_eq!(
18332 f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
18333 "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
18334 );
18335 assert!(
18336 f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
18337 .contains("wrong number of arguments")
18338 );
18339 assert!(
18340 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
18341 .contains("wrong number of arguments")
18342 );
18343 assert_eq!(
18344 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
18345 "-ERR T-Digest: wrong keyword\r\n"
18346 );
18347 assert_eq!(
18350 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
18351 "-ERR T-Digest: key does not exist\r\n"
18352 );
18353 assert!(
18354 f.run(&[b"TDIGEST.INFO", b"d"])
18355 .contains("Observations\r\n:12\r\n")
18356 );
18357 assert_eq!(
18360 f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
18361 "-ERR T-Digest: key does not exist\r\n"
18362 );
18363 }
18364
18365 #[test]
18367 fn a_digest_answers_doubles_and_a_map_on_resp3() {
18368 let mut f = Fixture::new();
18369 f.run(&[b"HELLO", b"3"]);
18370 f.run(&[b"TDIGEST.CREATE", b"s"]);
18371 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
18372 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
18373 assert_eq!(
18374 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
18375 "*2\r\n,1\r\n,4\r\n"
18376 );
18377 assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
18378 assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
18380 assert_eq!(
18381 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
18382 "*1\r\n,-inf\r\n"
18383 );
18384 f.run(&[b"TDIGEST.CREATE", b"e"]);
18385 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
18386 assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
18388 assert_eq!(
18391 f.run(&[b"TDIGEST.INFO", b"s"]),
18392 "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
18393 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
18394 +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
18395 );
18396 }
18397
18398 #[test]
18401 fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
18402 let mut f = Fixture::new();
18403 f.run(&[b"SET", b"s", b"text"]);
18404 for cmd in [
18405 vec![&b"TDIGEST.CREATE"[..], b"s"],
18406 vec![&b"TDIGEST.RESET"[..], b"s"],
18407 vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
18408 vec![&b"TDIGEST.MIN"[..], b"s"],
18409 vec![&b"TDIGEST.MAX"[..], b"s"],
18410 vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
18411 vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
18412 vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
18413 vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
18414 vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
18415 vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
18416 vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
18417 vec![&b"TDIGEST.INFO"[..], b"s"],
18418 ] {
18419 let name = String::from_utf8_lossy(cmd[0]).into_owned();
18420 let reply = f.run(&cmd);
18421 assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
18422 }
18423 f.run(&[b"TDIGEST.CREATE", b"t"]);
18425 assert!(
18426 f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
18427 .starts_with("-WRONGTYPE")
18428 );
18429 assert!(
18430 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
18431 .starts_with("-WRONGTYPE")
18432 );
18433 assert_eq!(
18434 f.run(&[b"COPY", b"t", b"t2"]),
18435 "-ERR not supported for this module key\r\n"
18436 );
18437 assert_eq!(
18438 f.run(&[b"DUMP", b"t"]),
18439 "-ERR DUMP is not supported for this module key\r\n"
18440 );
18441 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
18442 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
18443 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
18444 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
18445 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
18446 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
18447 assert_eq!(
18450 f.run(&[b"TDIGEST.INFO", b"t3"]),
18451 "-ERR T-Digest: key does not exist\r\n"
18452 );
18453 assert_eq!(
18456 f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
18457 "-ERR T-Digest: key does not exist\r\n"
18458 );
18459 }
18460
18461 fn without_memory(reply: &str) -> String {
18471 let head = "+memoryUsage\r\n:";
18472 let at = reply.find(head).expect("every TS.INFO reports memory");
18473 let rest = &reply[at + head.len()..];
18474 let end = rest.find("\r\n").expect("and it is a whole number");
18475 format!("{}{}", &reply[..at + head.len()], &rest[end..])
18476 }
18477
18478 #[test]
18481 fn a_series_is_made_empty_and_reports_on_itself() {
18482 let mut f = Fixture::new();
18483 assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
18484 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18485 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
18486 assert_eq!(
18490 without_memory(&f.run(&[b"TS.INFO", b"t"])),
18491 "*28\r\n\
18492 +totalSamples\r\n:0\r\n\
18493 +memoryUsage\r\n:\r\n\
18494 +firstTimestamp\r\n:0\r\n\
18495 +lastTimestamp\r\n:0\r\n\
18496 +retentionTime\r\n:0\r\n\
18497 +chunkCount\r\n:1\r\n\
18498 +chunkSize\r\n:4096\r\n\
18499 +chunkType\r\n+compressed\r\n\
18500 +duplicatePolicy\r\n+block\r\n\
18501 +labels\r\n*0\r\n\
18502 +sourceKey\r\n$-1\r\n\
18503 +rules\r\n*0\r\n\
18504 +ignoreMaxTimeDiff\r\n:0\r\n\
18505 +ignoreMaxValDiff\r\n$1\r\n0\r\n"
18506 );
18507 assert_eq!(
18510 f.run(&[b"TS.CREATE", b"t"]),
18511 "-ERR TSDB: key already exists\r\n"
18512 );
18513 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18514 assert_eq!(
18515 f.run(&[b"TS.CREATE", b"str"]),
18516 "-ERR TSDB: key already exists\r\n"
18517 );
18518 assert_eq!(
18521 f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
18522 "-ERR TSDB: Couldn't parse RETENTION\r\n"
18523 );
18524 let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
18530 assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
18531 assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
18532 assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
18533 assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
18534 assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
18535 assert_eq!(
18536 f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
18537 "-ERR TSDB: the key is not a TSDB key\r\n"
18538 );
18539 assert_eq!(
18541 f.run(&[b"TS.INFO", b"nope"]),
18542 "-ERR TSDB: the key does not exist\r\n"
18543 );
18544 assert_eq!(
18545 f.run(&[b"TS.GET", b"nope"]),
18546 "-ERR TSDB: the key does not exist\r\n"
18547 );
18548 assert_eq!(
18549 f.run(&[b"TS.ALTER", b"nope"]),
18550 "-ERR TSDB: the key does not exist\r\n"
18551 );
18552 assert_eq!(
18553 f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
18554 "-ERR TSDB: the key does not exist\r\n"
18555 );
18556 }
18557
18558 #[test]
18561 fn the_options_are_a_keyword_scan_and_not_a_grammar() {
18562 let mut f = Fixture::new();
18563 assert_eq!(
18564 f.run(&[
18565 b"TS.CREATE",
18566 b"t",
18567 b"RETENTION",
18568 b"5000",
18569 b"ENCODING",
18570 b"UNCOMPRESSED",
18571 b"CHUNK_SIZE",
18572 b"128",
18573 b"DUPLICATE_POLICY",
18574 b"LAST",
18575 b"IGNORE",
18576 b"10",
18577 b"0.5",
18578 b"LABELS",
18579 b"room",
18580 b"kitchen"
18581 ]),
18582 "+OK\r\n"
18583 );
18584 let info = f.run(&[b"TS.INFO", b"t"]);
18585 assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
18586 assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
18587 assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
18588 assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
18589 assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
18590 assert!(
18593 info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
18594 "{info}"
18595 );
18596 assert!(
18597 info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
18598 "{info}"
18599 );
18600
18601 assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
18603 assert_eq!(
18607 f.run(&[
18608 b"TS.CREATE",
18609 b"g",
18610 b"LABELS",
18611 b"a",
18612 b"b",
18613 b"RETENTION",
18614 b"5"
18615 ]),
18616 "+OK\r\n"
18617 );
18618 let greedy = f.run(&[b"TS.INFO", b"g"]);
18619 assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
18620 assert!(
18621 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"),
18622 "{greedy}"
18623 );
18624
18625 assert_eq!(
18627 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
18628 "-ERR TSDB: Couldn't parse LABELS\r\n"
18629 );
18630 assert_eq!(
18631 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
18632 "-ERR TSDB: Couldn't parse LABELS\r\n"
18633 );
18634 assert_eq!(
18635 f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
18636 "-ERR TSDB: Couldn't parse RETENTION\r\n"
18637 );
18638 assert_eq!(
18641 f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
18642 "-TSDB: Couldn't parse RETENTION\r\n"
18643 );
18644 assert_eq!(
18645 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
18646 "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
18647 );
18648 assert_eq!(
18649 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
18650 "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
18651 );
18652 assert_eq!(
18653 f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
18654 "-ERR TSDB: unknown ENCODING parameter\r\n"
18655 );
18656 assert!(
18659 f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
18660 .contains("wrong number of arguments for 'ts.create' command")
18661 );
18662 assert_eq!(
18663 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
18664 "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
18665 );
18666 assert_eq!(
18667 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
18668 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18669 );
18670 assert_eq!(
18671 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
18672 "-ERR TSDB: Couldn't parse IGNORE\r\n"
18673 );
18674 assert_eq!(
18675 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
18676 "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
18677 );
18678 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
18679
18680 assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
18683 let after = f.run(&[b"TS.INFO", b"t"]);
18684 assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
18685 assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
18686 assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
18687 assert_eq!(
18688 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
18689 "-ERR TSDB: unknown ENCODING parameter\r\n"
18690 );
18691 assert_eq!(
18693 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
18694 "+OK\r\n"
18695 );
18696 assert!(
18697 f.run(&[b"TS.INFO", b"t"])
18698 .contains("+chunkType\r\n+uncompressed\r\n")
18699 );
18700 }
18701
18702 #[test]
18705 fn samples_land_where_they_are_put_and_the_newest_comes_back() {
18706 let mut f = Fixture::new();
18707 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
18708 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18710 assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
18711 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
18714 assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
18715 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
18716 assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
18719 assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
18720
18721 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18724 assert_eq!(
18725 f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
18726 "-ERR TSDB: invalid value\r\n"
18727 );
18728 for bad in [
18732 &b".5"[..],
18733 b"1.",
18734 b"+1",
18735 b" 1",
18736 b"0x10",
18737 b"inf",
18738 b"1e400",
18739 b"--1",
18740 b"1e",
18741 ] {
18742 assert_eq!(
18743 f.run(&[b"TS.ADD", b"v", b"1", bad]),
18744 "-ERR TSDB: invalid value\r\n",
18745 "{}",
18746 String::from_utf8_lossy(bad)
18747 );
18748 }
18749 assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
18751
18752 assert_eq!(
18755 f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
18756 "-ERR TSDB: invalid timestamp\r\n"
18757 );
18758 assert_eq!(
18759 f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
18760 "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
18761 );
18762
18763 assert_eq!(
18766 f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
18767 "-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"
18768 );
18769 assert_eq!(
18770 f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
18771 ":300\r\n"
18772 );
18773 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
18774 assert_eq!(
18777 f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
18778 ":1\r\n"
18779 );
18780 assert_eq!(
18781 f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
18782 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18783 );
18784
18785 assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
18788 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
18789 assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
18790 assert_eq!(
18791 f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
18792 "-ERR TSDB: Timestamp is older than retention\r\n"
18793 );
18794 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
18796 assert!(
18797 f.run(&[b"TS.INFO", b"r"])
18798 .contains("+totalSamples\r\n:1\r\n")
18799 );
18800
18801 assert_eq!(
18804 f.run(&[
18805 b"TS.CREATE",
18806 b"i",
18807 b"DUPLICATE_POLICY",
18808 b"LAST",
18809 b"IGNORE",
18810 b"10",
18811 b"0.5"
18812 ]),
18813 "+OK\r\n"
18814 );
18815 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
18816 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
18817 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
18818 }
18819
18820 #[test]
18823 fn a_madd_answers_each_triple_and_creates_nothing() {
18824 let mut f = Fixture::new();
18825 assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
18826 assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
18827 assert_eq!(
18828 f.run(&[
18829 b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
18830 ]),
18831 "*3\r\n:100\r\n:100\r\n:200\r\n"
18832 );
18833 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18836 assert_eq!(
18837 f.run(&[
18838 b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
18839 ]),
18840 "*3\r\n\
18841 -ERR TSDB: the key is not a TSDB key\r\n\
18842 -ERR TSDB: the key is not a TSDB key\r\n\
18843 :300\r\n"
18844 );
18845 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
18846 assert_eq!(
18848 f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
18849 "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
18850 );
18851 assert!(
18853 f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
18854 .contains("wrong number of arguments for 'ts.madd' command")
18855 );
18856 }
18857
18858 #[test]
18860 fn an_increment_walks_the_newest_value_up_and_down() {
18861 let mut f = Fixture::new();
18862 assert_eq!(
18863 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18864 ":100\r\n"
18865 );
18866 assert_eq!(
18867 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18868 ":100\r\n"
18869 );
18870 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
18873 assert_eq!(
18874 f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
18875 ":200\r\n"
18876 );
18877 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
18878 assert_eq!(
18881 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
18882 "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
18883 );
18884 assert_eq!(
18888 f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
18889 ":1\r\n"
18890 );
18891 assert_eq!(
18892 f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
18893 ":1\r\n"
18894 );
18895 assert_eq!(
18896 f.run(&[b"TS.INCRBY", b"t", b"nan"]),
18897 "-ERR TSDB: invalid increase/decrease value\r\n"
18898 );
18899 assert_eq!(
18900 f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
18901 "-ERR TSDB: invalid increase/decrease value\r\n"
18902 );
18903 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18906 assert_eq!(
18907 f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
18908 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
18909 );
18910 assert_eq!(
18915 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
18916 "-ERR TSDB: invalid timestamp\r\n"
18917 );
18918 assert_eq!(
18921 f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
18922 format!(":{}\r\n", f.server.now_ms())
18923 );
18924 assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
18926 assert_eq!(
18927 f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
18928 "-ERR TSDB: cannot increment/decrement NaN value\r\n"
18929 );
18930 }
18931
18932 #[test]
18934 fn deleting_takes_out_a_span_and_answers_how_many_went() {
18935 let mut f = Fixture::new();
18936 for at in [b"100".as_slice(), b"200", b"300", b"400"] {
18937 f.run(&[b"TS.ADD", b"t", at, b"1"]);
18938 }
18939 assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
18940 assert!(
18941 f.run(&[b"TS.INFO", b"t"])
18942 .contains("+totalSamples\r\n:2\r\n")
18943 );
18944 assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
18946 assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
18948 let empty = f.run(&[b"TS.INFO", b"t"]);
18951 assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
18952 assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
18953 assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
18954 assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
18955 assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
18956 assert_eq!(
18958 f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
18959 "-ERR TSDB: wrong fromTimestamp\r\n"
18960 );
18961 assert_eq!(
18962 f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
18963 "-ERR TSDB: wrong toTimestamp\r\n"
18964 );
18965 assert_eq!(
18966 f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
18967 "-ERR TSDB: wrong fromTimestamp\r\n"
18968 );
18969 }
18970
18971 #[test]
18974 fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
18975 let mut f = Fixture::new();
18976 f.out = Out::new(Proto::Resp3);
18977 assert_eq!(
18978 f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
18979 "+OK\r\n"
18980 );
18981 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
18982 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
18984 assert_eq!(
18985 without_memory(&f.run(&[b"TS.INFO", b"t"])),
18986 "%14\r\n\
18987 +totalSamples\r\n:1\r\n\
18988 +memoryUsage\r\n:\r\n\
18989 +firstTimestamp\r\n:100\r\n\
18990 +lastTimestamp\r\n:100\r\n\
18991 +retentionTime\r\n:0\r\n\
18992 +chunkCount\r\n:1\r\n\
18993 +chunkSize\r\n:4096\r\n\
18994 +chunkType\r\n+compressed\r\n\
18995 +duplicatePolicy\r\n+block\r\n\
18996 +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18997 +sourceKey\r\n_\r\n\
18998 +rules\r\n%0\r\n\
18999 +ignoreMaxTimeDiff\r\n:0\r\n\
19000 +ignoreMaxValDiff\r\n,0\r\n"
19001 );
19002 }
19003
19004 #[test]
19007 fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
19008 let mut f = Fixture::new();
19009 for (at, v) in [
19010 (b"100".as_slice(), b"1".as_slice()),
19011 (b"200", b"2"),
19012 (b"300", b"3"),
19013 (b"400", b"4"),
19014 ] {
19015 f.run(&[b"TS.ADD", b"t", at, v]);
19016 }
19017 assert_eq!(
19018 f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
19019 "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
19020 *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
19021 );
19022 assert_eq!(
19024 f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
19025 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
19026 );
19027 assert_eq!(
19030 f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
19031 "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
19032 );
19033 assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
19035 assert_eq!(
19037 f.run(&[
19038 b"TS.RANGE",
19039 b"t",
19040 b"-",
19041 b"+",
19042 b"FILTER_BY_VALUE",
19043 b"2",
19044 b"3"
19045 ]),
19046 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
19047 );
19048 assert_eq!(
19049 f.run(&[
19050 b"TS.RANGE",
19051 b"t",
19052 b"-",
19053 b"+",
19054 b"FILTER_BY_TS",
19055 b"100",
19056 b"400"
19057 ]),
19058 "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
19059 );
19060 assert_eq!(
19062 f.run(&[
19063 b"TS.RANGE",
19064 b"t",
19065 b"-",
19066 b"+",
19067 b"ZZZ",
19068 b"FILTER_BY_TS",
19069 b"400"
19070 ]),
19071 "*1\r\n*2\r\n:400\r\n+4\r\n"
19072 );
19073 assert_eq!(
19075 f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
19076 "*1\r\n*2\r\n:100\r\n+1\r\n"
19077 );
19078 }
19079
19080 #[test]
19082 fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
19083 let mut f = Fixture::new();
19084 for (at, v) in [
19085 (b"100".as_slice(), b"1".as_slice()),
19086 (b"200", b"2"),
19087 (b"300", b"3"),
19088 (b"400", b"4"),
19089 ] {
19090 f.run(&[b"TS.ADD", b"t", at, v]);
19091 }
19092 assert_eq!(
19093 f.run(&[
19094 b"TS.RANGE",
19095 b"t",
19096 b"-",
19097 b"+",
19098 b"AGGREGATION",
19099 b"avg",
19100 b"200"
19101 ]),
19102 "*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"
19103 );
19104 assert_eq!(
19107 f.run(&[
19108 b"TS.RANGE",
19109 b"t",
19110 b"-",
19111 b"+",
19112 b"AGGREGATION",
19113 b"min,max,count",
19114 b"200"
19115 ]),
19116 "*3\r\n\
19117 *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
19118 *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
19119 *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
19120 );
19121 assert_eq!(
19123 f.run(&[
19124 b"TS.RANGE",
19125 b"t",
19126 b"-",
19127 b"+",
19128 b"AGGREGATION",
19129 b"avg",
19130 b"200",
19131 b"BUCKETTIMESTAMP",
19132 b"+"
19133 ]),
19134 "*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"
19135 );
19136 assert_eq!(
19138 f.run(&[
19139 b"TS.RANGE",
19140 b"t",
19141 b"100",
19142 b"400",
19143 b"ALIGN",
19144 b"100",
19145 b"AGGREGATION",
19146 b"sum",
19147 b"200"
19148 ]),
19149 "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
19150 );
19151 assert_eq!(
19154 f.run(&[
19155 b"TS.RANGE",
19156 b"t",
19157 b"-",
19158 b"+",
19159 b"AGGREGATION",
19160 b"count",
19161 b"200"
19162 ]),
19163 "*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"
19164 );
19165 assert_eq!(
19166 f.run(&[
19167 b"TS.RANGE",
19168 b"t",
19169 b"-",
19170 b"+",
19171 b"AGGREGATION",
19172 b"count",
19173 b"200",
19174 b"COUNT",
19175 b"1"
19176 ]),
19177 "*1\r\n*2\r\n:0\r\n+1\r\n"
19178 );
19179 }
19180
19181 #[test]
19184 fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
19185 let mut f = Fixture::new();
19186 for (at, v) in [
19187 (b"0".as_slice(), b"1".as_slice()),
19188 (b"100", b"2"),
19189 (b"500", b"nan"),
19190 (b"600", b"3"),
19191 ] {
19192 f.run(&[b"TS.ADD", b"g", at, v]);
19193 }
19194 assert_eq!(
19197 f.run(&[
19198 b"TS.RANGE",
19199 b"g",
19200 b"-",
19201 b"+",
19202 b"AGGREGATION",
19203 b"avg",
19204 b"100"
19205 ]),
19206 "*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"
19207 );
19208 assert_eq!(
19210 f.run(&[
19211 b"TS.RANGE",
19212 b"g",
19213 b"-",
19214 b"+",
19215 b"AGGREGATION",
19216 b"sum",
19217 b"100",
19218 b"EMPTY"
19219 ]),
19220 "*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\
19221 *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\
19222 *2\r\n:600\r\n+3\r\n"
19223 );
19224 assert_eq!(
19229 f.run(&[
19230 b"TS.RANGE",
19231 b"g",
19232 b"-",
19233 b"+",
19234 b"AGGREGATION",
19235 b"last",
19236 b"100",
19237 b"EMPTY"
19238 ]),
19239 "*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\
19240 *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\
19241 *2\r\n:600\r\n+3\r\n"
19242 );
19243 assert_eq!(
19244 f.run(&[
19245 b"TS.REVRANGE",
19246 b"g",
19247 b"-",
19248 b"+",
19249 b"AGGREGATION",
19250 b"last",
19251 b"100",
19252 b"EMPTY"
19253 ]),
19254 "*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\
19255 *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\
19256 *2\r\n:0\r\n+1\r\n"
19257 );
19258 assert_eq!(
19261 f.run(&[
19262 b"TS.RANGE",
19263 b"g",
19264 b"500",
19265 b"600",
19266 b"AGGREGATION",
19267 b"last",
19268 b"100",
19269 b"EMPTY"
19270 ]),
19271 "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
19272 );
19273 }
19274
19275 #[test]
19278 fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
19279 let mut f = Fixture::new();
19280 f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
19281 f.run(&[b"SET", b"str", b"x"]);
19282 let cases: &[(&[&[u8]], &str)] = &[
19283 (
19284 &[b"TS.RANGE", b"t"],
19285 "-ERR wrong number of arguments for 'ts.range' command\r\n",
19286 ),
19287 (
19289 &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
19290 "-ERR TSDB: the key does not exist\r\n",
19291 ),
19292 (
19293 &[b"TS.RANGE", b"str", b"-", b"+"],
19294 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
19295 ),
19296 (
19297 &[b"TS.RANGE", b"t", b"abc", b"+"],
19298 "-ERR TSDB: wrong fromTimestamp\r\n",
19299 ),
19300 (
19301 &[b"TS.RANGE", b"t", b"-", b"abc"],
19302 "-ERR TSDB: wrong toTimestamp\r\n",
19303 ),
19304 (
19305 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
19306 "-ERR TSDB: COUNT argument is missing\r\n",
19307 ),
19308 (
19309 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
19310 "-ERR TSDB: Couldn't parse COUNT\r\n",
19311 ),
19312 (
19313 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
19314 "-ERR TSDB: Invalid COUNT value\r\n",
19315 ),
19316 (
19317 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
19318 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19319 ),
19320 (
19321 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
19322 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19323 ),
19324 (
19325 &[
19326 b"TS.RANGE",
19327 b"t",
19328 b"-",
19329 b"+",
19330 b"AGGREGATION",
19331 b"nope",
19332 b"100",
19333 ],
19334 "-ERR TSDB: Unknown aggregation type\r\n",
19335 ),
19336 (
19337 &[
19338 b"TS.RANGE",
19339 b"t",
19340 b"-",
19341 b"+",
19342 b"AGGREGATION",
19343 b"avg,,min",
19344 b"100",
19345 ],
19346 "-ERR TSDB: Empty aggregation type in list\r\n",
19347 ),
19348 (
19350 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
19351 "-ERR TSDB: Unknown aggregation type\r\n",
19352 ),
19353 (
19354 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
19355 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19356 ),
19357 (
19358 &[
19359 b"TS.RANGE",
19360 b"t",
19361 b"-",
19362 b"+",
19363 b"AGGREGATION",
19364 b"avg",
19365 b"100",
19366 b"X",
19367 b"EMPTY",
19368 ],
19369 "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
19370 ),
19371 (
19372 &[
19373 b"TS.RANGE",
19374 b"t",
19375 b"-",
19376 b"+",
19377 b"AGGREGATION",
19378 b"avg",
19379 b"100",
19380 b"BUCKETTIMESTAMP",
19381 b"z",
19382 ],
19383 "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
19384 ),
19385 (
19386 &[
19387 b"TS.RANGE",
19388 b"t",
19389 b"-",
19390 b"+",
19391 b"AGGREGATION",
19392 b"avg",
19393 b"100",
19394 b"X",
19395 b"Y",
19396 b"BUCKETTIMESTAMP",
19397 b"-",
19398 ],
19399 "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
19400 AGGREGATION flag\r\n",
19401 ),
19402 (
19403 &[
19404 b"TS.RANGE",
19405 b"t",
19406 b"-",
19407 b"+",
19408 b"ALIGN",
19409 b"z",
19410 b"AGGREGATION",
19411 b"avg",
19412 b"100",
19413 ],
19414 "-ERR TSDB: unknown ALIGN parameter\r\n",
19415 ),
19416 (
19417 &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
19418 "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
19419 ),
19420 (
19421 &[
19422 b"TS.RANGE",
19423 b"t",
19424 b"-",
19425 b"+",
19426 b"ALIGN",
19427 b"-",
19428 b"AGGREGATION",
19429 b"avg",
19430 b"100",
19431 ],
19432 "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
19433 ),
19434 (
19435 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
19436 "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
19437 ),
19438 (
19439 &[
19440 b"TS.RANGE",
19441 b"t",
19442 b"-",
19443 b"+",
19444 b"FILTER_BY_VALUE",
19445 b"x",
19446 b"2",
19447 ],
19448 "-ERR TSDB: Couldn't parse MIN\r\n",
19449 ),
19450 (
19451 &[
19452 b"TS.RANGE",
19453 b"t",
19454 b"-",
19455 b"+",
19456 b"FILTER_BY_VALUE",
19457 b"1",
19458 b"y",
19459 ],
19460 "-ERR TSDB: Couldn't parse MAX\r\n",
19461 ),
19462 (
19463 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
19464 "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
19465 ),
19466 ];
19467 for (argv, want) in cases {
19468 let got = f.run(argv);
19469 assert_eq!(&got, want, "{:?}", argv.last());
19470 }
19471 f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
19475 f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
19476 assert_eq!(
19477 f.run(&[
19478 b"TS.RANGE",
19479 b"wide",
19480 b"-",
19481 b"+",
19482 b"AGGREGATION",
19483 b"avg",
19484 b"1",
19485 b"EMPTY"
19486 ]),
19487 "-ERR TSDB: the requested range holds too many empty buckets\r\n"
19488 );
19489 }
19490
19491 #[test]
19493 fn resp3_writes_a_read_value_as_a_double() {
19494 let mut f = Fixture::new();
19495 f.out = Out::new(Proto::Resp3);
19496 for (at, v) in [
19497 (b"0".as_slice(), b"1".as_slice()),
19498 (b"100", b"2"),
19499 (b"500", b"nan"),
19500 (b"600", b"3"),
19501 ] {
19502 f.run(&[b"TS.ADD", b"g", at, v]);
19503 }
19504 assert_eq!(
19505 f.run(&[
19506 b"TS.RANGE",
19507 b"g",
19508 b"0",
19509 b"100",
19510 b"AGGREGATION",
19511 b"avg,min",
19512 b"200"
19513 ]),
19514 "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
19515 );
19516 assert_eq!(
19517 f.run(&[
19518 b"TS.RANGE",
19519 b"g",
19520 b"500",
19521 b"600",
19522 b"AGGREGATION",
19523 b"last",
19524 b"100",
19525 b"EMPTY"
19526 ]),
19527 "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
19528 );
19529 }
19530
19531 fn joined() -> Fixture {
19534 let mut f = Fixture::new();
19535 f.run(&[b"TS.CREATE", b"z"]);
19536 for (at, v) in [
19537 (b"10".as_slice(), b"1".as_slice()),
19538 (b"20", b"2"),
19539 (b"40", b"4"),
19540 (b"50", b"5"),
19541 ] {
19542 f.run(&[b"TS.ADD", b"x", at, v]);
19543 }
19544 for (at, v) in [
19545 (b"20".as_slice(), b"20".as_slice()),
19546 (b"30", b"30"),
19547 (b"50", b"50"),
19548 (b"60", b"60"),
19549 ] {
19550 f.run(&[b"TS.ADD", b"y", at, v]);
19551 }
19552 f
19553 }
19554
19555 #[test]
19559 fn an_nrange_joins_its_keys_on_the_timestamp() {
19560 let mut f = joined();
19561 assert_eq!(
19563 f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
19564 "*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\
19565 *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
19566 );
19567 assert_eq!(
19569 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
19570 "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
19571 *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19572 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19573 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19574 *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
19575 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19576 );
19577 assert_eq!(
19580 f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
19581 "*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"
19582 );
19583 assert_eq!(
19584 f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
19585 "*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"
19586 );
19587 assert_eq!(
19590 f.run(&[
19591 b"TS.NREVRANGE",
19592 b"2",
19593 b"x",
19594 b"y",
19595 b"-",
19596 b"+",
19597 b"COUNT",
19598 b"1"
19599 ]),
19600 "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19601 );
19602 assert_eq!(
19603 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
19604 "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
19605 );
19606 assert_eq!(
19608 f.run(&[
19609 b"TS.NRANGE",
19610 b"2",
19611 b"x",
19612 b"y",
19613 b"-",
19614 b"+",
19615 b"FILTER_BY_VALUE",
19616 b"2",
19617 b"30"
19618 ]),
19619 "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19620 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19621 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19622 *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
19623 );
19624 }
19625
19626 #[test]
19630 fn an_nrange_aggregation_names_one_reduction_a_key() {
19631 let mut f = joined();
19632 assert_eq!(
19633 f.run(&[
19634 b"TS.NRANGE",
19635 b"2",
19636 b"x",
19637 b"y",
19638 b"-",
19639 b"+",
19640 b"AGGREGATION",
19641 b"sum",
19642 b"sum",
19643 b"20"
19644 ]),
19645 "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
19646 *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
19647 *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
19648 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19649 );
19650 assert_eq!(
19652 f.run(&[
19653 b"TS.NRANGE",
19654 b"2",
19655 b"x",
19656 b"y",
19657 b"-",
19658 b"+",
19659 b"AGGREGATION",
19660 b"sum,count",
19661 b"avg",
19662 b"20"
19663 ]),
19664 "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
19665 *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
19666 *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
19667 *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
19668 );
19669 assert_eq!(
19672 f.run(&[
19673 b"TS.NRANGE",
19674 b"2",
19675 b"x",
19676 b"y",
19677 b"-",
19678 b"+",
19679 b"AGGREGATION",
19680 b"avg",
19681 b"sum",
19682 b"100",
19683 b"EMPTY",
19684 b"BUCKETTIMESTAMP",
19685 b"end"
19686 ]),
19687 "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
19688 );
19689 assert_eq!(
19692 f.run(&[
19693 b"TS.NRANGE",
19694 b"2",
19695 b"x",
19696 b"y",
19697 b"-",
19698 b"+",
19699 b"AGGREGATION",
19700 b"avg",
19701 b"COUNT",
19702 b"100"
19703 ]),
19704 "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
19705 );
19706 }
19707
19708 #[test]
19711 fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
19712 let mut f = joined();
19713 f.run(&[b"SET", b"str", b"hi"]);
19714 let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
19715 let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
19716 must be equal to numkeys\r\n";
19717 let cases: &[(&[&[u8]], &str)] = &[
19718 (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
19719 (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
19720 (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
19721 (
19724 &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
19725 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19726 ),
19727 (
19728 &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
19729 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19730 ),
19731 (
19734 &[
19735 b"TS.NRANGE",
19736 b"2",
19737 b"x",
19738 b"y",
19739 b"abc",
19740 b"+",
19741 b"AGGREGATION",
19742 b"nope",
19743 b"sum",
19744 b"100",
19745 ],
19746 "-ERR TSDB: Unknown aggregation type\r\n",
19747 ),
19748 (
19749 &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
19750 "-ERR TSDB: wrong fromTimestamp\r\n",
19751 ),
19752 (
19753 &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
19754 "-ERR TSDB: wrong toTimestamp\r\n",
19755 ),
19756 (
19760 &[
19761 b"TS.NRANGE",
19762 b"2",
19763 b"x",
19764 b"y",
19765 b"-",
19766 b"+",
19767 b"AGGREGATION",
19768 b"avg",
19769 ],
19770 numkeys,
19771 ),
19772 (
19773 &[
19774 b"TS.NRANGE",
19775 b"2",
19776 b"x",
19777 b"y",
19778 b"-",
19779 b"+",
19780 b"AGGREGATION",
19781 b"100",
19782 b"sum",
19783 b"100",
19784 ],
19785 numkeys,
19786 ),
19787 (
19788 &[
19789 b"TS.NRANGE",
19790 b"2",
19791 b"x",
19792 b"y",
19793 b"-",
19794 b"+",
19795 b"AGGREGATION",
19796 b"avg",
19797 b"sum",
19798 b"sum",
19799 b"100",
19800 ],
19801 numkeys,
19802 ),
19803 (
19804 &[
19805 b"TS.NRANGE",
19806 b"2",
19807 b"x",
19808 b"y",
19809 b"-",
19810 b"+",
19811 b"AGGREGATION",
19812 b"avg",
19813 b"sum",
19814 b"abc",
19815 ],
19816 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19817 ),
19818 (
19819 &[
19820 b"TS.NRANGE",
19821 b"2",
19822 b"x",
19823 b"y",
19824 b"-",
19825 b"+",
19826 b"AGGREGATION",
19827 b"avg",
19828 b"sum",
19829 b"0",
19830 ],
19831 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19832 ),
19833 (
19836 &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
19837 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19838 ),
19839 (
19840 &[
19841 b"TS.NRANGE",
19842 b"1",
19843 b"x",
19844 b"-",
19845 b"+",
19846 b"AGGREGATION",
19847 b"100",
19848 b"200",
19849 ],
19850 "-ERR TSDB: Unknown aggregation type\r\n",
19851 ),
19852 (
19854 &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
19855 "-ERR TSDB: the key does not exist\r\n",
19856 ),
19857 (
19858 &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
19859 "-ERR WRONGTYPE Operation against a key \
19860 holding the wrong kind of value\r\n",
19861 ),
19862 ];
19863 for (argv, want) in cases {
19864 let got = f.run(argv);
19865 assert_eq!(&got, want, "{argv:?}");
19866 }
19867 }
19868
19869 #[test]
19871 fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
19872 let mut f = joined();
19873 assert_eq!(
19874 f.run(&[b"TS.READ", b"x", b"-"]),
19875 "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
19876 *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19877 );
19878 assert_eq!(
19881 f.run(&[b"TS.READ", b"x", b"+"]),
19882 "*1\r\n*2\r\n:50\r\n+5\r\n"
19883 );
19884 assert_eq!(
19885 f.run(&[b"TS.READ", b"x", b"25"]),
19886 "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19887 );
19888 assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
19891 assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
19892 assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
19893 assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
19894 assert_eq!(
19898 f.run(&[b"TS.READ", b"x", b"abc"]),
19899 "-TSDB: invalid timestamp\r\n"
19900 );
19901 assert_eq!(
19902 f.run(&[b"TS.READ", b"x", b"-1"]),
19903 "-TSDB: invalid timestamp\r\n"
19904 );
19905 f.run(&[b"SET", b"str", b"hi"]);
19906 assert_eq!(
19907 f.run(&[b"TS.READ", b"str", b"-"]),
19908 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19909 );
19910 assert_eq!(
19913 f.run(&[b"TS.READ", b"x"]),
19914 "-ERR wrong number of arguments for 'ts.read' command\r\n"
19915 );
19916 assert_eq!(
19917 f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
19918 "-ERR wrong number of arguments for 'ts.read' command\r\n"
19919 );
19920 }
19921
19922 #[test]
19925 fn getkeys_reads_the_count_of_a_joined_read() {
19926 let mut f = Fixture::new();
19927 assert_eq!(
19928 f.run(&[
19929 b"COMMAND",
19930 b"GETKEYS",
19931 b"TS.NRANGE",
19932 b"2",
19933 b"a",
19934 b"b",
19935 b"-",
19936 b"+"
19937 ]),
19938 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
19939 );
19940 assert_eq!(
19941 f.run(&[
19942 b"COMMAND",
19943 b"GETKEYS",
19944 b"TS.NREVRANGE",
19945 b"1",
19946 b"a",
19947 b"-",
19948 b"+"
19949 ]),
19950 "*1\r\n$1\r\na\r\n"
19951 );
19952 for n in [b"0".as_slice(), b"9", b"abc"] {
19955 assert_eq!(
19956 f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
19957 "-ERR Invalid arguments specified for command\r\n"
19958 );
19959 }
19960 }
19961
19962 fn labelled() -> Fixture {
19964 let mut f = Fixture::new();
19965 f.run(&[
19966 b"TS.CREATE",
19967 b"a",
19968 b"LABELS",
19969 b"room",
19970 b"kitchen",
19971 b"x",
19972 b"1",
19973 ]);
19974 f.run(&[
19975 b"TS.CREATE",
19976 b"b",
19977 b"LABELS",
19978 b"room",
19979 b"bedroom",
19980 b"x",
19981 b"2",
19982 ]);
19983 f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
19984 f.run(&[b"TS.CREATE", b"d"]);
19985 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
19986 f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
19987 f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
19988 f
19989 }
19990
19991 #[test]
19994 fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
19995 let mut f = labelled();
19996 let cases: &[(&[&[u8]], &str)] = &[
19997 (
20000 &[b"TS.QUERYINDEX", b"room=kitchen"],
20001 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
20002 ),
20003 (
20004 &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
20005 "*1\r\n$1\r\na\r\n",
20006 ),
20007 (
20008 &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
20009 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
20010 ),
20011 (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
20014 (
20016 &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
20017 "*1\r\n$1\r\nc\r\n",
20018 ),
20019 (
20020 &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
20021 "*1\r\n$1\r\na\r\n",
20022 ),
20023 (
20024 &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
20025 "-ERR TSDB: please provide at least one matcher\r\n",
20026 ),
20027 (
20030 &[b"TS.QUERYINDEX", b"room==kitchen"],
20031 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
20032 ),
20033 (
20034 &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
20035 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
20036 ),
20037 (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
20038 (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
20041 (
20042 &[b"TS.QUERYINDEX", b"=(1)"],
20043 "-ERR TSDB: failed parsing labels\r\n",
20044 ),
20045 (
20046 &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
20047 "-ERR TSDB: failed parsing labels\r\n",
20048 ),
20049 (
20050 &[b"TS.QUERYINDEX", b"room=(kitchen"],
20051 "-ERR TSDB: failed parsing labels\r\n",
20052 ),
20053 (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
20054 (
20055 &[b"TS.QUERYINDEX", b"nonsense"],
20056 "-ERR TSDB: failed parsing labels\r\n",
20057 ),
20058 (
20060 &[b"TS.QUERYINDEX", b"room!=kitchen"],
20061 "-ERR TSDB: please provide at least one matcher\r\n",
20062 ),
20063 (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
20065 (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
20066 (
20067 &[b"TS.QUERYINDEX"],
20068 "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
20069 ),
20070 ];
20071 for (argv, want) in cases {
20072 let got = f.run(argv);
20073 assert_eq!(&got, want, "{:?}", argv.last());
20074 }
20075 }
20076
20077 #[test]
20079 fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
20080 let mut f = labelled();
20081 let cases: &[(&[&[u8]], &str)] = &[
20082 (
20083 &[b"TS.QUERYLABELS", b"LABELS"],
20084 "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
20085 ),
20086 (
20087 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
20088 "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
20089 ),
20090 (
20091 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
20092 "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
20093 ),
20094 (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
20097 (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
20098 (
20099 &[b"TS.QUERYLABELS", b"VALUES"],
20100 "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
20101 ),
20102 (
20103 &[b"TS.QUERYLABELS", b"ZZZ"],
20104 "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
20105 ),
20106 (
20107 &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
20108 "-ERR TSDB: unknown argument, expected FILTER\r\n",
20109 ),
20110 (
20111 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
20112 "-ERR TSDB: FILTER given with no filter expressions\r\n",
20113 ),
20114 (
20118 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
20119 "-ERR TSDB: please provide at least one matcher\r\n",
20120 ),
20121 (
20122 &[
20123 b"TS.QUERYLABELS",
20124 b"LABELS",
20125 b"FILTER",
20126 b"room=kitchen",
20127 b"x=",
20128 ],
20129 "*1\r\n$4\r\nroom\r\n",
20130 ),
20131 ];
20132 for (argv, want) in cases {
20133 let got = f.run(argv);
20134 assert_eq!(&got, want, "{:?}", argv.last());
20135 }
20136 }
20137
20138 #[test]
20141 fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
20142 let mut f = labelled();
20143 let cases: &[(&[&[u8]], &str)] = &[
20144 (
20147 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
20148 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
20149 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
20150 ),
20151 (
20152 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
20153 "*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\
20154 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
20155 *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",
20156 ),
20157 (
20159 &[
20160 b"TS.MGET",
20161 b"SELECTED_LABELS",
20162 b"x",
20163 b"FILTER",
20164 b"room=kitchen",
20165 ],
20166 "*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\
20167 *2\r\n:100\r\n+1.5\r\n\
20168 *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",
20169 ),
20170 (
20173 &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
20174 "*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",
20175 ),
20176 (
20177 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
20178 "*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\
20179 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
20180 ),
20181 (
20184 &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
20185 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
20186 ),
20187 (
20188 &[b"TS.MGET", b"a", b"b", b"c"],
20189 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
20190 ),
20191 (
20192 &[b"TS.MGET", b"FILTER"],
20193 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
20194 ),
20195 (
20198 &[
20199 b"TS.MGET",
20200 b"WITHLABELS",
20201 b"SELECTED_LABELS",
20202 b"x",
20203 b"FILTER",
20204 b"bad",
20205 ],
20206 "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
20207 ),
20208 (
20209 &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
20210 "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
20211 ),
20212 ];
20213 for (argv, want) in cases {
20214 let got = f.run(argv);
20215 assert_eq!(&got, want, "{:?}", argv.last());
20216 }
20217 }
20218
20219 #[test]
20222 fn resp3_writes_the_label_surface_as_sets_and_maps() {
20223 let mut f = labelled();
20224 f.out = Out::new(Proto::Resp3);
20225 let cases: &[(&[&[u8]], &str)] = &[
20226 (
20227 &[b"TS.QUERYINDEX", b"room=kitchen"],
20228 "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
20229 ),
20230 (
20231 &[b"TS.QUERYLABELS", b"LABELS"],
20232 "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
20233 ),
20234 (
20235 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
20236 "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
20237 ),
20238 (
20241 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
20242 "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
20243 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
20244 ),
20245 (
20246 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
20247 "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
20248 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
20249 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
20250 ),
20251 (
20252 &[
20253 b"TS.MGET",
20254 b"SELECTED_LABELS",
20255 b"x",
20256 b"FILTER",
20257 b"room=kitchen",
20258 ],
20259 "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
20260 *2\r\n:100\r\n,1.5\r\n\
20261 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
20262 ),
20263 (
20266 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
20267 "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
20268 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
20269 ),
20270 ];
20271 for (argv, want) in cases {
20272 let got = f.run(argv);
20273 assert_eq!(&got, want, "{:?}", argv.last());
20274 }
20275 }
20276
20277 fn spanned() -> Fixture {
20280 let mut f = labelled();
20281 f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
20282 f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
20283 f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
20284 f
20285 }
20286
20287 #[test]
20290 fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
20291 let mut f = spanned();
20292 let cases: &[(&[&[u8]], &str)] = &[
20293 (
20294 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
20295 "*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\
20296 *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",
20297 ),
20298 (
20301 &[
20302 b"TS.MREVRANGE",
20303 b"-",
20304 b"+",
20305 b"WITHLABELS",
20306 b"FILTER",
20307 b"room=kitchen",
20308 ],
20309 "*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\
20310 *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\
20311 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
20312 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
20313 ),
20314 (
20317 &[
20318 b"TS.MRANGE",
20319 b"-",
20320 b"+",
20321 b"SELECTED_LABELS",
20322 b"x",
20323 b"FILTER",
20324 b"room=kitchen",
20325 ],
20326 "*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\
20327 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
20328 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
20329 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
20330 ),
20331 (
20334 &[
20335 b"TS.MRANGE",
20336 b"-",
20337 b"+",
20338 b"FILTER",
20339 b"room=kitchen",
20340 b"GROUPBY",
20341 b"room",
20342 b"REDUCE",
20343 b"sum",
20344 ],
20345 "*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\
20346 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
20347 ),
20348 (
20351 &[
20352 b"TS.MRANGE",
20353 b"-",
20354 b"+",
20355 b"WITHLABELS",
20356 b"FILTER",
20357 b"room=kitchen",
20358 b"GROUPBY",
20359 b"room",
20360 b"REDUCE",
20361 b"max",
20362 ],
20363 "*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\
20364 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
20365 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
20366 *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",
20367 ),
20368 (
20370 &[
20371 b"TS.MREVRANGE",
20372 b"-",
20373 b"+",
20374 b"COUNT",
20375 b"1",
20376 b"FILTER",
20377 b"room=kitchen",
20378 b"GROUPBY",
20379 b"room",
20380 b"REDUCE",
20381 b"count",
20382 ],
20383 "*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",
20384 ),
20385 (
20387 &[
20388 b"TS.MRANGE",
20389 b"-",
20390 b"+",
20391 b"FILTER",
20392 b"room=kitchen",
20393 b"GROUPBY",
20394 b"nope",
20395 b"REDUCE",
20396 b"sum",
20397 ],
20398 "*0\r\n",
20399 ),
20400 (
20401 &[
20402 b"TS.MRANGE",
20403 b"-",
20404 b"+",
20405 b"AGGREGATION",
20406 b"sum,avg",
20407 b"100",
20408 b"FILTER",
20409 b"room=bedroom",
20410 ],
20411 "*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",
20412 ),
20413 (
20415 &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
20416 "-ERR TSDB: missing FILTER argument\r\n",
20417 ),
20418 (
20419 &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
20420 "-ERR TSDB: missing labels for filter argument\r\n",
20421 ),
20422 (
20423 &[
20424 b"TS.MRANGE",
20425 b"-",
20426 b"+",
20427 b"GROUPBY",
20428 b"room",
20429 b"REDUCE",
20430 b"sum",
20431 b"FILTER",
20432 b"room=kitchen",
20433 ],
20434 "-ERR TSDB: GROUPBY should always come after filter\r\n",
20435 ),
20436 (
20439 &[
20440 b"TS.MRANGE",
20441 b"-",
20442 b"+",
20443 b"FILTER",
20444 b"room=kitchen",
20445 b"GROUPBY",
20446 b"room",
20447 b"REDUCE",
20448 b"sum",
20449 b"x",
20450 ],
20451 "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
20452 ),
20453 (
20455 &[
20456 b"TS.MRANGE",
20457 b"-",
20458 b"+",
20459 b"FILTER",
20460 b"nope",
20461 b"GROUPBY",
20462 b"room",
20463 b"REDUCE",
20464 b"sum",
20465 b"x",
20466 ],
20467 "-ERR TSDB: failed parsing labels\r\n",
20468 ),
20469 (
20470 &[
20471 b"TS.MRANGE",
20472 b"-",
20473 b"+",
20474 b"FILTER",
20475 b"room=kitchen",
20476 b"GROUPBY",
20477 b"room",
20478 b"REDUCE",
20479 b"twa",
20480 ],
20481 "-ERR TSDB: Invalid reducer type\r\n",
20482 ),
20483 (
20484 &[
20485 b"TS.MRANGE",
20486 b"-",
20487 b"+",
20488 b"AGGREGATION",
20489 b"sum,avg",
20490 b"100",
20491 b"FILTER",
20492 b"room=kitchen",
20493 b"GROUPBY",
20494 b"room",
20495 b"REDUCE",
20496 b"sum",
20497 ],
20498 "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
20499 ),
20500 (
20503 &[
20504 b"TS.MRANGE",
20505 b"-",
20506 b"+",
20507 b"SELECTED_LABELS",
20508 b"COUNT",
20509 b"FILTER",
20510 b"room=kitchen",
20511 ],
20512 "-ERR TSDB: Couldn't parse COUNT\r\n",
20513 ),
20514 ];
20515 for (argv, want) in cases {
20516 let got = f.run(argv);
20517 assert_eq!(&got, want, "{argv:?}");
20518 }
20519 }
20520
20521 #[test]
20524 fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
20525 let mut f = spanned();
20526 f.out = Out::new(Proto::Resp3);
20527 let cases: &[(&[&[u8]], &str)] = &[
20528 (
20529 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
20530 "%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\
20531 *1\r\n*2\r\n:200\r\n,2\r\n",
20532 ),
20533 (
20536 &[
20537 b"TS.MRANGE",
20538 b"-",
20539 b"+",
20540 b"AGGREGATION",
20541 b"sum,avg",
20542 b"100",
20543 b"FILTER",
20544 b"room=bedroom",
20545 ],
20546 "%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\
20547 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
20548 ),
20549 (
20550 &[
20551 b"TS.MRANGE",
20552 b"-",
20553 b"+",
20554 b"FILTER",
20555 b"room=kitchen",
20556 b"GROUPBY",
20557 b"room",
20558 b"REDUCE",
20559 b"sum",
20560 ],
20561 "%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\
20562 $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\
20563 *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",
20564 ),
20565 (
20568 &[
20569 b"TS.MRANGE",
20570 b"-",
20571 b"+",
20572 b"WITHLABELS",
20573 b"FILTER",
20574 b"room=kitchen",
20575 b"GROUPBY",
20576 b"room",
20577 b"REDUCE",
20578 b"max",
20579 ],
20580 "%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\
20581 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
20582 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
20583 *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",
20584 ),
20585 (
20586 &[
20587 b"TS.MRANGE",
20588 b"-",
20589 b"+",
20590 b"FILTER",
20591 b"room=kitchen",
20592 b"GROUPBY",
20593 b"nope",
20594 b"REDUCE",
20595 b"sum",
20596 ],
20597 "%0\r\n",
20598 ),
20599 ];
20600 for (argv, want) in cases {
20601 let got = f.run(argv);
20602 assert_eq!(&got, want, "{argv:?}");
20603 }
20604 }
20605
20606 #[test]
20608 fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
20609 let mut f = Fixture::new();
20610 f.run(&[b"TS.CREATE", b"src"]);
20611 f.run(&[b"TS.CREATE", b"dst"]);
20612 f.run(&[b"SET", b"plain", b"v"]);
20613 let cases: &[(&[&[u8]], &str)] = &[
20614 (
20618 &[
20619 b"TS.CREATERULE",
20620 b"src",
20621 b"dst",
20622 b"AGGREGATION",
20623 b"nope",
20624 b"x",
20625 ],
20626 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
20627 ),
20628 (
20629 &[
20630 b"TS.CREATERULE",
20631 b"src",
20632 b"dst",
20633 b"AGGREGATION",
20634 b"nope",
20635 b"10",
20636 ],
20637 "-ERR TSDB: Unknown aggregation type\r\n",
20638 ),
20639 (
20640 &[
20641 b"TS.CREATERULE",
20642 b"src",
20643 b"dst",
20644 b"AGGREGATION",
20645 b"avg",
20646 b"0",
20647 ],
20648 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
20649 ),
20650 (
20651 &[
20652 b"TS.CREATERULE",
20653 b"src",
20654 b"dst",
20655 b"AGGREGATION",
20656 b"avg",
20657 b"10",
20658 b"x",
20659 ],
20660 "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
20661 ),
20662 (
20663 &[
20664 b"TS.CREATERULE",
20665 b"src",
20666 b"src",
20667 b"AGGREGATION",
20668 b"avg",
20669 b"10",
20670 ],
20671 "-ERR TSDB: the source key and destination key should be different\r\n",
20672 ),
20673 (
20677 &[
20678 b"TS.CREATERULE",
20679 b"nope",
20680 b"plain",
20681 b"AGGREGATION",
20682 b"avg",
20683 b"10",
20684 ],
20685 "-ERR TSDB: the key does not exist\r\n",
20686 ),
20687 (
20688 &[
20689 b"TS.CREATERULE",
20690 b"src",
20691 b"nope",
20692 b"AGGREGATION",
20693 b"avg",
20694 b"10",
20695 ],
20696 "-ERR TSDB: the key does not exist\r\n",
20697 ),
20698 (
20701 &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
20702 "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
20703 ),
20704 (
20705 &[
20706 b"TS.CREATERULE",
20707 b"src",
20708 b"dst",
20709 b"AGGREGATION",
20710 b"avg",
20711 b"10",
20712 ],
20713 "+OK\r\n",
20714 ),
20715 (
20718 &[
20719 b"TS.CREATERULE",
20720 b"src",
20721 b"dst",
20722 b"AGGREGATION",
20723 b"avg",
20724 b"10",
20725 ],
20726 "-ERR TSDB: the destination key already has a src rule\r\n",
20727 ),
20728 (
20731 &[
20732 b"TS.CREATERULE",
20733 b"dst",
20734 b"src",
20735 b"AGGREGATION",
20736 b"avg",
20737 b"10",
20738 ],
20739 "-ERR TSDB: the source key already has a source rule\r\n",
20740 ),
20741 (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
20742 (
20743 &[b"TS.DELETERULE", b"src", b"dst"],
20744 "-ERR TSDB: compaction rule does not exist\r\n",
20745 ),
20746 (
20750 &[b"TS.DELETERULE", b"src", b"nope"],
20751 "-ERR TSDB: compaction rule does not exist\r\n",
20752 ),
20753 (
20754 &[b"TS.DELETERULE", b"nope", b"dst"],
20755 "-ERR TSDB: the key does not exist\r\n",
20756 ),
20757 ];
20758 for (argv, want) in cases {
20759 let got = f.run(argv);
20760 assert_eq!(&got, want, "{argv:?}");
20761 }
20762 }
20763
20764 #[test]
20766 fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
20767 let mut f = Fixture::new();
20768 f.run(&[b"TS.CREATE", b"src"]);
20769 f.run(&[b"TS.CREATE", b"dst"]);
20770 f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
20773 f.run(&[
20774 b"TS.CREATERULE",
20775 b"src",
20776 b"dst",
20777 b"AGGREGATION",
20778 b"sum",
20779 b"100",
20780 ]);
20781 f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
20782 assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
20783 assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
20786 f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
20787 assert_eq!(
20788 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20789 "*1\r\n*2\r\n:0\r\n+2\r\n"
20790 );
20791 f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
20794 assert_eq!(
20795 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20796 "*1\r\n*2\r\n:0\r\n+11\r\n"
20797 );
20798 assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
20801 assert_eq!(
20802 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20803 "*1\r\n*2\r\n:0\r\n+8\r\n"
20804 );
20805 assert_eq!(
20806 f.run(&[b"TS.GET", b"dst", b"LATEST"]),
20807 "*2\r\n:100\r\n+4\r\n"
20808 );
20809 assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
20811 f.run(&[b"DEL", b"dst"]);
20812 assert_eq!(
20813 f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
20814 "-ERR TSDB: compaction rule does not exist\r\n"
20815 );
20816 }
20817
20818 #[test]
20821 fn xadd_ids_only_ever_go_up() {
20822 let mut f = Fixture::new();
20823 assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
20825 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
20827 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
20828 assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
20829 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20830
20831 assert!(
20832 f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
20833 .contains("equal or smaller")
20834 );
20835 assert!(
20836 f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
20837 .contains("must be greater than 0-0")
20838 );
20839 assert!(
20840 f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
20841 .contains("Invalid stream ID")
20842 );
20843 assert!(
20846 f.run(&[b"XADD", b"s", b"*", b"a"])
20847 .contains("wrong number of arguments")
20848 );
20849
20850 assert_eq!(
20853 f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
20854 "$-1\r\n"
20855 );
20856 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20857 assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
20858 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
20859 }
20860
20861 #[test]
20864 fn trimming_reads_its_options_the_way_redis_does() {
20865 let mut f = Fixture::new();
20866 for i in 1..=10u32 {
20867 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20868 }
20869 assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
20870 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20871 assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
20872 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20873
20874 assert!(
20878 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
20879 .contains("not an integer")
20880 );
20881 assert!(
20882 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
20883 .contains("MAXLEN argument must be >= 0")
20884 );
20885 assert!(
20888 f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
20889 .contains("without specifying a trimming strategy")
20890 );
20891 assert!(
20892 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
20893 .contains("without the special ~ option")
20894 );
20895 assert!(
20896 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
20897 .contains("at the same time are not compatible")
20898 );
20899 assert!(
20901 f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
20902 .contains("syntax error")
20903 );
20904 assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
20905 }
20906
20907 #[test]
20909 fn xrange_looks_the_key_up_before_it_reads_the_count() {
20910 let mut f = Fixture::new();
20911 f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
20912 f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
20913
20914 assert_eq!(
20915 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
20916 "*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\
20917 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20918 );
20919 assert_eq!(
20920 f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
20921 "*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"
20922 );
20923 assert_eq!(
20927 f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
20928 "*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\
20929 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20930 );
20931 assert_eq!(
20932 f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
20933 "*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"
20934 );
20935 assert!(
20936 f.run(&[b"XRANGE", b"s", b"(-", b"+"])
20937 .contains("Invalid stream ID")
20938 );
20939
20940 assert_eq!(
20944 f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
20945 "*0\r\n"
20946 );
20947 assert_eq!(
20948 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
20949 "*-1\r\n"
20950 );
20951 f.run(&[b"SET", b"str", b"v"]);
20952 assert!(
20953 f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
20954 .starts_with("-WRONGTYPE")
20955 );
20956 assert_eq!(
20958 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
20959 "*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"
20960 );
20961 }
20962
20963 #[test]
20965 fn a_bad_id_late_in_the_list_stops_the_whole_command() {
20966 let mut f = Fixture::new();
20967 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20968 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20969 assert!(
20970 f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
20971 .contains("Invalid stream ID")
20972 );
20973 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20974 assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
20975 assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
20976 assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
20977 assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
20978 }
20979
20980 #[test]
20982 fn xgroup_has_an_arity_per_subcommand() {
20983 let mut f = Fixture::new();
20984 assert!(
20985 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20986 .contains("requires the key")
20987 );
20988 assert_eq!(
20989 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
20990 "+OK\r\n"
20991 );
20992 assert!(
20995 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20996 .starts_with("-BUSYGROUP")
20997 );
20998 assert_eq!(
20999 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
21000 ":1\r\n"
21001 );
21002 assert_eq!(
21003 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
21004 ":0\r\n"
21005 );
21006 assert_eq!(
21007 f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
21008 ":0\r\n"
21009 );
21010
21011 let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
21013 assert!(
21014 short.contains("wrong number of arguments for 'xgroup|destroy' command"),
21015 "{short}"
21016 );
21017 let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
21019 assert!(
21020 odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
21021 "{odd}"
21022 );
21023 assert!(
21024 f.run(&[b"XGROUP", b"NOSUCH", b"s"])
21025 .contains("Try XGROUP HELP")
21026 );
21027
21028 assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
21029 assert!(
21030 f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
21031 .starts_with("-NOGROUP")
21032 );
21033 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
21034 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
21035 assert!(
21036 f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
21037 .contains("requires the key")
21038 );
21039 }
21040
21041 #[test]
21043 fn xreadgroup_hands_out_and_xack_takes_back() {
21044 let mut f = Fixture::new();
21045 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21046 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21047 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21048
21049 let first = f.run(&[
21050 b"XREADGROUP",
21051 b"GROUP",
21052 b"g",
21053 b"c1",
21054 b"COUNT",
21055 b"1",
21056 b"STREAMS",
21057 b"s",
21058 b">",
21059 ]);
21060 assert_eq!(
21061 first,
21062 "*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"
21063 );
21064 assert_eq!(
21067 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
21068 "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
21069 );
21070 assert_eq!(
21071 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
21072 "*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"
21073 );
21074
21075 assert_eq!(
21076 f.run(&[b"XPENDING", b"s", b"g"]),
21077 "*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"
21078 );
21079 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
21080 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
21081 assert_eq!(
21083 f.run(&[b"XPENDING", b"s", b"g"]),
21084 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
21085 );
21086
21087 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21090 f.run(&[b"XDEL", b"s", b"2-1"]);
21091 assert_eq!(
21092 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
21093 "*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"
21094 );
21095
21096 assert!(
21099 f.run(&[
21100 b"XREADGROUP",
21101 b"GROUP",
21102 b"nope",
21103 b"c",
21104 b"STREAMS",
21105 b"s",
21106 b"+"
21107 ])
21108 .starts_with("-NOGROUP")
21109 );
21110 assert!(
21111 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
21112 .contains("meaningless in the context of XREADGROUP")
21113 );
21114 assert!(
21115 f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
21116 .contains("only supported by XREADGROUP")
21117 );
21118 assert!(
21119 f.run(&[
21120 b"XREADGROUP",
21121 b"GROUP",
21122 b"g",
21123 b"c",
21124 b"STREAMS",
21125 b"s",
21126 b"a",
21127 b"b"
21128 ])
21129 .contains("Unbalanced 'xreadgroup' list of streams")
21130 );
21131 }
21132
21133 #[test]
21136 fn xread_with_no_block_writes_the_null_itself() {
21137 let mut f = Fixture::new();
21138 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21139 assert_eq!(
21140 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
21141 "*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"
21142 );
21143 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
21146 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
21147 f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
21148 assert_eq!(
21149 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
21150 "*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"
21151 );
21152 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
21154 assert_eq!(
21156 f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
21157 "*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"
21158 );
21159 assert_eq!(
21162 f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
21163 "*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"
21164 );
21165 assert!(
21167 f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
21168 .contains("not an integer")
21169 );
21170 assert!(
21171 f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
21172 .contains("timeout is negative")
21173 );
21174 assert!(
21175 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
21176 .contains("Unbalanced 'xread' list of streams")
21177 );
21178 }
21179
21180 #[test]
21182 fn a_blocked_xread_wakes_on_the_next_entry() {
21183 let mut f = Fixture::new();
21184 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21185 let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
21186 assert_eq!(flow, Flow::Block);
21187 assert!(reply.is_empty());
21188
21189 f.session = Session::new(8);
21194 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
21195 assert_eq!(flow, Flow::Block);
21196 assert_eq!(f.server.parked(), 2);
21197
21198 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21199 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";
21200 for client in [7, 8] {
21201 let mut out = Out::new(Proto::Resp2);
21202 assert!(f.server.serve_waiter(client, 0, &mut out));
21203 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
21204 }
21205
21206 f.server.forget_waiters(7);
21209 f.server.forget_waiters(8);
21210 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
21211 assert_eq!(flow, Flow::Block);
21212 let mut out = Out::new(Proto::Resp2);
21213 assert!(!f.server.serve_waiter(8, 0, &mut out));
21214 assert!(out.as_slice().is_empty());
21215 assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
21216 assert_eq!(
21217 core::str::from_utf8(out.as_slice()).expect("ascii"),
21218 "*-1\r\n"
21219 );
21220 }
21221
21222 #[test]
21224 fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
21225 let mut f = Fixture::new();
21226 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21227 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
21228 let (flow, _) = f.flow(&[
21229 b"XREADGROUP",
21230 b"GROUP",
21231 b"g",
21232 b"c",
21233 b"BLOCK",
21234 b"0",
21235 b"STREAMS",
21236 b"s",
21237 b">",
21238 ]);
21239 assert_eq!(flow, Flow::Block);
21240
21241 f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
21242 let mut out = Out::new(Proto::Resp2);
21243 assert!(f.server.serve_waiter(7, 0, &mut out));
21244 assert_eq!(
21247 core::str::from_utf8(out.as_slice()).expect("ascii"),
21248 "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
21249 );
21250 }
21251
21252 #[test]
21254 fn xclaim_reads_ids_until_one_will_not_parse() {
21255 let mut f = Fixture::new();
21256 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21257 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21258 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21259 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21260
21261 assert!(
21264 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
21265 .contains("Unrecognized XCLAIM option '-'")
21266 );
21267 assert_eq!(
21268 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
21269 "*1\r\n$3\r\n1-1\r\n"
21270 );
21271 f.run(&[b"XDEL", b"s", b"2-1"]);
21274 assert_eq!(
21275 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
21276 "*0\r\n"
21277 );
21278 assert!(
21279 f.run(&[b"XPENDING", b"s", b"g"])
21280 .starts_with("*4\r\n:1\r\n")
21281 );
21282 assert!(
21283 f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
21284 .starts_with("-NOGROUP")
21285 );
21286 assert!(
21287 f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
21288 .contains("Invalid min-idle-time argument for XCLAIM")
21289 );
21290 }
21291
21292 #[test]
21294 fn xautoclaim_reports_what_it_dropped() {
21295 let mut f = Fixture::new();
21296 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21297 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21298 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21299 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21300 f.run(&[b"XDEL", b"s", b"1-1"]);
21301
21302 assert_eq!(
21305 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
21306 "*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"
21307 );
21308 assert!(
21309 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
21310 .contains("COUNT must be > 0")
21311 );
21312 assert!(
21313 f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
21314 .starts_with("-NOGROUP")
21315 );
21316 }
21317
21318 #[test]
21320 fn xdelex_answers_one_integer_an_id() {
21321 let mut f = Fixture::new();
21322 for i in 1..=4 {
21323 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
21324 }
21325 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21326 f.run(&[
21327 b"XREADGROUP",
21328 b"GROUP",
21329 b"g",
21330 b"c",
21331 b"COUNT",
21332 b"2",
21333 b"STREAMS",
21334 b"s",
21335 b">",
21336 ]);
21337
21338 assert_eq!(
21340 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
21341 "*2\r\n:1\r\n:-1\r\n"
21342 );
21343 assert!(
21346 f.run(&[b"XPENDING", b"s", b"g"])
21347 .starts_with("*4\r\n:2\r\n")
21348 );
21349 assert_eq!(
21351 f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
21352 "*1\r\n:1\r\n"
21353 );
21354 assert_eq!(
21356 f.run(&[b"XPENDING", b"s", b"g"]),
21357 "*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"
21358 );
21359
21360 assert_eq!(
21364 f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
21365 "*2\r\n:2\r\n:2\r\n"
21366 );
21367
21368 assert_eq!(
21370 f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
21371 "*2\r\n:-1\r\n:-1\r\n"
21372 );
21373 assert!(
21375 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
21376 .starts_with("-ERR Invalid stream ID")
21377 );
21378 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
21379
21380 assert!(
21381 f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
21382 .contains("Number of IDs must be a positive integer")
21383 );
21384 assert!(
21385 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
21386 .contains("The `numids` parameter must match the number of arguments")
21387 );
21388 assert!(
21391 f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
21392 .starts_with("-ERR syntax error")
21393 );
21394 assert!(
21395 f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
21396 .starts_with("-ERR syntax error")
21397 );
21398 f.run(&[b"SET", b"str", b"v"]);
21400 assert!(
21401 f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
21402 .starts_with("-WRONGTYPE")
21403 );
21404 }
21405
21406 #[test]
21408 fn xackdel_reports_what_the_group_was_holding() {
21409 let mut f = Fixture::new();
21410 for i in 1..=3 {
21411 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
21412 }
21413 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21414 f.run(&[
21415 b"XREADGROUP",
21416 b"GROUP",
21417 b"g",
21418 b"c",
21419 b"COUNT",
21420 b"1",
21421 b"STREAMS",
21422 b"s",
21423 b">",
21424 ]);
21425
21426 assert_eq!(
21430 f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
21431 "*2\r\n:1\r\n:-1\r\n"
21432 );
21433 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
21434
21435 assert_eq!(
21437 f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
21438 "*1\r\n:-1\r\n"
21439 );
21440 assert_eq!(
21441 f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
21442 "*1\r\n:-1\r\n"
21443 );
21444
21445 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
21448 f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
21449 assert_eq!(
21450 f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
21451 "*1\r\n:2\r\n"
21452 );
21453 assert_eq!(
21454 f.run(&[b"XPENDING", b"s", b"g"]),
21455 "*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"
21456 );
21457 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
21458 }
21459
21460 #[test]
21462 fn xnack_releases_an_entry_for_the_next_claim() {
21463 let mut f = Fixture::new();
21464 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21465 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21466 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21467 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21468 f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
21471
21472 assert_eq!(
21473 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
21474 ":1\r\n"
21475 );
21476 assert_eq!(
21480 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
21481 "*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"
21482 );
21483 assert_eq!(
21485 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21486 "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
21487 );
21488 assert_eq!(
21490 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
21491 "*-1\r\n"
21492 );
21493 assert_eq!(
21495 f.run(&[
21496 b"XAUTOCLAIM",
21497 b"s",
21498 b"g",
21499 b"c2",
21500 b"99999999",
21501 b"-",
21502 b"JUSTID"
21503 ]),
21504 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
21505 );
21506
21507 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21511 assert!(
21512 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21513 .contains(":-1\r\n:1\r\n")
21514 );
21515 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21517 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21518 assert!(
21519 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21520 .contains(":-1\r\n:0\r\n")
21521 );
21522 f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
21524 assert!(
21525 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21526 .contains(":9223372036854775807\r\n")
21527 );
21528 f.run(&[
21529 b"XNACK",
21530 b"s",
21531 b"g",
21532 b"FATAL",
21533 b"IDS",
21534 b"1",
21535 b"1-1",
21536 b"RETRYCOUNT",
21537 b"3",
21538 ]);
21539 assert!(
21540 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21541 .contains(":-1\r\n:3\r\n")
21542 );
21543
21544 f.run(&[b"XACK", b"s", b"g", b"2-1"]);
21548 assert_eq!(
21549 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
21550 ":0\r\n"
21551 );
21552 assert_eq!(
21553 f.run(&[
21554 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
21555 ]),
21556 ":1\r\n"
21557 );
21558 assert!(
21559 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21560 .contains(":-1\r\n:0\r\n")
21561 );
21562 assert_eq!(
21564 f.run(&[
21565 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
21566 ]),
21567 ":0\r\n"
21568 );
21569
21570 assert_eq!(
21573 f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
21574 "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
21575 );
21576 assert!(
21577 f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
21578 .starts_with("-ERR")
21579 );
21580 assert!(
21582 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
21583 .contains("numids must be a positive integer")
21584 );
21585 assert!(
21586 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
21587 .contains("number of IDs doesn't match numids")
21588 );
21589 assert!(
21592 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
21593 .contains("Unrecognized XNACK option '2-1'")
21594 );
21595 }
21596
21597 #[test]
21599 fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
21600 let mut f = Fixture::new();
21601 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21602 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21603 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21604 f.run(&[
21605 b"XREADGROUP",
21606 b"GROUP",
21607 b"g",
21608 b"c1",
21609 b"COUNT",
21610 b"1",
21611 b"STREAMS",
21612 b"s",
21613 b">",
21614 ]);
21615
21616 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21617 assert!(info.starts_with("*20\r\n"), "{info}");
21620 assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
21621 assert!(
21622 info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
21623 "{info}"
21624 );
21625 assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
21626 assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
21627
21628 let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
21629 assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
21630 assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
21631 assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
21632 assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
21633
21634 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
21638 let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
21639 assert!(consumers.starts_with("*2\r\n"), "{consumers}");
21640 assert!(
21641 consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
21642 "{consumers}"
21643 );
21644 let c1 = consumers.find("c1").unwrap();
21646 let c2 = consumers.find("c2").unwrap();
21647 assert!(c1 < c2, "{consumers}");
21648
21649 let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
21650 assert!(full.starts_with("*18\r\n"), "{full}");
21651 assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
21652 assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
21653
21654 assert!(
21655 f.run(&[b"XINFO", b"STREAM", b"missing"])
21656 .contains("no such key")
21657 );
21658 assert!(
21659 f.run(&[b"XINFO", b"GROUPS", b"missing"])
21660 .contains("no such key")
21661 );
21662 assert!(
21663 f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
21664 .starts_with("-NOGROUP")
21665 );
21666 assert!(
21667 f.run(&[b"XINFO", b"NOSUCH", b"s"])
21668 .contains("Try XINFO HELP")
21669 );
21670 assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
21671 assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
21672 }
21673
21674 #[test]
21676 fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
21677 let mut f = Fixture::new();
21678 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21679 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21680 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21681
21682 let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
21683 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");
21684 assert_eq!(
21685 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21686 "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
21687 );
21688 assert_eq!(
21690 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
21691 "*0\r\n"
21692 );
21693 assert_eq!(
21694 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
21695 list
21696 );
21697 assert!(
21699 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
21700 .contains("syntax error")
21701 );
21702 assert!(
21703 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
21704 .contains("syntax error")
21705 );
21706 assert_eq!(
21707 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
21708 "*0\r\n"
21709 );
21710 assert!(
21711 f.run(&[b"XPENDING", b"missing", b"g"])
21712 .starts_with("-NOGROUP")
21713 );
21714 }
21715
21716 #[test]
21718 fn xsetid_will_not_go_below_what_is_there() {
21719 let mut f = Fixture::new();
21720 f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
21721 assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
21722 assert_eq!(
21723 f.run(&[
21724 b"XSETID",
21725 b"s",
21726 b"10-1",
21727 b"ENTRIESADDED",
21728 b"7",
21729 b"MAXDELETEDID",
21730 b"9-1"
21731 ]),
21732 "+OK\r\n"
21733 );
21734 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21735 assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
21736 assert!(
21737 info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
21738 "{info}"
21739 );
21740
21741 assert!(
21742 f.run(&[b"XSETID", b"s", b"1-1"])
21743 .contains("smaller than the target stream top item")
21744 );
21745 assert!(
21746 f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
21747 .contains("entries_added must be positive")
21748 );
21749 assert!(
21750 f.run(&[b"XSETID", b"missing", b"1-1"])
21751 .contains("no such key")
21752 );
21753 }
21754
21755 #[test]
21757 fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
21758 let mut f = Fixture::new();
21759 f.run(&[b"HELLO", b"3"]);
21760 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21761 assert_eq!(
21764 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
21765 "%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"
21766 );
21767 assert_eq!(
21770 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
21771 "*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"
21772 );
21773 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
21774 }
21775
21776 struct Mem {
21782 blobs: Vec<Vec<u8>>,
21783 }
21784
21785 impl yo_kv::cold::Blocks for Mem {
21786 fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
21787 self.blobs.push(bytes.to_vec());
21788 Ok(yo_common::Addr::new(
21789 yo_common::Space::Log,
21790 (self.blobs.len() - 1) as u64,
21791 ))
21792 }
21793
21794 fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
21795 self.blobs
21796 .get(at.offset() as usize)
21797 .map(Vec::as_slice)
21798 .ok_or_else(|| {
21799 yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
21800 })
21801 }
21802
21803 fn bytes(&self) -> u64 {
21804 self.blobs.iter().map(|b| b.len() as u64).sum()
21805 }
21806 }
21807
21808 fn filled(attach: bool) -> (Fixture, usize) {
21821 let mut f = Fixture::new();
21822 if attach {
21823 f.server
21824 .striped(0)
21825 .hold_stripe(0)
21826 .attach(Box::new(Mem { blobs: Vec::new() }));
21827 }
21828 let val = vec![b'v'; 256];
21829 for i in 0..24000u32 {
21830 let k = format!("key:{i:08}");
21831 f.run(&[b"SET", k.as_bytes(), &val]);
21832 }
21833 let full = f.server.memory_bytes();
21834 assert!(full > 3 * 1024 * 1024, "the arena is several segments");
21835 (f, full)
21836 }
21837
21838 fn press(f: &mut Fixture, limit: usize) {
21844 let val = vec![b'v'; 256];
21845 for i in 0..3000u32 {
21846 let k = format!("new:{i:08}");
21847 assert_eq!(
21848 f.run(&[b"SET", k.as_bytes(), &val]),
21849 "+OK\r\n",
21850 "write {i} was refused"
21851 );
21852 f.server.refresh_memory();
21853 if f.server.memory_bytes() <= limit {
21854 return;
21855 }
21856 }
21857 panic!(
21858 "it never got under: {} against {limit}",
21859 f.server.memory_bytes()
21860 );
21861 }
21862
21863 #[test]
21864 fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
21865 let mut f = Fixture::new();
21866 assert_eq!(
21867 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21868 "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
21869 "no limit is the default"
21870 );
21871 for (typed, bytes) in [
21874 (&b"0"[..], "0"),
21875 (b"1024", "1024"),
21876 (b"1k", "1000"),
21877 (b"1gb", "1073741824"),
21878 (b"-1", "-1"),
21879 ] {
21880 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
21881 assert_eq!(
21882 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21883 format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
21884 "set {}",
21885 String::from_utf8_lossy(typed)
21886 );
21887 }
21888 for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
21889 assert_eq!(
21890 f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
21891 "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
21892 "refused {}",
21893 String::from_utf8_lossy(bad)
21894 );
21895 }
21896 let info = f.run(&[b"INFO", b"memory"]);
21898 assert!(info.contains("maxstore:-1"), "{info}");
21899 assert!(info.contains("yo_memory_regime:evict"), "{info}");
21900 assert!(info.contains("yo_store_bytes:0"), "{info}");
21901 }
21902
21903 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21904 #[test]
21905 fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
21906 let (mut f, full) = filled(true);
21910 let keys = f.run(&[b"DBSIZE"]);
21911 assert!(
21912 f.run(&[b"INFO", b"memory"])
21913 .contains("yo_memory_regime:migrate"),
21914 "a database with somewhere to put values migrates"
21915 );
21916
21917 let limit = full - 2 * 1024 * 1024;
21918 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21919 f.run(&[
21920 b"CONFIG",
21921 b"SET",
21922 b"maxmemory",
21923 limit.to_string().as_bytes(),
21924 ]);
21925 press(&mut f, limit);
21926
21927 assert!(
21928 f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21929 "nothing was thrown away"
21930 );
21931 let after: usize = f.run(&[b"DBSIZE"])[1..]
21932 .trim_end()
21933 .parse()
21934 .expect("a count");
21935 let before: usize = keys[1..].trim_end().parse().expect("a count");
21936 assert!(after > before, "the keys that came in are all still here");
21937 assert!(
21938 f.server.store_bytes() > 0,
21939 "and what came out of memory went to the file"
21940 );
21941 let val = format!("$256\r\n{}\r\n", "v".repeat(256));
21944 assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
21945 assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
21946 }
21947
21948 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21949 #[test]
21950 fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
21951 let (mut f, full) = filled(true);
21955 f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
21956 assert!(
21957 f.run(&[b"INFO", b"memory"])
21958 .contains("yo_memory_regime:evict"),
21959 "nothing may go to the file"
21960 );
21961
21962 let limit = full - 2 * 1024 * 1024;
21963 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21964 f.run(&[
21965 b"CONFIG",
21966 b"SET",
21967 b"maxmemory",
21968 limit.to_string().as_bytes(),
21969 ]);
21970 press(&mut f, limit);
21971
21972 assert!(
21973 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21974 "keys were thrown away, which is what was asked for"
21975 );
21976 assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
21977 }
21978
21979 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21980 #[test]
21981 fn a_full_file_goes_back_to_evicting() {
21982 let (mut f, full) = filled(true);
21986 f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
21987 let limit = full - 2 * 1024 * 1024;
21988 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21989 f.run(&[
21990 b"CONFIG",
21991 b"SET",
21992 b"maxmemory",
21993 limit.to_string().as_bytes(),
21994 ]);
21995 press(&mut f, limit);
21996
21997 assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
21998 assert!(
21999 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
22000 "and then it started evicting"
22001 );
22002 assert!(
22003 f.run(&[b"INFO", b"memory"])
22004 .contains("yo_memory_regime:evict"),
22005 "and it says so"
22006 );
22007 }
22008 #[test]
22024 fn the_string_group_answers_the_same_however_many_stripes_there_are() {
22025 let script: &[&[&[u8]]] = &[
22026 &[b"SET", b"k1", b"v1"],
22029 &[b"SET", b"k2", b"v2"],
22030 &[b"GET", b"k1"],
22031 &[b"GET", b"nothing"],
22032 &[b"GETSET", b"k1", b"v1b"],
22033 &[b"SETNX", b"k1", b"no"],
22034 &[b"SETNX", b"k3", b"yes"],
22035 &[b"APPEND", b"k3", b"!"],
22036 &[b"STRLEN", b"k3"],
22037 &[b"SETRANGE", b"k3", b"1", b"XY"],
22038 &[b"GETRANGE", b"k3", b"0", b"-1"],
22039 &[b"INCR", b"n1"],
22040 &[b"INCRBY", b"n1", b"41"],
22041 &[b"DECRBY", b"n1", b"2"],
22042 &[b"INCRBYFLOAT", b"f1", b"1.5"],
22043 &[b"SETEX", b"e1", b"100", b"v"],
22044 &[b"PSETEX", b"e2", b"100000", b"v"],
22045 &[b"GETEX", b"e1", b"PERSIST"],
22046 &[b"GETDEL", b"k2"],
22047 &[b"GET", b"k2"],
22048 &[b"DIGEST", b"k1"],
22049 &[b"DELEX", b"k3"],
22050 &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
22053 &[b"MGET", b"a", b"b", b"c", b"missing"],
22054 &[b"MSETNX", b"d", b"4", b"e", b"5"],
22055 &[b"MSETNX", b"e", b"6", b"f", b"7"],
22056 &[b"MGET", b"d", b"e", b"f"],
22057 &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
22058 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
22059 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
22060 &[b"MGET", b"g", b"h"],
22061 &[b"SET", b"s1", b"ohmytext"],
22062 &[b"SET", b"s2", b"mynewtext"],
22063 &[b"LCS", b"s1", b"s2"],
22064 &[b"LCS", b"s1", b"s2", b"LEN"],
22065 &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
22066 &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
22067 &[b"LCS", b"s1", b"gone"],
22068 &[b"MSET", b"odd"],
22070 &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
22071 &[b"MGET"],
22072 ];
22073
22074 let mut one = Fixture::new();
22075 let mut many = Fixture::striped(8);
22076 for parts in script {
22077 let a = one.run(parts);
22078 let b = many.run(parts);
22079 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22080 }
22081 }
22082
22083 #[test]
22088 fn a_striped_database_spreads_the_keys_it_is_given() {
22089 let mut f = Fixture::striped(8);
22090 for i in 0..256 {
22091 let key = format!("key:{i}");
22092 f.run(&[b"SET", key.as_bytes(), b"v"]);
22093 }
22094 assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
22095 }
22096
22097 #[test]
22100 fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
22101 let mut one = Fixture::new();
22102 let mut many = Fixture::striped(8);
22103 for f in [&mut one, &mut many] {
22104 f.run(&[b"SET", b"str", b"v"]);
22105 f.server
22111 .striped(0)
22112 .hold(b"list")
22113 .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
22114 .expect("a new list");
22115 }
22116 assert_eq!(
22117 one.run(&[b"MGET", b"str", b"list", b"gone"]),
22118 many.run(&[b"MGET", b"str", b"list", b"gone"])
22119 );
22120 }
22121
22122 #[test]
22130 fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
22131 let script: &[&[&[u8]]] = &[
22132 &[b"SET", b"k1", b"v1"],
22133 &[b"SET", b"k2", b"v2"],
22134 &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
22135 &[b"TYPE", b"k1"],
22136 &[b"TYPE", b"gone"],
22137 &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
22138 &[b"EXPIRE", b"k1", b"100"],
22139 &[b"TTL", b"k1"],
22140 &[b"EXPIRE", b"k1", b"200", b"NX"],
22141 &[b"PERSIST", b"k1"],
22142 &[b"TTL", b"k1"],
22143 &[b"PEXPIREAT", b"k2", b"1900000000000"],
22144 &[b"EXPIRETIME", b"k2"],
22145 &[b"PEXPIRETIME", b"k2"],
22146 &[b"PERSIST", b"k2"],
22147 &[b"OBJECT", b"ENCODING", b"k1"],
22148 &[b"OBJECT", b"REFCOUNT", b"k1"],
22149 &[b"OBJECT", b"IDLETIME", b"k1"],
22150 &[b"OBJECT", b"FREQ", b"k1"],
22151 &[b"OBJECT", b"ENCODING", b"gone"],
22152 &[b"OBJECT", b"HELP"],
22153 &[b"RENAME", b"k1", b"k9"],
22154 &[b"GET", b"k9"],
22155 &[b"RENAME", b"gone", b"x"],
22156 &[b"RENAMENX", b"k9", b"k2"],
22157 &[b"RENAMENX", b"k9", b"k8"],
22158 &[b"GET", b"k8"],
22159 &[b"COPY", b"k8", b"c1"],
22160 &[b"COPY", b"k8", b"c1"],
22161 &[b"COPY", b"k8", b"c1", b"REPLACE"],
22162 &[b"COPY", b"k8", b"k8"],
22163 &[b"COPY", b"gone", b"c2"],
22164 &[b"COPY", b"k8", b"k8", b"DB", b"1"],
22165 &[b"COPY", b"k8", b"c9", b"DB", b"9"],
22166 &[b"MOVE", b"c1", b"1"],
22167 &[b"MOVE", b"c1", b"1"],
22168 &[b"MOVE", b"k8", b"0"],
22169 &[b"DEL", b"k2", b"gone"],
22170 &[b"UNLINK", b"k8", b"k8"],
22171 &[b"DBSIZE"],
22172 ];
22173
22174 let mut one = Fixture::new();
22175 let mut many = Fixture::striped(8);
22176 for parts in script {
22177 let a = one.run(parts);
22178 let b = many.run(parts);
22179 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22180 }
22181
22182 for f in [&mut one, &mut many] {
22188 f.run(&[b"SET", b"d1", b"payload"]);
22189 let payload = f
22190 .server
22191 .striped(0)
22192 .hold(b"d1")
22193 .dump(b"d1")
22194 .expect("a key that is there");
22195 assert!(
22196 f.run(&[b"DUMP", b"d1"])
22197 .starts_with(&format!("${}", payload.len())),
22198 "a payload of the length the store gave"
22199 );
22200 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
22201 assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
22202 assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
22203 assert_eq!(
22204 f.run(&[b"RESTORE", b"d2", b"0", &payload]),
22205 "-BUSYKEY Target key name already exists.\r\n"
22206 );
22207 assert_eq!(
22208 f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
22209 "-ERR DUMP payload version or checksum are wrong\r\n"
22210 );
22211 }
22212 }
22213
22214 #[test]
22221 fn a_scan_of_a_striped_database_walks_all_of_it() {
22222 let n = if cfg!(miri) { 80 } else { 500 };
22226 let mut f = Fixture::striped(8);
22227 for i in 0..n {
22228 let key = format!("key:{i}");
22229 f.run(&[b"SET", key.as_bytes(), b"v"]);
22230 }
22231
22232 let mut seen = Vec::new();
22233 let mut cursor = "0".to_owned();
22234 let mut calls = 0;
22235 loop {
22236 let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
22237 let (next, keys) = scan_reply(&reply);
22238 seen.extend(keys);
22239 cursor = next;
22240 calls += 1;
22241 assert!(calls < 5_000, "a scan that will not finish");
22242 if cursor == "0" {
22243 break;
22244 }
22245 }
22246 seen.sort();
22247 assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
22248 assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
22249
22250 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
22254 let (_, keys) = scan_reply(&reply);
22255 assert_eq!(keys.len(), 10, "key:40 through key:49");
22256 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
22257 let (_, keys) = scan_reply(&reply);
22258 assert!(keys.is_empty(), "nothing here is a list");
22259 }
22260
22261 #[test]
22267 fn a_random_key_can_come_from_any_stripe() {
22268 let mut f = Fixture::striped(8);
22269 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
22270 for i in 0..200 {
22271 let key = format!("key:{i}");
22272 f.run(&[b"SET", key.as_bytes(), b"v"]);
22273 }
22274 let mut homes = std::collections::HashSet::new();
22275 for _ in 0..200 {
22276 let got = f.run(&[b"RANDOMKEY"]);
22277 let key = got.split("\r\n").nth(1).expect("a key").to_owned();
22278 assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
22279 homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
22280 }
22281 assert_eq!(homes.len(), 8, "some stripe was never drawn from");
22282 }
22283
22284 fn apart(f: &mut Fixture, src: &str) -> String {
22288 let home = f.server.striped(0).stripe_of(src.as_bytes());
22289 for i in 0..1_000 {
22290 let dst = format!("dst:{i}");
22291 if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
22292 return dst;
22293 }
22294 }
22295 panic!("eight stripes and a thousand keys all landed in one place");
22296 }
22297
22298 #[test]
22301 fn a_rename_across_stripes_takes_everything_with_it() {
22302 let mut f = Fixture::striped(8);
22303 let dst = apart(&mut f, "src");
22304 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
22305
22306 f.run(&[b"SET", src, b"v"]);
22307 f.run(&[b"EXPIRE", src, b"100"]);
22308 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
22309 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
22310 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
22311 assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
22312
22313 f.server
22318 .striped(0)
22319 .hold(src)
22320 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
22321 .expect("a new list");
22322 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
22323 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
22324 assert_eq!(
22325 f.server.striped(0).hold(dst).llen(dst).expect("a list"),
22326 2,
22327 "the members are on the stripe the key moved to"
22328 );
22329
22330 f.run(&[b"SET", src, b"v"]);
22333 assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
22334 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
22335 assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
22336 }
22337
22338 #[test]
22340 fn a_copy_across_stripes_leaves_the_source_where_it_was() {
22341 let mut f = Fixture::striped(8);
22342 let dst = apart(&mut f, "src");
22343 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
22344
22345 f.run(&[b"SET", src, b"v"]);
22346 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
22347 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
22348 assert_eq!(
22349 f.run(&[b"COPY", src, dst]),
22350 ":0\r\n",
22351 "the destination is taken"
22352 );
22353 f.run(&[b"SET", src, b"w"]);
22354 assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
22355 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
22356
22357 f.run(&[b"DEL", src, dst]);
22361 f.server
22362 .striped(0)
22363 .hold(src)
22364 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
22365 .expect("a new list");
22366 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
22367 f.server
22368 .striped(0)
22369 .hold(src)
22370 .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
22371 .expect("a list that is there");
22372 assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
22373 assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
22374 }
22375
22376 #[test]
22384 fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
22385 let script: &[&[&[u8]]] = &[
22386 &[b"SET", b"k1", b"foobar"],
22387 &[b"SETBIT", b"b1", b"7", b"1"],
22388 &[b"SETBIT", b"b1", b"7", b"0"],
22389 &[b"GETBIT", b"k1", b"6"],
22390 &[b"GETBIT", b"k1", b"100"],
22391 &[b"BITCOUNT", b"k1"],
22392 &[b"BITCOUNT", b"k1", b"0", b"0"],
22393 &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
22394 &[b"BITPOS", b"k1", b"1"],
22395 &[b"BITPOS", b"k1", b"0", b"2"],
22396 &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
22397 &[
22398 b"BITFIELD",
22399 b"bf",
22400 b"SET",
22401 b"u8",
22402 b"0",
22403 b"255",
22404 b"GET",
22405 b"u8",
22406 b"0",
22407 ],
22408 &[
22409 b"BITFIELD",
22410 b"bf",
22411 b"OVERFLOW",
22412 b"SAT",
22413 b"INCRBY",
22414 b"u8",
22415 b"0",
22416 b"10",
22417 ],
22418 &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
22419 &[b"SET", b"s1", b"abc"],
22422 &[b"SET", b"s2", b"abd"],
22423 &[b"SET", b"s3", b"a"],
22424 &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
22425 &[b"GET", b"d1"],
22426 &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
22427 &[b"GET", b"d2"],
22428 &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
22429 &[b"STRLEN", b"d3"],
22430 &[b"BITOP", b"NOT", b"d4", b"s1"],
22431 &[b"STRLEN", b"d4"],
22432 &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
22433 &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
22434 &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
22435 &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
22436 &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
22439 &[b"EXISTS", b"d1"],
22440 &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
22441 &[b"GET", b"d9"],
22442 &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
22446 &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
22447 &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
22448 &[b"BITOP", b"DIFF", b"d1", b"s1"],
22449 &[b"BITOP", b"NOPE", b"d1", b"s1"],
22450 &[b"BITCOUNT", b"list"],
22451 &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
22452 ];
22453
22454 let mut one = Fixture::new();
22455 let mut many = Fixture::striped(8);
22456 for f in [&mut one, &mut many] {
22457 plant_list(f, b"list");
22458 }
22459 for parts in script {
22460 let a = one.run(parts);
22461 let b = many.run(parts);
22462 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22463 }
22464 }
22465
22466 fn plant_list(f: &mut Fixture, key: &[u8]) {
22472 f.server
22473 .striped(0)
22474 .hold(key)
22475 .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
22476 .expect("a new list");
22477 }
22478
22479 #[test]
22486 fn a_bitop_across_stripes_reads_every_source() {
22487 let mut f = Fixture::striped(8);
22488 let other = apart(&mut f, "src");
22489 let (src, far) = (b"src".as_slice(), other.as_bytes());
22490 assert_ne!(
22491 f.server.striped(0).stripe_of(src),
22492 f.server.striped(0).stripe_of(far),
22493 "the two keys are the point of the test"
22494 );
22495
22496 f.run(&[b"SET", src, b"abc"]);
22497 f.run(&[b"SET", far, b"abd"]);
22498 assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
22499 assert_eq!(
22500 f.run(&[b"GET", far]),
22501 "$3\r\nab`\r\n",
22502 "a destination that is also a source"
22503 );
22504 f.run(&[b"SET", far, b"abd"]);
22505 assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
22506 assert_eq!(
22507 f.run(&[b"GET", src]),
22508 "$3\r\n\0\0\x07\r\n",
22509 "and the other way round"
22510 );
22511
22512 f.run(&[b"SET", src, b"abc"]);
22516 f.run(&[b"DEL", far]);
22517 assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
22518 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
22519 f.run(&[b"SET", src, b"abc"]);
22520 f.run(&[b"DEL", far]);
22521 plant_list(&mut f, far);
22522 assert_eq!(
22523 f.run(&[b"BITOP", b"OR", b"out", src, far]),
22524 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22525 );
22526 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
22527 }
22528
22529 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
22535 #[test]
22536 fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
22537 let script: &[&[&[u8]]] = &[
22538 &[b"PFADD", b"h1", b"a", b"b", b"c"],
22539 &[b"PFADD", b"h1", b"a"],
22540 &[b"PFADD", b"h2"],
22541 &[b"PFADD", b"h2", b"c", b"d", b"e"],
22542 &[b"PFCOUNT", b"h1"],
22543 &[b"PFCOUNT", b"h2"],
22544 &[b"PFCOUNT", b"missing"],
22545 &[b"PFCOUNT", b"h1", b"h2"],
22547 &[b"PFCOUNT", b"h1", b"missing"],
22548 &[b"PFMERGE", b"m", b"h1", b"h2"],
22549 &[b"PFCOUNT", b"m"],
22550 &[b"STRLEN", b"m"],
22551 &[b"PFMERGE", b"m"],
22552 &[b"PFCOUNT", b"m"],
22553 &[b"PFMERGE", b"m2", b"missing"],
22554 &[b"PFCOUNT", b"m2"],
22555 &[b"PFDEBUG", b"ENCODING", b"h1"],
22558 &[b"PFDEBUG", b"DECODE", b"h1"],
22559 &[b"PFDEBUG", b"TODENSE", b"h1"],
22560 &[b"PFDEBUG", b"ENCODING", b"h1"],
22561 &[b"PFDEBUG", b"TODENSE", b"h1"],
22562 &[b"PFCOUNT", b"h1", b"h2"],
22563 &[b"PFSELFTEST"],
22564 &[b"SET", b"plain", b"not a sketch at all"],
22566 &[b"PFADD", b"plain", b"a"],
22567 &[b"PFCOUNT", b"plain"],
22568 &[b"PFCOUNT", b"h1", b"plain"],
22569 &[b"PFMERGE", b"plain", b"h1"],
22570 &[b"PFMERGE", b"m", b"plain"],
22571 &[b"PFDEBUG", b"ENCODING", b"gone"],
22572 &[b"PFDEBUG", b"NOPE", b"h1"],
22573 ];
22574
22575 let mut one = Fixture::new();
22576 let mut many = Fixture::striped(8);
22577 for parts in script {
22578 let a = one.run(parts);
22579 let b = many.run(parts);
22580 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22581 }
22582 }
22583
22584 #[test]
22593 fn the_set_group_answers_the_same_however_many_stripes_there_are() {
22594 const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
22595 let script: &[&[&[u8]]] = &[
22596 &[b"SADD", b"s1", b"a", b"b", b"c"],
22597 &[b"SADD", b"s1", b"a"],
22598 &[b"SADD", b"s2", b"b", b"c", b"d"],
22599 &[b"SADD", b"ints", b"1", b"2", b"3"],
22600 &[b"SCARD", b"s1"],
22601 &[b"SISMEMBER", b"s1", b"a"],
22602 &[b"SISMEMBER", b"s1", b"z"],
22603 &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
22604 &[b"SMEMBERS", b"s1"],
22605 &[b"SREM", b"s1", b"c"],
22606 &[b"SADD", b"s1", b"c"],
22607 &[b"SSCAN", b"s1", b"0"],
22608 &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
22609 &[b"SADD", b"one", b"m"],
22612 &[b"SRANDMEMBER", b"one"],
22613 &[b"SRANDMEMBER", b"one", b"-3"],
22614 &[b"SRANDMEMBER", b"gone"],
22615 &[b"SPOP", b"one"],
22616 &[b"SPOP", b"one"],
22617 &[b"SPOP", b"gone", b"2"],
22618 &[b"SMOVE", b"s1", b"s2", b"a"],
22620 &[b"SMOVE", b"s1", b"s2", b"zzz"],
22621 &[b"SMOVE", b"gone", b"s2", b"a"],
22622 &[b"SMEMBERS", b"s1"],
22623 &[b"SMEMBERS", b"s2"],
22624 &[b"SINTER", b"s1", b"s2"],
22626 &[b"SUNION", b"s1", b"s2"],
22627 &[b"SDIFF", b"s2", b"s1"],
22628 &[b"SINTER", b"s1", b"gone"],
22629 &[b"SUNION", b"s1", b"gone"],
22630 &[b"SDIFF", b"gone", b"s1"],
22631 &[b"SINTER", b"ints", b"s1"],
22632 &[b"SINTERCARD", b"2", b"s1", b"s2"],
22633 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
22634 &[b"SUNIONCARD", b"2", b"s1", b"s2"],
22635 &[b"SDIFFCARD", b"2", b"s2", b"s1"],
22636 &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
22637 &[b"SMEMBERS", b"d1"],
22638 &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
22639 &[b"SCARD", b"d2"],
22640 &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
22641 &[b"SCARD", b"d3"],
22642 &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
22645 &[b"EXISTS", b"d4"],
22646 &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
22648 &[b"SCARD", b"s2"],
22649 &[b"SET", b"str", b"v"],
22651 &[b"SADD", b"str", b"a"],
22652 &[b"SINTER", b"s1", b"str"],
22653 &[b"SINTERSTORE", b"d5", b"s1", b"str"],
22654 &[b"EXISTS", b"d5"],
22655 &[b"SMOVE", b"str", b"s2", b"a"],
22656 &[b"SMOVE", b"s1", b"str", b"b"],
22657 &[b"SMOVE", b"gone", b"str", b"b"],
22658 &[b"SINTERCARD", b"0", b"s1"],
22659 &[b"SINTERCARD", b"3", b"s1", b"s2"],
22660 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
22661 &[b"SPOP", b"s1", b"-1"],
22662 ];
22663
22664 let mut one = Fixture::new();
22665 let mut many = Fixture::striped(8);
22666 for parts in script {
22667 let a = one.run(parts);
22668 let b = many.run(parts);
22669 let name = String::from_utf8_lossy(parts[0]).to_uppercase();
22670 if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
22671 assert_eq!(sorted(&a), sorted(&b), "{name}");
22672 } else {
22673 assert_eq!(a, b, "{name}");
22674 }
22675 }
22676 }
22677
22678 #[test]
22680 fn a_set_operation_across_stripes_reads_every_set() {
22681 let mut f = Fixture::striped(8);
22682 let second = apart(&mut f, "s1");
22683 let third = apart(&mut f, &second);
22684 let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
22685
22686 f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
22687 f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
22688 assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
22689 assert_eq!(
22690 sorted(&f.run(&[b"SUNION", s1, s2])),
22691 ["a", "b", "c", "d"],
22692 "a union of two stripes is both of them"
22693 );
22694 assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
22695 assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
22696 assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
22697 assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
22698
22699 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
22701 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
22702 assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
22703 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
22704 assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
22705 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
22706
22707 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
22710 assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
22711 f.run(&[b"SET", s3, b"v"]);
22712 assert_eq!(
22713 f.run(&[b"SINTER", s1, s3]),
22714 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22715 );
22716 assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
22717 }
22718
22719 #[test]
22721 fn a_move_across_stripes_takes_the_member_with_it() {
22722 let mut f = Fixture::striped(8);
22723 let other = apart(&mut f, "src");
22724 let (src, dst) = (b"src".as_slice(), other.as_bytes());
22725
22726 f.run(&[b"SADD", src, b"a", b"b"]);
22727 f.run(&[b"SADD", dst, b"c"]);
22728 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
22729 assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
22730 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
22731 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
22732
22733 f.run(&[b"DEL", dst]);
22736 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
22737 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
22738 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
22739
22740 f.run(&[b"SET", dst, b"v"]);
22744 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
22745 f.run(&[b"SADD", src, b"b"]);
22746 assert_eq!(
22747 f.run(&[b"SMOVE", src, dst, b"b"]),
22748 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22749 );
22750 }
22751
22752 #[test]
22754 fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
22755 let mut f = Fixture::striped(8);
22756 let other = apart(&mut f, "src");
22757 let (src, far) = (b"src".as_slice(), other.as_bytes());
22758
22759 for i in 0..150 {
22760 let ele = format!("e:{i}");
22761 f.run(&[b"PFADD", src, ele.as_bytes()]);
22762 }
22763 for i in 150..200 {
22764 let ele = format!("e:{i}");
22765 f.run(&[b"PFADD", far, ele.as_bytes()]);
22766 }
22767 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
22770 assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
22771 assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
22772
22773 let dest = apart(&mut f, &other);
22776 assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
22777 assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
22778 assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
22779 assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
22780 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
22781 }
22782
22783 #[test]
22790 fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
22791 let script: &[&[&[u8]]] = &[
22792 &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
22793 &[b"ZADD", b"z1", b"NX", b"9", b"a"],
22794 &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
22795 &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
22796 &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
22797 &[b"ZINCRBY", b"z1", b"1.5", b"b"],
22798 &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
22799 &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
22800 &[b"ZADD", b"one", b"1", b"m"],
22801 &[b"ZCARD", b"z1"],
22802 &[b"ZCARD", b"gone"],
22803 &[b"ZSCORE", b"z1", b"a"],
22804 &[b"ZSCORE", b"z1", b"zz"],
22805 &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
22806 &[b"ZRANK", b"z1", b"c"],
22807 &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
22808 &[b"ZREVRANK", b"z1", b"c"],
22809 &[b"ZRANK", b"z1", b"gone"],
22810 &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
22811 &[b"ZCOUNT", b"z1", b"(1", b"3"],
22812 &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
22813 &[b"ZRANGE", b"z1", b"0", b"-1"],
22815 &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
22816 &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
22817 &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
22818 &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
22819 &[b"ZREVRANGE", b"z1", b"0", b"-1"],
22820 &[
22821 b"ZRANGEBYSCORE",
22822 b"z1",
22823 b"-inf",
22824 b"+inf",
22825 b"LIMIT",
22826 b"1",
22827 b"1",
22828 ],
22829 &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
22830 &[b"ZSCAN", b"z1", b"0"],
22831 &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
22832 &[b"ZRANDMEMBER", b"one"],
22835 &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
22836 &[b"ZRANDMEMBER", b"gone"],
22837 &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
22839 &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
22840 &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
22841 &[b"EXISTS", b"d0"],
22842 &[b"ZUNION", b"2", b"z1", b"z2"],
22844 &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
22845 &[
22846 b"ZUNION",
22847 b"2",
22848 b"z1",
22849 b"z2",
22850 b"WEIGHTS",
22851 b"2",
22852 b"3",
22853 b"AGGREGATE",
22854 b"MAX",
22855 b"WITHSCORES",
22856 ],
22857 &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
22858 &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
22859 &[b"ZDIFF", b"2", b"gone", b"z1"],
22860 &[b"ZINTERCARD", b"2", b"z1", b"z2"],
22861 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
22862 &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
22863 &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
22864 &[
22865 b"ZINTERSTORE",
22866 b"d2",
22867 b"2",
22868 b"z1",
22869 b"z2",
22870 b"AGGREGATE",
22871 b"MIN",
22872 ],
22873 &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
22874 &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
22875 &[b"ZCARD", b"d3"],
22876 &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
22879 &[b"EXISTS", b"d4"],
22880 &[b"SADD", b"plain", b"a", b"x"],
22883 &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
22884 &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
22885 &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
22887 &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
22888 &[b"ZREM", b"d5", b"x", b"nothere"],
22890 &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
22891 &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
22892 &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
22893 &[b"ZPOPMIN", b"z1"],
22894 &[b"ZPOPMAX", b"z1", b"2"],
22895 &[b"ZPOPMIN", b"gone"],
22896 &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
22897 &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
22898 &[b"SET", b"str", b"v"],
22900 &[b"ZADD", b"str", b"1", b"a"],
22901 &[b"ZSCORE", b"str", b"a"],
22902 &[b"ZADD", b"z1", b"nan", b"a"],
22903 &[b"ZUNION", b"2", b"z1", b"str"],
22904 &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
22905 &[b"EXISTS", b"d6"],
22906 &[b"ZINTERCARD", b"0", b"z1"],
22907 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
22908 &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
22909 &[b"ZMPOP", b"1", b"str", b"MIN"],
22910 &[b"ZPOPMIN", b"z1", b"-1"],
22911 ];
22912
22913 let mut one = Fixture::new();
22914 let mut many = Fixture::striped(8);
22915 for parts in script {
22916 let a = one.run(parts);
22917 let b = many.run(parts);
22918 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22919 }
22920 }
22921
22922 #[test]
22924 fn a_sorted_set_operation_across_stripes_reads_every_input() {
22925 let mut f = Fixture::striped(8);
22926 let second = apart(&mut f, "z1");
22927 let third = apart(&mut f, &second);
22928 let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
22929
22930 f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
22931 f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
22932 assert_eq!(
22935 f.run(&[b"ZUNION", b"2", z1, z2]),
22936 "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
22937 );
22938 assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
22939 assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
22940 assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
22941 assert_eq!(
22942 f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
22943 ":1\r\n"
22944 );
22945
22946 assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
22949 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
22950 assert_eq!(
22951 f.run(&[
22952 b"ZUNIONSTORE",
22953 z3,
22954 b"2",
22955 z1,
22956 z2,
22957 b"WEIGHTS",
22958 b"2",
22959 b"3",
22960 b"AGGREGATE",
22961 b"MAX"
22962 ]),
22963 ":3\r\n"
22964 );
22965 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
22966 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
22967 assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
22968 assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
22969 assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
22970
22971 let popped = format!(
22974 "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
22975 second.len()
22976 );
22977 assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
22978 f.run(&[b"ZADD", z2, b"3", b"b"]);
22979
22980 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
22983 assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
22984 f.run(&[b"SET", z3, b"v"]);
22985 assert_eq!(
22986 f.run(&[b"ZUNION", b"2", z1, z3]),
22987 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22988 );
22989 assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
22990
22991 assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
22995 assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
22996 assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
22997 }
22998
22999 #[test]
23001 fn a_range_store_across_stripes_copies_the_window() {
23002 let mut f = Fixture::striped(8);
23003 let other = apart(&mut f, "src");
23004 let third = apart(&mut f, &other);
23005 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
23006
23007 f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
23008 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
23009 assert_eq!(
23010 f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
23011 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
23012 );
23013 assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
23014
23015 assert_eq!(
23018 f.run(&[
23019 b"ZRANGESTORE",
23020 dst,
23021 src,
23022 b"+inf",
23023 b"-inf",
23024 b"BYSCORE",
23025 b"REV",
23026 b"LIMIT",
23027 b"0",
23028 b"2"
23029 ]),
23030 ":2\r\n"
23031 );
23032 assert_eq!(
23033 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
23034 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
23035 );
23036
23037 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
23040 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
23041 f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
23042 f.run(&[b"SET", plain, b"v"]);
23043 assert_eq!(
23044 f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
23045 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23046 );
23047 assert_eq!(
23048 f.run(&[b"ZCARD", dst]),
23049 ":3\r\n",
23050 "and left the destination"
23051 );
23052 }
23053
23054 #[test]
23061 fn the_list_group_answers_the_same_however_many_stripes_there_are() {
23062 let script: &[&[&[u8]]] = &[
23063 &[b"RPUSH", b"l1", b"a", b"b", b"c"],
23064 &[b"LPUSH", b"l1", b"z"],
23065 &[b"RPUSHX", b"l1", b"d"],
23066 &[b"LPUSHX", b"gone", b"x"],
23067 &[b"RPUSHX", b"gone", b"x"],
23068 &[b"LLEN", b"l1"],
23069 &[b"LLEN", b"gone"],
23070 &[b"LRANGE", b"l1", b"0", b"-1"],
23071 &[b"LRANGE", b"l1", b"1", b"2"],
23072 &[b"LRANGE", b"l1", b"5", b"9"],
23073 &[b"LINDEX", b"l1", b"0"],
23074 &[b"LINDEX", b"l1", b"-1"],
23075 &[b"LINDEX", b"l1", b"99"],
23076 &[b"LSET", b"l1", b"0", b"y"],
23077 &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
23078 &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
23079 &[b"LPOS", b"l1", b"b"],
23080 &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
23081 &[b"LPOS", b"l1", b"nothere"],
23082 &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
23083 &[b"LREM", b"l1", b"1", b"aa"],
23084 &[b"LTRIM", b"l1", b"0", b"3"],
23085 &[b"LRANGE", b"l1", b"0", b"-1"],
23086 &[b"LPOP", b"l1"],
23087 &[b"RPOP", b"l1"],
23088 &[b"LPOP", b"l1", b"2"],
23089 &[b"LPOP", b"gone"],
23090 &[b"LPOP", b"gone", b"2"],
23091 &[b"EXISTS", b"l1"],
23092 &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
23095 &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
23096 &[b"RPOPLPUSH", b"src", b"dst"],
23097 &[b"LRANGE", b"dst", b"0", b"-1"],
23098 &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
23099 &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
23100 &[
23101 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
23102 ],
23103 &[
23104 b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
23105 ],
23106 &[b"LRANGE", b"dst", b"0", b"-1"],
23107 &[
23108 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
23109 ],
23110 &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
23111 &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
23112 &[b"LMPOP", b"1", b"gone", b"LEFT"],
23113 &[b"RPUSH", b"q", b"a", b"b", b"c"],
23116 &[b"BLPOP", b"gone", b"q", b"0"],
23117 &[b"BRPOP", b"q", b"0"],
23118 &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
23119 &[b"RPUSH", b"q", b"x", b"y", b"z"],
23120 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
23121 &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
23122 &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
23123 &[b"BLPOP", b"q", b"0"],
23124 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
23125 &[b"SET", b"plain", b"v"],
23127 &[b"LPUSH", b"plain", b"a"],
23128 &[b"LLEN", b"plain"],
23129 &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
23130 &[b"LRANGE", b"dst", b"0", b"-1"],
23131 &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
23132 &[b"LSET", b"gone", b"0", b"v"],
23133 &[b"LSET", b"dst", b"99", b"v"],
23134 &[b"LPOP", b"dst", b"-1"],
23135 &[b"LMPOP", b"0", b"dst", b"LEFT"],
23136 &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
23137 ];
23138
23139 let mut one = Fixture::new();
23140 let mut many = Fixture::striped(8);
23141 for parts in script {
23142 let a = one.run(parts);
23143 let b = many.run(parts);
23144 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23145 }
23146 }
23147
23148 #[test]
23150 fn a_list_move_across_stripes_takes_the_elements_with_it() {
23151 let mut f = Fixture::striped(8);
23152 let other = apart(&mut f, "src");
23153 let third = apart(&mut f, &other);
23154 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
23155
23156 f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
23157 assert_eq!(
23158 f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
23159 "$1\r\na\r\n"
23160 );
23161 assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
23162 assert_eq!(
23163 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
23164 "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
23165 "one went on each end of the destination"
23166 );
23167 assert_eq!(
23168 f.run(&[b"LRANGE", src, b"0", b"-1"]),
23169 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
23170 );
23171
23172 assert_eq!(
23174 f.run(&[
23175 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
23176 ]),
23177 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
23178 );
23179 assert_eq!(
23180 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
23181 "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
23182 );
23183 assert_eq!(
23184 f.run(&[b"EXISTS", src]),
23185 ":0\r\n",
23186 "and the source is gone with its last element"
23187 );
23188
23189 f.run(&[b"RPUSH", src, b"e", b"f"]);
23192 assert_eq!(
23193 f.run(&[
23194 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
23195 ]),
23196 "*-1\r\n"
23197 );
23198 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
23199 assert_eq!(
23200 f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
23201 "$-1\r\n"
23202 );
23203 assert_eq!(
23204 f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
23205 "*-1\r\n"
23206 );
23207
23208 f.run(&[b"SET", plain, b"v"]);
23212 assert_eq!(
23213 f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
23214 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23215 );
23216 assert_eq!(
23217 f.run(&[b"LLEN", src]),
23218 ":2\r\n",
23219 "and left the source alone"
23220 );
23221 assert_eq!(
23222 f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
23223 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23224 );
23225 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
23226 }
23227
23228 #[test]
23234 fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
23235 let mut f = Fixture::striped(8);
23236 let other = apart(&mut f, "q");
23237 let (q, far) = (b"q".as_slice(), other.as_bytes());
23238
23239 assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
23240 assert_eq!(f.server.parked(), 1);
23241 f.run(&[b"RPUSH", far, b"v"]);
23242 let mut out = Out::new(Proto::Resp2);
23243 assert!(f.server.serve_waiter(7, 0, &mut out));
23244 let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
23245 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
23246 assert_eq!(
23247 f.run(&[b"EXISTS", far]),
23248 ":0\r\n",
23249 "and it took the element with it"
23250 );
23251
23252 f.server.forget_waiters(7);
23255 assert_eq!(
23256 f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
23257 Flow::Block
23258 );
23259 f.run(&[b"RPUSH", q, b"w"]);
23260 let mut out = Out::new(Proto::Resp2);
23261 assert!(f.server.serve_waiter(7, 0, &mut out));
23262 assert_eq!(
23263 core::str::from_utf8(out.as_slice()).expect("ascii"),
23264 "$1\r\nw\r\n"
23265 );
23266 assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
23267 }
23268
23269 #[test]
23275 fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
23276 let script: &[&[&[u8]]] = &[
23277 &[b"XADD", b"s", b"1-1", b"a", b"1"],
23278 &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
23279 &[b"XADD", b"s", b"3-1", b"d", b"4"],
23280 &[b"XADD", b"s", b"1-1", b"e", b"5"],
23281 &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
23282 &[b"XLEN", b"s"],
23283 &[b"XLEN", b"gone"],
23284 &[b"XRANGE", b"s", b"-", b"+"],
23285 &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
23286 &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
23287 &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
23288 &[b"XREVRANGE", b"s", b"+", b"-"],
23289 &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
23290 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
23291 &[b"XREAD", b"STREAMS", b"s", b"$"],
23292 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
23294 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
23295 &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
23296 &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
23297 &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
23298 &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
23299 &[
23300 b"XREADGROUP",
23301 b"GROUP",
23302 b"g",
23303 b"c1",
23304 b"COUNT",
23305 b"1",
23306 b"STREAMS",
23307 b"s",
23308 b"0",
23309 ],
23310 &[
23311 b"XREADGROUP",
23312 b"GROUP",
23313 b"nope",
23314 b"c1",
23315 b"STREAMS",
23316 b"s",
23317 b">",
23318 ],
23319 &[b"XPENDING", b"s", b"g"],
23320 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
23321 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
23322 &[b"XPENDING", b"s", b"nope"],
23323 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
23324 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
23325 &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
23326 &[b"XACK", b"s", b"g", b"1-1"],
23327 &[b"XACK", b"s", b"g", b"1-1"],
23328 &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
23329 &[b"XPENDING", b"s", b"g"],
23330 &[b"XINFO", b"STREAM", b"s"],
23331 &[b"XINFO", b"GROUPS", b"s"],
23332 &[b"XINFO", b"CONSUMERS", b"s", b"g"],
23333 &[b"XINFO", b"STREAM", b"gone"],
23334 &[b"XDEL", b"s", b"3-1"],
23336 &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
23337 &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
23338 &[b"XADD", b"s", b"9-1", b"z", b"9"],
23339 &[b"XTRIM", b"s", b"MAXLEN", b"1"],
23340 &[b"XTRIM", b"s", b"MINID", b"9"],
23341 &[b"XSETID", b"s", b"99-1"],
23342 &[b"XSETID", b"s", b"1-1"],
23343 &[b"XLEN", b"s"],
23344 &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
23345 &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
23346 &[b"XGROUP", b"DESTROY", b"s", b"g"],
23347 &[b"XGROUP", b"DESTROY", b"s", b"g"],
23348 &[b"SET", b"plain", b"v"],
23350 &[b"XADD", b"plain", b"1-1", b"a", b"1"],
23351 &[b"XLEN", b"plain"],
23352 &[b"XREAD", b"STREAMS", b"plain", b"0"],
23353 &[b"XRANGE", b"s", b"bogus", b"+"],
23354 &[b"XADD", b"s", b"1-1", b"a"],
23355 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
23356 &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
23357 ];
23358
23359 let mut one = Fixture::new();
23360 let mut many = Fixture::striped(8);
23361 for parts in script {
23362 let a = one.run(parts);
23363 let b = many.run(parts);
23364 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23365 }
23366 }
23367
23368 #[test]
23374 fn a_stream_read_across_stripes_reads_every_key() {
23375 let mut f = Fixture::striped(8);
23376 let other = apart(&mut f, "s1");
23377 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
23378
23379 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
23380 f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
23381 let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
23382 assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
23383 assert!(got.contains("1-1"), "the first one is in there: {got}");
23384 assert!(got.contains("2-1"), "and so is the second: {got}");
23385
23386 f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
23389 let got = f.run(&[
23390 b"XREADGROUP",
23391 b"GROUP",
23392 b"g",
23393 b"c",
23394 b"STREAMS",
23395 s1,
23396 s2,
23397 b">",
23398 b">",
23399 ]);
23400 assert!(got.starts_with("-NOGROUP"), "{got}");
23401 assert_eq!(
23402 f.run(&[b"XPENDING", s1, b"g"]),
23403 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
23404 "and read nothing from the key that did have the group"
23405 );
23406
23407 f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
23408 let got = f.run(&[
23409 b"XREADGROUP",
23410 b"GROUP",
23411 b"g",
23412 b"c",
23413 b"STREAMS",
23414 s1,
23415 s2,
23416 b">",
23417 b">",
23418 ]);
23419 assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
23420 }
23421
23422 #[test]
23424 fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
23425 let mut f = Fixture::striped(8);
23426 let other = apart(&mut f, "s1");
23427 let (s1, far) = (b"s1".as_slice(), other.as_bytes());
23428 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
23429 f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
23430
23431 assert_eq!(
23432 f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
23433 .0,
23434 Flow::Block
23435 );
23436 f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
23437 let mut out = Out::new(Proto::Resp2);
23438 assert!(f.server.serve_waiter(7, 0, &mut out));
23439 let want = format!(
23440 "*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",
23441 other.len()
23442 );
23443 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
23444 }
23445
23446 #[test]
23448 fn the_json_group_answers_the_same_however_many_stripes_there_are() {
23449 let script: &[&[&[u8]]] = &[
23450 &[
23451 b"JSON.SET",
23452 b"d",
23453 b"$",
23454 br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
23455 ],
23456 &[b"JSON.SET", b"d", b"$.a", b"2"],
23457 &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
23458 &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
23459 &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
23460 &[b"JSON.GET", b"d"],
23461 &[b"JSON.GET", b"d", b"$.b"],
23462 &[b"JSON.GET", b"gone", b"$"],
23463 &[b"JSON.TYPE", b"d", b"$.b"],
23464 &[b"JSON.TYPE", b"d", b"$.s"],
23465 &[b"JSON.TOGGLE", b"d", b"$.t"],
23466 &[b"JSON.ARRLEN", b"d", b"$.b"],
23467 &[b"JSON.OBJLEN", b"d", b"$"],
23468 &[b"JSON.OBJKEYS", b"d", b"$"],
23469 &[b"JSON.STRLEN", b"d", b"$.s"],
23470 &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
23471 &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
23472 &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
23473 &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
23474 &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
23475 &[b"JSON.ARRPOP", b"d", b"$.b"],
23476 &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
23477 &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
23478 &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
23479 &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
23480 &[b"JSON.RESP", b"d", b"$.b"],
23481 &[b"JSON.DEBUG", b"MEMORY", b"d"],
23482 &[b"JSON.CLEAR", b"d", b"$.b"],
23483 &[b"JSON.DEL", b"d", b"$.m"],
23484 &[b"JSON.FORGET", b"d", b"$.nothere"],
23485 &[
23487 b"JSON.MSET",
23488 b"m1",
23489 b"$",
23490 b"1",
23491 b"m2",
23492 b"$",
23493 b"2",
23494 b"m3",
23495 b"$",
23496 b"3",
23497 ],
23498 &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
23499 &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
23500 &[b"JSON.GET", b"m1", b"$"],
23501 &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
23502 &[b"JSON.GET", b"m2", b"$"],
23503 &[b"SET", b"plain", b"v"],
23505 &[b"JSON.GET", b"plain", b"$"],
23506 &[b"JSON.SET", b"plain", b"$", b"1"],
23507 &[b"JSON.MGET", b"m1", b"plain", b"$"],
23508 &[b"JSON.SET", b"d", b"$.b", b"["],
23509 &[b"JSON.DEL", b"plain"],
23510 ];
23511
23512 let mut one = Fixture::new();
23513 let mut many = Fixture::striped(8);
23514 for parts in script {
23515 let a = one.run(parts);
23516 let b = many.run(parts);
23517 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23518 }
23519 }
23520
23521 #[test]
23528 fn a_json_multi_write_across_stripes_reaches_every_key() {
23529 let mut f = Fixture::striped(8);
23530 let second = apart(&mut f, "m1");
23531 let third = apart(&mut f, &second);
23532 let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
23533
23534 assert_eq!(
23535 f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
23536 "+OK\r\n"
23537 );
23538 assert_eq!(
23539 f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
23540 "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
23541 );
23542
23543 assert_eq!(
23546 f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
23547 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
23548 );
23549 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
23550
23551 assert_eq!(
23555 f.run(&[
23556 b"JSON.MSET",
23557 m1,
23558 b"$",
23559 b"9",
23560 m2,
23561 b"$.deep",
23562 b"9",
23563 m3,
23564 b"$",
23565 b"7"
23566 ]),
23567 "$-1\r\n"
23568 );
23569 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
23570 assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
23571 assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
23572 }
23573
23574 #[test]
23576 fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
23577 let script: &[&[&[u8]]] = &[
23578 &[
23579 b"GEOADD",
23580 b"g",
23581 b"13.361389",
23582 b"38.115556",
23583 b"palermo",
23584 b"15.087269",
23585 b"37.502669",
23586 b"catania",
23587 ],
23588 &[
23589 b"GEOADD",
23590 b"g",
23591 b"NX",
23592 b"13.361389",
23593 b"38.115556",
23594 b"palermo",
23595 ],
23596 &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
23597 &[b"GEOPOS", b"g", b"palermo", b"nothere"],
23598 &[b"GEOHASH", b"g", b"palermo", b"catania"],
23599 &[b"GEODIST", b"g", b"palermo", b"catania"],
23600 &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
23601 &[b"GEODIST", b"g", b"palermo", b"nothere"],
23602 &[
23603 b"GEOSEARCH",
23604 b"g",
23605 b"FROMLONLAT",
23606 b"15",
23607 b"37",
23608 b"BYRADIUS",
23609 b"200",
23610 b"KM",
23611 b"ASC",
23612 b"WITHCOORD",
23613 b"WITHDIST",
23614 b"WITHHASH",
23615 ],
23616 &[
23617 b"GEOSEARCH",
23618 b"g",
23619 b"FROMMEMBER",
23620 b"palermo",
23621 b"BYBOX",
23622 b"400",
23623 b"400",
23624 b"KM",
23625 b"DESC",
23626 ],
23627 &[
23628 b"GEORADIUS",
23629 b"g",
23630 b"15",
23631 b"37",
23632 b"200",
23633 b"KM",
23634 b"COUNT",
23635 b"1",
23636 ],
23637 &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
23638 &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
23639 &[
23640 b"GEOSEARCHSTORE",
23641 b"dst",
23642 b"g",
23643 b"FROMLONLAT",
23644 b"15",
23645 b"37",
23646 b"BYRADIUS",
23647 b"200",
23648 b"KM",
23649 ],
23650 &[b"ZRANGE", b"dst", b"0", b"-1"],
23651 &[
23652 b"GEOSEARCHSTORE",
23653 b"dst",
23654 b"g",
23655 b"FROMLONLAT",
23656 b"15",
23657 b"37",
23658 b"BYRADIUS",
23659 b"1",
23660 b"M",
23661 b"STOREDIST",
23662 ],
23663 &[b"EXISTS", b"dst"],
23664 &[
23665 b"GEORADIUS",
23666 b"g",
23667 b"15",
23668 b"37",
23669 b"200",
23670 b"KM",
23671 b"STORE",
23672 b"dst",
23673 ],
23674 &[b"ZCARD", b"dst"],
23675 &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
23677 &[b"SET", b"plain", b"v"],
23678 &[b"GEOPOS", b"plain", b"a"],
23679 &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
23680 &[
23681 b"GEOSEARCHSTORE",
23682 b"dst",
23683 b"g",
23684 b"FROMLONLAT",
23685 b"15",
23686 b"37",
23687 b"BYRADIUS",
23688 b"200",
23689 b"KM",
23690 b"WITHCOORD",
23691 ],
23692 ];
23693
23694 let mut one = Fixture::new();
23695 let mut many = Fixture::striped(8);
23696 for parts in script {
23697 let a = one.run(parts);
23698 let b = many.run(parts);
23699 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23700 }
23701 }
23702
23703 #[test]
23705 fn a_geo_search_store_across_stripes_writes_what_it_found() {
23706 let mut f = Fixture::striped(8);
23707 let other = apart(&mut f, "g");
23708 let third = apart(&mut f, &other);
23709 let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
23710
23711 f.run(&[
23712 b"GEOADD",
23713 g,
23714 b"13.361389",
23715 b"38.115556",
23716 b"palermo",
23717 b"15.087269",
23718 b"37.502669",
23719 b"catania",
23720 ]);
23721 assert_eq!(
23722 f.run(&[
23723 b"GEOSEARCHSTORE",
23724 dst,
23725 g,
23726 b"FROMLONLAT",
23727 b"15",
23728 b"37",
23729 b"BYRADIUS",
23730 b"200",
23731 b"KM",
23732 b"ASC",
23733 ]),
23734 ":2\r\n"
23735 );
23736 assert_eq!(
23737 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
23738 "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
23739 "the geohash is the score, so the order is not the search order"
23740 );
23741 assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
23742
23743 assert_eq!(
23746 f.run(&[
23747 b"GEOSEARCHSTORE",
23748 dst,
23749 g,
23750 b"FROMMEMBER",
23751 b"palermo",
23752 b"BYRADIUS",
23753 b"200",
23754 b"KM",
23755 b"STOREDIST",
23756 ]),
23757 ":2\r\n"
23758 );
23759 assert_eq!(
23760 f.run(&[b"ZSCORE", dst, b"palermo"]),
23761 "$1\r\n0\r\n",
23762 "the centre is nought away from itself"
23763 );
23764
23765 assert_eq!(
23769 f.run(&[
23770 b"GEOSEARCHSTORE",
23771 dst,
23772 g,
23773 b"FROMLONLAT",
23774 b"0",
23775 b"0",
23776 b"BYRADIUS",
23777 b"1",
23778 b"M",
23779 ]),
23780 ":0\r\n"
23781 );
23782 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
23783 f.run(&[
23784 b"GEOSEARCHSTORE",
23785 dst,
23786 g,
23787 b"FROMLONLAT",
23788 b"15",
23789 b"37",
23790 b"BYRADIUS",
23791 b"200",
23792 b"KM",
23793 ]);
23794 f.run(&[b"SET", plain, b"v"]);
23795 assert_eq!(
23796 f.run(&[
23797 b"GEOSEARCHSTORE",
23798 dst,
23799 plain,
23800 b"FROMLONLAT",
23801 b"15",
23802 b"37",
23803 b"BYRADIUS",
23804 b"200",
23805 b"KM",
23806 ]),
23807 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23808 );
23809 assert_eq!(
23810 f.run(&[b"ZCARD", dst]),
23811 ":2\r\n",
23812 "and left the destination"
23813 );
23814 }
23815
23816 #[test]
23822 fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
23823 let script: &[&[&[u8]]] = &[
23824 &[
23825 b"TS.CREATE",
23826 b"ts:a",
23827 b"LABELS",
23828 b"sensor",
23829 b"a",
23830 b"room",
23831 b"1",
23832 ],
23833 &[b"TS.CREATE", b"ts:a"],
23834 &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
23835 &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
23836 &[
23837 b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
23838 ],
23839 &[
23840 b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
23841 ],
23842 &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
23843 &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
23844 &[b"TS.GET", b"ts:a"],
23845 &[b"TS.GET", b"gone"],
23846 &[b"TS.RANGE", b"ts:a", b"-", b"+"],
23847 &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
23848 &[
23849 b"TS.RANGE",
23850 b"ts:a",
23851 b"-",
23852 b"+",
23853 b"AGGREGATION",
23854 b"avg",
23855 b"2000",
23856 ],
23857 &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
23858 &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23859 &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23860 &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
23861 &[b"TS.READ", b"ts:a", b"0"],
23862 &[b"TS.READ", b"ts:a", b"+"],
23863 &[b"TS.QUERYINDEX", b"sensor=a"],
23865 &[b"TS.QUERYINDEX", b"room=1"],
23866 &[b"TS.QUERYINDEX", b"room=9"],
23867 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
23868 &[
23869 b"TS.QUERYLABELS",
23870 b"VALUES",
23871 b"sensor",
23872 b"FILTER",
23873 b"room=1",
23874 ],
23875 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
23876 &[
23877 b"TS.MGET",
23878 b"SELECTED_LABELS",
23879 b"sensor",
23880 b"FILTER",
23881 b"sensor=a",
23882 ],
23883 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
23884 &[
23885 b"TS.MREVRANGE",
23886 b"-",
23887 b"+",
23888 b"WITHLABELS",
23889 b"FILTER",
23890 b"sensor=a",
23891 ],
23892 &[
23893 b"TS.MRANGE",
23894 b"-",
23895 b"+",
23896 b"FILTER",
23897 b"room=1",
23898 b"GROUPBY",
23899 b"room",
23900 b"REDUCE",
23901 b"max",
23902 ],
23903 &[b"TS.INFO", b"ts:a"],
23904 &[
23906 b"TS.CREATERULE",
23907 b"ts:a",
23908 b"ts:down",
23909 b"AGGREGATION",
23910 b"avg",
23911 b"1000",
23912 ],
23913 &[b"TS.CREATE", b"ts:down"],
23914 &[
23915 b"TS.CREATERULE",
23916 b"ts:a",
23917 b"ts:down",
23918 b"AGGREGATION",
23919 b"avg",
23920 b"1000",
23921 ],
23922 &[b"TS.ADD", b"ts:a", b"5000", b"4"],
23923 &[b"TS.ADD", b"ts:a", b"6000", b"5"],
23924 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23925 &[b"TS.GET", b"ts:down", b"LATEST"],
23926 &[b"TS.INFO", b"ts:down"],
23927 &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
23928 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23929 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23930 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23931 &[b"TS.DEL", b"ts:a", b"0", b"1000"],
23932 &[b"SET", b"plain", b"v"],
23934 &[b"TS.ADD", b"plain", b"1", b"1"],
23935 &[b"TS.GET", b"plain"],
23936 &[b"TS.READ", b"plain", b"0"],
23937 &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
23938 &[b"TS.RANGE", b"gone", b"-", b"+"],
23939 &[b"TS.INFO", b"gone"],
23940 ];
23941
23942 let mut one = Fixture::new();
23943 let mut many = Fixture::striped(8);
23944 for parts in script {
23945 let a = one.run(parts);
23946 let b = many.run(parts);
23947 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23948 }
23949 }
23950
23951 #[test]
23961 fn a_compaction_rule_across_stripes_reaches_both_ends() {
23962 let mut many = Fixture::striped(8);
23963 let other = apart(&mut many, "src");
23964 let (src, dst) = (b"src".as_slice(), other.as_bytes());
23965 let mut one = Fixture::new();
23966 let mut both = |parts: &[&[u8]]| {
23967 let a = one.run(parts);
23968 let b = many.run(parts);
23969 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23970 a
23971 };
23972
23973 both(&[b"TS.CREATE", src]);
23974 both(&[b"TS.CREATE", dst]);
23975 assert_eq!(
23976 both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
23977 "+OK\r\n"
23978 );
23979 both(&[b"TS.ADD", src, b"1000", b"1"]);
23980 both(&[b"TS.ADD", src, b"1500", b"3"]);
23981 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23984 let open = both(&[b"TS.GET", dst, b"LATEST"]);
23985 assert!(open.contains(":1000"), "the open bucket is folded: {open}");
23986
23987 both(&[b"TS.ADD", src, b"2000", b"5"]);
23990 let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
23991 assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
23992 assert!(got.contains(":1000"), "{got}");
23993
23994 both(&[b"TS.DEL", src, b"1000", b"1999"]);
23996 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23997
23998 assert!(
24001 both(&[b"TS.INFO", dst]).contains("src"),
24002 "the source is named"
24003 );
24004 assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
24005 assert_eq!(
24006 both(&[b"TS.DELETERULE", src, dst]),
24007 "-ERR TSDB: compaction rule does not exist\r\n"
24008 );
24009 }
24010
24011 #[test]
24013 fn a_label_query_across_stripes_finds_every_series() {
24014 let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
24015 let mut many = Fixture::striped(8);
24016 let mut homes: Vec<usize> = names
24017 .iter()
24018 .map(|name| many.server.striped(0).stripe_of(name))
24019 .collect();
24020 homes.sort_unstable();
24021 homes.dedup();
24022 assert!(homes.len() > 1, "the six keys are not all on one stripe");
24023
24024 let mut one = Fixture::new();
24025 let mut both = |parts: &[&[u8]]| {
24026 let a = one.run(parts);
24027 let b = many.run(parts);
24028 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24029 a
24030 };
24031 for name in &names {
24032 both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
24033 both(&[b"TS.ADD", name, b"1000", b"1"]);
24034 }
24035
24036 let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
24037 assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
24038 assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
24039 assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
24040 assert_eq!(
24041 both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
24042 "*1\r\n$4\r\nroom\r\n"
24043 );
24044 }
24045
24046 #[test]
24053 fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
24054 let script: &[&[&[u8]]] = &[
24055 &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
24056 &[b"HMSET", b"h", b"c", b"3"],
24057 &[b"HSETNX", b"h", b"a", b"9"],
24058 &[b"HSETNX", b"h", b"d", b"4"],
24059 &[b"HGET", b"h", b"a"],
24060 &[b"HGET", b"h", b"nope"],
24061 &[b"HMGET", b"h", b"a", b"nope"],
24062 &[b"HLEN", b"h"],
24063 &[b"HEXISTS", b"h", b"a"],
24064 &[b"HSTRLEN", b"h", b"a"],
24065 &[b"HGETALL", b"h"],
24066 &[b"HKEYS", b"h"],
24067 &[b"HVALS", b"h"],
24068 &[b"HINCRBY", b"h", b"a", b"5"],
24069 &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
24070 &[b"HSCAN", b"h", b"0"],
24071 &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
24072 &[b"HSCAN", b"h", b"0", b"NOVALUES"],
24073 &[b"HDEL", b"h", b"d"],
24074 &[b"HSET", b"one", b"f", b"v"],
24075 &[b"HRANDFIELD", b"one"],
24076 &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
24077 &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
24079 &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
24080 &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
24081 &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
24082 &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
24083 &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
24084 &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
24085 &[b"HGET", b"h", b"b"],
24086 &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
24088 &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
24089 &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
24090 &[b"HGET", b"h", b"e"],
24091 &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
24093 &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
24094 &[b"HGETALL", b"imp"],
24095 &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
24096 &[b"HIMPORT", b"DISCARD", b"fs"],
24097 &[b"SET", b"plain", b"v"],
24099 &[b"HSET", b"plain", b"a", b"1"],
24100 &[b"HGETALL", b"plain"],
24101 &[b"HGET", b"gone", b"a"],
24102 &[b"HINCRBY", b"h", b"a", b"nan"],
24103 ];
24104
24105 let mut one = Fixture::new();
24106 let mut many = Fixture::striped(8);
24107 one.server.set_clock_ms(1_700_000_000_000);
24111 many.server.set_clock_ms(1_700_000_000_000);
24112 for parts in script {
24113 let a = one.run(parts);
24114 let b = many.run(parts);
24115 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24116 }
24117 }
24118
24119 #[test]
24121 fn the_array_group_answers_the_same_however_many_stripes_there_are() {
24122 let script: &[&[&[u8]]] = &[
24123 &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
24124 &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
24125 &[b"ARGET", b"a", b"1"],
24126 &[b"ARGET", b"a", b"99"],
24127 &[b"ARMGET", b"a", b"0", b"5", b"99"],
24128 &[b"ARGETRANGE", b"a", b"0", b"7"],
24129 &[b"ARLEN", b"a"],
24130 &[b"ARCOUNT", b"a"],
24131 &[b"ARINSERT", b"a", b"m", b"n"],
24132 &[b"ARSCAN", b"a", b"0", b"20"],
24133 &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
24134 &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
24135 &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
24136 &[b"ARLASTITEMS", b"a", b"2"],
24137 &[b"ARLASTITEMS", b"a", b"2", b"REV"],
24138 &[b"ARNEXT", b"a"],
24139 &[b"ARSEEK", b"a", b"3"],
24140 &[b"AROP", b"a", b"0", b"20", b"USED"],
24141 &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
24142 &[b"ARINFO", b"a"],
24143 &[b"ARINFO", b"a", b"FULL"],
24144 &[b"ARDEL", b"a", b"0"],
24145 &[b"ARDELRANGE", b"a", b"1", b"2"],
24146 &[b"ARCOUNT", b"a"],
24147 &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
24148 &[b"ARGETRANGE", b"r", b"0", b"9"],
24149 &[b"SET", b"plain", b"v"],
24151 &[b"ARGET", b"plain", b"0"],
24152 &[b"ARSET", b"plain", b"0", b"v"],
24153 &[b"ARGET", b"gone", b"0"],
24154 &[b"ARSET", b"a", b"bad", b"v"],
24155 ];
24156
24157 let mut one = Fixture::new();
24158 let mut many = Fixture::striped(8);
24159 for parts in script {
24160 let a = one.run(parts);
24161 let b = many.run(parts);
24162 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24163 }
24164 }
24165
24166 #[test]
24172 fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
24173 let script: &[&[&[u8]]] = &[
24174 &[b"G.NADD", b"g", b"n1", b"name", b"one"],
24175 &[b"G.NADD", b"g", b"n2", b"name", b"two"],
24176 &[b"G.NADD", b"g", b"n3"],
24177 &[b"G.NGET", b"g", b"n1"],
24178 &[b"G.NGET", b"g", b"gone"],
24179 &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
24180 &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
24181 &[b"G.OUT", b"g", b"n1", b"knows"],
24182 &[b"G.IN", b"g", b"n2", b"knows"],
24183 &[b"G.DEG", b"g", b"n1", b"knows"],
24184 &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
24185 &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
24186 &[b"G.PATH", b"g", b"n1", b"n3"],
24187 &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
24188 &[b"G.NDEL", b"g", b"n3"],
24189 &[b"G.NGET", b"g", b"n3"],
24190 &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
24192 &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
24193 &[b"VCARD", b"v"],
24194 &[b"VDIM", b"v"],
24195 &[b"VEMB", b"v", b"e1"],
24196 &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
24197 &[b"VSIM", b"v", b"ELE", b"e1"],
24198 &[b"VISMEMBER", b"v", b"e1"],
24199 &[b"VISMEMBER", b"v", b"gone"],
24200 &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
24201 &[b"VGETATTR", b"v", b"e1"],
24202 &[b"VRANGE", b"v", b"-", b"+"],
24203 &[b"VLINKS", b"v", b"e1"],
24204 &[b"VINFO", b"v"],
24205 &[b"VREM", b"v", b"e2"],
24206 &[b"VCARD", b"v"],
24207 &[b"SET", b"plain", b"v"],
24209 &[b"G.NGET", b"plain", b"n1"],
24210 &[b"VCARD", b"plain"],
24211 &[b"G.NADD", b"gone2", b"n"],
24212 &[b"VEMB", b"gone3", b"e"],
24213 ];
24214
24215 let mut one = Fixture::new();
24216 let mut many = Fixture::striped(8);
24217 for parts in script {
24218 let a = one.run(parts);
24219 let b = many.run(parts);
24220 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24221 }
24222 }
24223
24224 #[test]
24227 fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
24228 let script: &[&[&[u8]]] = &[
24229 &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
24231 &[b"BF.ADD", b"bf", b"a"],
24232 &[b"BF.ADD", b"bf", b"a"],
24233 &[b"BF.MADD", b"bf", b"b", b"c"],
24234 &[b"BF.EXISTS", b"bf", b"a"],
24235 &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
24236 &[b"BF.CARD", b"bf"],
24237 &[b"BF.INFO", b"bf"],
24238 &[b"BF.INFO", b"bf", b"CAPACITY"],
24239 &[b"BF.DEBUG", b"bf"],
24240 &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
24241 &[b"BF.EXISTS", b"made", b"x"],
24242 &[b"BF.SCANDUMP", b"bf", b"0"],
24243 &[b"CF.RESERVE", b"cf", b"100"],
24245 &[b"CF.ADD", b"cf", b"a"],
24246 &[b"CF.ADDNX", b"cf", b"a"],
24247 &[b"CF.COUNT", b"cf", b"a"],
24248 &[b"CF.EXISTS", b"cf", b"a"],
24249 &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
24250 &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
24251 &[b"CF.DEL", b"cf", b"a"],
24252 &[b"CF.COMPACT", b"cf"],
24253 &[b"CF.INFO", b"cf"],
24254 &[b"CF.DEBUG", b"cf"],
24255 &[b"CF.SCANDUMP", b"cf", b"0"],
24256 &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
24258 &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
24259 &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
24260 &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
24261 &[b"CMS.INFO", b"cms"],
24262 &[b"TOPK.RESERVE", b"tk", b"3"],
24264 &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
24265 &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
24266 &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
24267 &[b"TOPK.COUNT", b"tk", b"a", b"c"],
24268 &[b"TOPK.LIST", b"tk"],
24269 &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
24270 &[b"TOPK.INFO", b"tk"],
24271 &[b"TDIGEST.CREATE", b"td"],
24273 &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
24274 &[b"TDIGEST.MIN", b"td"],
24275 &[b"TDIGEST.MAX", b"td"],
24276 &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
24277 &[b"TDIGEST.CDF", b"td", b"3"],
24278 &[b"TDIGEST.RANK", b"td", b"3"],
24279 &[b"TDIGEST.REVRANK", b"td", b"3"],
24280 &[b"TDIGEST.BYRANK", b"td", b"0"],
24281 &[b"TDIGEST.BYREVRANK", b"td", b"0"],
24282 &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
24283 &[b"TDIGEST.INFO", b"td"],
24284 &[b"TDIGEST.RESET", b"td"],
24285 &[b"TDIGEST.MIN", b"td"],
24286 &[b"SET", b"plain", b"v"],
24288 &[b"BF.ADD", b"plain", b"a"],
24289 &[b"CF.ADD", b"plain", b"a"],
24290 &[b"CMS.QUERY", b"plain", b"a"],
24291 &[b"TOPK.ADD", b"plain", b"a"],
24292 &[b"TDIGEST.ADD", b"plain", b"1"],
24293 &[b"CMS.INFO", b"gone"],
24294 &[b"TOPK.INFO", b"gone"],
24295 &[b"TDIGEST.INFO", b"gone"],
24296 ];
24297
24298 let mut one = Fixture::new();
24299 let mut many = Fixture::striped(8);
24300 for parts in script {
24301 let a = one.run(parts);
24302 let b = many.run(parts);
24303 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24304 }
24305 }
24306
24307 #[test]
24314 fn a_sketch_merge_across_stripes_reads_every_source() {
24315 let mut many = Fixture::striped(8);
24316 let other = apart(&mut many, "s1");
24317 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
24318 let mut one = Fixture::new();
24319 let mut both = |parts: &[&[u8]]| {
24320 let a = one.run(parts);
24321 let b = many.run(parts);
24322 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24323 a
24324 };
24325
24326 for key in [b"cd".as_slice(), s1, s2] {
24330 both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
24331 }
24332 both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
24333 both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
24334 assert_eq!(
24335 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
24336 "+OK\r\n",
24337 "the merge took both sources"
24338 );
24339 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
24340 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
24342 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
24343 both(&[b"SET", b"plain", b"v"]);
24345 assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
24346 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
24347
24348 both(&[b"FLUSHALL"]);
24351 both(&[b"TDIGEST.CREATE", b"td"]);
24352 both(&[b"TDIGEST.CREATE", s1]);
24353 both(&[b"TDIGEST.CREATE", s2]);
24354 both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
24355 both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
24356 assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
24357 assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
24358 assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
24359 }
24360
24361 #[test]
24368 fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
24369 let script: &[&[&[u8]]] = &[
24370 &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
24371 &[b"SORT", b"l"],
24372 &[b"SORT", b"l", b"DESC"],
24373 &[b"SORT", b"l", b"ALPHA"],
24374 &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
24375 &[b"SORT_RO", b"l"],
24376 &[
24379 b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
24380 ],
24381 &[b"SORT", b"l", b"BY", b"w_*"],
24382 &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
24383 &[b"DEL", b"w_2"],
24384 &[b"SORT", b"l", b"BY", b"w_*"],
24385 &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
24388 &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
24389 &[b"HSET", b"h_1", b"f", b"9"],
24391 &[b"HSET", b"h_2", b"f", b"8"],
24392 &[b"HSET", b"h_3", b"f", b"7"],
24393 &[b"HSET", b"h_10", b"f", b"6"],
24394 &[b"SORT", b"l", b"BY", b"h_*->f"],
24395 &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
24396 &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
24398 &[b"LRANGE", b"out", b"0", b"-1"],
24399 &[b"SORT", b"l", b"STORE", b"l"],
24400 &[b"LRANGE", b"l", b"0", b"-1"],
24401 &[b"SORT", b"missing", b"STORE", b"out"],
24404 &[b"EXISTS", b"out"],
24405 &[b"SADD", b"s", b"c", b"a", b"b"],
24408 &[b"SORT", b"s", b"ALPHA"],
24409 &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
24410 &[b"LRANGE", b"out", b"0", b"-1"],
24411 &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
24412 &[b"SORT", b"z", b"BY", b"nosort"],
24413 &[b"SORT", b"z", b"ALPHA", b"DESC"],
24414 &[b"SET", b"str", b"v"],
24417 &[b"SORT", b"str"],
24418 &[b"RPUSH", b"words", b"one", b"two"],
24419 &[b"SORT", b"words"],
24420 &[b"SORT_RO", b"l", b"STORE", b"out"],
24421 ];
24422
24423 let mut one = Fixture::new();
24424 let mut many = Fixture::striped(8);
24425 for parts in script {
24426 let a = one.run(parts);
24427 let b = many.run(parts);
24428 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24429 }
24430 }
24431
24432 #[test]
24439 fn a_sort_across_stripes_reads_every_pattern_key() {
24440 let mut f = Fixture::striped(8);
24441 let out = apart(&mut f, "l");
24442 let (list, dest) = (b"l".as_slice(), out.as_bytes());
24443
24444 f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
24445 f.run(&[
24446 b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
24447 ]);
24448 f.run(&[
24449 b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
24450 ]);
24451
24452 let db = f.server.striped(0);
24455 let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
24456 .iter()
24457 .map(|k| db.stripe_of(k.as_slice()))
24458 .collect();
24459 assert!(
24460 weights.iter().any(|s| *s != weights[0]),
24461 "the four weight keys all landed on one stripe, so this proves nothing"
24462 );
24463
24464 assert_eq!(
24465 f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
24466 "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
24467 "the order came off the weights and the answer off the data keys"
24468 );
24469 assert_eq!(
24470 f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
24471 ":4\r\n"
24472 );
24473 assert_eq!(
24474 f.run(&[b"LRANGE", dest, b"0", b"-1"]),
24475 "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
24476 "the destination is on a stripe of its own and got the whole answer"
24477 );
24478 }
24479
24480 #[test]
24488 fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
24489 let mut f = Fixture::striped(8);
24490 let other = apart(&mut f, "h");
24491 let (first, second) = (b"h".as_slice(), other.as_bytes());
24492
24493 assert_eq!(
24494 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
24495 "+OK\r\n"
24496 );
24497 assert_eq!(
24498 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
24499 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
24500 "the read comes off one stripe and has to answer for all of them"
24501 );
24502 for key in [first, second] {
24503 f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
24504 assert_eq!(
24505 f.run(&[b"OBJECT", b"ENCODING", key]),
24506 "$8\r\nlistpack\r\n",
24507 "two fields is still under the ladder"
24508 );
24509 f.run(&[b"HSET", key, b"c", b"3"]);
24510 assert_eq!(
24511 f.run(&[b"OBJECT", b"ENCODING", key]),
24512 "$9\r\nhashtable\r\n",
24513 "three fields is over it, on whichever stripe the key is on"
24514 );
24515 }
24516
24517 assert_eq!(
24520 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
24521 "+OK\r\n"
24522 );
24523 let db = f.server.striped(0);
24524 assert!(
24525 (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
24526 "a stripe kept the old policy"
24527 );
24528 }
24529
24530 fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
24536 let search = f.server.search.lock();
24537 let index = search.named(name).expect("the index is there");
24538 (index.held.docs.len(), index.held.docs.last())
24539 }
24540
24541 #[test]
24544 fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
24545 let mut f = Fixture::new();
24546 f.run(&[
24547 b"FT.CREATE",
24548 b"ix",
24549 b"PREFIX",
24550 b"1",
24551 b"p:",
24552 b"SCHEMA",
24553 b"t",
24554 b"TEXT",
24555 ]);
24556 f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
24557 assert_eq!(held(&f, b"ix"), (1, 1));
24558 f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
24559 assert_eq!(held(&f, b"ix"), (1, 1));
24560
24561 f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
24564 f.run(&[b"HDEL", b"p:1", b"u"]);
24565 assert_eq!(held(&f, b"ix"), (1, 3));
24566 let search = f.server.search.lock();
24567 let index = search.named(b"ix").expect("there");
24568 assert_eq!(index.held.docs.id(b"p:1"), Some(3));
24569 }
24570
24571 #[test]
24574 fn a_fresh_index_reads_the_keys_that_were_already_there() {
24575 let mut f = Fixture::new();
24576 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24577 f.run(&[b"SET", b"p:str", b"not a hash"]);
24578 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24579 f.run(&[
24580 b"FT.CREATE",
24581 b"ix",
24582 b"PREFIX",
24583 b"1",
24584 b"p:",
24585 b"SCHEMA",
24586 b"t",
24587 b"TEXT",
24588 ]);
24589
24590 assert_eq!(held(&f, b"ix"), (1, 1));
24591 let search = f.server.search.lock();
24592 let index = search.named(b"ix").expect("there");
24593 assert_eq!(index.trouble.whole().failures(), 0);
24594 }
24595
24596 #[test]
24599 fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
24600 let mut f = Fixture::new();
24601 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24602 f.run(&[
24603 b"FT.CREATE",
24604 b"ix",
24605 b"PREFIX",
24606 b"1",
24607 b"p:",
24608 b"SKIPINITIALSCAN",
24609 b"SCHEMA",
24610 b"t",
24611 b"TEXT",
24612 ]);
24613 assert_eq!(held(&f, b"ix"), (0, 0));
24614 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24615 assert_eq!(held(&f, b"ix"), (1, 1));
24616 }
24617
24618 #[test]
24626 fn only_a_real_change_gives_the_document_a_new_number() {
24627 let mut f = Fixture::new();
24628 f.run(&[
24629 b"FT.CREATE",
24630 b"ix",
24631 b"PREFIX",
24632 b"1",
24633 b"p:",
24634 b"SCHEMA",
24635 b"t",
24636 b"TEXT",
24637 ]);
24638 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24639 assert_eq!(held(&f, b"ix"), (1, 1));
24640
24641 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24642 assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
24643
24644 for quiet in [
24645 vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
24646 vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
24647 vec![b"HGET".as_slice(), b"p:1", b"t"],
24648 vec![b"HGETALL".as_slice(), b"p:1"],
24649 vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
24650 vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
24651 vec![
24652 b"HGETEX".as_slice(),
24653 b"p:1",
24654 b"EX",
24655 b"100",
24656 b"FIELDS",
24657 b"1",
24658 b"t",
24659 ],
24660 vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
24661 ] {
24662 f.run(&quiet);
24663 assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
24664 }
24665
24666 f.run(&[b"HSET", b"p:2", b"n", b"1"]);
24668 f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
24669 assert_eq!(held(&f, b"ix"), (2, 4));
24670 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
24676 assert_eq!(held(&f, b"ix"), (1, 5));
24677 }
24678
24679 #[test]
24685 fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
24686 fn refused(f: &Fixture, name: &[u8]) -> u64 {
24688 let search = f.server.search.lock();
24689 let index = search.named(name).expect("the index is there");
24690 index.trouble.whole().failures()
24691 }
24692
24693 let mut f = Fixture::new();
24694 f.run(&[
24695 b"FT.CREATE",
24696 b"ix",
24697 b"PREFIX",
24698 b"1",
24699 b"p:",
24700 b"SCHEMA",
24701 b"t",
24702 b"TEXT",
24703 ]);
24704 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24705 assert_eq!(held(&f, b"ix"), (1, 1));
24706 f.run(&[b"HDEL", b"p:1", b"t"]);
24707 assert_eq!(
24708 held(&f, b"ix"),
24709 (0, 1),
24710 "HDEL of the last field spends none"
24711 );
24712 assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
24713
24714 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24715 assert_eq!(held(&f, b"ix"), (1, 2));
24716 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
24717 assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
24718 assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
24719
24720 f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
24721 assert_eq!(held(&f, b"ix"), (1, 4));
24722 f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
24723 assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
24724
24725 f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
24728 assert_eq!(held(&f, b"ix"), (1, 6));
24729 f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
24730 assert_eq!(held(&f, b"ix"), (0, 7));
24731 assert_eq!(refused(&f, b"ix"), 1);
24732 }
24733
24734 #[test]
24738 fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
24739 let mut f = Fixture::new();
24740 f.run(&[
24741 b"FT.CREATE",
24742 b"ix",
24743 b"PREFIX",
24744 b"1",
24745 b"p:",
24746 b"SCHEMA",
24747 b"t",
24748 b"TEXT",
24749 b"u",
24750 b"TEXT",
24751 ]);
24752 f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
24753 assert_eq!(held(&f, b"ix"), (1, 1));
24754 f.run(&[
24755 b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24756 ]);
24757 assert_eq!(
24758 held(&f, b"ix"),
24759 (1, 3),
24760 "the key lived and the field did not"
24761 );
24762
24763 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24765 assert_eq!(held(&f, b"ix"), (2, 4));
24766 f.run(&[
24767 b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24768 ]);
24769 assert_eq!(held(&f, b"ix"), (1, 6));
24770 }
24771
24772 fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
24775 let search = f.server.search.lock();
24776 let index = search.named(name).expect("the index is there");
24777 index.held.docs.id(key)
24778 }
24779
24780 fn indexed() -> Fixture {
24783 let mut f = Fixture::new();
24784 f.run(&[
24785 b"FT.CREATE",
24786 b"ix",
24787 b"PREFIX",
24788 b"1",
24789 b"p:",
24790 b"SCHEMA",
24791 b"t",
24792 b"TEXT",
24793 ]);
24794 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24795 f
24796 }
24797
24798 #[test]
24801 fn a_key_a_keyspace_command_takes_away_loses_its_document() {
24802 for take in [
24803 vec![b"DEL".as_slice(), b"p:1"],
24804 vec![b"UNLINK".as_slice(), b"p:1"],
24805 vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
24806 vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
24807 ] {
24808 let mut f = indexed();
24809 assert_eq!(held(&f, b"ix"), (1, 1));
24810 f.run(&take);
24811 assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
24812 let search = f.server.search.lock();
24813 let index = search.named(b"ix").expect("the index is there");
24814 assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
24815 }
24816
24817 let mut f = indexed();
24819 f.run(&[b"EXPIRE", b"p:1", b"1000"]);
24820 assert_eq!(held(&f, b"ix"), (1, 1));
24821 f.run(&[b"PERSIST", b"p:1"]);
24822 assert_eq!(held(&f, b"ix"), (1, 1));
24823 }
24824
24825 #[test]
24829 fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
24830 let mut f = indexed();
24831 f.run(&[b"RENAME", b"p:1", b"p:2"]);
24832 assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
24833 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24834 assert_eq!(number(&f, b"ix", b"p:1"), None);
24835
24836 f.run(&[b"RENAME", b"p:2", b"q:1"]);
24837 assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
24838
24839 f.run(&[b"RENAME", b"q:1", b"p:3"]);
24840 assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
24841 assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
24842
24843 f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
24846 assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
24847 assert_eq!(held(&f, b"ix"), (2, 3));
24848 f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
24849 assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
24850 }
24851
24852 #[test]
24855 fn a_rename_over_a_document_leaves_one_of_them() {
24856 let mut f = indexed();
24857 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24858 assert_eq!(held(&f, b"ix"), (2, 2));
24859 f.run(&[b"RENAME", b"p:1", b"p:2"]);
24860 assert_eq!(held(&f, b"ix"), (1, 2));
24861 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24862 }
24863
24864 #[test]
24868 fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
24869 let mut f = indexed();
24870 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24871 f.run(&[b"COPY", b"q:1", b"p:2"]);
24872 assert_eq!(held(&f, b"ix"), (2, 2));
24873 assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
24874
24875 f.run(&[b"COPY", b"p:1", b"q:2"]);
24877 assert_eq!(held(&f, b"ix"), (2, 2));
24878
24879 f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
24881 assert_eq!(held(&f, b"ix"), (2, 3));
24882 assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
24883
24884 f.run(&[b"SET", b"s:1", b"plain"]);
24887 f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
24888 assert_eq!(held(&f, b"ix"), (1, 3));
24889 let dump = f.run(&[b"DUMP", b"q:1"]);
24890 assert!(dump.starts_with('$'), "{dump}");
24891 }
24892
24893 #[test]
24898 fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
24899 let mut f = indexed();
24900 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24901 assert_eq!(held(&f, b"ix"), (2, 2));
24902 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24905 assert_eq!(held(&f, b"ix"), (2, 3));
24906 f.run(&[b"DEL", b"p:2"]);
24909 assert_eq!(held(&f, b"ix"), (1, 3));
24910 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24911 assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
24912
24913 let dump = f.run(&[b"DUMP", b"p:1"]);
24915 assert!(dump.starts_with('$'), "{dump}");
24916 f.run(&[b"SELECT", b"1"]);
24917 f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
24918 f.run(&[b"RENAME", b"q:1", b"p:3"]);
24919 assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
24920 }
24921
24922 #[test]
24925 fn a_move_leaves_the_document_where_it_is() {
24926 let mut f = indexed();
24927 f.run(&[b"MOVE", b"p:1", b"1"]);
24928 assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
24929 assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
24930
24931 f.run(&[b"SELECT", b"1"]);
24932 f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
24933 assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
24934 f.run(&[b"DEL", b"p:1"]);
24935 assert_eq!(held(&f, b"ix"), (0, 2));
24936 }
24937
24938 #[test]
24940 fn a_flush_drops_the_indexes() {
24941 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24942 let mut f = indexed();
24943 f.run(&[flush]);
24944 assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
24945 assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
24946 }
24947
24948 let mut f = indexed();
24951 f.run(&[b"SELECT", b"9"]);
24952 f.run(&[b"FLUSHDB"]);
24953 assert!(f.server.search.lock().is_empty());
24954 }
24955
24956 fn tagged() -> Fixture {
24959 let mut f = Fixture::new();
24960 f.run(&[
24961 b"FT.CREATE",
24962 b"tv",
24963 b"PREFIX",
24964 b"1",
24965 b"tv:",
24966 b"SCHEMA",
24967 b"g",
24968 b"AS",
24969 b"gg",
24970 b"TAG",
24971 b"h",
24972 b"TAG",
24973 b"SEPARATOR",
24974 b"|",
24975 b"CASESENSITIVE",
24976 b"n",
24977 b"NUMERIC",
24978 ]);
24979 f.run(&[
24980 b"HSET",
24981 b"tv:1",
24982 b"g",
24983 b"Red, BLUE ",
24984 b"h",
24985 b"Aa|bB",
24986 b"n",
24987 b"1",
24988 ]);
24989 f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
24990 f
24991 }
24992
24993 #[test]
24997 fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
24998 let mut f = tagged();
24999 assert_eq!(
25000 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
25001 "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
25002 );
25003 assert_eq!(
25004 f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
25005 "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
25006 );
25007 }
25008
25009 #[test]
25012 fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
25013 let mut f = tagged();
25014 for (name, want) in [
25015 (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
25016 (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
25017 (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
25018 ] {
25019 assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
25020 }
25021 assert_eq!(
25022 f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
25023 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
25024 );
25025 }
25026
25027 #[test]
25030 fn asking_for_tag_values_counts_a_use_of_the_index() {
25031 let mut f = tagged();
25032 let uses = |f: &mut Fixture| {
25033 let reply = f.run(&[b"FT.INFO", b"tv"]);
25034 let at = reply.find("number_of_uses").expect("the field is reported");
25035 let value = reply[at..].split("\r\n").nth(1).unwrap();
25036 value.trim_start_matches(':').parse::<i64>().unwrap()
25037 };
25038 let before = uses(&mut f);
25039 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
25040 f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
25041 assert_eq!(uses(&mut f), before + 3);
25043 }
25044
25045 #[test]
25048 fn a_tag_field_with_nothing_in_it_answers_empty() {
25049 let mut f = Fixture::new();
25050 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
25051 assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
25052 }
25053
25054 #[test]
25057 fn a_dictionary_is_not_a_key() {
25058 let mut f = Fixture::new();
25059 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
25060 assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
25061 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
25062 assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
25063 }
25064
25065 #[test]
25068 fn a_dictionary_counts_the_terms_it_had_not_seen() {
25069 let mut f = Fixture::new();
25070 assert_eq!(
25071 f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
25072 ":3\r\n"
25073 );
25074 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
25075 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
25076 assert_eq!(
25077 f.run(&[b"FT.DICTDUMP", b"d"]),
25078 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
25079 );
25080 assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
25081 }
25082
25083 #[test]
25087 fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
25088 let mut f = Fixture::new();
25089 assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
25090 assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
25091 }
25092
25093 #[test]
25095 fn a_flush_drops_the_dictionaries() {
25096 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
25097 let mut f = Fixture::new();
25098 f.run(&[b"FT.DICTADD", b"d", b"a"]);
25099 f.run(&[flush]);
25100 assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
25101 }
25102 }
25103
25104 fn profiling() -> Fixture {
25109 let mut f = Fixture::new();
25110 f.run(&[
25111 b"FT.CREATE",
25112 b"ix",
25113 b"PREFIX",
25114 b"1",
25115 b"p:",
25116 b"SCHEMA",
25117 b"t",
25118 b"TEXT",
25119 b"n",
25120 b"NUMERIC",
25121 ]);
25122 f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
25123 f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
25124 f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
25125 f
25126 }
25127
25128 fn timeless(reply: &str) -> String {
25131 const KEYS: &[&str] = &[
25132 "+Total profile time",
25133 "+Parsing time",
25134 "+Workers queue time",
25135 "+Pipeline creation time",
25136 "+Time",
25137 ];
25138 let mut out = String::new();
25139 let mut parts = reply.split("\r\n").peekable();
25140 while let Some(part) = parts.next() {
25141 out.push_str(part);
25142 out.push_str("\r\n");
25143 if !KEYS.contains(&part) {
25144 continue;
25145 }
25146 match parts.next() {
25149 Some(head) if head.starts_with('$') => {
25150 parts.next();
25151 }
25152 _ => {}
25153 }
25154 out.push_str("<t>\r\n");
25155 }
25156 out.truncate(out.len() - 2);
25158 out
25159 }
25160
25161 #[test]
25164 fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
25165 let mut f = profiling();
25166 assert_eq!(
25167 timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
25168 "*2\r\n\
25169 *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\
25170 $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\
25171 *4\r\n+Shards\r\n*1\r\n*14\r\n\
25172 +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
25173 +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
25174 +Warning\r\n*1\r\n+None\r\n\
25175 +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
25176 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
25177 +Estimated number of matches\r\n:2\r\n\
25178 +Result processors profile\r\n*4\r\n\
25179 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
25180 *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
25181 *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
25182 *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
25183 +Coordinator\r\n*0\r\n"
25184 );
25185 let mut g = profiling();
25186 g.run(&[b"HELLO", b"3"]);
25187 let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
25188 assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
25189 assert!(
25190 three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
25191 "{three}"
25192 );
25193 assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
25194 assert!(
25195 three.contains(
25196 "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
25197 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
25198 +Estimated number of matches\r\n:2\r\n"
25199 ),
25200 "{three}"
25201 );
25202 }
25203
25204 #[test]
25207 fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
25208 let mut f = profiling();
25209 let tree = |f: &mut Fixture, query: &[u8]| {
25210 let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
25211 let at = reply.find("+Iterators profile").expect("a tree");
25212 let end = reply.find("+Result processors").expect("a list of steps");
25213 reply[at..end].to_string()
25214 };
25215 assert_eq!(
25216 tree(&mut f, b"alpha beta"),
25217 "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
25218 +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
25219 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
25220 +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
25221 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
25222 +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
25223 );
25224 assert!(tree(&mut f, b"alpha|beta").starts_with(
25225 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
25226 +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
25227 ));
25228 assert!(tree(&mut f, b"-alpha").starts_with(
25231 "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
25232 +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
25233 ));
25234 assert!(tree(&mut f, b"~alpha").starts_with(
25235 "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
25236 +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
25237 ));
25238 assert_eq!(
25240 tree(&mut f, b"*"),
25241 "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
25242 +Number of reading operations\r\n:3\r\n"
25243 );
25244 assert!(tree(&mut f, b"@n:[1 2]").starts_with(
25245 "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
25246 $19\r\n1.000000 - 2.000000\r\n"
25247 ));
25248 }
25249
25250 #[test]
25253 fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
25254 let mut f = profiling();
25255 f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
25256 let tree = |f: &mut Fixture, words: &[&[u8]]| {
25257 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
25258 argv.extend_from_slice(words);
25259 let reply = timeless(&f.run(&argv));
25260 let at = reply.find("+Iterators profile").expect("a tree");
25261 let end = reply.find("+Result processors").expect("a list of steps");
25262 reply[at..end].to_string()
25263 };
25264 assert_eq!(
25265 tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
25266 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
25267 +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
25268 +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
25269 +The number of iterators in the union is 2\r\n"
25270 );
25271 assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
25272 assert!(
25273 tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
25274 .contains("+Child iterators\r\n*2\r\n")
25275 );
25276 assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
25280 assert!(
25281 tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
25282 );
25283 }
25284
25285 #[test]
25288 fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
25289 let mut f = profiling();
25290 let steps = |f: &mut Fixture, words: &[&[u8]]| {
25291 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
25292 argv.extend_from_slice(words);
25293 let reply = timeless(&f.run(&argv));
25294 let at = reply.find("+Result processors").expect("a list of steps");
25295 let end = reply.find("+Coordinator").expect("an end");
25296 let mut out = Vec::new();
25297 let mut parts = reply[at..end].split("\r\n").peekable();
25298 while let Some(part) = parts.next() {
25299 if part == "+Type" {
25300 out.push(parts.next().unwrap_or_default().to_string());
25301 }
25302 }
25303 out
25304 };
25305 assert_eq!(
25306 steps(&mut f, &[]),
25307 ["+Index", "+Scorer", "+Sorter", "+Loader"]
25308 );
25309 assert_eq!(
25310 steps(&mut f, &[b"NOCONTENT"]),
25311 ["+Index", "+Scorer", "+Sorter"]
25312 );
25313 assert_eq!(
25316 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
25317 ["+Index", "+Counter"]
25318 );
25319 assert_eq!(
25322 steps(&mut f, &[b"SORTBY", b"n"]),
25323 ["+Index", "+Sorter", "+Loader"]
25324 );
25325 assert_eq!(
25326 steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
25327 ["+Index", "+Scorer", "+Sorter", "+Loader"]
25328 );
25329 assert_eq!(
25330 steps(&mut f, &[b"HIGHLIGHT"]),
25331 ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
25332 );
25333 assert_eq!(
25334 steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
25335 ["+Index", "+Scorer", "+Sorter"]
25336 );
25337 }
25338
25339 #[test]
25342 fn a_pipeline_names_every_step_after_what_it_runs() {
25343 let mut f = profiling();
25344 let steps = |f: &mut Fixture, words: &[&[u8]]| {
25345 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
25346 argv.extend_from_slice(words);
25347 let reply = timeless(&f.run(&argv));
25348 let at = reply.find("+Result processors").expect("a list of steps");
25349 let end = reply.find("+Coordinator").expect("an end");
25350 let mut out = Vec::new();
25351 let mut parts = reply[at..end].split("\r\n").peekable();
25352 while let Some(part) = parts.next() {
25353 if part == "+Type" {
25354 out.push(parts.next().unwrap_or_default().to_string());
25355 }
25356 }
25357 out
25358 };
25359 assert_eq!(steps(&mut f, &[]), ["+Index"]);
25360 assert_eq!(
25361 steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
25362 ["+Index", "+Projector - Literal 1"]
25363 );
25364 assert_eq!(
25365 steps(
25366 &mut f,
25367 &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
25368 ),
25369 ["+Index", "+Loader", "+Projector - Operator *"]
25370 );
25371 assert_eq!(
25372 steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
25373 ["+Index", "+Loader", "+Filter - Predicate >"]
25374 );
25375 assert_eq!(
25376 steps(
25377 &mut f,
25378 &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
25379 ),
25380 ["+Index", "+Loader", "+Grouper"]
25381 );
25382 assert_eq!(
25383 steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
25384 ["+Index", "+Loader", "+Sorter"]
25385 );
25386 assert_eq!(
25387 steps(&mut f, &[b"LIMIT", b"0", b"2"]),
25388 ["+Index", "+Pager/Limiter"]
25389 );
25390 assert_eq!(
25393 steps(
25394 &mut f,
25395 &[
25396 b"ADDSCORES",
25397 b"LOAD",
25398 b"1",
25399 b"@n",
25400 b"APPLY",
25401 b"@__score",
25402 b"AS",
25403 b"s"
25404 ]
25405 ),
25406 [
25407 "+Index",
25408 "+Scorer",
25409 "+Loader",
25410 "+Projector - Property __score"
25411 ]
25412 );
25413 }
25414
25415 #[test]
25423 fn a_sortable_field_is_read_without_the_key_being_opened() {
25424 let mut f = Fixture::new();
25425 f.run(&[
25426 b"FT.CREATE",
25427 b"sx",
25428 b"PREFIX",
25429 b"1",
25430 b"s:",
25431 b"SCHEMA",
25432 b"n",
25433 b"NUMERIC",
25434 b"SORTABLE",
25435 b"g",
25436 b"TAG",
25437 ]);
25438 f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
25439 f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
25440 let loads = |f: &mut Fixture, words: &[&[u8]]| {
25441 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
25442 argv.extend_from_slice(words);
25443 f.run(&argv).contains("+Loader")
25444 };
25445 assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
25446 assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
25447 assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
25448 assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
25449 assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
25450 assert!(loads(
25451 &mut f,
25452 &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
25453 ));
25454 assert!(loads(&mut f, &[b"LOAD", b"*"]));
25455 }
25456
25457 #[test]
25460 fn a_profile_checks_its_own_words_before_it_runs_anything() {
25461 let mut f = profiling();
25462 assert_eq!(
25463 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
25464 "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
25465 );
25466 assert_eq!(
25467 f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
25468 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
25469 );
25470 assert_eq!(
25473 f.run(&[
25474 b"FT.PROFILE",
25475 b"ix",
25476 b"LIMITED",
25477 b"SEARCH",
25478 b"QUERY",
25479 b"alpha"
25480 ]),
25481 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
25482 );
25483 assert_eq!(
25484 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
25485 "-The QUERY keyword is expected\r\n"
25486 );
25487 assert_eq!(
25488 f.run(&[
25489 b"FT.PROFILE",
25490 b"ix",
25491 b"AGGREGATE",
25492 b"QUERY",
25493 b"alpha",
25494 b"WITHCURSOR"
25495 ]),
25496 "-FT.PROFILE does not support cursor\r\n"
25497 );
25498 assert_eq!(
25501 f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
25502 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
25503 );
25504 assert_eq!(
25505 f.run(&[
25506 b"FT.PROFILE",
25507 b"ix",
25508 b"SEARCH",
25509 b"QUERY",
25510 b"alpha",
25511 b"extra"
25512 ]),
25513 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
25514 );
25515 }
25516
25517 #[test]
25519 fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
25520 let mut f = profiling();
25521 let one = f.run(&[
25522 b"FT.PROFILE",
25523 b"ix",
25524 b"search",
25525 b"limited",
25526 b"query",
25527 b"alpha",
25528 ]);
25529 let two = f.run(&[
25530 b"FT.PROFILE",
25531 b"ix",
25532 b"SEARCH",
25533 b"LIMITED",
25534 b"QUERY",
25535 b"alpha",
25536 ]);
25537 assert_eq!(timeless(&one), timeless(&two));
25538 }
25539
25540 #[test]
25545 fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
25546 let mut f = profiling();
25547 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
25548 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25549
25550 let mut f = profiling();
25551 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25552 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25553
25554 let mut f = profiling();
25555 assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
25556 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25557
25558 let mut f = profiling();
25559 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
25560 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25561 }
25562
25563 #[test]
25566 fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
25567 let mut f = profiling();
25568 let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
25569 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
25570 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
25571 assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
25573 }
25574
25575 #[test]
25578 fn dropping_the_documents_leaves_a_key_the_index_never_read() {
25579 let mut f = profiling();
25580 f.run(&[b"SET", b"p:4", b"alpha"]);
25581 f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
25582 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25583 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25584 assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
25585 }
25586
25587 #[test]
25590 fn another_index_over_the_same_keys_loses_the_documents_too() {
25591 let mut f = profiling();
25592 f.run(&[
25593 b"FT.CREATE",
25594 b"other",
25595 b"PREFIX",
25596 b"1",
25597 b"p:",
25598 b"SCHEMA",
25599 b"t",
25600 b"TEXT",
25601 ]);
25602 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25603 assert_eq!(
25604 f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
25605 "*1\r\n:0\r\n"
25606 );
25607 }
25608
25609 #[test]
25612 fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
25613 let mut f = profiling();
25614 assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
25615 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25616 assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
25617 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25618 }
25619
25620 #[test]
25625 fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
25626 let mut f = Fixture::new();
25627 assert_eq!(
25628 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25629 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25630 );
25631 assert_eq!(
25632 f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25633 "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
25634 );
25635 let mut g = Fixture::new();
25636 g.run(&[b"HELLO", b"3"]);
25637 assert_eq!(
25638 g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25639 "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25640 );
25641 assert_eq!(
25642 g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25643 "%1\r\n+EXTLOAD\r\n_\r\n"
25644 );
25645 }
25646
25647 #[test]
25650 fn a_help_row_carries_the_description_and_the_value_together() {
25651 let mut f = Fixture::new();
25652 assert_eq!(
25653 f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25654 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25655 +Value\r\n$3\r\n500\r\n"
25656 );
25657 let mut g = Fixture::new();
25658 g.run(&[b"HELLO", b"3"]);
25659 assert_eq!(
25660 g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25661 "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
25662 +Value\r\n$3\r\n500\r\n"
25663 );
25664 }
25665
25666 #[test]
25669 fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
25670 let mut f = Fixture::new();
25671 assert_eq!(
25672 f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
25673 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25674 );
25675 for name in [
25676 b"TIMEOUT*".as_slice(),
25677 b"?IMEOUT",
25678 b"*TIMEOUT*",
25679 b"TIME",
25680 b"NOSUCH",
25681 b"",
25682 ] {
25683 assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
25684 }
25685 assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
25686 assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
25687 }
25688
25689 #[test]
25692 fn a_read_ignores_whatever_follows_the_name() {
25693 let mut f = Fixture::new();
25694 assert_eq!(
25695 f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
25696 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25697 );
25698 assert_eq!(
25699 f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
25700 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25701 +Value\r\n$3\r\n500\r\n"
25702 );
25703 }
25704
25705 #[test]
25708 fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
25709 let mut f = Fixture::new();
25710 assert_eq!(
25711 f.run(&[b"FT.CONFIG"]),
25712 "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
25713 );
25714 for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
25715 let want = format!(
25716 "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
25717 String::from_utf8_lossy(sub)
25718 );
25719 assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
25720 }
25721 assert_eq!(
25722 f.run(&[b"ft.config", b"get"]),
25723 "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
25724 );
25725 assert_eq!(
25726 f.run(&[b"FT.CONFIG", b"bogus"]),
25727 "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
25728 );
25729 }
25730
25731 #[test]
25734 fn a_write_checks_the_name_then_the_setting_then_the_value() {
25735 let mut f = Fixture::new();
25736 for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
25737 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
25738 cmd.extend(tail);
25739 assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
25740 }
25741 for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
25742 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
25743 cmd.extend(tail);
25744 assert_eq!(
25745 f.run(&cmd),
25746 "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
25747 );
25748 }
25749 assert_eq!(
25750 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
25751 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
25752 );
25753 }
25754
25755 #[test]
25758 fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
25759 let mut f = Fixture::new();
25760 assert_eq!(
25761 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
25762 "+OK\r\n"
25763 );
25764 assert_eq!(
25765 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
25766 "+EXCESSARGS\r\n"
25767 );
25768 assert_eq!(
25769 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25770 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
25771 );
25772 }
25773
25774 #[test]
25777 fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
25778 let mut f = Fixture::new();
25779 for (given, want) in [
25780 (b"0x10".as_slice(), "16"),
25781 (b"0X1f", "31"),
25782 (b"+0x10", "16"),
25783 (b"+5", "5"),
25784 (b"010", "10"),
25785 (b"08", "8"),
25786 (b"0777", "777"),
25787 (b"1e3", "1000"),
25788 (b"0.0", "0"),
25789 (b"-0.0", "0"),
25790 ] {
25791 assert_eq!(
25792 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25793 "+OK\r\n",
25794 "{given:?}"
25795 );
25796 let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
25797 assert_eq!(
25798 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25799 want,
25800 "{given:?}"
25801 );
25802 }
25803 for given in [
25804 b" 5".as_slice(),
25805 b"5 ",
25806 b"1.5",
25807 b"1e-3",
25808 b"x",
25809 b"",
25810 b"0b11",
25811 b"0xg",
25812 b"nan",
25813 b"inf",
25814 b"1e100",
25815 b"99999999999999999999",
25816 ] {
25817 assert_eq!(
25818 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25819 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25820 "{given:?}"
25821 );
25822 }
25823 }
25824
25825 #[test]
25828 fn a_negative_is_answered_by_whichever_reader_found_it() {
25829 let mut f = Fixture::new();
25830 for given in [b"-1".as_slice(), b"-16"] {
25831 assert_eq!(
25832 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25833 "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
25834 "{given:?}"
25835 );
25836 }
25837 for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
25838 assert_eq!(
25839 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25840 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25841 "{given:?}"
25842 );
25843 }
25844 let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
25845 for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
25846 assert_eq!(
25847 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25848 "+OK\r\n",
25849 "{given:?}"
25850 );
25851 assert_eq!(
25852 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25853 unlimited,
25854 "{given:?}"
25855 );
25856 }
25857 }
25858
25859 #[test]
25862 fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
25863 let mut f = Fixture::new();
25864 for (given, want) in [
25865 (b"2147483647".as_slice(), "2147483647"),
25866 (b"2147483648", "unlimited"),
25867 (b"4294967295", "unlimited"),
25868 (b"9223372036854775806", "unlimited"),
25869 (b"0", "0"),
25870 ] {
25871 assert_eq!(
25872 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25873 "+OK\r\n",
25874 "{given:?}"
25875 );
25876 let want = format!(
25877 "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
25878 want.len()
25879 );
25880 assert_eq!(
25881 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25882 want,
25883 "{given:?}"
25884 );
25885 }
25886 }
25887
25888 #[test]
25891 fn a_number_out_of_range_names_the_limit_it_crossed() {
25892 let mut f = Fixture::new();
25893 let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
25894 for (name, given) in [
25895 (b"MINPREFIX".as_slice(), b"0".as_slice()),
25896 (b"MAX_AGGREGATE_GROUPS", b"0"),
25897 (b"BM25STD_TANH_FACTOR", b"0"),
25898 (b"DEFAULT_DIALECT", b"0"),
25899 (b"MINSTEMLEN", b"4294967296"),
25900 (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
25901 (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
25902 (b"CONNECT_TIMEOUT", b"2147483648"),
25903 ] {
25904 assert_eq!(
25905 f.run(&[b"FT.CONFIG", b"SET", name, given]),
25906 bounds,
25907 "{name:?}"
25908 );
25909 }
25910 for (name, given, want) in [
25911 (
25912 b"MINSTEMLEN".as_slice(),
25913 b"1".as_slice(),
25914 "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
25915 ),
25916 (
25917 b"MAX_AGGREGATE_GROUPS",
25918 b"67108865",
25919 "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
25920 ),
25921 (
25922 b"WORKERS",
25923 b"17",
25924 "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
25925 ),
25926 (
25927 b"_NUMERIC_RANGES_PARENTS",
25928 b"3",
25929 "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
25930 depth for balance\r\n",
25931 ),
25932 (
25933 b"DEFAULT_DIALECT",
25934 b"5",
25935 "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
25936 ),
25937 (
25938 b"_BG_INDEX_MEM_PCT_THR",
25939 b"101",
25940 "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
25941 100%\r\n",
25942 ),
25943 (
25944 b"BM25STD_TANH_FACTOR",
25945 b"10001",
25946 "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
25947 inclusive\r\n",
25948 ),
25949 (
25950 b"BG_INDEX_SLEEP_DURATION_US",
25951 b"1000000",
25952 "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
25953 999999 (usleep POSIX limit)\r\n",
25954 ),
25955 ] {
25956 assert_eq!(
25957 f.run(&[b"FT.CONFIG", b"SET", name, given]),
25958 want,
25959 "{name:?}"
25960 );
25961 }
25962 }
25963
25964 #[test]
25967 fn the_trimming_delays_are_checked_against_one_another() {
25968 let mut f = Fixture::new();
25969 assert_eq!(
25970 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
25971 "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
25972 _MAX_TRIM_DELAY_MS (5000)\r\n"
25973 );
25974 assert_eq!(
25975 f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
25976 "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
25977 _MIN_TRIM_DELAY_MS (2000)\r\n"
25978 );
25979 assert_eq!(
25980 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
25981 "+OK\r\n"
25982 );
25983 }
25984
25985 #[test]
25988 fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
25989 let mut f = Fixture::new();
25990 assert_eq!(
25991 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
25992 "+OK\r\n"
25993 );
25994 assert_eq!(
25995 f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
25996 "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
25997 );
25998 assert_eq!(
25999 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
26000 "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
26001 );
26002 assert_eq!(
26003 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
26004 "+OK\r\n"
26005 );
26006 assert_eq!(
26007 f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
26008 "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
26009 );
26010 assert_eq!(
26011 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
26012 "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
26013 );
26014 let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
26015 for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
26016 assert_eq!(
26017 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
26018 bad,
26019 "{given:?}"
26020 );
26021 }
26022 assert_eq!(
26023 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
26024 "+OK\r\n"
26025 );
26026 }
26027
26028 #[test]
26031 fn a_yes_or_no_setting_takes_those_two_words_only() {
26032 let mut f = Fixture::new();
26033 assert_eq!(
26034 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
26035 "+OK\r\n"
26036 );
26037 assert_eq!(
26038 f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
26039 "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
26040 );
26041 for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
26042 assert_eq!(
26043 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
26044 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
26045 "{given:?}"
26046 );
26047 }
26048 }
26049
26050 #[test]
26053 fn two_names_for_one_setting_move_together() {
26054 let mut f = Fixture::new();
26055 f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
26056 assert_eq!(
26057 f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
26058 "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
26059 );
26060 f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
26061 assert_eq!(
26062 f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
26063 "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
26064 );
26065 let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
26066 let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
26067 f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
26068 assert_eq!(
26069 f.run(&[b"FT.CONFIG", b"GET", short]),
26070 "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
26071 );
26072 assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
26073 assert_eq!(
26074 f.run(&[b"FT.CONFIG", b"GET", long]),
26075 "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
26076 );
26077 }
26078
26079 #[test]
26081 fn a_password_reads_back_as_stars_whatever_was_written() {
26082 let mut f = Fixture::new();
26083 assert_eq!(
26084 f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
26085 "+OK\r\n"
26086 );
26087 assert_eq!(
26088 f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
26089 "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
26090 );
26091 }
26092
26093 #[test]
26096 fn a_flush_leaves_the_settings_alone() {
26097 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
26098 let mut f = Fixture::new();
26099 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
26100 f.run(&[flush]);
26101 assert_eq!(
26102 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
26103 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
26104 "{flush:?}"
26105 );
26106 }
26107 }
26108
26109 fn debugging() -> Fixture {
26114 let mut f = Fixture::new();
26115 f.run(&[
26116 b"FT.CREATE",
26117 b"dx",
26118 b"PREFIX",
26119 b"1",
26120 b"d:",
26121 b"SCHEMA",
26122 b"t",
26123 b"TEXT",
26124 b"g",
26125 b"TAG",
26126 b"n",
26127 b"NUMERIC",
26128 b"s",
26129 b"TEXT",
26130 b"SORTABLE",
26131 ]);
26132 f.run(&[
26133 b"HSET",
26134 b"d:1",
26135 b"t",
26136 b"running dogs",
26137 b"g",
26138 b"red,blue",
26139 b"n",
26140 b"1",
26141 b"s",
26142 b"Alpha",
26143 ]);
26144 f.run(&[
26145 b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
26146 ]);
26147 f.run(&[
26148 b"HSET",
26149 b"d:3",
26150 b"t",
26151 b"dogs alpha",
26152 b"g",
26153 b"green",
26154 b"n",
26155 b"3",
26156 ]);
26157 f
26158 }
26159
26160 #[test]
26163 fn a_term_dump_lists_the_stems_beside_the_words() {
26164 let mut f = debugging();
26165 assert_eq!(
26166 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
26167 "*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\
26168 $4\r\ndogs\r\n$7\r\nrunning\r\n"
26169 );
26170 }
26171
26172 #[test]
26175 fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
26176 let mut f = debugging();
26177 assert_eq!(
26178 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
26179 "*2\r\n:1\r\n:2\r\n"
26180 );
26181 assert_eq!(
26182 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
26183 "*2\r\n:1\r\n:2\r\n"
26184 );
26185 for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
26186 assert_eq!(
26187 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
26188 "-Can not find the inverted index\r\n",
26189 "{term:?}"
26190 );
26191 }
26192 }
26193
26194 #[test]
26197 fn a_tag_dump_pairs_every_value_with_its_documents() {
26198 let mut f = debugging();
26199 assert_eq!(
26200 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
26201 "*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\
26202 *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
26203 );
26204 }
26205
26206 #[test]
26209 fn a_number_dump_answers_a_single_range() {
26210 let mut f = debugging();
26211 assert_eq!(
26212 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
26213 "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
26214 );
26215 }
26216
26217 #[test]
26220 fn a_geo_field_is_dumped_as_a_numeric_one() {
26221 let mut f = Fixture::new();
26222 f.run(&[
26223 b"FT.CREATE",
26224 b"gx",
26225 b"PREFIX",
26226 b"1",
26227 b"q:",
26228 b"SCHEMA",
26229 b"loc",
26230 b"GEO",
26231 b"gg",
26232 b"AS",
26233 b"tag",
26234 b"TAG",
26235 ]);
26236 f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
26237 f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
26238 assert_eq!(
26239 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
26240 "*1\r\n*2\r\n:1\r\n:2\r\n"
26241 );
26242 assert_eq!(
26243 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
26244 "-Could not find given field in index spec\r\n"
26245 );
26246 }
26247
26248 #[test]
26251 fn a_dump_takes_the_attribute_and_not_the_identifier() {
26252 let mut f = Fixture::new();
26253 f.run(&[
26254 b"FT.CREATE",
26255 b"zx",
26256 b"PREFIX",
26257 b"1",
26258 b"z:",
26259 b"SCHEMA",
26260 b"gg",
26261 b"AS",
26262 b"tag",
26263 b"TAG",
26264 ]);
26265 f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
26266 assert_eq!(
26267 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
26268 "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
26269 );
26270 assert_eq!(
26271 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
26272 "-Could not find given field in index spec\r\n"
26273 );
26274 }
26275
26276 #[test]
26279 fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
26280 let mut f = debugging();
26281 assert_eq!(
26282 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
26283 "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
26284 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
26285 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
26286 +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\
26287 $5\r\nvalue\r\n$5\r\nalpha\r\n"
26288 );
26289 let mut g = debugging();
26290 g.run(&[b"HELLO", b"3"]);
26291 assert_eq!(
26292 g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
26293 "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
26294 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
26295 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
26296 +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\
26297 $5\r\nvalue\r\n$5\r\nalpha\r\n"
26298 );
26299 }
26300
26301 #[test]
26304 fn a_document_with_no_sortable_value_drops_the_key() {
26305 let mut f = debugging();
26306 assert_eq!(
26307 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
26308 "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
26309 +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"
26310 );
26311 }
26312
26313 #[test]
26316 fn the_flag_word_spells_out_the_bits_it_carries() {
26317 let mut f = Fixture::new();
26318 f.run(&[
26319 b"FT.CREATE",
26320 b"nx",
26321 b"NOOFFSETS",
26322 b"PREFIX",
26323 b"1",
26324 b"o:",
26325 b"SCHEMA",
26326 b"t",
26327 b"TEXT",
26328 ]);
26329 f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
26330 assert!(
26331 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
26332 .contains("$6\r\n(0x0):\r\n")
26333 );
26334 }
26335
26336 #[test]
26339 fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
26340 let mut f = debugging();
26341 assert!(
26342 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
26343 .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
26344 );
26345 }
26346
26347 #[test]
26350 fn a_document_row_reads_its_keyword_at_a_fixed_place() {
26351 let mut f = debugging();
26352 let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
26353 assert_eq!(
26354 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
26355 want
26356 );
26357 assert_eq!(
26358 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
26359 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
26360 );
26361 assert_eq!(
26362 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
26363 "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
26364 );
26365 }
26366
26367 #[test]
26370 fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
26371 let mut f = debugging();
26372 assert_eq!(
26373 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
26374 "-Document not found in index\r\n"
26375 );
26376 assert_eq!(
26377 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
26378 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
26379 );
26380 }
26381
26382 #[test]
26385 fn a_document_number_goes_both_ways() {
26386 let mut f = debugging();
26387 assert_eq!(
26388 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
26389 "$3\r\nd:2\r\n"
26390 );
26391 assert_eq!(
26392 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
26393 ":2\r\n"
26394 );
26395 assert_eq!(
26396 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
26397 ":0\r\n"
26398 );
26399 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
26400 for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
26401 assert_eq!(
26402 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
26403 "-document was removed\r\n",
26404 "{id:?}"
26405 );
26406 }
26407 }
26408
26409 #[test]
26412 fn a_document_number_is_read_the_strict_way() {
26413 let mut f = debugging();
26414 for id in [
26415 b"x".as_slice(),
26416 b"1.5",
26417 b" 1",
26418 b"+1",
26419 b"01",
26420 b"0x1",
26421 b"",
26422 b"9223372036854775808",
26423 b"18446744073709551615",
26424 ] {
26425 assert_eq!(
26426 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
26427 "-bad id given\r\n",
26428 "{id:?}"
26429 );
26430 }
26431 }
26432
26433 #[test]
26436 fn a_dump_keeps_a_number_the_table_has_given_up() {
26437 let mut f = debugging();
26438 f.run(&[b"DEL", b"d:2"]);
26439 assert_eq!(
26440 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
26441 "*2\r\n:1\r\n:2\r\n"
26442 );
26443 assert_eq!(
26444 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
26445 "-document was removed\r\n"
26446 );
26447 assert_eq!(
26448 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
26449 ":0\r\n"
26450 );
26451 }
26452
26453 #[test]
26456 fn a_rewrite_takes_a_number_of_its_own() {
26457 let mut f = debugging();
26458 f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
26459 assert_eq!(
26460 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
26461 ":4\r\n"
26462 );
26463 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
26464 assert_eq!(
26465 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
26466 "-document was removed\r\n"
26467 );
26468 assert_eq!(
26469 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
26470 "*2\r\n:1\r\n:2\r\n"
26471 );
26472 }
26473
26474 #[test]
26476 fn a_dump_follows_an_alias() {
26477 let mut f = debugging();
26478 f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
26479 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
26480 assert_eq!(
26481 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
26482 "$3\r\nd:1\r\n"
26483 );
26484 }
26485
26486 #[test]
26489 fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
26490 let mut f = debugging();
26491 assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
26492 assert_eq!(
26493 f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
26494 "-Can not create a search ctx\r\n"
26495 );
26496 assert_eq!(
26497 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
26498 "-Can not create a search ctx\r\n"
26499 );
26500 }
26501
26502 #[test]
26505 fn an_empty_field_dumps_as_nothing_at_all() {
26506 let mut f = Fixture::new();
26507 f.run(&[
26508 b"FT.CREATE",
26509 b"ex",
26510 b"PREFIX",
26511 b"1",
26512 b"e:",
26513 b"SCHEMA",
26514 b"t",
26515 b"TEXT",
26516 b"g",
26517 b"TAG",
26518 b"n",
26519 b"NUMERIC",
26520 ]);
26521 assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
26522 assert_eq!(
26523 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
26524 "*0\r\n"
26525 );
26526 assert_eq!(
26527 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
26528 "*0\r\n"
26529 );
26530 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
26531 }
26532
26533 #[test]
26536 fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
26537 let mut f = debugging();
26538 for (sub, extra) in [
26539 (b"DUMP_TERMS".as_slice(), 1),
26540 (b"GET_MAX_DOC_ID", 1),
26541 (b"DUMP_INVIDX", 2),
26542 (b"DUMP_TAGIDX", 2),
26543 (b"DUMP_NUMIDX", 2),
26544 (b"IDTODOCID", 2),
26545 (b"DOCIDTOID", 2),
26546 ] {
26547 let want = format!(
26548 "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
26549 str::from_utf8(sub).unwrap()
26550 );
26551 for given in [extra - 1, extra + 1] {
26552 let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
26553 cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
26554 assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
26555 }
26556 let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
26557 right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
26558 assert_ne!(f.run(&right), want, "{sub:?}");
26559 }
26560 assert_eq!(
26561 f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
26562 "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
26563 );
26564 }
26565
26566 #[test]
26569 fn the_help_names_the_subcommands_that_answer() {
26570 let mut f = Fixture::new();
26571 let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
26572 $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
26573 $14\r\nGET_MAX_DOC_ID\r\n";
26574 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
26575 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
26576 }
26577
26578 #[test]
26583 fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
26584 let mut f = Fixture::new();
26585 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26586 assert_eq!(
26587 f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
26588 "+OK\r\n"
26589 );
26590 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
26591 assert_eq!(
26592 f.run(&[b"FT.SYNDUMP", b"e"]),
26593 "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
26594 $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
26595 );
26596 }
26597
26598 #[test]
26602 fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
26603 let mut f = Fixture::new();
26604 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26605 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
26606 assert_eq!(
26607 f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
26608 "$69\r\nUNION {\n jogging\n ~gr(expanded)\n +jog(expanded)\n jog(expanded)\n}\n\r\n"
26609 );
26610 }
26611
26612 #[test]
26621 fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
26622 let mut f = Fixture::new();
26623 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26624 f.run(&[b"HSET", b"d1", b"t", b"boy"]);
26625 f.run(&[b"HSET", b"d2", b"t", b"runs"]);
26626 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
26627 assert_eq!(
26628 f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
26629 "*2\r\n:1\r\n$2\r\nd1\r\n"
26630 );
26631 assert_eq!(
26632 f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
26633 "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
26634 );
26635 }
26636
26637 #[test]
26640 fn a_synonym_command_on_a_name_that_is_not_there_fails() {
26641 let mut f = Fixture::new();
26642 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
26643 assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
26644 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
26645 }
26646
26647 #[test]
26651 fn params_counts_the_words_before_it_pairs_them_up() {
26652 let mut f = Fixture::new();
26653 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26654 let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
26655 Expected an argument, but none provided\r\n";
26656 let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
26657 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
26658 assert_eq!(
26659 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
26660 none
26661 );
26662 assert_eq!(
26663 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
26664 odd
26665 );
26666 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
26667 assert_eq!(
26668 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
26669 "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
26670 );
26671 }
26672
26673 fn vectored(f: &mut Fixture) {
26683 f.run(&[
26684 b"FT.CREATE",
26685 b"h",
26686 b"SCHEMA",
26687 b"t",
26688 b"TEXT",
26689 b"v",
26690 b"VECTOR",
26691 b"FLAT",
26692 b"6",
26693 b"TYPE",
26694 b"FLOAT32",
26695 b"DIM",
26696 b"2",
26697 b"DISTANCE_METRIC",
26698 b"L2",
26699 ]);
26700 let at: [&[u8]; 5] = [
26701 b"\x00\x00\x80\x40\x00\x00\x00\x00",
26702 b"\x00\x00\x40\x40\x00\x00\x00\x00",
26703 b"\x00\x00\x00\x40\x00\x00\x00\x00",
26704 b"\x00\x00\x80\x3f\x00\x00\x00\x00",
26705 b"\x00\x00\x00\x00\x00\x00\x00\x00",
26706 ];
26707 for (n, point) in at.iter().enumerate() {
26708 let key = format!("d{}", n + 1);
26709 let word: &[u8] = match n {
26710 0 | 1 | 3 => b"alpha",
26711 _ => b"beta",
26712 };
26713 f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
26714 }
26715 }
26716
26717 const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
26719
26720 #[test]
26724 fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
26725 let mut f = Fixture::new();
26726 vectored(&mut f);
26727 assert_eq!(
26728 f.run(&[
26729 b"FT.SEARCH",
26730 b"h",
26731 b"*=>[KNN 5 @v $vec]",
26732 b"PARAMS",
26733 b"2",
26734 b"vec",
26735 ORIGIN,
26736 b"DIALECT",
26737 b"2",
26738 b"NOCONTENT",
26739 ]),
26740 "*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"
26741 );
26742 assert_eq!(
26743 f.run(&[
26744 b"FT.SEARCH",
26745 b"h",
26746 b"*=>[KNN 3 @v $vec]",
26747 b"PARAMS",
26748 b"2",
26749 b"vec",
26750 ORIGIN,
26751 b"DIALECT",
26752 b"2",
26753 b"NOCONTENT",
26754 ]),
26755 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26756 );
26757 }
26758
26759 #[test]
26762 fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
26763 let mut f = Fixture::new();
26764 vectored(&mut f);
26765 for (radius, want) in [
26766 ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
26767 ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
26768 (
26769 "9",
26770 "*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",
26771 ),
26772 ] {
26773 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26774 assert_eq!(
26775 f.run(&[
26776 b"FT.SEARCH",
26777 b"h",
26778 query.as_bytes(),
26779 b"PARAMS",
26780 b"2",
26781 b"vec",
26782 ORIGIN,
26783 b"DIALECT",
26784 b"2",
26785 b"NOCONTENT",
26786 ]),
26787 want,
26788 "radius {radius}"
26789 );
26790 }
26791 }
26792
26793 #[test]
26797 fn a_knn_measures_what_the_query_in_front_of_it_matched() {
26798 let mut f = Fixture::new();
26799 vectored(&mut f);
26800 assert_eq!(
26801 f.run(&[
26802 b"FT.SEARCH",
26803 b"h",
26804 b"alpha=>[KNN 2 @v $vec]",
26805 b"PARAMS",
26806 b"2",
26807 b"vec",
26808 ORIGIN,
26809 b"DIALECT",
26810 b"2",
26811 b"NOCONTENT",
26812 ]),
26813 "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
26814 );
26815 }
26816
26817 #[test]
26826 fn a_count_and_a_radius_are_refused_in_their_own_words() {
26827 let mut f = Fixture::new();
26828 vectored(&mut f);
26829 let ask = |f: &mut Fixture, query: &str| {
26830 f.run(&[
26831 b"FT.SEARCH",
26832 b"h",
26833 query.as_bytes(),
26834 b"PARAMS",
26835 b"2",
26836 b"vec",
26837 ORIGIN,
26838 b"DIALECT",
26839 b"2",
26840 b"NOCONTENT",
26841 ])
26842 };
26843 for (query, at, near) in [
26844 ("*=>[KNN -1 @v $vec]", 8, "-1"),
26845 ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
26846 ("*=>[KNN +3 @v $vec]", 8, "+3"),
26847 ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
26848 ("*=>[KNN abc @v $vec]", 8, "abc"),
26849 ("*=>[KNN 3 $vec]", 10, "vec"),
26850 ("*=>[KNN 3 @v vec]", 13, "vec"),
26851 ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
26852 ] {
26853 assert_eq!(
26854 ask(&mut f, query),
26855 format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
26856 "{query}"
26857 );
26858 }
26859
26860 let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26863 query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
26864 assert_eq!(
26865 ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
26866 "*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"
26867 );
26868 assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
26869 assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
26870
26871 for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
26872 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26873 assert_eq!(
26874 ask(&mut f, &query),
26875 format!(
26876 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26877 negative radius ({printed}) given in a range query\r\n"
26878 ),
26879 "{query}"
26880 );
26881 }
26882 assert_eq!(
26884 ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
26885 "*2\r\n:1\r\n$2\r\nd5\r\n"
26886 );
26887 }
26888
26889 #[test]
26892 fn a_count_that_came_from_params_is_read_by_its_own_rules() {
26893 let mut f = Fixture::new();
26894 vectored(&mut f);
26895 let ask = |f: &mut Fixture, count: &[u8]| {
26896 f.run(&[
26897 b"FT.SEARCH",
26898 b"h",
26899 b"*=>[KNN $k @v $vec]",
26900 b"PARAMS",
26901 b"4",
26902 b"vec",
26903 ORIGIN,
26904 b"k",
26905 count,
26906 b"DIALECT",
26907 b"2",
26908 b"NOCONTENT",
26909 ])
26910 };
26911 let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
26912 assert_eq!(ask(&mut f, b"3"), three);
26913 assert_eq!(ask(&mut f, b" 3"), three);
26914 assert_eq!(ask(&mut f, b"+3"), three);
26915 for bad in [
26916 &b"3.0"[..],
26917 b"0x3",
26918 b"-1",
26919 b"abc",
26920 b"",
26921 b"99999999999999999999",
26922 ] {
26923 let value = String::from_utf8_lossy(bad).into_owned();
26924 assert_eq!(
26925 ask(&mut f, bad),
26926 format!(
26927 "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
26928 for parameter `k`\r\n"
26929 ),
26930 "{value}"
26931 );
26932 }
26933 assert_eq!(
26934 ask(&mut f, b"288230376151711777"),
26935 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26936 query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
26937 );
26938 }
26939
26940 #[test]
26943 fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
26944 let mut f = Fixture::new();
26945 vectored(&mut f);
26946 assert_eq!(
26947 f.run(&[
26948 b"FT.SEARCH",
26949 b"h",
26950 b"*=>[KNN 5 @v $vec]",
26951 b"PARAMS",
26952 b"2",
26953 b"vec",
26954 b"abc",
26955 b"DIALECT",
26956 b"2",
26957 b"NOCONTENT",
26958 ]),
26959 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26960 query vector blob size (3) does not match index's expected size (8).\r\n"
26961 );
26962 }
26963
26964 #[test]
26969 fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
26970 let mut f = Fixture::new();
26971 vectored(&mut f);
26972 let ask = |f: &mut Fixture, query: &str| {
26973 f.run(&[
26974 b"FT.SEARCH",
26975 b"h",
26976 query.as_bytes(),
26977 b"PARAMS",
26978 b"2",
26979 b"vec",
26980 ORIGIN,
26981 b"DIALECT",
26982 b"2",
26983 b"LIMIT",
26984 b"0",
26985 b"1",
26986 ])
26987 };
26988 assert_eq!(
26989 ask(&mut f, "*=>[KNN 3 @v $vec]"),
26990 "*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"
26991 );
26992 assert_eq!(
26993 ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
26994 "*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"
26995 );
26996 assert_eq!(
26997 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
26998 "*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"
26999 );
27000 assert_eq!(
27001 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
27002 "*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"
27003 );
27004 }
27005
27006 #[test]
27013 fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
27014 let mut f = Fixture::new();
27015 vectored(&mut f);
27016 let ask = |f: &mut Fixture, ret: &[&[u8]]| {
27017 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
27018 args.extend_from_slice(ret);
27019 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
27020 f.run(&args)
27021 };
27022 assert_eq!(
27023 ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
27024 "*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"
27025 );
27026 assert_eq!(
27027 ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
27028 "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
27029 );
27030 assert_eq!(
27031 ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
27032 "*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"
27033 );
27034 assert_eq!(
27035 ask(&mut f, &[b"RETURN", b"1", b"t"]),
27036 "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
27037 );
27038 assert_eq!(
27041 ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
27042 "*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"
27043 );
27044 assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
27045 }
27046
27047 #[test]
27051 fn a_sortby_can_name_a_distance_the_query_yielded() {
27052 let mut f = Fixture::new();
27053 vectored(&mut f);
27054 let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
27055 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
27056 if desc {
27057 args.push(b"DESC");
27058 }
27059 args.extend_from_slice(&[
27060 b"PARAMS",
27061 b"2",
27062 b"vec",
27063 ORIGIN,
27064 b"DIALECT",
27065 b"2",
27066 b"NOCONTENT",
27067 ]);
27068 f.run(&args)
27069 };
27070 assert_eq!(
27071 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
27072 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
27073 );
27074 assert_eq!(
27075 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
27076 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
27077 );
27078 assert_eq!(
27079 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
27080 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
27081 );
27082 let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
27085 not loaded nor in schema\r\n";
27086 assert_eq!(
27087 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
27088 missing
27089 );
27090 assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
27091 assert_eq!(
27094 ask(&mut f, "foo(", b"zz", false),
27095 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
27096 );
27097 }
27098
27099 #[test]
27101 fn two_vector_clauses_answer_two_distances() {
27102 let mut f = Fixture::new();
27103 vectored(&mut f);
27104 assert_eq!(
27105 f.run(&[
27106 b"FT.SEARCH",
27107 b"h",
27108 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
27109 b"RETURN",
27110 b"2",
27111 b"rr",
27112 b"__v_score",
27113 b"PARAMS",
27114 b"2",
27115 b"vec",
27116 ORIGIN,
27117 b"DIALECT",
27118 b"2",
27119 ]),
27120 "*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"
27121 );
27122 }
27123
27124 #[test]
27128 fn an_aggregation_answers_a_distance_nothing_asked_for() {
27129 let mut f = Fixture::new();
27130 vectored(&mut f);
27131 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
27132 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
27133 args.extend_from_slice(rest);
27134 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
27135 f.run(&args)
27136 };
27137 assert_eq!(
27138 ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
27139 "*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"
27140 );
27141 assert_eq!(
27142 ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
27143 "*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"
27144 );
27145 assert_eq!(
27146 ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
27147 "*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"
27148 );
27149 assert_eq!(
27151 ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
27152 "*3\r\n:1\r\n*0\r\n*0\r\n"
27153 );
27154 assert_eq!(
27155 ask(
27156 &mut f,
27157 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
27158 &[]
27159 ),
27160 "*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"
27161 );
27162 }
27163
27164 #[test]
27168 fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
27169 let mut f = Fixture::new();
27170 vectored(&mut f);
27171 const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
27173 let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
27174 f.run(&[
27175 b"FT.AGGREGATE",
27176 b"h",
27177 query.as_bytes(),
27178 b"LOAD",
27179 b"1",
27180 b"@t",
27181 b"PARAMS",
27182 b"2",
27183 b"vec",
27184 vec,
27185 b"DIALECT",
27186 b"2",
27187 ])
27188 };
27189 assert_eq!(
27190 ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
27191 "*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"
27192 );
27193 assert_eq!(
27195 ask(
27196 &mut f,
27197 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
27198 MIDDLE
27199 ),
27200 "*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"
27201 );
27202 }
27203
27204 #[test]
27208 fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
27209 let mut f = Fixture::new();
27210 vectored(&mut f);
27211 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
27212 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
27213 args.extend_from_slice(rest);
27214 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
27215 f.run(&args)
27216 };
27217 let knn = "*=>[KNN 2 @v $vec]";
27218 assert_eq!(
27219 ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
27220 "*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"
27221 );
27222 assert_eq!(
27223 ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
27224 "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
27225 );
27226 assert_eq!(
27227 ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
27228 "*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"
27229 );
27230 assert_eq!(
27231 ask(
27232 &mut f,
27233 knn,
27234 &[
27235 b"GROUPBY",
27236 b"1",
27237 b"@t",
27238 b"REDUCE",
27239 b"MAX",
27240 b"1",
27241 b"@__v_score",
27242 b"AS",
27243 b"m"
27244 ]
27245 ),
27246 "*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"
27247 );
27248 assert_eq!(
27249 ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
27250 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
27251 `__v_score`\r\n"
27252 );
27253 assert_eq!(
27254 ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
27255 "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
27256 );
27257 assert_eq!(
27258 ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
27259 "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
27260 schema\r\n"
27261 );
27262 }
27263
27264 #[test]
27272 fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
27273 let mut f = Fixture::new();
27274 vectored(&mut f);
27275 let ask = |f: &mut Fixture, rest: &[&[u8]]| {
27276 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
27277 args.extend_from_slice(rest);
27278 f.run(&args)
27279 };
27280 assert_eq!(
27281 ask(
27282 &mut f,
27283 &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
27284 ),
27285 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
27286 );
27287 assert_eq!(
27288 ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
27289 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
27290 );
27291 assert_eq!(
27292 ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
27293 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
27294 );
27295 assert_eq!(
27298 ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
27299 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
27300 );
27301 assert_eq!(
27302 ask(
27303 &mut f,
27304 &[
27305 b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
27306 ]
27307 ),
27308 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
27309 );
27310 assert_eq!(
27311 ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
27312 "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
27313 );
27314 }
27315
27316 #[test]
27319 fn a_vector_step_says_which_way_it_was_answered() {
27320 let mut f = Fixture::new();
27321 vectored(&mut f);
27322 let tree = |f: &mut Fixture, query: &[u8]| {
27323 let reply = timeless(&f.run(&[
27324 b"FT.PROFILE",
27325 b"h",
27326 b"AGGREGATE",
27327 b"QUERY",
27328 query,
27329 b"PARAMS",
27330 b"2",
27331 b"vec",
27332 ORIGIN,
27333 b"DIALECT",
27334 b"2",
27335 ]));
27336 let at = reply.find("+Iterators profile").expect("a tree");
27337 let end = reply.find("+Result processors").expect("a list of steps");
27338 reply[at..end].to_string()
27339 };
27340 assert_eq!(
27341 tree(&mut f, b"*=>[KNN 3 @v $vec]"),
27342 "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
27343 +Number of reading operations\r\n:3\r\n\
27344 +Vector search mode\r\n+STANDARD_KNN\r\n"
27345 );
27346 assert_eq!(
27348 tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
27349 tree(&mut f, b"*=>[KNN 3 @v $vec]")
27350 );
27351 assert_eq!(
27354 tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
27355 "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
27356 +Number of reading operations\r\n:4\r\n"
27357 );
27358 assert_eq!(
27359 tree(
27360 &mut f,
27361 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
27362 ),
27363 "+Iterators profile\r\n*8\r\n\
27364 +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
27365 +Number of reading operations\r\n:4\r\n\
27366 +Vector search mode\r\n+RANGE_QUERY\r\n"
27367 );
27368 }
27369
27370 #[test]
27374 fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
27375 let mut f = Fixture::new();
27376 vectored(&mut f);
27377 let ask = |f: &mut Fixture, query: &[u8]| {
27378 timeless(&f.run(&[
27379 b"FT.PROFILE",
27380 b"h",
27381 b"AGGREGATE",
27382 b"QUERY",
27383 query,
27384 b"PARAMS",
27385 b"2",
27386 b"vec",
27387 ORIGIN,
27388 b"DIALECT",
27389 b"2",
27390 ]))
27391 };
27392 let cut = |reply: &str| {
27393 let at = reply.find("+Iterators profile").expect("a tree");
27394 reply[at..].to_string()
27395 };
27396 assert_eq!(
27397 cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
27398 "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
27399 +Number of reading operations\r\n:3\r\n\
27400 +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
27401 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
27402 +Number of reading operations\r\n:3\r\n\
27403 +Estimated number of matches\r\n:3\r\n\
27404 +Result processors profile\r\n*2\r\n\
27405 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
27406 *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
27407 +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
27408 );
27409 assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
27412 "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
27413 +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
27414 ));
27415 assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
27418 }
27419
27420 #[test]
27424 fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
27425 let mut f = profiling();
27426 let steps = |f: &mut Fixture, words: &[&[u8]]| {
27427 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
27428 argv.extend_from_slice(words);
27429 let reply = timeless(&f.run(&argv));
27430 let at = reply.find("+Result processors").expect("a list of steps");
27431 reply[at..].to_string()
27432 };
27433 assert_eq!(
27434 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
27435 "+Result processors profile\r\n*2\r\n\
27436 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
27437 *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
27438 +Coordinator\r\n*0\r\n"
27439 );
27440 assert!(
27441 steps(
27442 &mut f,
27443 &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
27444 )
27445 .contains("+Type\r\n+Counter\r\n")
27446 );
27447 assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
27449 "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
27450 +Results processed\r\n:2\r\n"
27451 ));
27452 }
27453
27454 #[test]
27460 fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
27461 let mut f = Fixture::new();
27462 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27463 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27464 f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
27465 assert_eq!(
27466 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
27467 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27468 *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"
27469 );
27470 }
27471
27472 #[test]
27476 fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
27477 let mut f = Fixture::new();
27478 f.run(&[b"HELLO", b"3"]);
27479 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27480 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27481 assert_eq!(
27482 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
27483 "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
27484 *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
27485 );
27486 }
27487
27488 #[test]
27492 fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
27493 let mut f = Fixture::new();
27494 f.run(&[
27495 b"FT.CREATE",
27496 b"e",
27497 b"SCHEMA",
27498 b"a",
27499 b"TEXT",
27500 b"NOSTEM",
27501 b"b",
27502 b"TEXT",
27503 b"NOSTEM",
27504 ]);
27505 f.run(&[b"HSET", b"d1", b"b", b"world"]);
27506 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
27507 assert_eq!(
27508 f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
27509 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
27510 );
27511 }
27512
27513 #[test]
27517 fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
27518 let mut f = Fixture::new();
27519 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27520 f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
27521 assert_eq!(
27522 f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
27523 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
27524 *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"
27525 );
27526 assert_eq!(
27527 f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
27528 "*0\r\n"
27529 );
27530 assert_eq!(
27531 f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
27532 "-Dict does not exist: nope\r\n"
27533 );
27534 }
27535
27536 #[test]
27540 fn a_spellcheck_reads_its_arguments_leniently() {
27541 let mut f = Fixture::new();
27542 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27543 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27544 let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27545 *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
27546 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
27547 let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
27548 let args: &[&[u8]] = &[
27549 b"FT.SPELLCHECK",
27550 b"e",
27551 b"helqp",
27552 b"DISTANCE",
27553 b"1",
27554 b"DISTANCE",
27555 b"4",
27556 ];
27557 assert_eq!(f.run(args), none);
27558 assert_eq!(
27559 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
27560 "-bad distance given, distance must be a natural number between 1 to 4\r\n"
27561 );
27562 assert_eq!(
27563 f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
27564 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
27565 );
27566 }
27567
27568 #[test]
27573 fn an_add_answers_how_many_suggestions_are_in_there_now() {
27574 let mut f = Fixture::new();
27575 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
27576 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
27577 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
27578 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
27579 assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
27580 }
27581
27582 #[test]
27585 fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
27586 let mut f = Fixture::new();
27587 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27588 assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
27589 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
27590 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
27591 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
27592 assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
27593 assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
27594 assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
27595 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
27596 }
27597
27598 #[test]
27601 fn deleting_the_last_suggestion_deletes_the_key() {
27602 let mut f = Fixture::new();
27603 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27604 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
27605 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
27606 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
27607 assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
27608 }
27609
27610 #[test]
27613 fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
27614 let mut f = Fixture::new();
27615 f.run(&[b"SET", b"s", b"x"]);
27616 for cmd in [
27617 vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
27618 vec![&b"FT.SUGGET"[..], b"s", b"t"],
27619 vec![&b"FT.SUGDEL"[..], b"s", b"t"],
27620 vec![&b"FT.SUGLEN"[..], b"s"],
27621 ] {
27622 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
27623 }
27624 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
27625 }
27626
27627 #[test]
27630 fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
27631 let mut f = Fixture::new();
27632 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27633 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27634 f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
27635 assert_eq!(
27636 f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
27637 "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
27638 $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
27639 $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
27640 );
27641 assert_eq!(
27642 f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
27643 "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
27644 );
27645 assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
27646 }
27647
27648 #[test]
27650 fn fuzzy_allows_one_edit_and_nothing_allows_two() {
27651 let mut f = Fixture::new();
27652 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27653 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
27654 assert_eq!(
27655 f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
27656 "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
27657 );
27658 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
27659 }
27660
27661 #[test]
27663 fn a_lookup_answers_five_unless_it_is_told_otherwise() {
27664 let mut f = Fixture::new();
27665 for (term, score) in [
27666 (&b"a1"[..], &b"1"[..]),
27667 (b"a2", b"2"),
27668 (b"a3", b"3"),
27669 (b"a4", b"4"),
27670 (b"a5", b"5"),
27671 (b"a6", b"6"),
27672 ] {
27673 f.run(&[b"FT.SUGADD", b"s", term, score]);
27674 }
27675 assert_eq!(
27676 f.run(&[b"FT.SUGGET", b"s", b"a"]),
27677 "*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"
27678 );
27679 assert_eq!(
27680 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
27681 "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
27682 );
27683 assert!(
27685 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
27686 .starts_with("*6\r\n")
27687 );
27688 }
27689
27690 #[test]
27693 fn a_payload_comes_back_beside_the_term_or_a_null_does() {
27694 let mut f = Fixture::new();
27695 f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
27696 assert_eq!(
27697 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27698 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27699 );
27700 f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
27701 assert_eq!(
27702 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27703 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27704 );
27705 f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
27708 assert_eq!(
27709 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27710 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27711 );
27712 f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
27714 assert_eq!(
27715 f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
27716 "*2\r\n$5\r\nother\r\n$-1\r\n"
27717 );
27718 }
27719
27720 #[test]
27724 fn incr_adds_to_the_score_that_is_already_there() {
27725 let mut f = Fixture::new();
27726 for _ in 0..3 {
27727 f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
27728 }
27729 assert_eq!(
27730 f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
27731 "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
27732 );
27733 }
27734
27735 #[test]
27737 fn the_suggestion_errors_are_the_lines_the_module_sends() {
27738 let mut f = Fixture::new();
27739 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27740 assert_eq!(
27741 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
27742 "-ERR invalid score\r\n"
27743 );
27744 assert_eq!(
27746 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
27747 "-Unknown argument `NOPE`\r\n"
27748 );
27749 assert_eq!(
27750 f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
27751 "-Invalid payload: Expected an argument, but none provided\r\n"
27752 );
27753 assert!(
27755 f.run(&[
27756 b"FT.SUGADD",
27757 b"s",
27758 b"t",
27759 b"1",
27760 b"PAYLOAD",
27761 b"a",
27762 b"PAYLOAD",
27763 b"b"
27764 ])
27765 .contains("wrong number of arguments")
27766 );
27767 assert_eq!(
27768 f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
27769 "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
27770 );
27771 for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
27775 assert_eq!(
27776 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27777 "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
27778 "{}",
27779 String::from_utf8_lossy(max)
27780 );
27781 }
27782 for max in [
27783 &b"abc"[..],
27784 b"0.0",
27785 b"00",
27786 b"-0",
27787 b"+0",
27788 b"0.5",
27789 b"-1.5",
27790 b"1e400",
27791 ] {
27792 assert_eq!(
27793 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27794 "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
27795 "{}",
27796 String::from_utf8_lossy(max)
27797 );
27798 }
27799 for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
27800 assert_eq!(
27801 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27802 "*1\r\n$3\r\none\r\n",
27803 "{}",
27804 String::from_utf8_lossy(max)
27805 );
27806 }
27807 assert_eq!(
27808 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
27809 "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
27810 );
27811 assert_eq!(
27814 f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
27815 "-ERR invalid score\r\n"
27816 );
27817 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
27818 }
27819
27820 #[test]
27824 fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
27825 let mut f = Fixture::new();
27826 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27827 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
27828 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
27829 assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
27830 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
27831 assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
27832 assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
27833 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
27834 }
27835
27836 #[test]
27839 fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
27840 let mut f = Fixture::new();
27841 f.run(&[
27842 b"FT.CREATE",
27843 b"ix",
27844 b"PREFIX",
27845 b"1",
27846 b"p:",
27847 b"SCHEMA",
27848 b"n",
27849 b"NUMERIC",
27850 ]);
27851 f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
27852 assert_eq!(held(&f, b"ix"), (0, 0));
27853
27854 let reply = f.run(&[b"FT.INFO", b"ix"]);
27855 assert!(
27856 reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
27857 "{reply}"
27858 );
27859 assert!(reply.contains("hash_indexing_failures"), "{reply}");
27860 }
27861
27862 #[test]
27865 fn an_index_can_only_be_made_on_database_zero() {
27866 let mut f = Fixture::new();
27867 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
27868 f.run(&[b"SELECT", b"1"]);
27869 let refused = "-Cannot create index on db != 0\r\n";
27870 assert_eq!(
27871 f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
27872 refused
27873 );
27874 assert_eq!(
27876 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27877 refused
27878 );
27879 assert_eq!(
27881 f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
27882 refused
27883 );
27884 assert_eq!(
27886 f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27887 "+OK\r\n"
27888 );
27889 assert_eq!(f.server.search.lock().len(), 1);
27890 }
27891
27892 #[test]
27898 fn the_scan_is_one_database_and_the_following_is_all_of_them() {
27899 let mut f = Fixture::new();
27900 f.run(&[b"SELECT", b"1"]);
27901 f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
27902 f.run(&[b"SELECT", b"0"]);
27903 f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
27904 f.run(&[
27905 b"FT.CREATE",
27906 b"ix",
27907 b"PREFIX",
27908 b"1",
27909 b"p:",
27910 b"SCHEMA",
27911 b"t",
27912 b"TEXT",
27913 ]);
27914 assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
27915
27916 f.run(&[b"SELECT", b"1"]);
27917 f.run(&[b"HSET", b"p:8", b"t", b"later"]);
27918 assert_eq!(
27919 held(&f, b"ix"),
27920 (2, 2),
27921 "and then it follows every database"
27922 );
27923 }
27924
27925 fn corpus(f: &mut Fixture) {
27928 f.run(&[
27929 b"FT.CREATE",
27930 b"sx",
27931 b"PREFIX",
27932 b"1",
27933 b"d:",
27934 b"SCHEMA",
27935 b"t",
27936 b"TEXT",
27937 b"g",
27938 b"TAG",
27939 b"n",
27940 b"NUMERIC",
27941 ]);
27942 for (key, text, tag, number) in [
27943 (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
27944 (b"d:2", "alpha gamma", "bb", "2"),
27945 (b"d:3", "delta", "cc", "3"),
27946 (b"d:4", "alpha beta gamma", "aa,cc", "4"),
27947 ] {
27948 f.run(&[
27949 b"HSET",
27950 key,
27951 b"t",
27952 text.as_bytes(),
27953 b"g",
27954 tag.as_bytes(),
27955 b"n",
27956 number.as_bytes(),
27957 ]);
27958 }
27959 }
27960
27961 fn sortable(f: &mut Fixture) {
27965 f.run(&[
27966 b"FT.CREATE",
27967 b"sy",
27968 b"PREFIX",
27969 b"1",
27970 b"s:",
27971 b"SCHEMA",
27972 b"t",
27973 b"TEXT",
27974 b"SORTABLE",
27975 b"n",
27976 b"NUMERIC",
27977 b"SORTABLE",
27978 b"body",
27979 b"AS",
27980 b"b",
27981 b"TEXT",
27982 b"SORTABLE",
27983 b"p",
27984 b"TEXT",
27985 ]);
27986 for (key, text, number) in [
27987 (b"s:1".as_slice(), "Banana Split", "2"),
27988 (b"s:2", "apple", "10"),
27989 ] {
27990 f.run(&[
27991 b"HSET",
27992 key,
27993 b"t",
27994 text.as_bytes(),
27995 b"n",
27996 number.as_bytes(),
27997 b"body",
27998 text.as_bytes(),
27999 b"p",
28000 b"alpha",
28001 ]);
28002 }
28003 f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
28006 }
28007
28008 #[test]
28011 fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
28012 let mut f = Fixture::new();
28013 sortable(&mut f);
28014 assert_eq!(
28015 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
28016 "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
28017 );
28018 assert_eq!(
28019 f.run(&[
28020 b"FT.SEARCH",
28021 b"sy",
28022 b"alpha",
28023 b"SORTBY",
28024 b"n",
28025 b"DESC",
28026 b"NOCONTENT"
28027 ]),
28028 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
28029 );
28030 assert_eq!(
28033 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
28034 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
28035 );
28036 }
28037
28038 #[test]
28041 fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
28042 let mut f = Fixture::new();
28043 sortable(&mut f);
28044 f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
28045 assert_eq!(
28046 f.run(&[
28047 b"FT.SEARCH",
28048 b"sy",
28049 b"alpha",
28050 b"SORTBY",
28051 b"p",
28052 b"NOCONTENT",
28053 b"LIMIT",
28054 b"0",
28055 b"2"
28056 ]),
28057 "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
28058 );
28059 assert_eq!(
28062 f.run(&[
28063 b"FT.SEARCH",
28064 b"sy",
28065 b"alpha",
28066 b"SORTBY",
28067 b"p",
28068 b"WITHSORTKEYS",
28069 b"NOCONTENT",
28070 b"LIMIT",
28071 b"2",
28072 b"1"
28073 ]),
28074 "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
28075 );
28076 }
28077
28078 #[test]
28081 fn a_search_can_send_the_value_it_sorted_by_back() {
28082 let mut f = Fixture::new();
28083 sortable(&mut f);
28084 assert_eq!(
28085 f.run(&[
28086 b"FT.SEARCH",
28087 b"sy",
28088 b"alpha",
28089 b"SORTBY",
28090 b"n",
28091 b"WITHSORTKEYS",
28092 b"NOCONTENT"
28093 ]),
28094 concat!(
28095 "*7\r\n:3\r\n",
28096 "$3\r\ns:1\r\n$2\r\n#2\r\n",
28097 "$3\r\ns:2\r\n$3\r\n#10\r\n",
28098 "$3\r\ns:3\r\n$-1\r\n"
28099 )
28100 );
28101 assert_eq!(
28102 f.run(&[
28103 b"FT.SEARCH",
28104 b"sy",
28105 b"alpha",
28106 b"SORTBY",
28107 b"t",
28108 b"WITHSORTKEYS",
28109 b"NOCONTENT"
28110 ]),
28111 concat!(
28112 "*7\r\n:3\r\n",
28113 "$3\r\ns:2\r\n$6\r\n$apple\r\n",
28114 "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
28115 "$3\r\ns:3\r\n$-1\r\n"
28116 )
28117 );
28118 assert_eq!(
28121 f.run(&[
28122 b"FT.SEARCH",
28123 b"sy",
28124 b"banana",
28125 b"WITHSORTKEYS",
28126 b"NOCONTENT"
28127 ]),
28128 "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
28129 );
28130 }
28131
28132 #[test]
28135 fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
28136 let mut f = Fixture::new();
28137 sortable(&mut f);
28138 assert_eq!(
28142 f.run(&[
28143 b"FT.SEARCH",
28144 b"sy",
28145 b"alpha",
28146 b"SORTBY",
28147 b"b",
28148 b"LIMIT",
28149 b"0",
28150 b"1"
28151 ]),
28152 concat!(
28153 "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
28154 "$1\r\nb\r\n$5\r\napple\r\n",
28155 "$1\r\nt\r\n$5\r\napple\r\n",
28156 "$1\r\nn\r\n$2\r\n10\r\n",
28157 "$4\r\nbody\r\n$5\r\napple\r\n",
28158 "$1\r\np\r\n$5\r\nalpha\r\n"
28159 )
28160 );
28161 assert_eq!(
28164 f.run(&[
28165 b"FT.SEARCH",
28166 b"sy",
28167 b"alpha",
28168 b"SORTBY",
28169 b"b",
28170 b"RETURN",
28171 b"2",
28172 b"p",
28173 b"b",
28174 b"LIMIT",
28175 b"0",
28176 b"1"
28177 ]),
28178 concat!(
28179 "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
28180 "$1\r\nb\r\n$5\r\napple\r\n",
28181 "$1\r\np\r\n$5\r\nalpha\r\n"
28182 )
28183 );
28184 }
28185
28186 #[test]
28188 fn a_search_refuses_the_sorts_it_cannot_run() {
28189 let mut f = Fixture::new();
28190 sortable(&mut f);
28191 assert_eq!(
28192 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
28193 "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
28194 );
28195 assert_eq!(
28196 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
28197 "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
28198 );
28199 assert_eq!(
28200 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
28201 "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
28202 );
28203 assert_eq!(
28204 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
28205 "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
28206 );
28207 assert_eq!(
28210 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
28211 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
28212 );
28213 }
28214
28215 fn marking(f: &mut Fixture) {
28219 f.run(&[
28220 b"FT.CREATE",
28221 b"mk",
28222 b"ON",
28223 b"HASH",
28224 b"PREFIX",
28225 b"1",
28226 b"m:",
28227 b"SCHEMA",
28228 b"a",
28229 b"TEXT",
28230 b"b",
28231 b"TEXT",
28232 b"n",
28233 b"NUMERIC",
28234 b"g",
28235 b"TAG",
28236 ]);
28237 f.run(&[
28238 b"HSET",
28239 b"m:1",
28240 b"a",
28241 b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
28242 b"b",
28243 b"t1 t2 t3 t4 t5 t6 t7 t8",
28244 b"n",
28245 b"1",
28246 b"g",
28247 b"red",
28248 ]);
28249 }
28250
28251 #[test]
28254 fn a_summarize_cuts_a_field_down_to_what_matched() {
28255 let mut f = Fixture::new();
28256 marking(&mut f);
28257 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
28258 assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
28259 assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
28261 assert!(got.contains("$1\r\nr\r\n"), "{got}");
28263 }
28264
28265 #[test]
28269 fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
28270 let mut f = Fixture::new();
28271 marking(&mut f);
28272 let got = f.run(&[
28273 b"FT.SEARCH",
28274 b"mk",
28275 b"fox",
28276 b"SUMMARIZE",
28277 b"FRAGS",
28278 b"1",
28279 b"LEN",
28280 b"20",
28281 ]);
28282 assert!(
28283 got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
28284 "{got}"
28285 );
28286 let two = f.run(&[
28289 b"FT.SEARCH",
28290 b"mk",
28291 b"fox",
28292 b"SUMMARIZE",
28293 b"FRAGS",
28294 b"2",
28295 b"LEN",
28296 b"20",
28297 ]);
28298 assert!(
28299 two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
28300 "{two}"
28301 );
28302 }
28303
28304 #[test]
28307 fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
28308 let mut f = Fixture::new();
28309 marking(&mut f);
28310 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
28311 assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
28312 let both = f.run(&[
28313 b"FT.SEARCH",
28314 b"mk",
28315 b"fox",
28316 b"SUMMARIZE",
28317 b"LEN",
28318 b"2",
28319 b"HIGHLIGHT",
28320 ]);
28321 assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
28322 assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
28324 assert!(both.contains("$3\r\nred\r\n"), "{both}");
28325 let split = f.run(&[
28327 b"FT.SEARCH",
28328 b"mk",
28329 b"fox",
28330 b"SUMMARIZE",
28331 b"FIELDS",
28332 b"1",
28333 b"b",
28334 b"LEN",
28335 b"2",
28336 b"HIGHLIGHT",
28337 b"FIELDS",
28338 b"1",
28339 b"a",
28340 ]);
28341 assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
28342 }
28343
28344 #[test]
28346 fn a_highlight_does_not_mark_a_tag() {
28347 let mut f = Fixture::new();
28348 marking(&mut f);
28349 f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
28350 let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
28351 assert!(!got.contains("<b>"), "{got}");
28352 assert!(got.contains("red and blue"), "{got}");
28353 }
28354
28355 #[test]
28358 fn a_search_answers_a_total_and_then_the_rows() {
28359 let mut f = Fixture::new();
28360 corpus(&mut f);
28361 assert_eq!(
28362 f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
28363 "*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"
28364 );
28365 f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
28368 assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
28369 assert_eq!(
28372 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
28373 "*2\r\n:1\r\n$3\r\nd:3\r\n"
28374 );
28375 assert_eq!(
28376 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
28377 "*1\r\n:3\r\n"
28378 );
28379 }
28380
28381 #[test]
28384 fn the_window_is_ten_rows_and_a_million_wide_at_most() {
28385 let mut f = Fixture::new();
28386 corpus(&mut f);
28387 assert_eq!(
28388 f.run(&[
28389 b"FT.SEARCH",
28390 b"sx",
28391 b"alpha",
28392 b"NOCONTENT",
28393 b"LIMIT",
28394 b"1",
28395 b"1"
28396 ]),
28397 "*2\r\n:3\r\n$3\r\nd:2\r\n"
28398 );
28399 assert_eq!(
28400 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
28401 "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
28402 );
28403 assert_eq!(
28404 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
28405 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
28406 );
28407 assert_eq!(
28408 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
28409 "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
28410 );
28411 assert_eq!(
28412 f.run(&[
28413 b"FT.SEARCH",
28414 b"sx",
28415 b"alpha",
28416 b"NOCONTENT",
28417 b"LIMIT",
28418 b"999999",
28419 b"1000000"
28420 ]),
28421 "*1\r\n:3\r\n"
28422 );
28423 }
28424
28425 #[test]
28429 fn a_return_of_nothing_is_not_the_same_as_nocontent() {
28430 let mut f = Fixture::new();
28431 corpus(&mut f);
28432 let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
28433 assert_eq!(
28434 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
28435 bare
28436 );
28437 assert_eq!(
28438 f.run(&[
28439 b"FT.SEARCH",
28440 b"sx",
28441 b"delta",
28442 b"NOCONTENT",
28443 b"RETURN",
28444 b"1",
28445 b"t"
28446 ]),
28447 bare
28448 );
28449 assert_eq!(
28450 f.run(&[
28451 b"FT.SEARCH",
28452 b"sx",
28453 b"delta",
28454 b"RETURN",
28455 b"0",
28456 b"RETURN",
28457 b"1",
28458 b"t"
28459 ]),
28460 "*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"
28461 );
28462 }
28463
28464 #[test]
28467 fn the_count_after_return_counts_words() {
28468 let mut f = Fixture::new();
28469 corpus(&mut f);
28470 assert_eq!(
28473 f.run(&[
28474 b"FT.SEARCH",
28475 b"sx",
28476 b"delta",
28477 b"RETURN",
28478 b"3",
28479 b"t",
28480 b"AS",
28481 b"x"
28482 ]),
28483 "*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"
28484 );
28485 assert_eq!(
28488 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
28489 "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
28490 );
28491 assert_eq!(
28492 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
28493 "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
28494 );
28495 assert_eq!(
28499 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
28500 "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
28501 );
28502 }
28503
28504 #[test]
28507 fn a_filter_is_a_range_written_outside_the_query() {
28508 let mut f = Fixture::new();
28509 corpus(&mut f);
28510 assert_eq!(
28511 f.run(&[
28512 b"FT.SEARCH",
28513 b"sx",
28514 b"alpha",
28515 b"NOCONTENT",
28516 b"FILTER",
28517 b"n",
28518 b"2",
28519 b"4"
28520 ]),
28521 "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
28522 );
28523 assert_eq!(
28524 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
28525 "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
28526 );
28527 assert_eq!(
28528 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
28529 "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
28530 );
28531 assert_eq!(
28532 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
28533 "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
28534 );
28535 for field in [b"g".as_slice(), b"nope"] {
28538 assert_eq!(
28539 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
28540 "*1\r\n:0\r\n"
28541 );
28542 }
28543 }
28544
28545 #[test]
28548 fn the_index_is_found_before_the_arguments_are_read() {
28549 let mut f = Fixture::new();
28550 corpus(&mut f);
28551 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
28552 assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
28553 assert_eq!(
28554 f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
28555 missing
28556 );
28557 assert_eq!(
28560 f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
28561 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
28562 );
28563 }
28564
28565 #[test]
28568 fn inkeys_comes_off_the_total() {
28569 let mut f = Fixture::new();
28570 corpus(&mut f);
28571 assert_eq!(
28572 f.run(&[
28573 b"FT.SEARCH",
28574 b"sx",
28575 b"alpha",
28576 b"NOCONTENT",
28577 b"INKEYS",
28578 b"1",
28579 b"d:1"
28580 ]),
28581 "*2\r\n:1\r\n$3\r\nd:1\r\n"
28582 );
28583 assert_eq!(
28584 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
28585 "*1\r\n:0\r\n"
28586 );
28587 }
28588
28589 #[test]
28596 fn the_fields_are_read_from_the_session_database() {
28597 let mut f = Fixture::new();
28598 corpus(&mut f);
28599 f.run(&[b"SELECT", b"1"]);
28600 f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
28601 assert_eq!(
28604 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
28605 "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
28606 );
28607 assert_eq!(
28608 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28609 "*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"
28610 );
28611 }
28612
28613 #[test]
28616 fn the_third_protocol_answers_a_map_of_five() {
28617 let mut f = Fixture::new();
28618 corpus(&mut f);
28619 f.out = Out::new(Proto::Resp3);
28620 assert_eq!(
28621 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28622 concat!(
28623 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28624 "%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",
28625 "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28626 )
28627 );
28628 }
28629
28630 #[test]
28634 fn a_window_of_nothing_has_to_start_at_the_top() {
28635 let mut f = Fixture::new();
28636 corpus(&mut f);
28637 let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
28638 assert_eq!(
28639 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28640 refused
28641 );
28642 assert_eq!(
28643 f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28644 refused
28645 );
28646 assert_eq!(
28647 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28648 refused
28649 );
28650 assert_eq!(
28651 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
28652 "*1\r\n:3\r\n"
28653 );
28654 }
28655
28656 #[test]
28659 fn an_aggregation_answers_a_count_and_then_the_properties() {
28660 let mut f = Fixture::new();
28661 corpus(&mut f);
28662 assert_eq!(
28663 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
28664 "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
28665 );
28666 assert_eq!(
28670 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
28671 concat!(
28672 "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28673 "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
28674 "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
28675 )
28676 );
28677 assert_eq!(
28681 f.run(&[
28682 b"FT.AGGREGATE",
28683 b"sx",
28684 b"alpha",
28685 b"LOAD",
28686 b"1",
28687 b"@n",
28688 b"LIMIT",
28689 b"1",
28690 b"2"
28691 ]),
28692 "*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"
28693 );
28694 assert_eq!(
28696 f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
28697 "*1\r\n:0\r\n"
28698 );
28699 }
28700
28701 #[test]
28705 fn a_load_counts_words_and_can_rename_what_it_reads() {
28706 let mut f = Fixture::new();
28707 corpus(&mut f);
28708 assert_eq!(
28710 f.run(&[
28711 b"FT.AGGREGATE",
28712 b"sx",
28713 b"alpha",
28714 b"LOAD",
28715 b"3",
28716 b"@t",
28717 b"AS",
28718 b"text"
28719 ]),
28720 concat!(
28721 "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
28722 "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
28723 "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
28724 )
28725 );
28726 assert_eq!(
28727 f.run(&[
28728 b"FT.AGGREGATE",
28729 b"sx",
28730 b"alpha",
28731 b"LOAD",
28732 b"*",
28733 b"LIMIT",
28734 b"0",
28735 b"1"
28736 ]),
28737 concat!(
28738 "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28739 "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
28740 )
28741 );
28742 assert_eq!(
28744 f.run(&[
28745 b"FT.AGGREGATE",
28746 b"sx",
28747 b"alpha",
28748 b"LOAD",
28749 b"2",
28750 b"@n",
28751 b"@nope",
28752 b"LIMIT",
28753 b"0",
28754 b"2"
28755 ]),
28756 "*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"
28757 );
28758 }
28759
28760 #[test]
28763 fn a_load_refuses_a_count_it_cannot_use() {
28764 let mut f = Fixture::new();
28765 corpus(&mut f);
28766 let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
28767 assert_eq!(
28768 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
28769 format!("{head}Expected number of fields or `*`\r\n")
28770 );
28771 assert_eq!(
28772 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
28773 format!("{head}Value is outside acceptable bounds\r\n")
28774 );
28775 assert_eq!(
28776 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
28777 format!("{head}Expected an argument, but none provided\r\n")
28778 );
28779 assert_eq!(
28780 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
28781 format!("{head}Expected an argument, but none provided\r\n")
28782 );
28783 assert_eq!(
28787 f.run(&[
28788 b"FT.AGGREGATE",
28789 b"sx",
28790 b"alpha",
28791 b"LOAD",
28792 b"2",
28793 b"@t",
28794 b"AS"
28795 ]),
28796 "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
28797 );
28798 assert_eq!(
28801 f.run(&[
28802 b"FT.AGGREGATE",
28803 b"sx",
28804 b"alpha",
28805 b"LOAD",
28806 b"2",
28807 b"@t",
28808 b"AS",
28809 b"VERBATIM"
28810 ]),
28811 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
28812 );
28813 assert_eq!(
28817 f.run(&[
28818 b"FT.AGGREGATE",
28819 b"sx",
28820 b"alpha",
28821 b"LOAD",
28822 b"0",
28823 b"LIMIT",
28824 b"0",
28825 b"1"
28826 ]),
28827 "*2\r\n:1\r\n*0\r\n"
28828 );
28829 }
28830
28831 #[test]
28834 fn a_pipeline_step_closes_the_door_on_the_search_words() {
28835 let mut f = Fixture::new();
28836 corpus(&mut f);
28837 assert_eq!(
28838 f.run(&[
28839 b"FT.AGGREGATE",
28840 b"sx",
28841 b"alpha",
28842 b"LOAD",
28843 b"1",
28844 b"@t",
28845 b"VERBATIM"
28846 ]),
28847 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
28848 );
28849 assert_eq!(
28850 f.run(&[
28851 b"FT.AGGREGATE",
28852 b"sx",
28853 b"alpha",
28854 b"LIMIT",
28855 b"0",
28856 b"1",
28857 b"VERBATIM"
28858 ]),
28859 "*2\r\n:1\r\n*0\r\n"
28860 );
28861 for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
28864 let name = core::str::from_utf8(word).expect("the three words are text");
28865 assert_eq!(
28866 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
28867 format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
28868 );
28869 }
28870 }
28871
28872 #[test]
28875 fn addscores_writes_a_shorter_score_than_withscores() {
28876 let mut f = Fixture::new();
28877 corpus(&mut f);
28878 assert_eq!(
28879 f.run(&[
28880 b"FT.AGGREGATE",
28881 b"sx",
28882 b"alpha",
28883 b"ADDSCORES",
28884 b"LOAD",
28885 b"1",
28886 b"@n",
28887 b"LIMIT",
28888 b"0",
28889 b"2"
28890 ]),
28891 concat!(
28892 "*3\r\n:1\r\n",
28893 "*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",
28894 "*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"
28895 )
28896 );
28897 assert_eq!(
28901 f.run(&[
28902 b"FT.AGGREGATE",
28903 b"sx",
28904 b"alpha",
28905 b"NOCONTENT",
28906 b"WITHSCORES",
28907 b"LIMIT",
28908 b"0",
28909 b"2"
28910 ]),
28911 "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
28912 );
28913 assert_eq!(
28914 f.run(&[
28915 b"FT.AGGREGATE",
28916 b"sx",
28917 b"alpha",
28918 b"WITHSORTKEYS",
28919 b"LOAD",
28920 b"1",
28921 b"@n",
28922 b"LIMIT",
28923 b"0",
28924 b"1"
28925 ]),
28926 "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
28927 );
28928 }
28929
28930 #[test]
28933 fn a_normalising_scorer_answers_the_rows_backwards() {
28934 let mut f = Fixture::new();
28935 corpus(&mut f);
28936 assert_eq!(
28937 f.run(&[
28938 b"FT.AGGREGATE",
28939 b"sx",
28940 b"alpha",
28941 b"SCORER",
28942 b"BM25STD.NORM",
28943 b"ADDSCORES",
28944 b"LOAD",
28945 b"1",
28946 b"@n",
28947 b"LIMIT",
28948 b"1",
28949 b"2"
28950 ]),
28951 concat!(
28952 "*3\r\n:3\r\n",
28953 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
28954 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
28955 )
28956 );
28957 assert_eq!(
28960 f.run(&[
28961 b"FT.AGGREGATE",
28962 b"sx",
28963 b"alpha",
28964 b"SCORER",
28965 b"BM25STD.NORM",
28966 b"LOAD",
28967 b"1",
28968 b"@n",
28969 b"LIMIT",
28970 b"1",
28971 b"2"
28972 ]),
28973 "*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"
28974 );
28975 }
28976
28977 #[test]
28980 fn an_aggregation_answers_a_map_of_five_as_well() {
28981 let mut f = Fixture::new();
28982 corpus(&mut f);
28983 f.out = Out::new(Proto::Resp3);
28984 assert_eq!(
28985 f.run(&[
28986 b"FT.AGGREGATE",
28987 b"sx",
28988 b"alpha",
28989 b"ADDSCORES",
28990 b"WITHSCORES",
28991 b"WITHSORTKEYS",
28992 b"LOAD",
28993 b"1",
28994 b"@n",
28995 b"LIMIT",
28996 b"0",
28997 b"1"
28998 ]),
28999 concat!(
29000 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
29001 "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
29002 "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
29003 "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
29004 "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
29005 )
29006 );
29007 assert_eq!(
29010 f.run(&[
29011 b"FT.AGGREGATE",
29012 b"sx",
29013 b"alpha",
29014 b"NOCONTENT",
29015 b"LIMIT",
29016 b"0",
29017 b"1"
29018 ]),
29019 concat!(
29020 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
29021 "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
29022 )
29023 );
29024 }
29025 const INFO_FIELDS: &[&str] = &[
29033 "id",
29034 "addr",
29035 "laddr",
29036 "fd",
29037 "name",
29038 "age",
29039 "idle",
29040 "flags",
29041 "db",
29042 "sub",
29043 "psub",
29044 "ssub",
29045 "multi",
29046 "watch",
29047 "qbuf",
29048 "qbuf-free",
29049 "argv-mem",
29050 "multi-mem",
29051 "rbs",
29052 "rbp",
29053 "obl",
29054 "oll",
29055 "omem",
29056 "omem-shared",
29057 "omem-unshared",
29058 "tot-mem",
29059 "events",
29060 "cmd",
29061 "user",
29062 "redir",
29063 "resp",
29064 "lib-name",
29065 "lib-ver",
29066 "io-thread",
29067 "tot-net-in",
29068 "tot-net-out",
29069 "tot-cmds",
29070 "read-events",
29071 "avg-pipeline-len-sum",
29072 "avg-pipeline-len-cnt",
29073 ];
29074
29075 fn client_info(f: &mut Fixture) -> Vec<(String, String)> {
29078 let reply = f.run(&[b"CLIENT", b"INFO"]);
29079 let body = reply.split_once("\r\n").expect("a bulk header").1;
29080 let line = body.trim_end_matches("\r\n").trim_start_matches("txt:");
29083 assert!(
29084 line.ends_with('\n'),
29085 "the report ends in a newline: {line:?}"
29086 );
29087 line.trim_end()
29088 .split(' ')
29089 .map(|pair| {
29090 let (name, value) = pair.split_once('=').expect("name=value");
29091 (name.to_string(), value.to_string())
29092 })
29093 .collect()
29094 }
29095
29096 fn client_field(f: &mut Fixture, name: &str) -> String {
29098 client_info(f)
29099 .into_iter()
29100 .find(|(n, _)| n == name)
29101 .map(|(_, v)| v)
29102 .unwrap_or_else(|| panic!("no {name} field"))
29103 }
29104
29105 #[test]
29106 fn client_info_names_every_field_a_real_server_names() {
29107 let mut f = Fixture::new();
29108 let got: Vec<String> = client_info(&mut f).into_iter().map(|(n, _)| n).collect();
29109 assert_eq!(got, INFO_FIELDS);
29110 }
29111
29112 #[test]
29115 fn a_connection_with_no_socket_reports_no_address_and_no_descriptor() {
29116 let mut f = Fixture::new();
29117 assert_eq!(client_field(&mut f, "addr"), "");
29118 assert_eq!(client_field(&mut f, "laddr"), "");
29119 assert_eq!(client_field(&mut f, "fd"), "-1");
29120 assert_eq!(client_field(&mut f, "id"), "7");
29121 }
29122
29123 #[test]
29124 fn client_setname_takes_a_name_back_and_refuses_one_with_a_space_in_it() {
29125 let mut f = Fixture::new();
29126 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$-1\r\n");
29127 assert_eq!(f.run(&[b"CLIENT", b"SETNAME", b"worker"]), "+OK\r\n");
29128 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
29129 assert_eq!(client_field(&mut f, "name"), "worker");
29130 assert_eq!(
29131 f.run(&[b"CLIENT", b"SETNAME", b"two words"]),
29132 "-ERR Client names cannot contain spaces, newlines or special characters.\r\n"
29133 );
29134 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
29136 }
29137
29138 #[test]
29141 fn reset_clears_the_name_and_the_switches_and_keeps_the_library() {
29142 let mut f = Fixture::new();
29143 f.run(&[b"CLIENT", b"SETNAME", b"worker"]);
29144 f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME", b"yo-py"]);
29145 f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"1.2.3"]);
29146 f.run(&[b"CLIENT", b"NO-EVICT", b"on"]);
29147 f.run(&[b"CLIENT", b"NO-TOUCH", b"on"]);
29148 assert_eq!(client_field(&mut f, "flags"), "eT");
29149
29150 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
29151 assert_eq!(client_field(&mut f, "name"), "");
29152 assert_eq!(client_field(&mut f, "flags"), "N");
29153 assert_eq!(client_field(&mut f, "lib-name"), "yo-py");
29154 assert_eq!(client_field(&mut f, "lib-ver"), "1.2.3");
29155 }
29156
29157 #[test]
29158 fn client_setinfo_complains_the_way_a_real_server_does() {
29159 let mut f = Fixture::new();
29160 assert_eq!(
29161 f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME"]),
29162 "-ERR wrong number of arguments for 'client|setinfo' command\r\n"
29163 );
29164 assert_eq!(
29165 f.run(&[b"CLIENT", b"SETINFO", b"NOPE", b"x"]),
29166 "-ERR Unrecognized option 'NOPE'\r\n"
29167 );
29168 assert_eq!(
29169 f.run(&[b"CLIENT", b"SETINFO", b"lib-name", b"ok x"]),
29170 "-ERR lib-name cannot contain spaces, newlines or special characters.\r\n"
29171 );
29172 assert_eq!(
29173 f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"has space"]),
29174 "-ERR lib-ver cannot contain spaces, newlines or special characters.\r\n"
29175 );
29176 }
29177
29178 #[test]
29179 fn client_refuses_a_subcommand_it_does_not_have_and_arguments_it_did_not_ask_for() {
29180 let mut f = Fixture::new();
29181 assert_eq!(
29182 f.run(&[b"CLIENT", b"NOPE"]),
29183 "-ERR unknown subcommand 'NOPE'. Try CLIENT HELP.\r\n"
29184 );
29185 assert_eq!(
29186 f.run(&[b"CLIENT", b"GETNAME", b"extra"]),
29187 "-ERR wrong number of arguments for 'client|getname' command\r\n"
29188 );
29189 assert_eq!(
29190 f.run(&[b"CLIENT", b"NO-EVICT", b"maybe"]),
29191 "-ERR syntax error\r\n"
29192 );
29193 assert_eq!(
29194 f.run(&[b"CLIENT", b"REPLY", b"BAD"]),
29195 "-ERR syntax error\r\n"
29196 );
29197 }
29198
29199 #[test]
29203 fn client_info_counts_the_three_subscribe_namespaces_apart() {
29204 let mut f = Fixture::new();
29205 f.run(&[b"HELLO", b"3"]);
29208 f.run(&[b"SUBSCRIBE", b"a", b"b"]);
29209 f.run(&[b"PSUBSCRIBE", b"p*"]);
29210 f.run(&[b"SSUBSCRIBE", b"s"]);
29211 let info = client_info(&mut f);
29212 let at = |name: &str| {
29213 info.iter()
29214 .find(|(n, _)| n == name)
29215 .map(|(_, v)| v.clone())
29216 .unwrap()
29217 };
29218 assert_eq!(at("sub"), "2");
29219 assert_eq!(at("psub"), "1");
29220 assert_eq!(at("ssub"), "1");
29221 assert_eq!(at("flags"), "P");
29222 forget_session(&f.server, &mut f.session);
29223 }
29224
29225 #[test]
29228 fn client_info_reports_itself_as_the_command_running() {
29229 let mut f = Fixture::new();
29230 assert_eq!(client_field(&mut f, "cmd"), "client|info");
29231 f.run(&[b"GET", b"nothing"]);
29232 assert_eq!(client_field(&mut f, "cmd"), "client|info");
29235 }
29236
29237 #[test]
29239 fn the_command_field_is_lower_case_however_the_client_spelled_it() {
29240 let mut f = Fixture::new();
29241 let reply = f.run(&[b"CLIENT", b"Info"]);
29242 assert!(reply.contains("cmd=client|info"), "{reply}");
29243 }
29244
29245 #[test]
29246 fn client_help_lists_the_subcommands_that_are_here() {
29247 let mut f = Fixture::new();
29248 let reply = f.run(&[b"CLIENT", b"HELP"]);
29249 for sub in ["ID", "GETNAME", "SETNAME", "SETINFO", "INFO", "REPLY"] {
29250 assert!(reply.contains(sub), "no {sub} in {reply}");
29251 }
29252 assert!(!reply.contains("TRACKING"), "{reply}");
29255 }
29256}