1mod args;
55mod arrays;
56mod backup;
57mod bits;
58mod blocking;
59mod bloom;
60mod cms;
61mod cpu;
62mod cuckoo;
63mod geo;
64mod graph;
65mod hashes;
66mod himport;
67mod hll;
68mod indexing;
69mod json;
70mod keyspace;
71mod lists;
72mod lua;
73mod migrate;
74mod misses;
75mod multi;
76mod notify;
77mod pubsub;
78mod scan;
79mod scripting;
80mod search;
81mod server;
82mod sets;
83mod streams;
84mod strings;
85mod suggest;
86pub mod table;
87mod tdigest;
88mod topk;
89mod ts;
90mod vectors;
91mod vfilter;
92mod zsets;
93
94pub use args::Args;
95pub use blocking::{Parked, Waiters};
96pub(crate) use pubsub::Envelope;
97pub use server::parse_memory;
98pub use table::{COMMANDS, Spec, arity_ok, lookup};
99
100use crate::reply::Out;
101use std::cell::Cell;
102use std::path::{Path, PathBuf};
103use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
104use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize};
105use yo_common::lock::{Held, Lock};
106use yo_common::{Code, Error};
107use yo_kv::cold::Store;
108use yo_kv::{Clock, Db, Keyspace};
109use yo_search::Registry;
110
111use multi::Watches;
112use search::cursor::Cursors;
113
114pub const DATABASES: usize = 16;
121
122const ALL_DATABASES: u64 = if DATABASES == 64 {
129 u64::MAX
130} else {
131 (1u64 << DATABASES) - 1
132};
133const _: () = assert!(DATABASES <= 64);
134
135const EVICT_BUDGET: usize = 64;
147
148const NO_MAXSTORE: u64 = u64::MAX;
156
157const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum Flow {
166 Continue,
168 Close,
170 Block,
177}
178
179#[derive(Debug, Default)]
189pub struct Counter(AtomicU64);
190
191impl Counter {
192 fn bump(&self) {
194 self.0.store(self.get().wrapping_add(1), Relaxed);
195 }
196
197 fn drop_one(&self) {
204 self.0.store(self.get().saturating_sub(1), Relaxed);
205 }
206
207 fn get(&self) -> u64 {
209 self.0.load(Relaxed)
210 }
211
212 fn zero(&self) {
214 self.0.store(0, Relaxed);
215 }
216}
217
218#[derive(Debug, Default)]
224pub struct Stats {
225 clients: Counter,
227 connections: Counter,
229 commands: Counter,
231}
232
233impl Stats {
234 pub fn opened(&self) {
236 self.clients.bump();
237 self.connections.bump();
238 }
239
240 pub fn closed(&self) {
242 self.clients.drop_one();
243 }
244}
245
246#[derive(Debug, Clone, Copy, Default)]
248pub struct Totals {
249 pub clients: u64,
251 pub connections: u64,
253 pub commands: u64,
255}
256
257thread_local! {
258 static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
266}
267
268#[derive(Debug)]
279#[repr(align(64))]
280struct Local {
281 stats: Stats,
283 cmdstats: CommandStats,
285 dirty: AtomicU64,
294 turn: AtomicU64,
307 parked: AtomicUsize,
319}
320
321impl Default for Local {
322 fn default() -> Local {
323 Local {
324 stats: Stats::default(),
325 cmdstats: CommandStats::default(),
326 dirty: AtomicU64::new(0),
327 turn: AtomicU64::new(ALL_DATABASES),
328 parked: AtomicUsize::new(0),
329 }
330 }
331}
332
333impl Local {
334 fn mark(&self, dbs: u64) {
336 self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
337 }
338
339 fn note(&self, dbs: u64) {
341 self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
342 }
343
344 fn done(&self, at: usize) {
346 self.turn
347 .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
348 }
349
350 fn wanted(&self, at: usize) -> bool {
352 self.turn.load(Relaxed) & (1u64 << at) != 0
353 }
354
355 fn blocked(&self, n: usize) {
357 self.parked
358 .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
359 }
360
361 fn woke(&self, n: usize) {
363 self.parked
364 .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
365 }
366}
367
368fn one_thread() -> Box<[Local]> {
370 slots(1)
371}
372
373fn slots(threads: usize) -> Box<[Local]> {
375 (0..threads.max(1)).map(|_| Local::default()).collect()
376}
377
378fn working_dir() -> PathBuf {
385 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
386}
387
388#[derive(Debug, Clone, Copy, Default)]
397pub struct CommandStat {
398 pub calls: u64,
400 pub rejected: u64,
403 pub failed: u64,
405}
406
407impl CommandStat {
408 const fn seen(&self) -> bool {
414 self.calls != 0 || self.rejected != 0 || self.failed != 0
415 }
416}
417
418#[derive(Debug, Default)]
423struct Row {
424 calls: Counter,
426 rejected: Counter,
428 failed: Counter,
430}
431
432#[derive(Debug)]
439struct CommandStats(Box<[Row]>);
440
441impl Default for CommandStats {
442 fn default() -> CommandStats {
443 CommandStats((0..table::count()).map(|_| Row::default()).collect())
444 }
445}
446
447impl CommandStats {
448 fn at(&self, spec: &'static Spec) -> &Row {
450 &self.0[table::index_of(spec)]
451 }
452}
453
454pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
460
461const _: () = {
466 const fn shareable<T: Send + Sync>() {}
467 shareable::<Server>();
468};
469
470pub struct Server {
478 dbs: Vec<Db>,
479 width: usize,
485 clock: Clock,
486 started_ms: u64,
487 next_db: AtomicUsize,
496 conn_bytes: AtomicUsize,
513 maxmemory: AtomicU64,
521 store: Lock<Option<Box<StoreSource>>>,
538 maxstore: AtomicU64,
560 used: AtomicUsize,
577 evict_db: AtomicUsize,
587 expire_db: AtomicUsize,
594 expire_ms: AtomicU64,
604 waiters: Lock<Waiters>,
612 parked: AtomicUsize,
625 peers: Lock<migrate::Peers>,
637 locals: Box<[Local]>,
643 claimed: AtomicUsize,
645 next_client: AtomicU64,
653 dir: PathBuf,
661 backup: Lock<backup::State>,
671 sealed: AtomicBool,
681 search: Lock<Registry>,
696 cursors: Lock<Cursors>,
705 scripts: Lock<lua::Scripts>,
717 libraries: Lock<lua::library::Libraries>,
724 stopping: AtomicBool,
733 watches: Lock<Watches>,
740 watched: AtomicUsize,
747 pubsub: Lock<pubsub::Registry>,
754 subs: AtomicUsize,
760 mail: Box<[pubsub::Mailbox]>,
766 notify: AtomicU32,
773}
774
775impl Server {
776 #[must_use]
778 pub fn new() -> Server {
779 let clock = Clock::system();
780 Server {
781 dbs: (0..DATABASES)
782 .map(|_| Db::with_clock(clock.clone(), 1))
783 .collect(),
784 width: 1,
785 started_ms: clock.now_ms(),
786 clock,
787 next_db: AtomicUsize::new(0),
788 conn_bytes: AtomicUsize::new(0),
789 maxmemory: AtomicU64::new(0),
790 store: Lock::new(None),
791 maxstore: AtomicU64::new(NO_MAXSTORE),
792 used: AtomicUsize::new(0),
793 evict_db: AtomicUsize::new(0),
794 expire_db: AtomicUsize::new(0),
795 expire_ms: AtomicU64::new(0),
796 waiters: Lock::default(),
797 parked: AtomicUsize::new(0),
798 peers: Lock::default(),
799 locals: one_thread(),
800 claimed: AtomicUsize::new(0),
801 next_client: AtomicU64::new(1),
802 dir: working_dir(),
803 backup: Lock::default(),
804 sealed: AtomicBool::new(false),
805 search: Lock::new(Registry::new()),
806 cursors: Lock::default(),
807 scripts: Lock::default(),
808 libraries: Lock::default(),
809 stopping: AtomicBool::new(false),
810 watches: Lock::default(),
811 watched: AtomicUsize::new(0),
812 pubsub: Lock::default(),
813 subs: AtomicUsize::new(0),
814 notify: AtomicU32::new(0),
815 mail: pubsub::boxes(1),
816 }
817 }
818
819 #[must_use]
830 pub fn with_width(width: usize) -> Server {
831 let mut server = Server::new();
832 let clock = server.clock.clone();
836 server.dbs = (0..DATABASES)
837 .map(|_| Db::with_clock(clock.clone(), width))
838 .collect();
839 server.width = server.dbs[0].width();
840 server
841 }
842
843 #[must_use]
845 pub fn with_clock(clock: Clock) -> Server {
846 Server {
847 dbs: (0..DATABASES)
848 .map(|_| Db::with_clock(clock.clone(), 1))
849 .collect(),
850 width: 1,
851 started_ms: clock.now_ms(),
852 clock,
853 next_db: AtomicUsize::new(0),
854 conn_bytes: AtomicUsize::new(0),
855 maxmemory: AtomicU64::new(0),
856 store: Lock::new(None),
857 maxstore: AtomicU64::new(NO_MAXSTORE),
858 used: AtomicUsize::new(0),
859 evict_db: AtomicUsize::new(0),
860 expire_db: AtomicUsize::new(0),
861 expire_ms: AtomicU64::new(0),
862 waiters: Lock::default(),
863 parked: AtomicUsize::new(0),
864 peers: Lock::default(),
865 locals: one_thread(),
866 claimed: AtomicUsize::new(0),
867 next_client: AtomicU64::new(1),
868 dir: working_dir(),
869 backup: Lock::default(),
870 sealed: AtomicBool::new(false),
871 search: Lock::new(Registry::new()),
872 cursors: Lock::default(),
873 scripts: Lock::default(),
874 libraries: Lock::default(),
875 stopping: AtomicBool::new(false),
876 watches: Lock::default(),
877 watched: AtomicUsize::new(0),
878 pubsub: Lock::default(),
879 subs: AtomicUsize::new(0),
880 notify: AtomicU32::new(0),
881 mail: pubsub::boxes(1),
882 }
883 }
884
885 pub fn striped(&self, i: usize) -> &Db {
909 self.mine().mark(1u64 << i);
910 &self.dbs[i]
911 }
912
913 fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
920 self.dbs
921 .iter()
922 .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
923 }
924
925 const fn slots(&self) -> usize {
931 DATABASES * self.width
932 }
933
934 const fn slot_db(&self, i: usize) -> usize {
936 i / self.width
937 }
938
939 fn slot(&self, i: usize) -> Held<'_, Keyspace> {
941 let (db, stripe) = (i / self.width, i % self.width);
942 self.dbs[db].hold_stripe(stripe)
943 }
944
945 #[must_use]
947 pub fn dir(&self) -> &Path {
948 &self.dir
949 }
950
951 pub fn set_dir(&mut self, dir: PathBuf) {
958 self.dir = dir;
959 }
960
961 pub fn backup_expire(&self) {
967 backup::expire(self);
968 }
969
970 pub fn stop(&self) {
976 self.stopping.store(true, Release);
977 }
978
979 #[must_use]
985 pub fn stopping(&self) -> bool {
986 self.stopping.load(Acquire)
987 }
988
989 #[must_use]
996 pub fn striped_ref(&self, i: usize) -> &Db {
997 &self.dbs[i]
998 }
999
1000 fn settings(&self) -> Held<'_, Keyspace> {
1008 self.dbs[0].hold_stripe(0)
1009 }
1010
1011 pub fn refresh_clock(&self) {
1022 self.clock.refresh();
1023 }
1024
1025 pub fn advance_clock_ms(&self, ms: u64) {
1031 let now = self.clock.now_ms() + ms;
1032 self.set_clock_ms(now);
1033 }
1034
1035 pub fn set_clock_ms(&self, ms: u64) {
1043 self.clock.set(ms);
1044 }
1045
1046 #[must_use]
1048 pub fn uptime_secs(&self) -> u64 {
1049 self.clock.now_ms().saturating_sub(self.started_ms) / 1000
1050 }
1051
1052 #[must_use]
1060 pub fn memory_bytes(&self) -> usize {
1061 self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1062 }
1063
1064 #[must_use]
1070 pub fn dataset_bytes(&self) -> usize {
1071 self.keyspaces()
1072 .map(|db| db.map().arena().live_bytes() as usize)
1073 .sum()
1074 }
1075
1076 #[must_use]
1078 pub fn arena_bytes(&self) -> usize {
1079 self.keyspaces()
1080 .map(|db| db.map().arena().reserved_bytes() as usize)
1081 .sum()
1082 }
1083
1084 #[must_use]
1086 pub fn index_bytes(&self) -> usize {
1087 self.keyspaces()
1088 .map(|db| db.map().index().memory_bytes())
1089 .sum()
1090 }
1091
1092 #[must_use]
1099 pub fn compaction(&self) -> yo_kv::Compaction {
1100 self.keyspaces().map(|db| db.map().compaction()).fold(
1101 yo_kv::Compaction::default(),
1102 |a, b| yo_kv::Compaction {
1103 walked: a.walked + b.walked,
1104 moved: a.moved + b.moved,
1105 bytes: a.bytes + b.bytes,
1106 },
1107 )
1108 }
1109
1110 #[must_use]
1112 pub fn segment_count(&self) -> usize {
1113 self.keyspaces()
1114 .map(|db| db.map().arena().resident_segments())
1115 .sum()
1116 }
1117
1118 #[must_use]
1120 pub fn conn_bytes(&self) -> usize {
1121 self.conn_bytes.load(Relaxed)
1122 }
1123
1124 pub fn note_conn_bytes(&self, delta: isize) {
1132 self.conn_bytes
1138 .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1139 }
1140
1141 #[must_use]
1143 pub fn expired_keys(&self) -> u64 {
1144 self.keyspaces().map(|db| db.expired_keys()).sum()
1145 }
1146
1147 #[must_use]
1149 pub fn evicted_keys(&self) -> u64 {
1150 self.keyspaces().map(|db| db.evicted_keys()).sum()
1151 }
1152
1153 pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1159 (0..table::count())
1160 .map(|at| (table::name_at(at), self.command_stat(at)))
1161 .filter(|(_, row)| row.seen())
1162 }
1163
1164 fn command_stat(&self, at: usize) -> CommandStat {
1166 let mut sum = CommandStat::default();
1167 for thread in &self.locals {
1168 let row = &thread.cmdstats.0[at];
1169 sum.calls += row.calls.get();
1170 sum.rejected += row.rejected.get();
1171 sum.failed += row.failed.get();
1172 }
1173 sum
1174 }
1175
1176 pub fn counted(&self) -> &Stats {
1185 &self.mine().stats
1186 }
1187
1188 pub fn next_client(&self) -> u64 {
1193 self.next_client.fetch_add(1, Relaxed)
1194 }
1195
1196 pub fn my_slot(&self) -> usize {
1203 self.mine_at()
1204 }
1205
1206 fn mine(&self) -> &Local {
1208 &self.locals[self.mine_at()]
1209 }
1210
1211 fn mine_at(&self) -> usize {
1218 let mut slot = SLOT.get();
1219 if slot == usize::MAX {
1220 slot = self.claimed.fetch_add(1, Relaxed);
1221 SLOT.set(slot);
1222 }
1223 slot % self.locals.len()
1224 }
1225
1226 #[must_use]
1228 pub fn totals(&self) -> Totals {
1229 let mut sum = Totals::default();
1230 for thread in &self.locals {
1231 sum.clients += thread.stats.clients.get();
1232 sum.connections += thread.stats.connections.get();
1233 sum.commands += thread.stats.commands.get();
1234 }
1235 sum
1236 }
1237
1238 pub fn reset_stats(&self) {
1245 for thread in &self.locals {
1246 thread.stats.connections.zero();
1247 thread.stats.commands.zero();
1248 }
1249 }
1250
1251 pub fn set_threads(&mut self, threads: usize) {
1258 self.locals = slots(threads);
1259 self.mail = pubsub::boxes(threads);
1260 self.claimed = AtomicUsize::new(0);
1261 }
1262
1263 #[must_use]
1265 pub fn maxmemory(&self) -> u64 {
1266 self.maxmemory.load(Relaxed)
1267 }
1268
1269 pub fn set_maxmemory(&self, bytes: u64) {
1283 self.maxmemory.store(bytes, Relaxed);
1284 for db in &self.dbs {
1285 db.track_memory(bytes != 0);
1286 }
1287 self.used.store(self.settled_memory(), Relaxed);
1288 }
1289
1290 pub fn set_store_source(
1300 &mut self,
1301 source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1302 ) {
1303 *self.store.lock() = Some(Box::new(source));
1304 }
1305
1306 #[must_use]
1308 pub fn has_store_source(&self) -> bool {
1309 self.store.lock().is_some()
1310 }
1311
1312 fn attach_store(&self, at: usize) {
1319 if self.slot(at).store_bytes().is_some() {
1320 return;
1321 }
1322 let mut source = self.store.lock();
1326 let Some(source) = source.as_mut() else {
1327 return;
1328 };
1329 if let Some(blocks) = source(at) {
1330 self.slot(at).attach(blocks);
1331 }
1332 }
1333
1334 #[must_use]
1336 pub fn maxstore(&self) -> Option<u64> {
1337 match self.maxstore.load(Relaxed) {
1338 NO_MAXSTORE => None,
1339 bytes => Some(bytes),
1340 }
1341 }
1342
1343 pub fn set_maxstore(&self, bytes: Option<u64>) {
1349 self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1350 }
1351
1352 #[must_use]
1358 pub fn store_bytes(&self) -> u64 {
1359 self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1360 }
1361
1362 #[must_use]
1377 pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1378 let mut total = yo_kv::tier::Stats::default();
1379 for db in self.keyspaces() {
1380 let Some(tier) = db.tier() else { continue };
1381 let s = tier.stats();
1382 total.demoted += s.demoted;
1383 total.promoted += s.promoted;
1384 total.faults += s.faults;
1385 total.served += s.served;
1386 total.bytes_out += s.bytes_out;
1387 total.bytes_in += s.bytes_in;
1388 }
1389 total
1390 }
1391
1392 #[must_use]
1399 pub fn regime(&self) -> &'static str {
1400 if (0..self.slots()).any(|at| self.migrates(at)) {
1401 "migrate"
1402 } else {
1403 "evict"
1404 }
1405 }
1406
1407 fn migrates(&self, at: usize) -> bool {
1419 let cap = self.maxstore();
1420 if cap == Some(0) {
1421 return false;
1422 }
1423 let bytes = self.slot(at).store_bytes();
1427 match bytes {
1428 Some(held) => cap.is_none_or(|cap| held < cap),
1429 None => self.store.lock().is_some(),
1433 }
1434 }
1435
1436 pub fn refresh_memory(&self) {
1441 if self.maxmemory() != 0 {
1442 self.used.store(self.settled_memory(), Relaxed);
1443 }
1444 }
1445
1446 fn settled_memory(&self) -> usize {
1453 self.keyspaces()
1454 .map(|mut db| db.settled_memory_bytes())
1455 .sum::<usize>()
1456 + self.conn_bytes()
1457 }
1458
1459 pub fn make_room(&self) -> bool {
1493 let limit = self.maxmemory();
1494 if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1495 return true;
1496 }
1497 let mut used = self.settled_memory();
1502 self.used.store(used, Relaxed);
1503 let mut budget = EVICT_BUDGET;
1504 while used as u64 > limit {
1505 let over = used - limit as usize;
1506 if !self.relieve_step(over) {
1507 return false;
1508 }
1509 self.compact_hard_step();
1510 used = self.settled_memory();
1511 self.used.store(used, Relaxed);
1512 budget -= 1;
1513 if budget == 0 {
1514 break;
1515 }
1516 }
1517 true
1518 }
1519
1520 fn relieve_step(&self, over: usize) -> bool {
1537 let from = self.evict_db.load(Relaxed);
1538 for turn in 0..self.slots() {
1539 let i = (from + turn) % self.slots();
1540 let used = !self.slot(i).is_empty();
1543 let gave = if used && self.migrates(i) {
1544 self.attach_store(i);
1545 self.slot(i)
1550 .relieve(over)
1551 .is_ok_and(yo_kv::tier::Relief::made_room)
1552 } else {
1553 let armed = notify::arm(self, self.slot_db(i));
1559 let gone = self.slot(i).evict_one();
1560 notify::drain(self, armed);
1561 gone
1562 };
1563 if gave {
1564 self.evict_db.store((i + 1) % self.slots(), Relaxed);
1565 self.mine().mark(1u64 << self.slot_db(i));
1566 return true;
1567 }
1568 }
1569 false
1570 }
1571
1572 pub fn expire_slice(&self, budget: usize) -> usize {
1587 let now = self.clock.now_ms();
1588 if now == self.expire_ms.load(Relaxed) {
1589 return 0;
1590 }
1591 self.expire_ms.store(now, Relaxed);
1592 self.expire_step(budget)
1593 }
1594
1595 pub fn expire_step(&self, budget: usize) -> usize {
1611 let mut spent = 0;
1612 let from = self.expire_db.load(Relaxed);
1613 for turn in 0..self.slots() {
1614 if spent >= budget {
1615 break;
1616 }
1617 let i = (from + turn) % self.slots();
1618 let armed = notify::arm(self, self.slot_db(i));
1623 let c = self.slot(i).expire_cycle(budget - spent);
1624 notify::drain(self, armed);
1625 spent += c.examined;
1626 if c.expired > 0 {
1627 self.expire_db.store((i + 1) % self.slots(), Relaxed);
1628 self.mine().note(1u64 << self.slot_db(i));
1629 }
1630 }
1631 spent
1632 }
1633
1634 fn compact_hard_step(&self) -> Option<usize> {
1640 let from = self.next_db.load(Relaxed);
1641 for turn in 0..self.slots() {
1642 let i = (from + turn) % self.slots();
1643 if let Some(moved) = self.slot(i).compact_hard() {
1644 self.next_db.store((i + 1) % self.slots(), Relaxed);
1645 return Some(moved);
1646 }
1647 }
1648 None
1649 }
1650
1651 fn collect_marks(&self) {
1660 let mut marked = 0;
1661 for thread in &self.locals {
1662 marked |= thread.dirty.swap(0, Relaxed);
1663 }
1664 self.mine().note(marked);
1665 }
1666
1667 pub fn compact_step(&self) -> Option<usize> {
1680 self.collect_marks();
1681 let mine = self.mine();
1682 let from = self.next_db.load(Relaxed);
1683 for turn in 0..self.slots() {
1684 let i = (from + turn) % self.slots();
1685 let at = self.slot_db(i);
1689 if !mine.wanted(at) {
1690 continue;
1691 }
1692 if let Some(moved) = self.slot(i).compact_step() {
1693 self.next_db.store((i + 1) % self.slots(), Relaxed);
1694 return Some(moved);
1695 }
1696 if i % self.width == self.width - 1 {
1700 mine.done(at);
1701 }
1702 }
1703 None
1704 }
1705}
1706
1707impl Server {
1708 fn watching(&self) -> bool {
1717 self.watched.load(Relaxed) != 0
1718 }
1719
1720 pub(crate) fn notify_flags(&self) -> u32 {
1727 self.notify.load(Relaxed)
1728 }
1729
1730 pub(crate) fn set_notify_flags(&self, flags: u32) {
1732 self.notify.store(flags, Relaxed);
1733 }
1734
1735 fn recount(&self, watches: &Watches) {
1740 self.watched.store(watches.len(), Relaxed);
1741 }
1742}
1743
1744impl Default for Server {
1745 fn default() -> Server {
1746 Server::new()
1747 }
1748}
1749
1750pub struct Session {
1752 db: usize,
1753 id: u64,
1754 conn: u32,
1762 name: Vec<u8>,
1763 sets: himport::Fieldsets,
1769 scripted: bool,
1779 multi: Option<multi::Queue>,
1785 watching: Vec<multi::Watched>,
1792 running: bool,
1799 replay: crate::request::Argv,
1808 subs: Option<Box<pubsub::Subs>>,
1816}
1817
1818impl Session {
1819 #[must_use]
1821 pub fn new(id: u64) -> Session {
1822 Session {
1823 db: 0,
1824 id,
1825 conn: u32::MAX,
1826 name: Vec::new(),
1827 sets: himport::Fieldsets::default(),
1828 scripted: false,
1829 multi: None,
1830 watching: Vec::new(),
1831 running: false,
1832 replay: crate::request::Argv::new(),
1833 subs: None,
1834 }
1835 }
1836
1837 pub(crate) const fn scripted(&self) -> bool {
1839 self.scripted
1840 }
1841
1842 pub(crate) const fn running(&self) -> bool {
1844 self.running
1845 }
1846
1847 pub(crate) const fn set_conn(&mut self, conn: u32) {
1853 self.conn = conn;
1854 }
1855
1856 #[must_use]
1858 pub const fn id(&self) -> u64 {
1859 self.id
1860 }
1861
1862 #[must_use]
1864 pub const fn db(&self) -> usize {
1865 self.db
1866 }
1867
1868 #[must_use]
1870 pub fn name(&self) -> &[u8] {
1871 &self.name
1872 }
1873
1874 pub fn reset(&mut self) {
1879 self.db = 0;
1880 self.name.clear();
1881 self.sets.clear();
1885 }
1886
1887 fn set_name(&mut self, name: &[u8]) {
1889 yo_alloc::allow(|| {
1890 self.name.clear();
1891 self.name.extend_from_slice(name);
1892 });
1893 }
1894}
1895
1896pub fn forget_session(server: &Server, session: &mut Session) {
1905 multi::release(server, session);
1906 pubsub::release(server, session);
1907}
1908
1909pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1914 if args.is_empty() {
1917 return Flow::Continue;
1918 }
1919 resolved(server, session, lookup(args.name()), args, out)
1920}
1921
1922pub fn resolved(
1934 server: &Server,
1935 session: &mut Session,
1936 spec: Option<&'static Spec>,
1937 args: Args<'_>,
1938 out: &mut Out,
1939) -> Flow {
1940 if args.is_empty() {
1941 return Flow::Continue;
1942 }
1943 server.mine().stats.commands.bump();
1944
1945 let Some(spec) = spec else {
1952 multi::refuse(server, session, None, &args::unknown_command(args), out);
1953 return Flow::Continue;
1954 };
1955 if !arity_ok(spec, args.len()) {
1956 server.mine().cmdstats.at(spec).rejected.bump();
1957 multi::refuse(
1958 server,
1959 session,
1960 Some(spec),
1961 &args::wrong_arity(spec.name),
1962 out,
1963 );
1964 return Flow::Continue;
1965 }
1966 if session.in_multi()
1967 && let Some(e) = multi::refused_in_multi(spec)
1968 {
1969 server.mine().cmdstats.at(spec).rejected.bump();
1970 multi::refuse(server, session, Some(spec), &e, out);
1971 return Flow::Continue;
1972 }
1973
1974 if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1984 server.mine().cmdstats.at(spec).rejected.bump();
1985 session.dirty_multi();
1986 out.error_line(b"OOM ", OOM);
1987 return Flow::Continue;
1988 }
1989
1990 if let Some(e) = pubsub::refused(session, spec, out) {
1998 server.mine().cmdstats.at(spec).rejected.bump();
1999 multi::refuse(server, session, Some(spec), &e, out);
2000 return Flow::Continue;
2001 }
2002
2003 if session.queues(spec.name) {
2008 return multi::queue(session, args, out);
2009 }
2010
2011 server.mine().mark(match spec.group {
2018 "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
2019 | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
2020 1u64 << session.db
2021 }
2022 _ => ALL_DATABASES,
2023 });
2024
2025 let mark = out.len();
2026 let armed = notify::arm(server, session.db);
2037 misses::report(&server.dbs[session.db], session.db, spec, args);
2042 let done = if spec.flags.contains(&"blocking") {
2043 blocking::execute(server, session, spec, args, out)
2044 } else {
2045 match spec.group {
2046 "string" => {
2047 let db = session.db;
2048 strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2049 }
2050 "bitmap" => {
2054 let db = session.db;
2055 bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2056 }
2057 "hyperloglog" => {
2060 let db = session.db;
2061 hll::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2062 }
2063 "set" => {
2064 let db = session.db;
2065 sets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2066 }
2067 "hash" if spec.name == "himport" => {
2072 let db = session.db;
2073 himport::execute(&server.dbs[db], &mut session.sets, args, out)
2074 .map(|()| Flow::Continue)
2075 }
2076 "hash" => {
2081 let db = session.db;
2082 let changed = hashes::execute(&server.dbs[db], db, spec, args, out);
2083 changed.map(|changed| {
2084 indexing::changed(server, db, args.get(1), changed);
2085 Flow::Continue
2086 })
2087 }
2088 "list" => {
2089 let db = session.db;
2090 lists::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2091 }
2092 "zset" => {
2093 let db = session.db;
2094 zsets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2095 }
2096 "geo" => {
2100 let db = session.db;
2101 geo::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2102 }
2103 "array" => {
2104 let db = session.db;
2105 arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2106 }
2107 "graph" => {
2108 let db = session.db;
2109 graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2110 }
2111 "json" => {
2115 let db = session.db;
2116 json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2117 }
2118 "vector" => {
2119 let db = session.db;
2120 vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2121 }
2122 "bloom" => {
2123 let db = session.db;
2124 bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2125 }
2126 "cuckoo" => {
2127 let db = session.db;
2128 cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2129 }
2130 "cms" => {
2131 let db = session.db;
2132 cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2133 }
2134 "topk" => {
2135 let db = session.db;
2136 topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2137 }
2138 "tdigest" => {
2139 let db = session.db;
2140 tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2141 }
2142 "ts" => {
2143 let db = session.db;
2144 ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2145 }
2146 "stream" => {
2151 let db = session.db;
2152 let now = server.now_ms();
2153 streams::execute(&server.dbs[db], db, spec, args, now, out).map(|()| Flow::Continue)
2154 }
2155 "keyspace" if spec.name == "migrate" => {
2159 migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
2160 }
2161 "keyspace" => {
2167 let mut touched = indexing::Touched::new(server);
2168 let done =
2169 keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
2170 done.map(|()| {
2171 indexing::touched(server, &touched);
2172 Flow::Continue
2173 })
2174 }
2175 "search" if spec.name == "FT.SEARCH" => {
2182 search::find(server, session.db, args, out).map(|()| Flow::Continue)
2187 }
2188 "search" if spec.name == "FT.AGGREGATE" => {
2189 search::roll(server, session.db, args, out).map(|()| Flow::Continue)
2190 }
2191 "search" if spec.name == "FT.HYBRID" => {
2192 search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
2193 }
2194 "search" if spec.name == "FT.PROFILE" => {
2195 search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
2198 }
2199 "search" if spec.name.starts_with("FT.SUG") => {
2203 let db = session.db;
2204 suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2205 }
2206 "search"
2210 if matches!(
2211 spec.name,
2212 "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
2213 ) =>
2214 {
2215 let db = session.db;
2216 search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
2217 }
2218 "search" if spec.name == "FT.CURSOR" => {
2219 search::cursor::execute(server, args, out).map(|()| Flow::Continue)
2223 }
2224 "search" => {
2225 let db = session.db;
2226 let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
2227 made.map(|made| {
2228 match made {
2229 Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
2230 Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
2231 None => {}
2232 }
2233 Flow::Continue
2234 })
2235 }
2236 "scripting" => {
2237 scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
2238 }
2239 "transactions" => multi::execute(server, session, spec, args, out),
2240 "pubsub" => pubsub::execute(server, session, spec, args, out),
2244 _ => server::execute(server, session, spec, args, out),
2245 }
2246 };
2247 if let Err(e) = &done {
2255 misses::undo(spec, e);
2256 }
2257 notify::drain(server, armed);
2258
2259 let flow = match done {
2260 Ok(flow) => flow,
2261 Err(e) => {
2262 out.truncate(mark);
2263 write_error(out, &e);
2264 Flow::Continue
2265 }
2266 };
2267
2268 if server.watching() && spec.flags.contains(&"write") {
2273 multi::touched(server, session, spec, args);
2274 }
2275
2276 let row = server.mine().cmdstats.at(spec);
2287 row.calls.bump();
2288 if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
2289 row.failed.bump();
2290 }
2291 flow
2292}
2293
2294fn write_error(out: &mut Out, e: &Error) {
2304 let prefix: &[u8] = match e.code() {
2305 Code::WrongType => b"WRONGTYPE ",
2306 Code::Corrupt => b"INVALIDOBJ ",
2310 _ => b"ERR ",
2311 };
2312 out.error_line(prefix, e.message().as_bytes());
2313}
2314
2315#[cfg(test)]
2316mod tests {
2317 use super::*;
2318 use crate::proto::{Limits, Proto};
2319 use crate::request::Argv;
2320
2321 pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2326 let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2327 for p in parts {
2328 wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2329 wire.extend_from_slice(p);
2330 wire.extend_from_slice(b"\r\n");
2331 }
2332 wire
2333 }
2334
2335 struct Fixture {
2337 server: Server,
2338 session: Session,
2339 argv: Argv,
2340 out: Out,
2341 }
2342
2343 impl Fixture {
2344 fn new() -> Fixture {
2345 Fixture::on(Server::new())
2346 }
2347
2348 fn striped(width: usize) -> Fixture {
2350 Fixture::on(Server::with_width(width))
2351 }
2352
2353 fn on(server: Server) -> Fixture {
2354 Fixture {
2355 server,
2356 session: Session::new(7),
2357 argv: Argv::new(),
2358 out: Out::new(Proto::Resp2),
2359 }
2360 }
2361
2362 fn run(&mut self, parts: &[&[u8]]) -> String {
2364 self.flow(parts).1
2365 }
2366
2367 fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2373 let wire = encode(parts);
2374 self.argv.decode(&wire, &Limits::default()).unwrap();
2375 self.out.clear();
2376 execute(
2377 &self.server,
2378 &mut self.session,
2379 Args::new(&self.argv, &wire),
2380 &mut self.out,
2381 );
2382 self.out.as_slice().to_vec()
2383 }
2384
2385 fn advance(&mut self, ms: u64) {
2387 self.server.advance_clock_ms(ms);
2388 }
2389
2390 fn other(&mut self, parts: &[&[u8]]) -> String {
2395 self.other_in(self.session.db(), parts)
2396 }
2397
2398 fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
2400 let mut session = Session::new(8);
2401 session.db = db;
2402 let reply = self.by(&mut session, parts);
2403 forget_session(&self.server, &mut session);
2404 reply
2405 }
2406
2407 fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
2409 let wire = encode(parts);
2410 let mut argv = Argv::new();
2411 argv.decode(&wire, &Limits::default()).unwrap();
2412 let mut out = Out::new(Proto::Resp2);
2413 execute(&self.server, session, Args::new(&argv, &wire), &mut out);
2414 String::from_utf8_lossy(out.as_slice()).into_owned()
2415 }
2416
2417 fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2419 let wire = encode(parts);
2420 self.argv.decode(&wire, &Limits::default()).unwrap();
2421 self.out.clear();
2422 let flow = execute(
2423 &self.server,
2424 &mut self.session,
2425 Args::new(&self.argv, &wire),
2426 &mut self.out,
2427 );
2428 (
2429 flow,
2430 String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2431 )
2432 }
2433 }
2434
2435 #[test]
2436 fn multi_holds_commands_and_exec_runs_them() {
2437 let mut f = Fixture::new();
2438 assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
2439 assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
2440 assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
2441 assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
2443 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
2444 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
2445 }
2446
2447 #[test]
2456 fn the_second_exec_of_a_shape_does_not_grow_the_buffer() {
2457 let mut f = Fixture::new();
2458 for _ in 0..2 {
2459 f.run(&[b"MULTI"]);
2460 f.run(&[b"SET", b"k", b"1"]);
2461 f.run(&[b"INCR", b"k"]);
2462 f.run(&[b"EXEC"]);
2463 }
2464 let room = f.session.replay.room();
2465 assert!(room > 0, "the first transaction should have set the room");
2466 f.run(&[b"MULTI"]);
2467 f.run(&[b"SET", b"k", b"1"]);
2468 f.run(&[b"INCR", b"k"]);
2469 f.run(&[b"EXEC"]);
2470 assert_eq!(f.session.replay.room(), room);
2471 }
2472
2473 #[test]
2474 fn an_empty_transaction_answers_an_empty_array() {
2475 let mut f = Fixture::new();
2476 f.run(&[b"MULTI"]);
2477 assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
2478 }
2479
2480 #[test]
2481 fn exec_and_discard_want_a_transaction_to_be_open() {
2482 let mut f = Fixture::new();
2483 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2484 assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
2485 assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
2488 }
2489
2490 #[test]
2491 fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
2492 let mut f = Fixture::new();
2493 f.run(&[b"MULTI"]);
2494 assert_eq!(
2495 f.run(&[b"MULTI"]),
2496 "-ERR MULTI calls can not be nested\r\n",
2497 "nested MULTI is raised by the command and not by the funnel"
2498 );
2499 assert_eq!(
2500 f.run(&[b"WATCH", b"k"]),
2501 "-ERR WATCH inside MULTI is not allowed\r\n"
2502 );
2503 f.run(&[b"SET", b"k", b"1"]);
2504 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
2505 }
2506
2507 #[test]
2508 fn an_error_the_funnel_raises_kills_the_transaction() {
2509 for bad in [
2510 &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
2511 &[b"GET".as_slice()],
2512 ] {
2513 let mut f = Fixture::new();
2514 f.run(&[b"MULTI"]);
2515 assert!(f.run(bad).starts_with("-ERR "));
2516 assert_eq!(
2517 f.run(&[b"SET", b"k", b"1"]),
2518 "+QUEUED\r\n",
2519 "a dead transaction still answers QUEUED, which is Redis"
2520 );
2521 assert_eq!(
2522 f.run(&[b"EXEC"]),
2523 "-EXECABORT Transaction discarded because of previous errors.\r\n"
2524 );
2525 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2526 }
2527 }
2528
2529 #[test]
2530 fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
2531 let mut f = Fixture::new();
2532 f.run(&[b"MULTI"]);
2533 f.run(&[b"SET", b"k", b"1"]);
2534 assert_eq!(
2535 f.run(&[b"EXEC", b"x"]),
2536 "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
2537 );
2538 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2539 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2540 }
2541
2542 #[test]
2543 fn a_command_a_transaction_may_not_hold_kills_it() {
2544 let mut f = Fixture::new();
2545 f.run(&[b"MULTI"]);
2546 assert_eq!(
2547 f.run(&[b"SHUTDOWN", b"NOSAVE"]),
2548 "-ERR Command not allowed inside a transaction\r\n"
2549 );
2550 assert_eq!(
2551 f.run(&[b"EXEC"]),
2552 "-EXECABORT Transaction discarded because of previous errors.\r\n"
2553 );
2554 }
2555
2556 #[test]
2557 fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
2558 let mut f = Fixture::new();
2559 f.run(&[b"RPUSH", b"l", b"v"]);
2560 f.run(&[b"MULTI"]);
2561 f.run(&[b"INCR", b"l"]);
2562 f.run(&[b"SET", b"y", b"2"]);
2563 assert_eq!(
2564 f.run(&[b"EXEC"]),
2565 "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
2566 );
2567 assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
2568 }
2569
2570 #[test]
2571 fn discard_and_reset_both_throw_the_queue_away() {
2572 let mut f = Fixture::new();
2573 f.run(&[b"MULTI"]);
2574 f.run(&[b"SET", b"k", b"1"]);
2575 assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
2576 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2577 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2578
2579 f.run(&[b"MULTI"]);
2580 f.run(&[b"SET", b"k", b"1"]);
2581 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2582 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2583 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2584 }
2585
2586 #[test]
2587 fn select_is_queued_and_applied_when_exec_runs_it() {
2588 let mut f = Fixture::new();
2589 f.run(&[b"MULTI"]);
2590 assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
2591 f.run(&[b"SET", b"k", b"1"]);
2592 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
2593 assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
2594 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
2595 }
2596
2597 #[test]
2598 fn a_write_by_another_connection_fails_the_transaction() {
2599 let mut f = Fixture::new();
2600 f.run(&[b"SET", b"k", b"1"]);
2601 assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
2602 f.other(&[b"SET", b"k", b"2"]);
2603 f.run(&[b"MULTI"]);
2604 f.run(&[b"GET", b"k"]);
2605 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2606 }
2607
2608 #[test]
2609 fn a_write_that_puts_the_same_value_back_still_fails_it() {
2610 let mut f = Fixture::new();
2611 f.run(&[b"SET", b"k", b"1"]);
2612 f.run(&[b"WATCH", b"k"]);
2613 f.other(&[b"SET", b"k", b"1"]);
2614 f.run(&[b"MULTI"]);
2615 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2616 }
2617
2618 #[test]
2619 fn a_read_by_another_connection_does_not() {
2620 let mut f = Fixture::new();
2621 f.run(&[b"SET", b"k", b"1"]);
2622 f.run(&[b"WATCH", b"k"]);
2623 f.other(&[b"GET", b"k"]);
2624 f.other(&[b"STRLEN", b"k"]);
2625 f.run(&[b"MULTI"]);
2626 f.run(&[b"GET", b"k"]);
2627 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2628 }
2629
2630 #[test]
2631 fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
2632 let mut f = Fixture::new();
2633 f.run(&[b"WATCH", b"k"]);
2634 f.other(&[b"DEL", b"k"]);
2635 f.run(&[b"MULTI"]);
2636 f.run(&[b"PING"]);
2637 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2638 f.run(&[b"WATCH", b"k"]);
2640 f.other(&[b"SET", b"k", b"1"]);
2641 f.run(&[b"MULTI"]);
2642 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2643 }
2644
2645 #[test]
2646 fn a_watched_key_that_expires_fails_the_transaction() {
2647 let mut f = Fixture::new();
2648 f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
2649 f.run(&[b"WATCH", b"k"]);
2650 f.run(&[b"MULTI"]);
2651 f.advance(100);
2652 assert_eq!(
2653 f.run(&[b"EXEC"]),
2654 "*-1\r\n",
2655 "nothing wrote to the key, so only the liveness check can catch this"
2656 );
2657 }
2658
2659 #[test]
2660 fn every_way_a_transaction_ends_lets_go_of_the_watches() {
2661 for end in [
2662 &[b"EXEC".as_slice()] as &[&[u8]],
2663 &[b"DISCARD".as_slice()],
2664 &[b"UNWATCH".as_slice()],
2665 &[b"RESET".as_slice()],
2666 ] {
2667 let mut f = Fixture::new();
2668 f.run(&[b"SET", b"k", b"1"]);
2669 f.run(&[b"WATCH", b"k"]);
2670 if end[0] != b"UNWATCH" && end[0] != b"RESET" {
2671 f.run(&[b"MULTI"]);
2672 }
2673 f.run(end);
2674 assert!(!f.server.watching(), "{end:?} left a row behind");
2675 f.other(&[b"SET", b"k", b"2"]);
2677 f.run(&[b"MULTI"]);
2678 f.run(&[b"GET", b"k"]);
2679 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
2680 }
2681 }
2682
2683 #[test]
2684 fn a_connection_going_away_lets_go_of_its_watches() {
2685 let mut f = Fixture::new();
2686 f.run(&[b"SET", b"k", b"1"]);
2687 f.run(&[b"WATCH", b"k"]);
2688 assert!(f.server.watching());
2689 forget_session(&f.server, &mut f.session);
2690 assert!(!f.server.watching());
2691 }
2692
2693 #[test]
2694 fn watching_the_same_key_twice_is_one_watch() {
2695 let mut f = Fixture::new();
2696 f.run(&[b"SET", b"k", b"1"]);
2697 f.run(&[b"WATCH", b"k", b"k"]);
2698 f.run(&[b"UNWATCH"]);
2699 assert!(
2700 !f.server.watching(),
2701 "the row counts watchers, so a doubled watch would leave one behind"
2702 );
2703 }
2704
2705 #[test]
2706 fn two_connections_can_watch_the_same_key() {
2707 let mut f = Fixture::new();
2708 f.run(&[b"SET", b"k", b"1"]);
2709 f.run(&[b"WATCH", b"k"]);
2710 let mut second = Session::new(9);
2711 second.db = f.session.db();
2712 assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
2713 forget_session(&f.server, &mut second);
2715 assert!(f.server.watching());
2716 f.other(&[b"SET", b"k", b"2"]);
2717 f.run(&[b"MULTI"]);
2718 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2719 }
2720
2721 #[test]
2722 fn flushdb_fails_a_watch_on_a_key_that_was_there() {
2723 let mut f = Fixture::new();
2724 f.run(&[b"SET", b"k", b"1"]);
2725 f.run(&[b"WATCH", b"k"]);
2726 f.other(&[b"FLUSHDB"]);
2727 f.run(&[b"MULTI"]);
2728 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2729 }
2730
2731 #[test]
2732 fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
2733 let mut f = Fixture::new();
2734 f.run(&[b"WATCH", b"k"]);
2735 f.other(&[b"FLUSHDB"]);
2736 f.run(&[b"MULTI"]);
2737 f.run(&[b"PING"]);
2738 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2739 }
2740
2741 #[test]
2742 fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
2743 let mut f = Fixture::new();
2744 f.run(&[b"SET", b"k", b"1"]);
2745 f.run(&[b"WATCH", b"k"]);
2746 let elsewhere = f.session.db() + 1;
2748 f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
2749 f.run(&[b"MULTI"]);
2750 f.run(&[b"GET", b"k"]);
2751 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2752 }
2753
2754 #[test]
2755 fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
2756 let mut f = Fixture::new();
2757 f.run(&[b"RPUSH", b"src", b"1"]);
2758 f.run(&[b"WATCH", b"dst"]);
2759 f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
2760 f.run(&[b"MULTI"]);
2761 assert_eq!(
2762 f.run(&[b"EXEC"]),
2763 "*-1\r\n",
2764 "SORT is movablekeys, so every watched key in the database is asked"
2765 );
2766 }
2767
2768 #[test]
2769 fn a_server_nobody_is_watching_says_so() {
2770 let mut f = Fixture::new();
2771 assert!(!f.server.watching());
2772 f.run(&[b"SET", b"k", b"1"]);
2773 assert!(!f.server.watching());
2774 }
2775
2776 #[test]
2782 fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
2783 let mut f = Fixture::new();
2784 assert_eq!(
2785 f.run(&[b"SUBSCRIBE", b"a", b"b"]),
2786 "*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"
2787 );
2788 assert_eq!(
2789 f.run(&[b"PSUBSCRIBE", b"c*"]),
2790 "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
2791 );
2792 assert_eq!(
2793 f.run(&[b"SSUBSCRIBE", b"s"]),
2794 "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
2795 );
2796 assert_eq!(
2799 f.run(&[b"SUBSCRIBE", b"a"]),
2800 "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
2801 );
2802 }
2803
2804 #[test]
2807 fn unsubscribe_answers_for_names_it_was_not_holding_too() {
2808 let mut f = Fixture::new();
2809 f.run(&[b"SUBSCRIBE", b"a"]);
2810
2811 assert_eq!(
2814 f.run(&[b"UNSUBSCRIBE", b"zz"]),
2815 "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
2816 );
2817 f.run(&[b"SUBSCRIBE", b"b"]);
2819 f.run(&[b"PSUBSCRIBE", b"p*"]);
2820 assert_eq!(
2821 f.run(&[b"UNSUBSCRIBE"]),
2822 "*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"
2823 );
2824 assert_eq!(
2827 f.run(&[b"UNSUBSCRIBE"]),
2828 "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
2829 "the pattern is still held, so the count is one"
2830 );
2831 assert_eq!(
2832 f.run(&[b"SUNSUBSCRIBE"]),
2833 "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
2834 "shard channels are counted on their own"
2835 );
2836 }
2837
2838 #[test]
2843 fn the_subscribe_gate_does_not_reach_inside_exec() {
2844 let mut f = Fixture::new();
2845 f.run(&[b"SET", b"k", b"1"]);
2846 f.run(&[b"MULTI"]);
2847 assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
2848 assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
2849 assert_eq!(
2850 f.run(&[b"EXEC"]),
2851 "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
2852 );
2853 assert_eq!(
2856 f.run(&[b"GET", b"k"]),
2857 "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
2858 );
2859 }
2860
2861 #[test]
2864 fn exec_sent_by_a_subscriber_aborts_the_transaction() {
2865 let mut f = Fixture::new();
2866 f.run(&[b"MULTI"]);
2867 f.run(&[b"SET", b"k", b"1"]);
2868 f.run(&[b"SUBSCRIBE", b"z"]);
2869 f.run(&[b"EXEC"]);
2870 f.run(&[b"MULTI"]);
2871 assert_eq!(
2872 f.run(&[b"EXEC"]),
2873 "-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"
2874 );
2875 }
2876
2877 #[test]
2880 fn reset_lets_go_of_every_subscription() {
2881 let mut f = Fixture::new();
2882 f.run(&[b"SUBSCRIBE", b"a"]);
2883 f.run(&[b"PSUBSCRIBE", b"p*"]);
2884 f.run(&[b"SSUBSCRIBE", b"s"]);
2885 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2886 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2887 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2888 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
2889 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2891 }
2892
2893 #[test]
2896 fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
2897 let mut f = Fixture::new();
2898 let mut sub = Session::new(9);
2899 f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
2900 f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
2901 f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
2902
2903 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
2904 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
2905 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
2906 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
2907 assert_eq!(
2908 f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
2909 "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
2910 );
2911 assert_eq!(
2912 f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
2913 "*2\r\n$1\r\na\r\n:1\r\n",
2914 "the shard channel and the channel share a name and not a count"
2915 );
2916 assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
2917
2918 forget_session(&f.server, &mut sub);
2919 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2920 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2921 }
2922
2923 #[test]
2926 fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
2927 let mut f = Fixture::new();
2928 assert_eq!(
2929 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2930 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2931 );
2932 assert_eq!(
2933 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
2934 "+OK\r\n"
2935 );
2936 assert_eq!(
2939 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2940 "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
2941 );
2942 assert_eq!(
2943 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
2944 "+OK\r\n"
2945 );
2946 assert_eq!(
2947 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2948 "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
2949 );
2950 }
2951
2952 #[test]
2953 fn a_letter_the_notification_setting_does_not_know_is_refused() {
2954 let mut f = Fixture::new();
2955 assert_eq!(
2956 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
2957 "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
2958 - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
2959 );
2960 assert_eq!(
2963 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2964 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2965 );
2966 }
2967
2968 #[test]
2974 fn pubsub_says_no_two_different_ways() {
2975 let mut f = Fixture::new();
2976 assert_eq!(
2977 f.run(&[b"PUBSUB"]),
2978 "-ERR wrong number of arguments for 'pubsub' command\r\n"
2979 );
2980 assert_eq!(
2981 f.run(&[b"PUBSUB", b"NOPE"]),
2982 "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
2983 );
2984 assert_eq!(
2985 f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
2986 "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
2987 );
2988 assert_eq!(
2989 f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
2990 "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
2991 );
2992 assert_eq!(
2993 f.run(&[b"PUBSUB", b"HELP", b"x"]),
2994 "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
2995 );
2996 }
2997
2998 #[test]
3001 fn publishing_to_nobody_answers_zero() {
3002 let mut f = Fixture::new();
3003 assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
3004 assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
3005 assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
3007 }
3008
3009 #[test]
3013 fn a_publish_counts_the_deliveries_and_not_the_clients() {
3014 let mut f = Fixture::new();
3015 let mut sub = Session::new(9);
3016 f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
3017 f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
3018 f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
3019 assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
3020 forget_session(&f.server, &mut sub);
3021 }
3022
3023 #[cfg_attr(miri, ignore = "megabytes through the arena")]
3036 #[test]
3037 fn rewriting_the_same_keys_does_not_grow_the_server() {
3038 let mut f = Fixture::new();
3039 let val = vec![b'v'; 1024];
3040 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3041
3042 for k in &keys {
3043 f.run(&[b"SET", k, &val]);
3044 }
3045 f.server.compact_step();
3046 let after_first = f.server.memory_bytes();
3047
3048 for _ in 0..500 {
3053 for k in &keys {
3054 f.run(&[b"SET", k, &val]);
3055 }
3056 f.server.compact_step();
3057 }
3058
3059 assert!(
3060 f.server.memory_bytes() <= after_first * 2,
3061 "held {} after five hundred passes against {after_first} after one",
3062 f.server.memory_bytes()
3063 );
3064 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3065 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3066 }
3067
3068 #[cfg_attr(miri, ignore = "megabytes through the arena")]
3085 #[test]
3086 fn a_database_nobody_started_on_is_still_collected() {
3087 let mut f = Fixture::new();
3088 assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
3089 let val = vec![b'v'; 1024];
3090 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3091
3092 for k in &keys {
3093 f.run(&[b"SET", k, &val]);
3094 }
3095 while f.server.compact_step().is_some() {}
3096 assert!(
3097 !f.server.mine().wanted(9),
3098 "database nine was drained and should not be asked again until it is written to"
3099 );
3100 let after_first = f.server.memory_bytes();
3101
3102 for _ in 0..500 {
3103 for k in &keys {
3104 f.run(&[b"SET", k, &val]);
3105 }
3106 f.server.compact_step();
3107 }
3108
3109 assert!(
3110 f.server.memory_bytes() <= after_first * 2,
3111 "held {} after five hundred passes against {after_first} after one",
3112 f.server.memory_bytes()
3113 );
3114 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3115 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3116 f.run(&[b"SELECT", b"0"]);
3118 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3119 }
3120
3121 #[test]
3122 fn a_command_goes_from_bytes_to_bytes() {
3123 let mut f = Fixture::new();
3124 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3125 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
3126 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3127 assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
3128 assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
3130 assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
3131 }
3132
3133 #[test]
3134 fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
3135 let mut f = Fixture::new();
3136 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
3137 assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
3140 assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
3141 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3142 assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
3144 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3145 }
3146
3147 #[test]
3148 fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
3149 let mut f = Fixture::new();
3150 f.run(&[b"SET", b"k", b"v"]);
3151 assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
3154 assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
3155 }
3156
3157 #[test]
3158 fn touch_counts_the_way_exists_counts() {
3159 let mut f = Fixture::new();
3160 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3161 assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
3162 assert_eq!(
3163 f.run(&[b"TOUCH", b"a", b"a"]),
3164 ":2\r\n",
3165 "twice counts twice"
3166 );
3167 assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
3168 assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
3169 }
3170
3171 #[test]
3172 fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
3173 let mut f = Fixture::new();
3174 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3175 f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
3176
3177 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
3178 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3179 assert_eq!(
3180 f.run(&[b"TTL", b"b"]),
3181 ":100\r\n",
3182 "the source's and not b's"
3183 );
3184 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3185 }
3186
3187 #[test]
3188 fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
3189 let mut f = Fixture::new();
3190 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
3191 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
3194 }
3195
3196 #[test]
3197 fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
3198 let mut f = Fixture::new();
3199 f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
3200
3201 assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
3202 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3203 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
3206 assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
3207 assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
3208 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
3209 }
3210
3211 #[test]
3212 fn renaming_a_set_does_not_touch_a_member() {
3213 let mut f = Fixture::new();
3214 for i in 0..300 {
3215 f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
3216 }
3217 let before = f.server.memory_bytes();
3218
3219 assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
3220 assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
3221 assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
3222 assert!(
3223 f.server.memory_bytes().abs_diff(before) < 256,
3224 "the members were copied: {} against {before}",
3225 f.server.memory_bytes()
3226 );
3227 }
3228
3229 #[test]
3230 fn a_copy_is_a_second_value_and_not_a_second_name() {
3231 let mut f = Fixture::new();
3232 f.run(&[b"SADD", b"s", b"m1", b"m2"]);
3233
3234 assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
3235 f.run(&[b"SADD", b"t", b"m3"]);
3236 assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
3237 assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
3238 }
3239
3240 #[test]
3250 fn every_type_can_be_copied() {
3251 let mut f = Fixture::new();
3252 f.run(&[b"SET", b"str", b"v1"]);
3253 f.run(&[b"SADD", b"set", b"m1"]);
3254 f.run(&[b"HSET", b"hash", b"f", b"v"]);
3255 f.run(&[b"RPUSH", b"list", b"a", b"b"]);
3256 f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
3257
3258 for name in [
3259 &b"str"[..],
3260 &b"set"[..],
3261 &b"hash"[..],
3262 &b"list"[..],
3263 &b"zset"[..],
3264 ] {
3265 let dst = [name, b":copy"].concat();
3266 assert_eq!(
3267 f.run(&[b"COPY", name, &dst]),
3268 ":1\r\n",
3269 "copying {}",
3270 String::from_utf8_lossy(name)
3271 );
3272 assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
3273 }
3274
3275 assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
3276 let mut want = String::from("*2\r\n");
3277 want.push_str("$1\r\na\r\n$1\r\nb\r\n");
3278 want
3279 });
3280 assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
3281
3282 f.run(&[b"RPUSH", b"list:copy", b"c"]);
3284 assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
3285 assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
3286 }
3287
3288 #[test]
3289 fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
3290 let mut f = Fixture::new();
3291 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3292 f.run(&[b"SET", b"b", b"v2"]);
3293
3294 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
3295 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3296 assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
3297 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3298 assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
3299 assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
3300 }
3301
3302 #[test]
3303 fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
3304 let mut f = Fixture::new();
3305 f.run(&[b"SET", b"a", b"v1"]);
3306
3307 assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
3310 f.run(&[b"SELECT", b"1"]);
3311 assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
3312 assert_eq!(
3313 f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
3314 ":0\r\n",
3315 "taken"
3316 );
3317 assert_eq!(
3318 f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
3319 ":1\r\n"
3320 );
3321 }
3322
3323 #[test]
3324 fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
3325 let mut f = Fixture::new();
3326 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3327 assert_eq!(
3328 f.run(&[b"SORT", b"l"]),
3329 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3330 );
3331 assert_eq!(
3333 f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
3334 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3335 );
3336 assert_eq!(
3337 f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
3338 "*1\r\n$1\r\n2\r\n"
3339 );
3340 }
3341
3342 #[test]
3343 fn sort_reads_a_key_per_element_for_by_and_for_get() {
3344 let mut f = Fixture::new();
3345 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
3346 f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
3347 assert_eq!(
3350 f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
3351 "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
3352 );
3353 }
3354
3355 #[test]
3356 fn sort_store_writes_a_list_and_answers_its_length() {
3357 let mut f = Fixture::new();
3358 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3359 assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
3360 assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
3361 assert_eq!(
3362 f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
3363 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3364 );
3365 assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
3368 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
3369 }
3370
3371 #[test]
3372 fn sort_ro_does_not_know_the_word_store() {
3373 let mut f = Fixture::new();
3374 f.run(&[b"RPUSH", b"l", b"2", b"1"]);
3375 assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
3376 assert_eq!(
3377 f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
3378 "-ERR syntax error\r\n"
3379 );
3380 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3381 }
3382
3383 #[test]
3384 fn sort_refuses_what_it_cannot_sort() {
3385 let mut f = Fixture::new();
3386 assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
3387 f.run(&[b"SET", b"s", b"x"]);
3388 assert_eq!(
3389 f.run(&[b"SORT", b"s"]),
3390 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
3391 );
3392 f.run(&[b"RPUSH", b"words", b"one", b"two"]);
3393 assert_eq!(
3394 f.run(&[b"SORT", b"words"]),
3395 "-ERR One or more scores can't be converted into double\r\n"
3396 );
3397 assert_eq!(
3398 f.run(&[b"SORT", b"words", b"ALPHA"]),
3399 "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
3400 );
3401 assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
3402 }
3403
3404 #[test]
3405 fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
3406 let mut f = Fixture::new();
3407 assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
3408 assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
3409 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3410 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3411 assert_eq!(
3412 f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
3413 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3414 );
3415 assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
3418 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3419 }
3420
3421 #[test]
3422 fn move_answers_zero_when_either_end_says_no() {
3423 let mut f = Fixture::new();
3424 assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
3425 assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
3426 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3427 assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
3428 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3429 assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
3432 assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
3433 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3434 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
3435 }
3436
3437 #[test]
3438 fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
3439 let mut f = Fixture::new();
3440 assert_eq!(
3441 f.run(&[b"MOVE", b"a", b"0"]),
3442 "-ERR source and destination objects are the same\r\n"
3443 );
3444 assert_eq!(
3445 f.run(&[b"MOVE", b"a", b"99"]),
3446 "-ERR DB index is out of range\r\n"
3447 );
3448 assert_eq!(
3449 f.run(&[b"MOVE", b"a", b"-1"]),
3450 "-ERR DB index is out of range\r\n"
3451 );
3452 assert_eq!(
3453 f.run(&[b"MOVE", b"a", b"x"]),
3454 "-ERR value is not an integer or out of range\r\n"
3455 );
3456 }
3457
3458 #[test]
3459 fn swapdb_swaps_what_two_connections_would_see() {
3460 let mut f = Fixture::new();
3461 assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
3462 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3463 assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
3464 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3465
3466 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3467 assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
3469 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3470 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3471 assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
3473 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3474 }
3475
3476 #[test]
3481 fn a_wide_server_puts_its_databases_on_its_own_clock() {
3482 let mut f = Fixture::striped(8);
3483 f.server.set_clock_ms(1_700_000_000_000);
3484 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
3485 assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
3486 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3487 f.server.set_clock_ms(1_700_000_050_000);
3488 assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
3489 }
3490
3491 #[test]
3496 fn swapdb_swaps_every_stripe_of_a_wide_database() {
3497 let mut f = Fixture::striped(4);
3498 for i in 0..16u32 {
3499 let key = format!("k{i}");
3500 assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
3501 }
3502 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3503 assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
3504 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3505
3506 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3507 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3508 assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
3509 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3510 assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
3511 for i in 0..16u32 {
3512 let key = format!("k{i}");
3513 assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
3514 }
3515 }
3516
3517 #[test]
3518 fn swapdb_says_which_index_it_could_not_read() {
3519 let mut f = Fixture::new();
3520 assert_eq!(
3521 f.run(&[b"SWAPDB", b"x", b"1"]),
3522 "-ERR invalid first DB index\r\n"
3523 );
3524 assert_eq!(
3525 f.run(&[b"SWAPDB", b"0", b"y"]),
3526 "-ERR invalid second DB index\r\n"
3527 );
3528 assert_eq!(
3532 f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
3533 "-ERR invalid first DB index\r\n"
3534 );
3535 assert_eq!(
3536 f.run(&[b"SWAPDB", b"0", b"99"]),
3537 "-ERR DB index is out of range\r\n"
3538 );
3539 assert_eq!(
3540 f.run(&[b"SWAPDB", b"-1", b"0"]),
3541 "-ERR DB index is out of range\r\n"
3542 );
3543 }
3544
3545 #[test]
3546 fn wait_answers_zero_replicas_without_waiting() {
3547 let mut f = Fixture::new();
3548 assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
3549 assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
3550 assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
3553 assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
3556 assert_eq!(
3557 f.run(&[b"WAIT", b"x", b"0"]),
3558 "-ERR value is not an integer or out of range\r\n"
3559 );
3560 assert_eq!(
3561 f.run(&[b"WAIT", b"0", b"-1"]),
3562 "-ERR timeout is negative\r\n"
3563 );
3564 assert_eq!(
3565 f.run(&[b"WAIT", b"0", b"1.5"]),
3566 "-ERR timeout is not an integer or out of range\r\n"
3567 );
3568 }
3569
3570 #[test]
3571 fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
3572 let mut f = Fixture::new();
3573 assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
3574 assert_eq!(
3575 f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
3576 "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
3577 );
3578 assert_eq!(
3579 f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
3580 "-ERR value is out of range, value must between 0 and 1\r\n"
3581 );
3582 assert_eq!(
3583 f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
3584 "-ERR value is out of range, must be positive\r\n"
3585 );
3586 assert_eq!(
3589 f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
3590 "-ERR timeout is negative\r\n"
3591 );
3592 }
3593
3594 fn payload(reply: &[u8]) -> Vec<u8> {
3598 let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
3599 reply[head + 2..reply.len() - 2].to_vec()
3600 }
3601
3602 #[test]
3603 fn a_value_survives_a_dump_and_a_restore() {
3604 let mut f = Fixture::new();
3605 f.run(&[b"SET", b"s", b"hello"]);
3606 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
3607 f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
3608 f.run(&[b"SADD", b"u", b"x", b"y"]);
3609 f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
3610 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
3611
3612 for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
3613 let mut copy = key.to_vec();
3614 copy.push(b'2');
3615 let bytes = payload(&f.raw(&[b"DUMP", key]));
3616 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
3617 assert_eq!(f.run(&[b"TYPE", ©]), f.run(&[b"TYPE", key]));
3618 }
3619
3620 assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
3621 assert_eq!(
3622 f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
3623 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
3624 );
3625 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
3626 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
3627 assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
3628 assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
3629 assert_eq!(
3632 f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
3633 f.run(&[b"OBJECT", b"ENCODING", b"t"])
3634 );
3635 }
3636
3637 #[test]
3638 fn a_dumped_hash_keeps_its_field_deadlines() {
3639 let mut f = Fixture::new();
3640 f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
3641 assert_eq!(
3642 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
3643 "*1\r\n:1\r\n"
3644 );
3645 let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
3646 assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
3647 assert_eq!(
3648 f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
3649 "*2\r\n:-1\r\n:100\r\n"
3650 );
3651 }
3652
3653 #[test]
3654 fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
3655 let mut f = Fixture::new();
3656 f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
3657 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3658 assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
3659 assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
3660 assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
3661 assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
3662 assert_eq!(
3665 f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
3666 "+OK\r\n"
3667 );
3668 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3669 }
3670
3671 #[test]
3672 fn dump_answers_nothing_for_a_key_that_is_not_there() {
3673 let mut f = Fixture::new();
3674 assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
3675 f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
3676 f.advance(50);
3677 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
3678 }
3679
3680 #[test]
3681 fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
3682 let mut f = Fixture::new();
3683 f.run(&[b"SET", b"a", b"first"]);
3684 f.run(&[b"SET", b"b", b"second"]);
3685 let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
3686 assert_eq!(
3687 f.run(&[b"RESTORE", b"a", b"0", &bytes]),
3688 "-BUSYKEY Target key name already exists.\r\n"
3689 );
3690 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
3691 assert_eq!(
3692 f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
3693 "+OK\r\n"
3694 );
3695 assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
3696 }
3697
3698 #[test]
3702 fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
3703 let mut f = Fixture::new();
3704 f.run(&[b"SET", b"a", b"v"]);
3705 assert_eq!(
3706 f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
3707 "-BUSYKEY Target key name already exists.\r\n"
3708 );
3709 assert_eq!(
3712 f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
3713 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3714 );
3715 }
3716
3717 #[test]
3718 fn restore_can_tell_a_bad_footer_from_bad_bytes() {
3719 let mut f = Fixture::new();
3720 f.run(&[b"SET", b"a", b"hello"]);
3721 let good = payload(&f.raw(&[b"DUMP", b"a"]));
3722
3723 let mut flipped = good.clone();
3724 flipped[2] ^= 0x40;
3725 assert_eq!(
3726 f.run(&[b"RESTORE", b"b", b"0", &flipped]),
3727 "-ERR DUMP payload version or checksum are wrong\r\n"
3728 );
3729 assert_eq!(
3730 f.run(&[b"RESTORE", b"b", b"0", b"short"]),
3731 "-ERR DUMP payload version or checksum are wrong\r\n"
3732 );
3733 let mut truncated = good[..1].to_vec();
3737 truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
3738 let crc = yo_common::crc::crc64(0, &truncated);
3739 truncated.extend_from_slice(&crc.to_le_bytes());
3740 assert_eq!(
3741 f.run(&[b"RESTORE", b"b", b"0", &truncated]),
3742 "-ERR Bad data format\r\n"
3743 );
3744 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
3745 }
3746
3747 #[test]
3748 fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
3749 let mut f = Fixture::new();
3750 f.run(&[b"SET", b"a", b"v"]);
3751 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3752 assert_eq!(
3753 f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
3754 "-ERR Invalid TTL value, must be >= 0\r\n"
3755 );
3756 assert_eq!(
3757 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
3758 "-ERR Invalid IDLETIME value, must be >= 0\r\n"
3759 );
3760 assert_eq!(
3761 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
3762 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3763 );
3764 assert_eq!(
3766 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
3767 "+OK\r\n"
3768 );
3769 assert_eq!(
3770 f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
3771 "+OK\r\n"
3772 );
3773 }
3774
3775 #[test]
3779 fn restore_takes_idletime_or_freq_and_not_both() {
3780 let mut f = Fixture::new();
3781 f.run(&[b"SET", b"a", b"v"]);
3782 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3783 assert_eq!(
3784 f.run(&[
3785 b"RESTORE",
3786 b"b",
3787 b"0",
3788 &bytes,
3789 b"IDLETIME",
3790 b"1",
3791 b"FREQ",
3792 b"2"
3793 ]),
3794 "-ERR syntax error\r\n"
3795 );
3796 assert_eq!(
3797 f.run(&[
3798 b"RESTORE",
3799 b"b",
3800 b"0",
3801 &bytes,
3802 b"FREQ",
3803 b"2",
3804 b"IDLETIME",
3805 b"1"
3806 ]),
3807 "-ERR syntax error\r\n"
3808 );
3809 assert_eq!(
3810 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
3811 "-ERR syntax error\r\n"
3812 );
3813 assert_eq!(
3814 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
3815 "-ERR syntax error\r\n"
3816 );
3817 }
3818
3819 #[test]
3820 fn copy_checks_its_options_before_it_looks_for_anything() {
3821 let mut f = Fixture::new();
3822 assert_eq!(
3825 f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
3826 "-ERR DB index is out of range\r\n"
3827 );
3828 assert_eq!(
3829 f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
3830 "-ERR DB index is out of range\r\n"
3831 );
3832 assert_eq!(
3833 f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
3834 "-ERR value is not an integer or out of range\r\n"
3835 );
3836 assert_eq!(
3837 f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
3838 "-ERR syntax error\r\n"
3839 );
3840 assert_eq!(
3841 f.run(&[b"COPY", b"a", b"a"]),
3842 "-ERR source and destination objects are the same\r\n"
3843 );
3844 assert_eq!(
3846 f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
3847 ":0\r\n"
3848 );
3849 }
3850
3851 #[test]
3852 fn time_is_two_bulk_strings_and_moves() {
3853 let mut f = Fixture::new();
3854 let first = f.run(&[b"TIME"]);
3855 assert!(first.starts_with("*2\r\n$"), "got {first}");
3856 let parts: Vec<&str> = first.split("\r\n").collect();
3857 let secs: i64 = parts[2].parse().expect("seconds as decimal text");
3858 let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
3859 assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
3860 assert!((0..1_000_000).contains(µs), "got {micros}");
3861 assert_ne!(first, f.run(&[b"TIME"]));
3865 }
3866
3867 #[test]
3868 fn a_keyspace_scan_walks_every_key_once() {
3869 let n = if cfg!(miri) { 96 } else { 500 };
3873 let mut f = Fixture::new();
3874 for i in 0..n {
3875 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
3876 }
3877
3878 let mut seen: Vec<String> = Vec::new();
3879 let mut cursor = "0".to_owned();
3880 let mut calls = 0;
3881 loop {
3882 let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
3883 seen.extend(keys);
3884 cursor = next;
3885 calls += 1;
3886 assert!(calls < 10_000, "the cursor is not advancing");
3887 if cursor == "0" {
3888 break;
3889 }
3890 }
3891
3892 seen.sort();
3893 seen.dedup();
3894 assert_eq!(seen.len(), n, "every key once and only once");
3895 assert!(calls > 1, "{n} keys came back in one batch");
3898 }
3899
3900 #[test]
3901 fn a_scan_narrows_by_pattern_and_by_type() {
3902 let mut f = Fixture::new();
3903 f.run(&[b"SET", b"str", b"v"]);
3904 f.run(&[b"SADD", b"members", b"a"]);
3905 f.run(&[b"HSET", b"fields", b"f", b"v"]);
3906
3907 let all = |f: &mut Fixture, args: &[&[u8]]| {
3908 let mut out: Vec<String> = Vec::new();
3909 let mut cursor = "0".to_owned();
3910 loop {
3911 let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
3912 line.extend_from_slice(args);
3913 let (next, keys) = scan_reply(&f.run(&line));
3914 out.extend(keys);
3915 cursor = next;
3916 if cursor == "0" {
3917 break;
3918 }
3919 }
3920 out.sort();
3921 out
3922 };
3923
3924 assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
3925 assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
3926 assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
3927 assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
3929 assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
3931 assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
3932 assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
3934 }
3935
3936 #[test]
3937 fn a_scan_says_what_is_wrong_with_it() {
3938 let mut f = Fixture::new();
3939 assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
3940 assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
3941 assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
3942 assert_eq!(
3943 f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
3944 "-ERR syntax error\r\n"
3945 );
3946 assert_eq!(
3947 f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
3948 "-ERR value is not an integer or out of range\r\n"
3949 );
3950 assert_eq!(
3951 f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
3952 "-ERR syntax error\r\n"
3953 );
3954 assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
3959 }
3960
3961 #[test]
3962 fn keys_and_randomkey_look_at_the_whole_database() {
3963 let mut f = Fixture::new();
3964 assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
3965 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
3966
3967 for name in ["one", "two", "three"] {
3968 f.run(&[b"SET", name.as_bytes(), b"v"]);
3969 }
3970 assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
3971 assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
3972 assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
3973
3974 for _ in 0..50 {
3975 let got = f.run(&[b"RANDOMKEY"]);
3976 assert!(
3977 ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
3978 "got {got}"
3979 );
3980 }
3981 }
3982
3983 #[test]
3984 fn a_walk_does_not_answer_keys_that_have_expired() {
3985 let mut f = Fixture::new();
3986 f.run(&[b"SET", b"alive", b"v"]);
3987 f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
3988 f.server.advance_clock_ms(2);
3989 assert_eq!(
3990 f.run(&[b"DBSIZE"]),
3991 ":2\r\n",
3992 "nothing has collected it yet"
3993 );
3994
3995 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3996 let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3997 assert_eq!(keys, ["alive"]);
3998 for _ in 0..20 {
3999 assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
4000 }
4001 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4004 }
4005
4006 #[test]
4007 fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
4008 let mut f = Fixture::new();
4009 f.run(&[b"SET", b"k", b"v"]);
4010 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
4011 assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
4012
4013 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
4014 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4015 let ms = int(&f.run(&[b"PTTL", b"k"]));
4016 assert!((99_000..=100_000).contains(&ms), "got {ms}");
4017
4018 let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
4020 let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4021 assert_eq!(at, (at_ms + 500) / 1000);
4022 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
4023
4024 assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
4025 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
4026 assert_eq!(
4027 f.run(&[b"PERSIST", b"k"]),
4028 ":0\r\n",
4029 "nothing to take off the second time"
4030 );
4031 assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
4032 assert_eq!(
4033 f.run(&[b"GET", b"k"]),
4034 "$1\r\nv\r\n",
4035 "and the value went through all of that untouched"
4036 );
4037 }
4038
4039 #[test]
4040 fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
4041 let mut f = Fixture::new();
4042 f.run(&[b"SET", b"str", b"v"]);
4043 f.run(&[b"SADD", b"set", b"a", b"b"]);
4044 f.run(&[b"HSET", b"hash", b"f", b"v"]);
4045
4046 for key in [b"str".as_slice(), b"set", b"hash"] {
4047 assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
4048 assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
4049 }
4050 assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
4053 assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
4054 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
4055 }
4056
4057 #[test]
4058 fn a_deadline_that_has_already_gone_deletes_the_key_now() {
4059 let mut f = Fixture::new();
4060 for key in [b"a".as_slice(), b"b", b"c", b"d"] {
4061 f.run(&[b"SET", key, b"v"]);
4062 }
4063 assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
4067 assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
4068 assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
4069 assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
4070 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4071 assert_eq!(
4072 f.run(&[b"EXPIRE", b"a", b"100"]),
4073 ":0\r\n",
4074 "and the key really went, so there is nothing to put a deadline on"
4075 );
4076 }
4077
4078 #[test]
4079 fn the_four_conditions_decide_whether_the_deadline_moves() {
4080 let mut f = Fixture::new();
4081 f.run(&[b"SET", b"k", b"v"]);
4082
4083 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
4084 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
4085 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
4086 assert_eq!(
4087 f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
4088 ":1\r\n",
4089 "no deadline reads as infinitely far away, so LT passes where GT fails"
4090 );
4091
4092 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
4093 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
4094 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4095 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
4096 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
4097 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4098
4099 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
4102 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
4103 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
4104 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
4105 }
4106
4107 #[test]
4108 fn the_conditions_are_a_set_and_not_a_keyword() {
4109 let mut f = Fixture::new();
4110 f.run(&[b"SET", b"k", b"v"]);
4111
4112 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
4113 assert_eq!(
4114 f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
4115 ":0\r\n",
4116 "the same keyword twice means it once, and NX now has a deadline to fail on"
4117 );
4118
4119 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
4122 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4123 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
4124 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
4125 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4126 f.run(&[b"PERSIST", b"k"]);
4127 assert_eq!(
4128 f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
4129 ":0\r\n",
4130 "where LT on its own would have taken it"
4131 );
4132 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
4133 }
4134
4135 #[test]
4136 fn a_key_is_gone_once_its_moment_passes() {
4137 let mut f = Fixture::new();
4138 f.run(&[b"SET", b"k", b"v"]);
4139 f.run(&[b"EXPIRE", b"k", b"100"]);
4140
4141 let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4142 f.server.set_clock_ms(at as u64 + 1);
4143 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4144 assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
4145 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
4146 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4147 }
4148
4149 #[test]
4150 fn the_expiry_commands_refuse_what_a_real_server_refuses() {
4151 let mut f = Fixture::new();
4152 f.run(&[b"SET", b"k", b"v"]);
4153 for (bad, want) in [
4154 (
4155 &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
4156 "-ERR value is not an integer or out of range\r\n",
4157 ),
4158 (
4159 &[b"EXPIRE", b"k", b"100", b"MAYBE"],
4160 "-ERR Unsupported option MAYBE\r\n",
4161 ),
4162 (
4163 &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
4164 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4165 ),
4166 (
4167 &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
4168 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4169 ),
4170 (
4171 &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
4172 "-ERR GT and LT options at the same time are not compatible\r\n",
4173 ),
4174 (
4177 &[b"EXPIRE", b"k", b"9223372036854775807"],
4178 "-ERR invalid expire time in 'expire' command\r\n",
4179 ),
4180 (
4181 &[b"EXPIREAT", b"k", b"9223372036854775807"],
4182 "-ERR invalid expire time in 'expireat' command\r\n",
4183 ),
4184 (
4185 &[b"PEXPIRE", b"k", b"9223372036854775807"],
4186 "-ERR invalid expire time in 'pexpire' command\r\n",
4187 ),
4188 ] {
4189 assert_eq!(f.run(bad), want, "for {bad:?}");
4190 }
4191 assert_eq!(
4192 f.run(&[b"TTL", b"k"]),
4193 ":-1\r\n",
4194 "and none of those put a deadline on anything"
4195 );
4196
4197 assert_eq!(
4201 f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
4202 ":1\r\n"
4203 );
4204 assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
4205 }
4206
4207 #[test]
4208 fn flushing_empties_this_database_or_every_one_of_them() {
4209 let mut f = Fixture::new();
4210 f.run(&[b"SELECT", b"0"]);
4211 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4212 f.run(&[b"SELECT", b"1"]);
4213 f.run(&[b"SET", b"c", b"3"]);
4214 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4215 assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
4218 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4219 f.run(&[b"SELECT", b"0"]);
4221 assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
4222 assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
4223 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4224 f.run(&[b"SELECT", b"1"]);
4225 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4226 assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
4229 assert_eq!(
4230 f.run(&[b"FLUSHDB", b"sync", b"sync"]),
4231 "-ERR syntax error\r\n"
4232 );
4233 }
4234
4235 #[test]
4236 fn the_script_cache_and_the_library_set_answer_for_being_empty() {
4237 let mut f = Fixture::new();
4238 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4239 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
4240 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
4241 assert_eq!(
4244 f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
4245 "*2\r\n:0\r\n:0\r\n"
4246 );
4247 assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
4248 assert_eq!(
4249 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
4250 "*0\r\n"
4251 );
4252 assert_eq!(
4253 f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
4254 "-ERR Library not found\r\n"
4255 );
4256
4257 assert_eq!(
4260 f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
4261 "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
4262 );
4263 assert_eq!(
4264 f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
4265 "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
4266 );
4267 assert_eq!(
4272 f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
4273 "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
4274 );
4275 assert_eq!(
4276 f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
4277 "-ERR Unknown argument bogus\r\n"
4278 );
4279 assert_eq!(
4280 f.run(&[b"SCRIPT", b"EXISTS"]),
4281 "-ERR wrong number of arguments for 'script|exists' command\r\n"
4282 );
4283
4284 assert_eq!(
4285 f.run(&[b"FUNCTION", b"NOPE"]),
4286 "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
4287 );
4288 }
4289
4290 #[test]
4291 fn the_script_cache_holds_what_was_loaded_into_it() {
4292 let mut f = Fixture::new();
4293 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4296 assert_eq!(
4297 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4298 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4299 );
4300 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4301 assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
4302 assert_eq!(
4305 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4306 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4307 );
4308 assert!(
4309 f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
4310 .starts_with("-ERR Error compiling script"),
4311 );
4312
4313 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4314 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
4315 assert_eq!(
4316 f.run(&[b"EVALSHA", sha, b"0"]),
4317 "-NOSCRIPT No matching script. Please use EVAL.\r\n"
4318 );
4319
4320 assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
4323 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4324
4325 assert_eq!(
4328 f.run(&[b"SCRIPT", b"KILL"]),
4329 "-NOTBUSY No scripts in execution right now.\r\n"
4330 );
4331 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
4332 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
4333 assert_eq!(
4334 f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
4335 "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
4336 );
4337 }
4338
4339 #[test]
4340 fn eval_counts_its_keys_before_it_compiles_anything() {
4341 let mut f = Fixture::new();
4342 assert_eq!(
4343 f.run(&[b"EVAL", b"return 1"]),
4344 "-ERR wrong number of arguments for 'eval' command\r\n"
4345 );
4346 assert_eq!(
4347 f.run(&[b"EVAL", b"return 1", b"abc"]),
4348 "-ERR value is not an integer or out of range\r\n"
4349 );
4350 assert_eq!(
4351 f.run(&[b"EVAL", b"return 1", b"-1"]),
4352 "-ERR Number of keys can't be negative\r\n"
4353 );
4354 assert_eq!(
4355 f.run(&[b"EVAL", b"return 1", b"1"]),
4356 "-ERR Number of keys can't be greater than number of args\r\n"
4357 );
4358 assert_eq!(
4360 f.run(&[
4361 b"EVAL",
4362 b"return {KEYS[1],KEYS[2],ARGV[1]}",
4363 b"2",
4364 b"a",
4365 b"b",
4366 b"c"
4367 ]),
4368 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4369 );
4370 assert_eq!(
4371 f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
4372 ":0\r\n"
4373 );
4374 assert_eq!(
4375 f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
4376 ":2\r\n"
4377 );
4378 }
4379
4380 #[test]
4381 fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
4382 let mut f = Fixture::new();
4383 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4384
4385 assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
4388 assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
4389 assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
4390 assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
4391 assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
4392 assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
4393 assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
4394
4395 assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
4396 assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
4397 assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
4399 assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
4400 assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
4401 assert_eq!(eval(&mut f, b""), "$-1\r\n");
4402
4403 assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
4406 assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
4407 assert_eq!(
4408 eval(&mut f, b"return {1,'a',{2}}"),
4409 "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
4410 );
4411
4412 assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
4414 assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
4415 assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
4416 assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
4417 assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a b\r\n");
4420 assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
4423 assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
4424 }
4425
4426 #[test]
4427 fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
4428 let mut f = Fixture::new();
4429 assert_eq!(
4432 f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
4433 "$3\r\n3.5\r\n"
4434 );
4435 assert_eq!(
4436 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4437 "$3\r\n123\r\n"
4438 );
4439 assert_eq!(
4440 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4441 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4442 );
4443 assert_eq!(
4444 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4445 "*1\r\n$1\r\na\r\n"
4446 );
4447 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
4448
4449 f.out = Out::new(Proto::Resp3);
4450 assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
4451 assert_eq!(
4452 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4453 "(123\r\n"
4454 );
4455 assert_eq!(
4456 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4457 "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
4458 );
4459 assert_eq!(
4460 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4461 "~1\r\n$1\r\na\r\n"
4462 );
4463 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
4464 }
4465
4466 #[test]
4467 fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
4468 let mut f = Fixture::new();
4469 f.run(&[b"SET", b"s", b"hello"]);
4470 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4471 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4472
4473 assert_eq!(
4474 eval(&mut f, b"return type(redis.call('get','s'))"),
4475 "$6\r\nstring\r\n"
4476 );
4477 assert_eq!(
4478 eval(&mut f, b"return type(redis.call('llen','l'))"),
4479 "$6\r\nnumber\r\n"
4480 );
4481 assert_eq!(
4482 eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
4483 "$5\r\ntable\r\n"
4484 );
4485 assert_eq!(
4488 eval(&mut f, b"return redis.call('set','s','v')['ok']"),
4489 "$2\r\nOK\r\n"
4490 );
4491 assert_eq!(
4494 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4495 "$5\r\nfalse\r\n"
4496 );
4497 assert_eq!(
4498 eval(
4499 &mut f,
4500 b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
4501 ),
4502 "$3\r\nnil\r\n"
4503 );
4504 assert_eq!(
4506 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4507 "$5\r\nfalse\r\n"
4508 );
4509 }
4510
4511 #[test]
4512 fn an_error_from_a_script_names_the_line_it_came_from() {
4513 let mut f = Fixture::new();
4514 assert_eq!(
4517 f.run(&[b"EVAL", b"error('boom')", b"0"]),
4518 "-ERR user_script:1: boom script: \
4519 82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
4520 );
4521 assert_eq!(
4523 f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
4524 "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
4525 );
4526 assert!(
4528 f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
4529 .starts_with("-structured script: "),
4530 );
4531 assert_eq!(
4534 f.run(&[b"EVAL", b"return this is not lua", b"0"]),
4535 "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
4536 );
4537
4538 assert_eq!(
4541 f.run(&[
4542 b"EVAL",
4543 b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
4544 b"0"
4545 ]),
4546 "$8\r\nstring:z\r\n"
4547 );
4548 assert_eq!(
4549 f.run(&[
4550 b"EVAL",
4551 b"local a, b = pcall(function() error({a=1}) end) return type(b)",
4552 b"0"
4553 ]),
4554 "$5\r\ntable\r\n"
4555 );
4556 }
4557
4558 #[test]
4559 fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
4560 let mut f = Fixture::new();
4561 let sentence = |f: &mut Fixture, body: &[u8]| {
4562 let reply = f.run(&[b"EVAL", body, b"0"]);
4563 reply.split(" script: ").next().unwrap().to_owned()
4564 };
4565
4566 assert_eq!(
4567 sentence(&mut f, b"return redis.call()"),
4568 "-ERR Please specify at least one argument for this redis lib call"
4569 );
4570 assert_eq!(
4571 sentence(&mut f, b"return redis.call('get', {})"),
4572 "-ERR Lua redis lib command arguments must be strings or integers"
4573 );
4574 assert_eq!(
4575 sentence(&mut f, b"return redis.call('nosuchcmd')"),
4576 "-ERR Unknown Redis command called from script"
4577 );
4578 assert_eq!(
4579 sentence(&mut f, b"return redis.call('get')"),
4580 "-ERR Wrong number of args calling Redis command from script"
4581 );
4582 for name in [
4586 &b"return redis.call('multi')"[..],
4587 b"return redis.call('exec')",
4588 b"return redis.call('watch','k')",
4589 b"return redis.call('subscribe','c')",
4590 b"return redis.call('debug','jmap')",
4591 b"return redis.call('eval','return 1',0)",
4592 b"return redis.call('config','get','maxmemory')",
4593 ] {
4594 assert_eq!(
4595 sentence(&mut f, name),
4596 "-ERR This Redis command is not allowed from script",
4597 "for {}",
4598 String::from_utf8_lossy(name)
4599 );
4600 }
4601 assert!(
4604 f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
4605 .starts_with('*'),
4606 );
4607
4608 assert_eq!(
4611 f.run(&[
4612 b"EVAL",
4613 b"local x = redis.pcall('nosuchcmd') return x.err",
4614 b"0"
4615 ]),
4616 "$44\r\nERR Unknown Redis command called from script\r\n"
4617 );
4618 assert_eq!(
4620 f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
4621 "-ERR Unknown Redis command called from script\r\n"
4622 );
4623 }
4624
4625 #[test]
4626 fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
4627 let mut f = Fixture::new();
4628 f.run(&[b"SET", b"k", b"v"]);
4629 assert_eq!(
4630 f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
4631 "$1\r\nv\r\n"
4632 );
4633 assert!(
4634 f.run(&[
4635 b"EVAL_RO",
4636 b"return redis.call('set', KEYS[1], 'x')",
4637 b"1",
4638 b"k"
4639 ])
4640 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
4641 );
4642 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
4644 assert_eq!(
4645 f.run(&[
4646 b"EVAL",
4647 b"return redis.call('set', KEYS[1], 'x')",
4648 b"1",
4649 b"k"
4650 ]),
4651 "+OK\r\n"
4652 );
4653 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
4654
4655 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4657 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
4658 assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
4659 }
4660
4661 #[test]
4662 fn a_script_cannot_leave_anything_behind_for_the_next_one() {
4663 let mut f = Fixture::new();
4664 for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
4667 let reply = f.run(&[b"EVAL", body, b"0"]);
4668 assert!(
4669 reply
4670 .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
4671 "{body:?} gave {reply}",
4672 );
4673 }
4674 for body in [
4677 &b"rawset(redis, 'call', 1)"[..],
4678 b"rawset(_G, 'zz', 1)",
4679 b"setmetatable(_G, {})",
4680 b"setmetatable(redis, {})",
4681 ] {
4682 let reply = f.run(&[b"EVAL", body, b"0"]);
4683 assert!(
4684 reply.starts_with("-ERR Attempt to modify a readonly table script: "),
4685 "{body:?} gave {reply}",
4686 );
4687 }
4688 assert!(
4691 f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
4692 .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
4693 );
4694 assert_eq!(
4697 f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
4698 "$3\r\nnil\r\n"
4699 );
4700
4701 assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
4704 assert_eq!(
4705 f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
4706 "$8\r\nfunction\r\n"
4707 );
4708 assert_eq!(
4709 f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
4710 "$8\r\nfunction\r\n"
4711 );
4712 }
4713
4714 #[test]
4715 fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
4716 let mut f = Fixture::new();
4717 assert_eq!(
4721 f.run(&[
4722 b"EVAL",
4723 b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
4724 b"0",
4725 ]),
4726 ":23\r\n"
4727 );
4728 assert_eq!(
4729 f.run(&[
4730 b"EVAL",
4731 b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
4732 table.sort(t) return table.concat(t, ' ')",
4733 b"0",
4734 ]),
4735 "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
4736 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
4737 acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
4738 set_repl setresp sha1hex status_reply\r\n"
4739 );
4740 assert_eq!(
4743 f.run(&[
4744 b"EVAL",
4745 b"local n = 0 for k, v in pairs(redis) do \
4746 if type(v) == 'function' then n = n + 1 end end return n",
4747 b"0",
4748 ]),
4749 ":12\r\n"
4750 );
4751 assert_eq!(
4753 f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
4754 "$6\r\nstring\r\n"
4755 );
4756 assert_eq!(
4757 f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
4758 "$8\r\nfunction\r\n"
4759 );
4760 assert_eq!(
4761 f.run(&[
4762 b"EVAL",
4763 b"return tostring(rawget(redis, 'nosuchfield'))",
4764 b"0",
4765 ]),
4766 "$3\r\nnil\r\n"
4767 );
4768 for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
4771 assert!(
4772 f.run(&[b"EVAL", body, b"0"])
4773 .contains("Attempt to modify a readonly table script: "),
4774 "{body:?}",
4775 );
4776 }
4777 assert_eq!(
4780 f.run(&[
4781 b"EVAL",
4782 b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
4783 b"0",
4784 ]),
4785 ":2\r\n"
4786 );
4787 assert_eq!(
4788 f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
4789 "$3\r\nnil\r\n"
4790 );
4791 assert_eq!(
4792 f.run(&[
4793 b"EVAL",
4794 b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
4795 return type(f)",
4796 b"0",
4797 ]),
4798 "$8\r\nfunction\r\n"
4799 );
4800 }
4801
4802 #[test]
4803 fn a_script_gets_the_bit_library_a_real_server_carries() {
4804 let mut f = Fixture::new();
4805 for (body, want) in [
4808 ("bit.tobit(1)", ":1\r\n"),
4809 ("bit.tobit(2^32 + 1)", ":1\r\n"),
4810 ("bit.tobit(2^31)", ":-2147483648\r\n"),
4811 ("bit.tobit(0xffffffff)", ":-1\r\n"),
4812 ("bit.tobit(1.5)", ":2\r\n"),
4814 ("bit.tobit(2.5)", ":2\r\n"),
4815 ("bit.bnot(0)", ":-1\r\n"),
4816 ("bit.band(0xff, 0x0f)", ":15\r\n"),
4817 ("bit.band(1, 2, 3)", ":0\r\n"),
4818 ("bit.bor(1, 2, 4)", ":7\r\n"),
4819 ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
4820 ("bit.lshift(1, 31)", ":-2147483648\r\n"),
4822 ("bit.lshift(1, 32)", ":1\r\n"),
4823 ("bit.lshift(1, 33)", ":2\r\n"),
4824 ("bit.rshift(-1, 1)", ":2147483647\r\n"),
4825 ("bit.arshift(-1, 1)", ":-1\r\n"),
4826 ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
4827 ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
4828 ("bit.bswap(0x12345678)", ":2018915346\r\n"),
4829 ("bit.tobit('0x10')", ":16\r\n"),
4832 ] {
4833 let script = format!("return {body}");
4834 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4835 }
4836 for (body, want) in [
4839 ("bit.tohex(1)", "00000001"),
4840 ("bit.tohex(-1)", "ffffffff"),
4841 ("bit.tohex(255, 2)", "ff"),
4842 ("bit.tohex(255, -8)", "000000FF"),
4843 ("bit.tohex(0x87654321, 4)", "4321"),
4844 ("bit.tohex(1, 0)", ""),
4845 ("bit.tohex(1, 9)", "00000001"),
4846 ] {
4847 let script = format!("return {body}");
4848 assert_eq!(
4849 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4850 format!("${}\r\n{want}\r\n", want.len()),
4851 "{body}",
4852 );
4853 }
4854 for (body, want) in [
4857 (
4858 "return bit.band()",
4859 "bad argument #1 to 'band' (number expected, got no value)",
4860 ),
4861 (
4862 "return bit.band('x')",
4863 "bad argument #1 to 'band' (number expected, got string)",
4864 ),
4865 (
4866 "return bit.tobit(true)",
4867 "bad argument #1 to 'tobit' (number expected, got boolean)",
4868 ),
4869 (
4870 "return bit.lshift(1)",
4871 "bad argument #2 to 'lshift' (number expected, got no value)",
4872 ),
4873 ] {
4874 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4875 assert!(
4876 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4877 "{body} gave {reply}",
4878 );
4879 }
4880 assert_eq!(
4883 f.run(&[
4884 b"EVAL",
4885 b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
4886 b"0",
4887 ]),
4888 "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
4889 );
4890 assert_eq!(
4892 f.run(&[
4893 b"EVAL",
4894 b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
4895 table.sort(t) return table.concat(t, ' ')",
4896 b"0",
4897 ]),
4898 "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
4899 );
4900 for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
4901 assert!(
4902 f.run(&[b"EVAL", body, b"0"])
4903 .contains("Attempt to modify a readonly table script: "),
4904 "{body:?}",
4905 );
4906 }
4907 }
4908
4909 #[test]
4910 fn a_script_gets_the_cjson_library_a_real_server_carries() {
4911 let mut f = Fixture::new();
4912 for (body, want) in [
4916 ("cjson.encode(nil)", "null"),
4917 ("cjson.encode(true)", "true"),
4918 ("cjson.encode(cjson.null)", "null"),
4919 ("cjson.encode(100)", "100"),
4920 ("cjson.encode(1/3)", "0.33333333333333"),
4921 ("cjson.encode(1e300)", "1e+300"),
4922 ("cjson.encode(2^53)", "9.007199254741e+15"),
4923 ("cjson.encode({})", "{}"),
4924 ("cjson.encode({1,2,3})", "[1,2,3]"),
4925 ("cjson.encode({a=1})", "{\"a\":1}"),
4926 ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
4927 ("cjson.encode({[0]=1})", "{\"0\":1}"),
4928 ("cjson.encode('a\\nb')", "\"a\\nb\""),
4929 ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
4931 ("cjson.encode('\\11')", "\"\\u000b\""),
4932 (
4935 "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
4936 "[1,[2,{\"a\":null}]]",
4937 ),
4938 ("cjson.encode(cjson.decode('[]'))", "{}"),
4941 ] {
4942 let script = format!("return {body}");
4943 assert_eq!(
4944 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4945 format!("${}\r\n{want}\r\n", want.len()),
4946 "{body}",
4947 );
4948 }
4949 for (body, want) in [
4952 ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
4953 ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
4954 ("cjson.decode('0x10')", ":16\r\n"),
4955 ("cjson.decode('+1')", ":1\r\n"),
4956 ("cjson.decode('01')", ":1\r\n"),
4957 ("cjson.decode(1) + 1", ":2\r\n"),
4958 ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
4960 ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
4961 ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
4962 ] {
4963 let script = format!("return {body}");
4964 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4965 }
4966 for (body, want) in [
4968 (
4969 "cjson.encode_number_precision(3) return cjson.encode(1/3)",
4970 "0.333",
4971 ),
4972 (
4973 "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
4974 "null",
4975 ),
4976 (
4977 "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
4978 "inf",
4979 ),
4980 (
4981 "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
4982 "{\"1\":1,\"100\":1}",
4983 ),
4984 (
4985 "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
4986 "[]",
4987 ),
4988 ("return tostring(cjson.encode_max_depth())", "1000"),
4989 ("return tostring(cjson.encode_keep_buffer(false))", "false"),
4990 ("return tostring(cjson.encode_sparse_array())", "false"),
4991 ("return tostring(cjson.encode_number_precision())", "14"),
4994 ] {
4995 assert_eq!(
4996 f.run(&[b"EVAL", body.as_bytes(), b"0"]),
4997 format!("${}\r\n{want}\r\n", want.len()),
4998 "{body}",
4999 );
5000 }
5001 for (body, want) in [
5003 (
5004 "return cjson.encode(1/0)",
5005 "Cannot serialise number: must not be NaN or Inf",
5006 ),
5007 (
5008 "return cjson.encode({[1]=1,[100]=1})",
5009 "Cannot serialise table: excessively sparse array",
5010 ),
5011 (
5012 "return cjson.encode({[true]=1})",
5013 "Cannot serialise boolean: table key must be a number or string",
5014 ),
5015 (
5016 "return cjson.encode(tostring)",
5017 "Cannot serialise function: type not supported",
5018 ),
5019 (
5020 "return cjson.encode()",
5021 "bad argument #1 to 'encode' (expected 1 argument)",
5022 ),
5023 (
5024 "return cjson.decode('[1,2')",
5025 "Expected comma or array end but found T_END at character 5",
5026 ),
5027 (
5028 "return cjson.decode('{\"a\" 1}')",
5029 "Expected colon but found T_NUMBER at character 6",
5030 ),
5031 (
5032 "return cjson.decode('tru')",
5033 "Expected value but found invalid token at character 1",
5034 ),
5035 (
5036 "return cjson.decode('[1] 2')",
5037 "Expected the end but found T_NUMBER at character 5",
5038 ),
5039 (
5040 "return cjson.encode_max_depth(0)",
5041 "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
5042 ),
5043 (
5044 "return cjson.encode_invalid_numbers('yes')",
5045 "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
5046 ),
5047 (
5048 "return cjson.encode_max_depth(1, 2)",
5049 "bad argument #2 to 'encode_max_depth' (found too many arguments)",
5050 ),
5051 ] {
5052 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5053 assert!(
5054 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5055 "{body} gave {reply}",
5056 );
5057 }
5058 assert_eq!(
5061 f.run(&[
5062 b"EVAL",
5063 b"local n = cjson.new() n.encode_number_precision(3) \
5064 return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
5065 b"0",
5066 ]),
5067 "$22\r\n0.33333333333333 0.333\r\n"
5068 );
5069 let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
5071 decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
5072 encode_max_depth encode_number_precision encode_sparse_array new null";
5073 assert_eq!(
5074 f.run(&[
5075 b"EVAL",
5076 b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
5077 table.sort(t) return table.concat(t, ' ')",
5078 b"0",
5079 ]),
5080 format!("${}\r\n{names}\r\n", names.len())
5081 );
5082 for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
5083 assert!(
5084 f.run(&[b"EVAL", body, b"0"])
5085 .contains("Attempt to modify a readonly table script: "),
5086 "{body:?}",
5087 );
5088 }
5089 }
5090
5091 #[test]
5092 fn a_script_gets_the_struct_library_a_real_server_carries() {
5093 let mut f = Fixture::new();
5094 for (body, want) in [
5097 ("#struct.pack('i4', 1)", ":4\r\n"),
5098 ("#struct.pack('l', 1)", ":8\r\n"),
5099 ("#struct.pack('d', 1)", ":8\r\n"),
5100 ("#struct.pack('f', 1)", ":4\r\n"),
5101 ("#struct.pack('s', 'abc')", ":4\r\n"),
5102 ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
5103 ("#struct.pack('x')", ":1\r\n"),
5104 ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
5105 ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
5106 ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
5107 ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
5110 ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
5111 ("#struct.pack('c0', 'abcd')", ":4\r\n"),
5114 ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
5115 ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
5116 ] {
5117 let script = format!("return {body}");
5118 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5119 }
5120 for (body, want) in [
5123 ("struct.size('i')", ":4\r\n"),
5124 ("struct.size('l')", ":8\r\n"),
5125 ("struct.size('T')", ":8\r\n"),
5126 ("struct.size('h')", ":2\r\n"),
5127 ("struct.size('c10')", ":10\r\n"),
5128 ("struct.size('ic')", ":5\r\n"),
5129 ("struct.size('!8ic')", ":5\r\n"),
5130 ("struct.size('!4i')", ":4\r\n"),
5131 ("struct.size('bd')", ":9\r\n"),
5134 ("struct.size('!bd')", ":16\r\n"),
5135 ("struct.size('A')", ":0\r\n"),
5136 ("struct.size('7')", ":0\r\n"),
5137 ] {
5138 let script = format!("return {body}");
5139 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5140 }
5141 for (body, want) in [
5144 ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
5145 ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
5146 ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
5147 ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
5148 ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
5149 (
5150 "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
5151 ":-70000\r\n",
5152 ),
5153 ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
5154 ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
5158 ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
5159 ] {
5160 let script = format!("return {body}");
5161 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5162 }
5163 for (body, want) in [
5164 ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
5165 ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
5166 ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
5167 ] {
5168 let script = format!("return {body}");
5169 assert_eq!(
5170 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5171 format!("${}\r\n{want}\r\n", want.len()),
5172 "{body}",
5173 );
5174 }
5175 for (body, want) in [
5178 (
5179 "return struct.pack()",
5180 "bad argument #1 to 'pack' (string expected, got no value)",
5181 ),
5182 (
5185 "return struct.pack('i4')",
5186 "bad argument #2 to 'pack' (number expected, got nil)",
5187 ),
5188 (
5191 "return struct.pack('c6', 'abc')",
5192 "bad argument #3 to 'pack' (string too short)",
5193 ),
5194 (
5195 "return struct.pack('A', 'x')",
5196 "bad argument #1 to 'pack' (invalid format option 'A')",
5197 ),
5198 (
5199 "return struct.pack('i33', 1)",
5200 "integral size 33 is larger than limit of 32",
5201 ),
5202 (
5203 "return struct.pack('!3i', 1)",
5204 "alignment 3 is not a power of 2",
5205 ),
5206 (
5207 "return struct.unpack()",
5208 "bad argument #1 to 'unpack' (string expected, got no value)",
5209 ),
5210 (
5211 "return struct.unpack('i4')",
5212 "bad argument #2 to 'unpack' (string expected, got no value)",
5213 ),
5214 (
5215 "return struct.unpack('i4', 'ab')",
5216 "bad argument #2 to 'unpack' (data string too short)",
5217 ),
5218 (
5219 "return struct.unpack('i1', 'abc', 0)",
5220 "bad argument #3 to 'unpack' (offset must be 1 or greater)",
5221 ),
5222 (
5223 "return struct.unpack('c0', 'abc')",
5224 "format 'c0' needs a previous size",
5225 ),
5226 (
5227 "return struct.unpack('s', 'abc')",
5228 "unfinished string in data",
5229 ),
5230 (
5231 "return struct.size()",
5232 "bad argument #1 to 'size' (string expected, got no value)",
5233 ),
5234 (
5235 "return struct.size('s')",
5236 "bad argument #1 to 'size' (option 's' has no fixed size)",
5237 ),
5238 (
5239 "return struct.size('c0')",
5240 "bad argument #1 to 'size' (option 'c0' has no fixed size)",
5241 ),
5242 ] {
5243 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5244 assert!(
5245 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5246 "{body} gave {reply}",
5247 );
5248 }
5249 let names = "pack size unpack";
5251 assert_eq!(
5252 f.run(&[
5253 b"EVAL",
5254 b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
5255 table.sort(t) return table.concat(t, ' ')",
5256 b"0",
5257 ]),
5258 format!("${}\r\n{names}\r\n", names.len())
5259 );
5260 for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
5261 assert!(
5262 f.run(&[b"EVAL", body, b"0"])
5263 .contains("Attempt to modify a readonly table script: "),
5264 "{body:?}",
5265 );
5266 }
5267 }
5268
5269 #[test]
5270 fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
5271 let mut f = Fixture::new();
5272 let hex = "local function hx(s) return (string.gsub(s, '.', \
5275 function(c) return string.format('%02x', string.byte(c)) end)) end ";
5276 for (body, want) in [
5277 ("cmsgpack.pack(nil)", "c0"),
5278 ("cmsgpack.pack(true)", "c3"),
5279 ("cmsgpack.pack(false)", "c2"),
5280 ("cmsgpack.pack(0)", "00"),
5281 ("cmsgpack.pack(127)", "7f"),
5282 ("cmsgpack.pack(128)", "cc80"),
5283 ("cmsgpack.pack(-1)", "ff"),
5284 ("cmsgpack.pack(-33)", "d0df"),
5285 ("cmsgpack.pack(65535)", "cdffff"),
5286 ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
5287 ("cmsgpack.pack(2^53)", "cf0020000000000000"),
5288 ("cmsgpack.pack(-2^63)", "d38000000000000000"),
5289 ("cmsgpack.pack(2^64)", "ca5f800000"),
5292 ("cmsgpack.pack(1.5)", "ca3fc00000"),
5293 ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
5294 ("cmsgpack.pack('abc')", "a3616263"),
5295 ("cmsgpack.pack('')", "a0"),
5296 ("cmsgpack.pack({})", "90"),
5297 ("cmsgpack.pack({1, 2})", "920102"),
5298 ("cmsgpack.pack({a = 1})", "81a16101"),
5299 ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
5300 (
5303 "(function() local t = {} local c = t \
5304 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
5305 "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
5306 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
5307 ),
5308 ] {
5309 let script = format!("{hex} return hx({body})");
5310 assert_eq!(
5311 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5312 format!("${}\r\n{want}\r\n", want.len()),
5313 "{body}",
5314 );
5315 }
5316 for (body, want) in [
5320 ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
5321 ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
5322 ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
5323 ("select('#', cmsgpack.unpack(''))", 0),
5324 ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
5325 ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5326 ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5327 ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
5328 ("select(1, cmsgpack.unpack_one('\\1'))", -1),
5329 ("select(1, cmsgpack.unpack_one('', 0))", -1),
5330 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
5331 ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
5332 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
5335 ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
5336 ] {
5337 let script = format!("return {body}");
5338 assert_eq!(
5339 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5340 format!(":{want}\r\n"),
5341 "{body}",
5342 );
5343 }
5344 for (body, want) in [
5345 ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
5346 ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
5347 ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
5348 (
5349 "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
5350 "inf",
5351 ),
5352 ("cmsgpack._NAME", "cmsgpack"),
5353 ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
5354 (
5355 "cmsgpack._COPYRIGHT",
5356 "Copyright (C) 2012, Salvatore Sanfilippo",
5357 ),
5358 (
5359 "cmsgpack._DESCRIPTION",
5360 "MessagePack C implementation for Lua",
5361 ),
5362 ] {
5363 let script = format!("return {body}");
5364 assert_eq!(
5365 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5366 format!("${}\r\n{want}\r\n", want.len()),
5367 "{body}",
5368 );
5369 }
5370 for (body, want) in [
5371 (
5374 "return cmsgpack.pack()",
5375 "bad argument #0 to 'pack' (MessagePack pack needs input.)",
5376 ),
5377 (
5378 "return cmsgpack.unpack()",
5379 "bad argument #1 to 'unpack' (string expected, got no value)",
5380 ),
5381 (
5382 "return cmsgpack.unpack(string.char(193))",
5383 "Bad data format in input.",
5384 ),
5385 (
5386 "return cmsgpack.unpack(string.char(204))",
5387 "Missing bytes in input.",
5388 ),
5389 (
5390 "return cmsgpack.unpack(string.char(146, 1))",
5391 "Missing bytes in input.",
5392 ),
5393 (
5394 "return cmsgpack.unpack_one('\\1', 5)",
5395 "Start offset 5 greater than input length 1.",
5396 ),
5397 (
5398 "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
5399 "Start offset 5 greater than input length 2.",
5400 ),
5401 (
5404 "return cmsgpack.unpack_one('\\1', -1)",
5405 "Invalid request to unpack with offset of -1 and limit of 1.",
5406 ),
5407 (
5408 "return cmsgpack.unpack_limit('\\1', -1, 0)",
5409 "Invalid request to unpack with offset of 0 and limit of 1.",
5410 ),
5411 ] {
5412 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5413 assert!(
5414 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5415 "{body} gave {reply}",
5416 );
5417 }
5418 let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
5420 assert_eq!(
5421 f.run(&[
5422 b"EVAL",
5423 b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
5424 table.sort(t) return table.concat(t, ' ')",
5425 b"0",
5426 ]),
5427 format!("${}\r\n{names}\r\n", names.len())
5428 );
5429 for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
5430 assert!(
5431 f.run(&[b"EVAL", body, b"0"])
5432 .contains("Attempt to modify a readonly table script: "),
5433 "{body:?}",
5434 );
5435 }
5436 assert_eq!(
5439 f.run(&[
5440 b"EVAL",
5441 b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
5442 b"0",
5443 ]),
5444 "$8\r\ncmsgpack\r\n"
5445 );
5446 }
5447
5448 const LIB: &[u8] = b"#!lua name=mylib\n\
5454 local counter = 0\n\
5455 redis.register_function{function_name = 'ping', description = 'says pong',\n\
5456 callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
5457 redis.register_function('count', function() counter = counter + 1 return counter end)\n\
5458 redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
5459 redis.register_function('setit', function(keys, args) \
5460 return redis.call('SET', keys[1], args[1]) end)\n\
5461 redis.register_function('raise', function() error('boom') end)\n";
5462
5463 const OTHER: &[u8] = b"#!lua name=other\n\
5465 redis.register_function('twice', function(keys, args) return 2 end)\n";
5466
5467 #[test]
5468 fn a_library_is_loaded_once_and_called_by_name_forever_after() {
5469 let mut f = Fixture::new();
5470 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5471 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5472 assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
5475 assert_eq!(
5479 f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
5480 "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5481 );
5482 assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
5483 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
5484 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5487 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
5488 assert_eq!(
5491 f.run(&[b"FCALL", b"raise", b"0"]),
5492 "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
5493 );
5494 assert_eq!(
5497 f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
5498 "-ERR Library not found\r\n"
5499 );
5500 assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
5501 assert_eq!(
5502 f.run(&[b"FCALL", b"ping", b"0"]),
5503 "-ERR Function not found\r\n"
5504 );
5505 }
5506
5507 #[test]
5508 fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
5509 let mut f = Fixture::new();
5510 for (code, want) in [
5511 (&b"return 1"[..], "ERR Missing library metadata"),
5512 (b"#!lua name=x", "ERR Invalid library metadata"),
5513 (b"#!\n", "ERR Library name was not given"),
5514 (b"#!lua\nx", "ERR Library name was not given"),
5515 (
5516 b"#!lua name=a name=b\nx",
5517 "ERR Invalid metadata value, name argument was given multiple times",
5518 ),
5519 (
5520 b"#!lua nome=a\nx",
5521 "ERR Invalid metadata value given: nome=a",
5522 ),
5523 (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
5524 (
5525 b"#!lua name=a-b\nx",
5526 "ERR Library names can only contain letters, numbers, or underscores(_) \
5527 and must be at least one character long",
5528 ),
5529 (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
5530 (
5531 b"#!lua name=c\nthis is not lua",
5532 "ERR Error compiling function: user_function:2: '=' expected near 'is'",
5533 ),
5534 (
5538 b"#!lua name=r\nerror('boom')",
5539 "ERR Error registering functions: ERR user_function:2: \
5540 Script attempted to access nonexistent global variable 'error'",
5541 ),
5542 (
5545 b"#!lua name=r\nredis.call('PING')",
5546 "ERR Error registering functions: ERR user_function:2: \
5547 Script attempted to access nonexistent global variable 'call'",
5548 ),
5549 (
5550 b"#!lua name=r\nx = 1",
5551 "ERR Error registering functions: ERR user_function:2: \
5552 Attempt to modify a readonly table",
5553 ),
5554 (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
5555 ] {
5556 assert_eq!(
5557 f.run(&[b"FUNCTION", b"LOAD", code]),
5558 format!("-{want}\r\n"),
5559 "{}",
5560 String::from_utf8_lossy(code),
5561 );
5562 }
5563 }
5564
5565 #[test]
5566 fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
5567 let mut f = Fixture::new();
5568 for (call, want) in [
5569 (
5570 &b"redis.register_function()"[..],
5571 "wrong number of arguments to redis.register_function",
5572 ),
5573 (
5574 b"redis.register_function('a', function() end, 1)",
5575 "wrong number of arguments to redis.register_function",
5576 ),
5577 (
5578 b"redis.register_function('a')",
5579 "calling redis.register_function with a single argument is only \
5580 applicable to Lua table (representing named arguments).",
5581 ),
5582 (
5583 b"redis.register_function({foo = 'a'})",
5584 "unknown argument given to redis.register_function",
5585 ),
5586 (
5587 b"redis.register_function({callback = function() end})",
5588 "redis.register_function must get a function name argument",
5589 ),
5590 (
5591 b"redis.register_function({function_name = 'a'})",
5592 "redis.register_function must get a callback argument",
5593 ),
5594 (
5595 b"redis.register_function({function_name = {}, callback = function() end})",
5596 "function_name argument given to redis.register_function must be a string",
5597 ),
5598 (
5599 b"redis.register_function({function_name = 'a', description = {}, \
5600 callback = function() end})",
5601 "description argument given to redis.register_function must be a string",
5602 ),
5603 (
5604 b"redis.register_function({function_name = 'a', callback = 1})",
5605 "callback argument given to redis.register_function must be a function",
5606 ),
5607 (
5608 b"redis.register_function({function_name = 'a', callback = function() end, \
5609 flags = 1})",
5610 "flags argument to redis.register_function must be a table \
5611 representing function flags",
5612 ),
5613 (
5614 b"redis.register_function({function_name = 'a', callback = function() end, \
5615 flags = {'zz'}})",
5616 "unknown flag given",
5617 ),
5618 (
5619 b"redis.register_function({}, function() end)",
5620 "first argument to redis.register_function must be a string",
5621 ),
5622 (
5623 b"redis.register_function('a', 1)",
5624 "second argument to redis.register_function must be a function",
5625 ),
5626 (
5627 b"redis.register_function('a-b', function() end)",
5628 "Library names can only contain letters, numbers, or underscores(_) \
5629 and must be at least one character long",
5630 ),
5631 (
5632 b"redis.register_function('d', function() end) \
5633 redis.register_function('d', function() end)",
5634 "Function already exists in the library",
5635 ),
5636 ] {
5637 let mut code = b"#!lua name=e\n".to_vec();
5638 code.extend_from_slice(call);
5639 assert_eq!(
5643 f.run(&[b"FUNCTION", b"LOAD", &code]),
5644 format!("-ERR Error registering functions: ERR {want}\r\n"),
5645 "{}",
5646 String::from_utf8_lossy(call),
5647 );
5648 }
5649 assert_eq!(
5652 f.run(&[
5653 b"FUNCTION",
5654 b"LOAD",
5655 b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
5656 ]),
5657 "$1\r\nn\r\n"
5658 );
5659 assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
5660 assert_eq!(
5663 f.run(&[
5664 b"FUNCTION",
5665 b"LOAD",
5666 b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
5667 redis.register_function('D', function() return 2 end)",
5668 ]),
5669 "$1\r\nc\r\n"
5670 );
5671 }
5672
5673 #[test]
5674 fn a_library_cannot_take_a_name_another_library_already_has() {
5675 let mut f = Fixture::new();
5676 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5677 assert_eq!(
5678 f.run(&[b"FUNCTION", b"LOAD", LIB]),
5679 "-ERR Library 'mylib' already exists\r\n"
5680 );
5681 assert_eq!(
5685 f.run(&[
5686 b"FUNCTION",
5687 b"LOAD",
5688 b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
5689 ]),
5690 "-ERR Function PING already exists\r\n"
5691 );
5692 assert_eq!(
5695 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
5696 "$5\r\nmylib\r\n"
5697 );
5698 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5701 assert_eq!(
5702 f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
5703 "-ERR Unknown option given: NOPE\r\n"
5704 );
5705 assert_eq!(
5708 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
5709 "-ERR Missing library metadata\r\n"
5710 );
5711 assert_eq!(
5712 f.run(&[b"FUNCTION", b"LOAD"]),
5713 "-ERR wrong number of arguments for 'function|load' command\r\n"
5714 );
5715 }
5716
5717 #[test]
5718 fn fcall_checks_the_name_before_it_looks_at_anything_else() {
5719 let mut f = Fixture::new();
5720 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5721 for (args, want) in [
5722 (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
5723 (&[b"ping", b"x"], "ERR Bad number of keys provided"),
5724 (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
5725 (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
5726 (
5727 &[b"ping", b"99999999999999999999"],
5728 "ERR Bad number of keys provided",
5729 ),
5730 (
5731 &[b"ping", b"3", b"a"],
5732 "ERR Number of keys can't be greater than number of args",
5733 ),
5734 (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
5735 ] {
5736 let mut wire: Vec<&[u8]> = vec![b"FCALL"];
5737 wire.extend_from_slice(args);
5738 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5739 }
5740 assert_eq!(
5743 f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
5744 "-ERR Can not execute a script with write flag using *_ro command.\r\n"
5745 );
5746 assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
5747 assert_eq!(
5748 f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
5749 "-ERR Function not found\r\n"
5750 );
5751 assert_eq!(
5754 f.run(&[
5755 b"FUNCTION",
5756 b"LOAD",
5757 b"#!lua name=w\nredis.register_function{function_name = 'w', \
5758 flags = {'no-writes'}, callback = function(keys) \
5759 return redis.call('SET', keys[1], 'x') end}",
5760 ]),
5761 "$1\r\nw\r\n"
5762 );
5763 assert!(
5764 f.run(&[b"FCALL", b"w", b"1", b"k"])
5765 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
5766 );
5767 }
5768
5769 #[test]
5770 fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
5771 let mut f = Fixture::new();
5772 let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
5775 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
5776 acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
5777 status_reply";
5778 let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
5779 gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
5780 rawequal rawget rawset redis select setmetatable string struct table \
5781 tonumber tostring type unpack xpcall";
5782 assert_eq!(
5783 f.run(&[
5784 b"FUNCTION",
5785 b"LOAD",
5786 b"#!lua name=g\n\
5787 local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
5788 table.sort(o) return table.concat(o, ' ') end\n\
5789 redis.register_function('names', function() return sorted(redis) end)\n\
5790 redis.register_function('globals', function() return sorted(_G) end)\n\
5791 redis.register_function('keysg', function() return KEYS[1] end)\n\
5792 redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
5793 redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
5794 redis.register_function('gwr', function() _G.pcall = 1 end)\n",
5795 ]),
5796 "$1\r\ng\r\n"
5797 );
5798 assert_eq!(
5799 f.run(&[b"FCALL", b"names", b"0"]),
5800 format!("${}\r\n{names}\r\n", names.len())
5801 );
5802 assert_eq!(
5803 f.run(&[b"FCALL", b"globals", b"0"]),
5804 format!("${}\r\n{globals}\r\n", globals.len())
5805 );
5806 assert!(
5809 f.run(&[b"FCALL", b"keysg", b"1", b"k"])
5810 .contains("nonexistent global variable 'KEYS'"),
5811 );
5812 assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
5815 for name in [&b"wr"[..], b"gwr"] {
5818 assert!(
5819 f.run(&[b"FCALL", name, b"0"])
5820 .contains("Attempt to modify a readonly table"),
5821 "{}",
5822 String::from_utf8_lossy(name),
5823 );
5824 }
5825 }
5826
5827 #[test]
5828 fn function_list_says_what_every_library_registered() {
5829 let mut f = Fixture::new();
5830 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5831 f.out = Out::new(Proto::Resp3);
5834 let listed = f.run(&[b"FUNCTION", b"LIST"]);
5835 assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
5836 assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
5837 assert!(listed.contains(
5838 "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
5839 $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
5840 ));
5841 assert!(listed.contains(
5844 "$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"
5845 ));
5846 assert!(!listed.contains("library_code"));
5847 assert!(
5848 f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
5849 .contains("library_code")
5850 );
5851 assert!(
5854 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
5855 .starts_with("*1\r\n")
5856 );
5857 assert_eq!(
5858 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
5859 "*0\r\n"
5860 );
5861 f.out = Out::new(Proto::Resp2);
5864 assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
5865 for (args, want) in [
5866 (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
5867 (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
5868 (
5869 &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
5870 "ERR Unknown argument LIBRARYNAME",
5871 ),
5872 (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
5873 ] {
5874 let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
5875 wire.extend_from_slice(args);
5876 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5877 }
5878 }
5879
5880 #[test]
5881 fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
5882 let mut f = Fixture::new();
5883 f.out = Out::new(Proto::Resp3);
5884 assert_eq!(
5885 f.run(&[b"FUNCTION", b"STATS"]),
5886 "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
5887 %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
5888 );
5889 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5890 assert!(
5891 f.run(&[b"FUNCTION", b"STATS"])
5892 .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
5893 );
5894 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
5895 assert!(
5896 f.run(&[b"FUNCTION", b"STATS"])
5897 .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
5898 );
5899 }
5900
5901 #[test]
5902 fn every_function_subcommand_complains_about_its_own_arity() {
5903 let mut f = Fixture::new();
5904 for (args, want) in [
5905 (
5906 &[&b"STATS"[..], b"X"][..],
5907 "ERR wrong number of arguments for 'function|stats' command",
5908 ),
5909 (
5910 &[b"KILL", b"X"],
5911 "ERR wrong number of arguments for 'function|kill' command",
5912 ),
5913 (
5914 &[b"HELP", b"X"],
5915 "ERR wrong number of arguments for 'function|help' command",
5916 ),
5917 (
5918 &[b"DELETE"],
5919 "ERR wrong number of arguments for 'function|delete' command",
5920 ),
5921 (
5922 &[b"DELETE", b"a", b"b"],
5923 "ERR wrong number of arguments for 'function|delete' command",
5924 ),
5925 (
5926 &[b"DUMP", b"X"],
5927 "ERR wrong number of arguments for 'function|dump' command",
5928 ),
5929 (
5930 &[b"RESTORE"],
5931 "ERR wrong number of arguments for 'function|restore' command",
5932 ),
5933 (
5936 &[b"RESTORE", b"a", b"FLUSH", b"X"],
5937 "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
5938 Try FUNCTION HELP.",
5939 ),
5940 (
5941 &[b"RESTORE", b"a", b"ZZ"],
5942 "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
5943 or REPLACE.",
5944 ),
5945 (
5948 &[b"FLUSH", b"SYNC", b"X"],
5949 "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
5950 Try FUNCTION HELP.",
5951 ),
5952 (
5953 &[b"FLUSH", b"ZZ"],
5954 "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
5955 ),
5956 (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
5957 ] {
5958 let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
5959 wire.extend_from_slice(args);
5960 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5961 }
5962 assert_eq!(
5963 f.run(&[b"FUNCTION"]),
5964 "-ERR wrong number of arguments for 'function' command\r\n"
5965 );
5966 assert_eq!(
5967 f.run(&[b"FUNCTION", b"KILL"]),
5968 "-NOTBUSY No scripts in execution right now.\r\n"
5969 );
5970 }
5971
5972 #[test]
5978 fn a_library_survives_a_dump_and_a_restore() {
5979 let mut f = Fixture::new();
5980 let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5981 assert_eq!(empty.len(), 10);
5982 assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
5983
5984 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5985 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5986 assert!(full.len() > empty.len());
5987
5988 assert_eq!(
5991 f.run(&[b"FUNCTION", b"RESTORE", &full]),
5992 "-ERR Library mylib already exists\r\n"
5993 );
5994 assert_eq!(
5995 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
5996 "+OK\r\n"
5997 );
5998 assert_eq!(
5999 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
6000 "+OK\r\n"
6001 );
6002 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6004
6005 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
6008 assert_eq!(
6009 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
6010 "+OK\r\n"
6011 );
6012 assert_eq!(
6013 f.run(&[b"FUNCTION", b"DELETE", b"other"]),
6014 "-ERR Library not found\r\n"
6015 );
6016 }
6017
6018 #[test]
6025 fn a_restore_that_fails_changes_nothing() {
6026 let mut f = Fixture::new();
6027 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6028 let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6029
6030 let reseal = |body: &[u8], version: u16| {
6034 let mut out = body.to_vec();
6035 out.extend_from_slice(&version.to_le_bytes());
6036 let crc = yo_common::crc::crc64(0, &out);
6037 out.extend_from_slice(&crc.to_le_bytes());
6038 out
6039 };
6040 let body = &good[..good.len() - 10];
6041
6042 let mut torn = good.clone();
6043 let n = torn.len();
6044 torn[n - 1] ^= 0xff;
6045 let future = reseal(body, 999);
6046 let mut pre_ga = body.to_vec();
6049 pre_ga[0] = 246;
6050 let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
6051 let mut other = body.to_vec();
6052 other[0] = 0;
6053 let other = reseal(&other, yo_kv::rdb::VERSION);
6054 let mut cut = body.to_vec();
6056 cut.truncate(body.len() - 1);
6057 let cut = reseal(&cut, yo_kv::rdb::VERSION);
6058
6059 for (bytes, want) in [
6060 (vec![], "ERR DUMP payload version or checksum are wrong"),
6061 (
6062 b"0123456789".to_vec(),
6063 "ERR DUMP payload version or checksum are wrong",
6064 ),
6065 (torn, "ERR DUMP payload version or checksum are wrong"),
6066 (future, "ERR DUMP payload version or checksum are wrong"),
6067 (pre_ga, "ERR Pre-GA function format not supported"),
6068 (other, "ERR given type is not a function"),
6069 (cut, "ERR Failed loading library payload"),
6070 ] {
6071 assert_eq!(
6072 f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
6073 format!("-{want}\r\n")
6074 );
6075 }
6076
6077 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6079 let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6080 assert_eq!(again, good);
6081 }
6082
6083 #[test]
6086 fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
6087 let mut f = Fixture::new();
6088 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6089 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6090 let clash =
6092 b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
6093 .as_slice();
6094 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
6095 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
6096 assert_eq!(
6097 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
6098 "-ERR Function ping already exists\r\n"
6099 );
6100 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
6102 }
6103
6104 #[test]
6105 fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
6106 let mut f = Fixture::new();
6107 assert_eq!(
6108 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
6109 "*1\r\n$1\r\nk\r\n"
6110 );
6111 assert_eq!(
6112 f.run(&[
6113 b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
6114 ]),
6115 "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
6116 );
6117 assert_eq!(
6120 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
6121 "*0\r\n"
6122 );
6123 assert_eq!(
6126 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
6127 "*0\r\n"
6128 );
6129 assert_eq!(
6130 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
6131 "*0\r\n"
6132 );
6133 assert_eq!(
6134 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
6135 "*0\r\n"
6136 );
6137 assert_eq!(
6139 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
6140 "-ERR Invalid number of arguments specified for command\r\n"
6141 );
6142 }
6143
6144 #[test]
6145 fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
6146 let mut f = Fixture::new();
6147 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
6148
6149 assert_eq!(
6150 eval(&mut f, b"return redis.sha1hex('')"),
6151 "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
6152 );
6153 assert_eq!(
6154 eval(&mut f, b"return redis.sha1hex('return 1')"),
6155 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
6156 );
6157 assert_eq!(
6160 eval(&mut f, b"return redis.error_reply('boom')"),
6161 "-ERR boom\r\n"
6162 );
6163 assert_eq!(
6164 eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
6165 "-WRONGTYPE nope\r\n"
6166 );
6167 assert_eq!(
6168 eval(&mut f, b"return redis.status_reply('fine')"),
6169 "+fine\r\n"
6170 );
6171 assert_eq!(
6174 eval(&mut f, b"return redis.error_reply(1)"),
6175 "-ERR wrong number or type of arguments\r\n"
6176 );
6177 assert_eq!(
6178 eval(&mut f, b"local x = redis.status_reply() return x.err"),
6179 "$37\r\nERR wrong number or type of arguments\r\n"
6180 );
6181
6182 assert_eq!(
6184 eval(
6185 &mut f,
6186 b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
6187 ),
6188 "$4\r\n0123\r\n"
6189 );
6190 assert_eq!(
6191 eval(
6192 &mut f,
6193 b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
6194 ),
6195 "$5\r\n01223\r\n"
6196 );
6197 assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
6199 assert_eq!(
6200 eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
6201 ":1\r\n"
6202 );
6203 assert_eq!(
6204 eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
6205 ":1\r\n"
6206 );
6207 assert_eq!(
6208 eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
6209 ":1\r\n"
6210 );
6211 assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
6213 assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
6214 assert!(
6215 eval(&mut f, b"redis.log('x', 'y')")
6216 .contains("First argument must be a number (log level)."),
6217 );
6218 assert!(
6219 eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
6220 .contains("Invalid command passed to redis.acl_check_cmd()"),
6221 );
6222 assert!(
6223 eval(&mut f, b"return redis.acl_check_cmd('get')")
6224 .contains("Wrong number of args for redis.acl_check_cmd()"),
6225 );
6226 }
6227
6228 #[test]
6229 fn a_counter_is_an_integer_and_not_a_string_of_digits() {
6230 let mut f = Fixture::new();
6231 assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
6232 assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
6233 assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
6234 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
6237 assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
6238 f.run(&[b"SET", b"k", b"hello"]);
6241 assert_eq!(
6242 f.run(&[b"INCR", b"k"]),
6243 "-ERR value is not an integer or out of range\r\n"
6244 );
6245 assert_eq!(
6246 f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
6247 "-ERR increment would produce NaN or Infinity\r\n"
6248 );
6249 }
6250
6251 #[test]
6256 fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
6257 let mut f = Fixture::new();
6258 assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
6259 assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
6262 assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
6263 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
6264 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
6265 assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
6266 assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
6267 assert_eq!(
6268 f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
6269 "*2\r\n:1\r\n:0\r\n",
6270 "a refused increment reports the value it left alone and applied nothing"
6271 );
6272 assert_eq!(
6273 f.run(&[
6274 b"INCREX",
6275 b"n",
6276 b"BYINT",
6277 b"5",
6278 b"UBOUND",
6279 b"3",
6280 b"SATURATE"
6281 ]),
6282 "*2\r\n:3\r\n:2\r\n"
6283 );
6284 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
6285 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
6286 }
6287
6288 #[test]
6289 fn the_same_answers_come_out_in_resp3_spelling() {
6290 let mut f = Fixture::new();
6291 assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
6292 assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
6293 assert_eq!(
6296 f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
6297 "*2\r\n,1.5\r\n,1.5\r\n"
6298 );
6299 assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
6300 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6303 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
6304 }
6305
6306 #[test]
6307 fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
6308 let mut f = Fixture::new();
6309 let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
6310 assert_eq!(flow, Flow::Continue);
6311 assert_eq!(
6312 reply,
6313 "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
6314 );
6315 let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
6318 assert_eq!(reply.matches("\r\n").count(), 1);
6319 }
6320
6321 #[test]
6322 fn arity_is_checked_before_the_command_is() {
6323 let mut f = Fixture::new();
6324 assert_eq!(
6325 f.run(&[b"GET"]),
6326 "-ERR wrong number of arguments for 'get' command\r\n"
6327 );
6328 assert_eq!(
6329 f.run(&[b"MSET", b"k"]),
6330 "-ERR wrong number of arguments for 'mset' command\r\n"
6331 );
6332 assert_eq!(
6336 f.run(&[b"PING", b"a", b"b"]),
6337 "-ERR wrong number of arguments for 'ping' command\r\n"
6338 );
6339 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
6340 assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
6341 assert_eq!(
6343 f.run(&[b"DELEX", b"k", b"IFEQ"]),
6344 "-ERR wrong number of arguments for 'delex' command\r\n"
6345 );
6346 }
6347
6348 #[test]
6352 fn the_option_combinations_are_the_ones_a_real_server_accepts() {
6353 let mut f = Fixture::new();
6354 let syntax = "-ERR syntax error\r\n";
6355 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
6356 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
6357 assert_eq!(
6358 f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
6359 syntax
6360 );
6361 assert_eq!(
6362 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
6363 syntax
6364 );
6365 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
6366 assert_eq!(
6368 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
6369 "+OK\r\n"
6370 );
6371 assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
6372 assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
6373 assert_eq!(
6375 f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
6376 syntax
6377 );
6378 assert_eq!(
6379 f.run(&[b"INCREX", b"n", b"ENX"]),
6380 "-ERR ENX flag requires an expiration\r\n"
6381 );
6382 assert_eq!(
6383 f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
6384 "-ERR UBOUND is not an integer or out of range\r\n"
6385 );
6386 assert_eq!(
6387 f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
6388 "-ERR LBOUND can't be greater than UBOUND\r\n"
6389 );
6390 assert_eq!(
6391 f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
6392 "-ERR If you want both the length and indexes, please just use IDX.\r\n"
6393 );
6394 }
6395
6396 #[test]
6400 fn the_expiry_rules_are_redis_own() {
6401 let mut f = Fixture::new();
6402 let bad = "-ERR invalid expire time in 'set' command\r\n";
6403 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
6404 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
6405 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
6406 assert_eq!(
6407 f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
6408 bad
6409 );
6410 assert_eq!(
6411 f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
6412 "-ERR value is not an integer or out of range\r\n"
6413 );
6414 assert_eq!(
6415 f.run(&[b"SETEX", b"k", b"0", b"v"]),
6416 "-ERR invalid expire time in 'setex' command\r\n"
6417 );
6418 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
6419 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
6420 assert_eq!(
6421 f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
6422 "-ERR syntax error\r\n",
6423 "the option list is still checked before the key is looked up"
6424 );
6425 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6427 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
6428 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6429 }
6430
6431 #[test]
6432 fn mset_takes_its_pairs_from_the_read_buffer() {
6433 let mut f = Fixture::new();
6434 assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
6435 assert_eq!(
6436 f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
6437 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
6438 );
6439 assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
6440 assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
6441 assert_eq!(
6442 f.run(&[b"MSETEX", b"2", b"e", b"5"]),
6443 "-ERR wrong number of key-value pairs\r\n"
6444 );
6445 assert_eq!(
6446 f.run(&[b"MSETEX", b"0", b"e", b"5"]),
6447 "-ERR invalid numkeys value\r\n"
6448 );
6449 assert_eq!(
6450 f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
6451 "-ERR invalid numkeys value\r\n"
6452 );
6453 }
6454
6455 #[test]
6456 fn lcs_answers_the_length_the_string_and_the_runs() {
6457 let mut f = Fixture::new();
6458 f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
6459 assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
6460 assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
6461 assert_eq!(
6462 f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
6463 "*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"
6464 );
6465 assert_eq!(
6468 f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
6469 "$6\r\nmytext\r\n"
6470 );
6471 }
6472
6473 #[test]
6474 fn select_moves_the_connection_and_the_databases_stay_apart() {
6475 let mut f = Fixture::new();
6476 f.run(&[b"SET", b"k", b"zero"]);
6477 assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
6478 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6479 f.run(&[b"SET", b"k", b"four"]);
6480 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
6481 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6482 assert_eq!(
6483 f.run(&[b"SELECT", b"99"]),
6484 "-ERR DB index is out of range\r\n"
6485 );
6486 assert_eq!(
6487 f.run(&[b"SELECT", b"-1"]),
6488 "-ERR DB index is out of range\r\n"
6489 );
6490 assert_eq!(
6491 f.run(&[b"SELECT", b"abc"]),
6492 "-ERR value is not an integer or out of range\r\n"
6493 );
6494 f.run(&[b"SELECT", b"4"]);
6496 f.run(&[b"RESET"]);
6497 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6498 }
6499
6500 #[test]
6501 fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
6502 let mut f = Fixture::new();
6503 let reply = f.run(&[b"HELLO"]);
6504 assert!(reply.starts_with("*14\r\n"), "{reply}");
6505 assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
6506 assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
6507 assert!(
6508 reply.contains(":7\r\n"),
6509 "the connection id is in there: {reply}"
6510 );
6511 assert_eq!(
6512 f.run(&[b"HELLO", b"4"]),
6513 "-NOPROTO unsupported protocol version\r\n"
6514 );
6515 assert_eq!(
6516 f.run(&[b"HELLO", b"abc"]),
6517 "-ERR Protocol version is not an integer or out of range\r\n"
6518 );
6519 assert_eq!(
6520 f.run(&[b"HELLO", b"3", b"SETNAME"]),
6521 "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
6522 );
6523 assert!(
6524 f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
6525 .starts_with("%7\r\n")
6526 );
6527 assert_eq!(f.session.name(), b"bob");
6528 f.run(&[b"RESET"]);
6529 assert_eq!(f.session.name(), b"");
6530 }
6531
6532 #[test]
6533 fn command_describes_this_server_in_the_shape_a_driver_reads() {
6534 let mut f = Fixture::new();
6535 let count = format!(":{}\r\n", COMMANDS.len());
6536 assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
6537 let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
6538 assert_eq!(
6539 info,
6540 "*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\
6541 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
6542 );
6543 assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
6545 assert_eq!(
6546 f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
6547 "*1\r\n$8\r\ngetrange\r\n"
6548 );
6549 assert_eq!(
6550 f.run(&[b"COMMAND", b"NOPE"]),
6551 "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
6552 );
6553 }
6554
6555 #[test]
6559 fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
6560 let mut f = Fixture::new();
6561 assert_eq!(
6562 f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
6563 "*1\r\n$1\r\nk\r\n"
6564 );
6565 assert_eq!(
6566 f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
6567 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6568 );
6569 assert_eq!(
6570 f.run(&[
6571 b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
6572 ]),
6573 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6574 );
6575 assert_eq!(
6576 f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
6577 "-ERR The command has no key arguments\r\n"
6578 );
6579 assert_eq!(
6580 f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
6581 "-ERR Invalid number of arguments specified for command\r\n"
6582 );
6583 }
6584
6585 #[test]
6586 fn config_answers_what_it_can_and_refuses_what_it_cannot() {
6587 let mut f = Fixture::new();
6588 assert_eq!(
6589 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6590 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
6591 );
6592 let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
6595 assert!(both.starts_with("*6\r\n"), "{both}");
6596 assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
6597 assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
6598 assert_eq!(
6599 f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
6600 "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
6601 );
6602 assert_eq!(
6603 f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
6604 "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
6605 );
6606 assert_eq!(
6607 f.run(&[b"CONFIG", b"GET"]),
6608 "-ERR wrong number of arguments for 'config|get' command\r\n"
6609 );
6610 assert_eq!(
6614 f.run(&[b"CONFIG", b"SET", b"appendonly"]),
6615 "-ERR wrong number of arguments for 'config|set' command\r\n"
6616 );
6617 assert_eq!(
6618 f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
6619 "-ERR syntax error\r\n"
6620 );
6621 assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
6622 assert_eq!(
6623 f.run(&[b"CONFIG", b"REWRITE"]),
6624 "-ERR The server is running without a config file\r\n"
6625 );
6626 }
6627
6628 #[test]
6629 fn the_eviction_policy_reads_back_what_was_written_to_it() {
6630 let mut f = Fixture::new();
6631 assert_eq!(
6632 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6633 "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
6634 );
6635 assert_eq!(
6636 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
6637 "+OK\r\n",
6638 "the name is matched without regard to case, like every other one"
6639 );
6640 assert_eq!(
6641 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6642 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6643 );
6644 assert!(
6646 f.run(&[b"INFO", b"memory"])
6647 .contains("maxmemory_policy:allkeys-lfu"),
6648 "INFO and CONFIG disagree about the policy"
6649 );
6650 assert_eq!(
6654 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
6655 "-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"
6656 );
6657 assert_eq!(
6660 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6661 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6662 );
6663 f.run(&[
6664 b"CONFIG",
6665 b"SET",
6666 b"hash-max-listpack-entries",
6667 b"7",
6668 b"maxmemory-policy",
6669 b"nonsense",
6670 ]);
6671 assert_eq!(
6672 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6673 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
6674 );
6675 }
6676
6677 #[test]
6678 fn the_three_eviction_numbers_read_back_too() {
6679 let mut f = Fixture::new();
6680 for (name, default, set) in [
6681 ("maxmemory-samples", "5", "12"),
6682 ("lfu-log-factor", "10", "3"),
6683 ("lfu-decay-time", "1", "60"),
6684 ] {
6685 let get = || {
6686 format!(
6687 "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
6688 name.len(),
6689 default.len()
6690 )
6691 };
6692 assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
6693 assert_eq!(
6694 f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
6695 "+OK\r\n"
6696 );
6697 assert_eq!(
6698 f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
6699 format!(
6700 "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
6701 name.len(),
6702 set.len()
6703 )
6704 );
6705 assert_eq!(
6708 f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
6709 format!(
6710 "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
6711 )
6712 );
6713 }
6714 }
6715
6716 #[test]
6717 fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
6718 let mut f = Fixture::new();
6719 assert_eq!(
6720 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6721 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
6722 "no limit is the default"
6723 );
6724 for (typed, bytes) in [
6727 (&b"1024"[..], "1024"),
6728 (b"1k", "1000"),
6729 (b"1kb", "1024"),
6730 (b"1M", "1000000"),
6731 (b"1Mb", "1048576"),
6732 (b"1gb", "1073741824"),
6733 (b"100mb", "104857600"),
6734 ] {
6735 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
6736 assert_eq!(
6737 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6738 format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
6739 "set {}",
6740 String::from_utf8_lossy(typed)
6741 );
6742 }
6743 assert!(
6744 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6745 "the report agrees with the setting"
6746 );
6747
6748 for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
6751 assert_eq!(
6752 f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
6753 "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
6754 "refused {}",
6755 String::from_utf8_lossy(bad)
6756 );
6757 }
6758 assert!(
6759 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6760 "and the refusal left the old one alone"
6761 );
6762 }
6763
6764 #[test]
6765 fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
6766 let mut f = Fixture::new();
6767 f.run(&[b"SET", b"here", b"already"]);
6768 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
6772 assert_eq!(
6773 f.run(&[b"SET", b"k", b"v"]),
6774 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6775 );
6776 assert_eq!(
6777 f.run(&[b"LPUSH", b"l", b"v"]),
6778 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6779 );
6780 assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
6782 assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
6783 assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
6784
6785 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6787 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6788 }
6789
6790 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
6794 #[test]
6795 fn an_allkeys_policy_makes_room_instead_of_refusing() {
6796 let mut f = Fixture::new();
6797 let val = vec![b'v'; 256];
6798 for i in 0..24000u32 {
6799 let k = format!("key:{i:08}");
6800 f.run(&[b"SET", k.as_bytes(), &val]);
6801 }
6802 let full = f.server.memory_bytes();
6803 assert!(
6804 full > 3 * 1024 * 1024,
6805 "the arena is several segments: {full}"
6806 );
6807
6808 let limit = full - 2 * 1024 * 1024;
6812 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
6813 f.run(&[
6814 b"CONFIG",
6815 b"SET",
6816 b"maxmemory",
6817 limit.to_string().as_bytes(),
6818 ]);
6819
6820 for i in 0..2000u32 {
6824 let k = format!("new:{i:08}");
6825 assert_eq!(
6826 f.run(&[b"SET", k.as_bytes(), &val]),
6827 "+OK\r\n",
6828 "write {i} was refused"
6829 );
6830 f.server.refresh_memory();
6831 if f.server.memory_bytes() <= limit {
6832 break;
6833 }
6834 }
6835 assert!(
6836 f.server.memory_bytes() <= limit,
6837 "it never got under: {} against {limit}",
6838 f.server.memory_bytes()
6839 );
6840 let info = f.run(&[b"INFO", b"stats"]);
6841 assert!(!info.contains("evicted_keys:0"), "{info}");
6842 assert!(
6843 f.run(&[b"DBSIZE"]) != ":0\r\n",
6844 "and it did not empty the database to get there"
6845 );
6846 }
6847
6848 #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
6859 #[test]
6860 fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
6861 let mut f = Fixture::new();
6868 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6869 let big = vec![b'v'; 200];
6870
6871 for i in 0..400u32 {
6872 let n = i.to_string();
6873 let n = n.as_bytes();
6874 f.run(&[b"SADD", b"s", n]);
6875 f.run(&[b"SADD", b"s2", &big]);
6876 f.run(&[b"HSET", b"h", n, &big]);
6877 f.run(&[b"RPUSH", b"l", &big]);
6878 f.run(&[b"ZADD", b"z", n, n]);
6879 f.run(&[b"ARSET", b"a", n, &big]);
6880 if i % 7 == 0 {
6881 f.run(&[b"SREM", b"s", n]);
6882 f.run(&[b"HDEL", b"h", n]);
6883 f.run(&[b"LPOP", b"l"]);
6884 f.run(&[b"ZREM", b"z", n]);
6885 f.run(&[b"ARDEL", b"a", n]);
6886 }
6887 if i % 53 == 0 {
6888 f.run(&[b"DEL", b"s2"]);
6891 }
6892 assert_eq!(
6893 f.server.settled_memory(),
6894 f.server.memory_bytes(),
6895 "after round {i}"
6896 );
6897 }
6898
6899 assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
6902 assert!(
6903 f.server.memory_bytes() > 512 * 1024,
6904 "{}",
6905 f.server.memory_bytes()
6906 );
6907
6908 f.run(&[b"FLUSHALL"]);
6910 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6911 }
6912
6913 #[test]
6914 fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
6915 let mut f = Fixture::new();
6920 for i in 0..200u32 {
6921 let n = i.to_string();
6922 f.run(&[b"SADD", b"s", n.as_bytes()]);
6923 f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
6924 }
6925 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6926 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6927
6928 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6929 for i in 200..400u32 {
6930 let n = i.to_string();
6931 f.run(&[b"SADD", b"s", n.as_bytes()]);
6932 }
6933 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6934 assert_eq!(
6935 f.server.settled_memory(),
6936 f.server.memory_bytes(),
6937 "the writes it was not watching are in the number it started from"
6938 );
6939 }
6940
6941 #[test]
6942 fn evicted_keys_and_expired_keys_are_different_numbers() {
6943 let mut f = Fixture::new();
6944 f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
6947 f.server.advance_clock_ms(20);
6948 f.run(&[b"GET", b"gone"]);
6949 let info = f.run(&[b"INFO", b"stats"]);
6950 assert!(info.contains("expired_keys:1"), "{info}");
6951 assert!(info.contains("evicted_keys:0"), "{info}");
6952 }
6953
6954 #[test]
6955 fn the_object_subcommands_follow_the_policy() {
6956 let mut f = Fixture::new();
6957 f.run(&[b"SET", b"s", b"v"]);
6958 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6962 assert!(
6963 f.run(&[b"OBJECT", b"FREQ", b"s"])
6964 .starts_with("-ERR An LFU maxmemory policy is not selected"),
6965 );
6966
6967 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
6968 assert!(
6969 f.run(&[b"OBJECT", b"IDLETIME", b"s"])
6970 .starts_with("-ERR An LFU maxmemory policy is selected"),
6971 );
6972 assert!(
6977 f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
6978 "FREQ should answer under an LFU policy"
6979 );
6980 }
6981
6982 #[test]
6983 fn object_says_which_rung_of_the_ladder_a_key_is_on() {
6984 let mut f = Fixture::new();
6985 f.run(&[b"SET", b"s", b"hello"]);
6986 f.run(&[b"SET", b"n", b"123"]);
6987 f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
6988 f.run(&[b"SADD", b"ss", b"a", b"b"]);
6989 f.run(&[b"HSET", b"h", b"f", b"v"]);
6990 for (key, want) in [
6991 (b"s".as_slice(), "embstr"),
6992 (b"n", "int"),
6993 (b"si", "intset"),
6994 (b"ss", "listpack"),
6995 (b"h", "listpack"),
6996 ] {
6997 let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
6998 assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
6999 }
7000
7001 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
7004 assert_eq!(
7005 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7006 "$10\r\nlistpackex\r\n"
7007 );
7008
7009 assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
7010 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
7011 assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
7012 }
7013
7014 #[test]
7015 fn object_answers_nil_for_a_key_that_is_not_there() {
7016 let mut f = Fixture::new();
7017 for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
7018 assert_eq!(
7019 f.run(&[b"OBJECT", sub, b"nokey"]),
7020 "$-1\r\n",
7021 "a nil and not an error, which is what 8.10.1 does"
7022 );
7023 }
7024 f.run(&[b"SET", b"s", b"v"]);
7027 assert!(
7028 f.run(&[b"OBJECT", b"FREQ", b"s"])
7029 .starts_with("-ERR An LFU maxmemory policy is not"),
7030 );
7031 assert_eq!(
7032 f.run(&[b"OBJECT", b"NOPE", b"s"]),
7033 "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
7034 );
7035 assert_eq!(
7036 f.run(&[b"OBJECT", b"ENCODING"]),
7037 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7038 );
7039 assert_eq!(
7040 f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
7041 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7042 );
7043 assert_eq!(
7044 f.run(&[b"OBJECT"]),
7045 "-ERR wrong number of arguments for 'object' command\r\n"
7046 );
7047 }
7048
7049 #[test]
7050 fn config_moves_the_ladder_and_object_encoding_agrees() {
7051 let mut f = Fixture::new();
7052 assert_eq!(
7053 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7054 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7055 "512 and not the 128 everyone remembers, which is what 8.10.1 says"
7056 );
7057 assert_eq!(
7060 f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
7061 "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
7062 );
7063 assert!(
7064 f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
7065 .starts_with("*8\r\n")
7066 );
7067 assert!(
7068 f.run(&[b"CONFIG", b"GET", b"set-max-*"])
7069 .starts_with("*6\r\n")
7070 );
7071
7072 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
7073 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
7074
7075 assert_eq!(
7076 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
7077 "+OK\r\n",
7078 "written under the old name and read back under the new one"
7079 );
7080 assert_eq!(
7081 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7082 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
7083 );
7084 assert_eq!(
7085 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7086 "$8\r\nlistpack\r\n",
7087 "the hash that already exists is left exactly where it was"
7088 );
7089 f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
7090 assert_eq!(
7091 f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
7092 "$9\r\nhashtable\r\n",
7093 "and the next one built goes straight to a table"
7094 );
7095
7096 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
7098 f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
7099 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
7100 f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
7101 f.run(&[b"SADD", b"s2", b"abcdefgh"]);
7102 assert_eq!(
7103 f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
7104 "$9\r\nhashtable\r\n"
7105 );
7106 }
7107
7108 #[test]
7109 fn config_set_takes_all_of_the_ladder_or_none_of_it() {
7110 let mut f = Fixture::new();
7111 assert_eq!(
7112 f.run(&[
7113 b"CONFIG",
7114 b"SET",
7115 b"hash-max-listpack-entries",
7116 b"7",
7117 b"set-max-listpack-entries",
7118 b"abc"
7119 ]),
7120 "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
7121 );
7122 assert_eq!(
7123 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7124 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7125 "the pair in front of the bad one did not go in"
7126 );
7127 assert_eq!(
7130 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
7131 "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
7132 );
7133 assert_eq!(
7134 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
7135 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
7136 );
7137 assert_eq!(
7140 f.run(&[
7141 b"CONFIG",
7142 b"SET",
7143 b"set-max-intset-entries",
7144 b"99999999999999999999"
7145 ]),
7146 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
7147 );
7148 assert_eq!(
7149 f.run(&[
7150 b"CONFIG",
7151 b"SET",
7152 b"set-max-intset-entries",
7153 b"9223372036854775807"
7154 ]),
7155 "+OK\r\n"
7156 );
7157 }
7158
7159 #[test]
7160 fn a_setting_moved_on_one_database_moved_on_all_of_them() {
7161 let mut f = Fixture::new();
7162 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
7163 f.run(&[b"SELECT", b"3"]);
7164 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7165 assert_eq!(
7166 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7167 "$9\r\nhashtable\r\n",
7168 "these are one server wide number in Redis, whatever a Keyspace carries"
7169 );
7170 }
7171
7172 #[test]
7173 fn info_reports_the_numbers_it_can_stand_behind() {
7174 let mut f = Fixture::new();
7175 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
7176 let all = f.run(&[b"INFO"]);
7177 assert!(all.contains("redis_version:8.8.0"), "{all}");
7178 assert!(
7179 all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
7180 "{all}"
7181 );
7182 assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
7183 assert!(all.contains("role:master"), "{all}");
7184 let clients = f.run(&[b"INFO", b"clients"]);
7186 assert!(clients.contains("connected_clients:0"), "{clients}");
7187 assert!(!clients.contains("redis_version"), "{clients}");
7188 assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
7189 }
7190
7191 #[test]
7198 fn commandstats_is_asked_for_and_replication_is_not() {
7199 let mut f = Fixture::new();
7200 for arg in ["", "all", "default", "everything"] {
7201 let info = if arg.is_empty() {
7202 f.run(&[b"INFO"])
7203 } else {
7204 f.run(&[b"INFO", arg.as_bytes()])
7205 };
7206 assert!(info.contains("redis_version"), "{arg}: {info}");
7207 assert!(info.contains("used_cpu_user"), "{arg}: {info}");
7208 assert!(info.contains("used_memory"), "{arg}: {info}");
7209 assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
7210 let asked = arg == "all" || arg == "everything";
7211 assert_eq!(
7212 info.contains("rejected_calls"),
7213 asked,
7214 "{arg} should{} carry the command counters: {info}",
7215 if asked { "" } else { " not" }
7216 );
7217 }
7218
7219 let cpu = f.run(&[b"INFO", b"cpu"]);
7220 assert!(cpu.contains("used_cpu_user"), "{cpu}");
7221 assert!(!cpu.contains("used_memory"), "{cpu}");
7222
7223 let stats = f.run(&[b"INFO", b"commandSTATS"]);
7226 assert!(!stats.contains("used_memory"), "{stats}");
7227 assert!(stats.contains("rejected_calls"), "{stats}");
7228
7229 let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
7231 assert!(pair.contains("used_cpu_user"), "{pair}");
7232 assert!(!pair.contains("master_repl_offset"), "{pair}");
7233
7234 let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
7235 assert!(with_all.contains("used_memory"), "{with_all}");
7236 assert!(with_all.contains("master_repl_offset"), "{with_all}");
7237 assert!(with_all.contains("rejected_calls"), "{with_all}");
7238 assert_eq!(
7240 with_all.matches("used_cpu_user_children").count(),
7241 1,
7242 "{with_all}"
7243 );
7244
7245 let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
7246 assert!(with_default.contains("used_memory"), "{with_default}");
7247 assert!(
7248 with_default.contains("master_repl_offset"),
7249 "{with_default}"
7250 );
7251 assert!(!with_default.contains("rejected_calls"), "{with_default}");
7252 assert_eq!(
7253 with_default.matches("used_cpu_user_children").count(),
7254 1,
7255 "{with_default}"
7256 );
7257 }
7258
7259 #[test]
7268 fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
7269 let mut f = Fixture::new();
7270 let info = f.run(&[b"INFO", b"memory"]);
7271 for field in [
7272 "total_system_memory:",
7273 "mem_cgroup_limit:",
7274 "mem_limit:",
7275 "mem_budget:",
7276 ] {
7277 assert!(info.contains(field), "no {field} in {info}");
7278 }
7279
7280 let field = |name: &str| -> u64 {
7281 info.lines()
7282 .find_map(|l| l.strip_prefix(name))
7283 .unwrap_or_else(|| panic!("no {name} in {info}"))
7284 .trim()
7285 .parse()
7286 .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
7287 };
7288 let limit = field("mem_limit:");
7289 assert_eq!(field("mem_budget:"), limit / 4, "{info}");
7290 if limit != 0 {
7293 let host = field("total_system_memory:");
7294 let cgroup = field("mem_cgroup_limit:");
7295 assert!(
7296 limit == host || limit == cgroup,
7297 "the limit came from neither number: {info}"
7298 );
7299 }
7300 }
7301
7302 #[test]
7311 fn a_command_counts_what_it_did_separately_from_what_it_refused() {
7312 let mut f = Fixture::new();
7313 f.run(&[b"SET", b"k", b"v"]);
7314 f.run(&[b"SET", b"k", b"w"]);
7315 f.run(&[b"LPUSH", b"k", b"x"]);
7317 f.run(&[b"LPUSH", b"k"]);
7319
7320 let stats = f.run(&[b"INFO", b"commandstats"]);
7321 assert!(
7322 stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
7323 "{stats}"
7324 );
7325 assert!(
7326 stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
7327 "{stats}"
7328 );
7329 assert!(
7330 !stats.contains("cmdstat_zadd"),
7331 "a command nobody has sent has no row: {stats}"
7332 );
7333 }
7334
7335 #[test]
7339 fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
7340 let (dead, live) = if cfg!(miri) {
7347 (375, 125)
7348 } else {
7349 (3_000, 1_000)
7350 };
7351 let mut f = Fixture::new();
7352 for i in 0..dead {
7353 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7354 }
7355 for i in 0..live {
7356 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7357 }
7358 let all = format!(":{}\r\n", dead + live);
7359 assert_eq!(f.run(&[b"DBSIZE"]), all);
7360 f.advance(100);
7361 assert_eq!(
7362 f.run(&[b"DBSIZE"]),
7363 all,
7364 "DBSIZE counts records and nothing has read past the dead ones yet"
7365 );
7366
7367 let rest = format!(":{live}\r\n");
7369 let mut spent = 0;
7370 for _ in 0..2_000 {
7371 spent += f.server.expire_step(4096);
7372 if f.run(&[b"DBSIZE"]) == rest {
7373 break;
7374 }
7375 }
7376 assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
7377 assert!(
7378 f.run(&[b"INFO", b"stats"])
7379 .contains(&format!("expired_keys:{dead}"))
7380 );
7381 for i in 0..live {
7382 assert_eq!(
7383 f.run(&[b"GET", format!("k{i}").as_bytes()]),
7384 "$1\r\nv\r\n",
7385 "it took a key that had no deadline"
7386 );
7387 }
7388 }
7389
7390 #[test]
7391 fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
7392 let n = if cfg!(miri) { 200 } else { 2_000 };
7396 let mut f = Fixture::new();
7397 for i in 0..n {
7398 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7399 }
7400 assert_eq!(f.server.expire_step(4096), 0);
7401 f.run(&[b"SELECT", b"3"]);
7403 f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
7404 f.advance(100);
7405 for _ in 0..64 {
7406 f.server.expire_step(4096);
7407 }
7408 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
7409 f.run(&[b"SELECT", b"0"]);
7410 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
7411 assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
7412 }
7413
7414 #[test]
7417 fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
7418 let mut f = Fixture::new();
7419 for i in 0..500u32 {
7420 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7421 }
7422 f.advance(100);
7423 let at = f.server.striped(0).now_ms();
7424 f.server.set_clock_ms(at);
7425 assert!(f.server.expire_slice(8) > 0, "the first one works");
7429 for _ in 0..1_000 {
7430 assert_eq!(
7431 f.server.expire_slice(8),
7432 0,
7433 "the millisecond has not moved and neither should this"
7434 );
7435 }
7436 assert!(
7437 f.server.striped(0).expires() > 400,
7438 "there is plenty left to take"
7439 );
7440 f.server.set_clock_ms(at + 1);
7441 assert!(f.server.expire_slice(8) > 0, "and then it goes again");
7442 }
7443
7444 #[test]
7447 fn info_keyspace_counts_the_keys_that_have_a_deadline() {
7448 let mut f = Fixture::new();
7449 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
7450 assert!(
7451 f.run(&[b"INFO", b"keyspace"])
7452 .contains("db0:keys=3,expires=0"),
7453 "none of them has one yet"
7454 );
7455 f.run(&[b"EXPIRE", b"a", b"1000"]);
7456 f.run(&[b"EXPIRE", b"b", b"1000"]);
7457 let two = f.run(&[b"INFO", b"keyspace"]);
7458 assert!(two.contains("db0:keys=3,expires=2"), "{two}");
7459 f.run(&[b"PERSIST", b"a"]);
7460 f.run(&[b"DEL", b"b"]);
7461 let none = f.run(&[b"INFO", b"keyspace"]);
7462 assert!(none.contains("db0:keys=2,expires=0"), "{none}");
7463
7464 f.run(&[b"SELECT", b"1"]);
7466 f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
7467 let both = f.run(&[b"INFO", b"keyspace"]);
7468 assert!(both.contains("db0:keys=2,expires=0"), "{both}");
7469 assert!(both.contains("db1:keys=1,expires=1"), "{both}");
7470 }
7471
7472 #[cfg(unix)]
7479 #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
7480 #[test]
7481 fn info_cpu_reports_processor_time_that_was_really_measured() {
7482 let mut f = Fixture::new();
7483 let cpu = f.run(&[b"INFO", b"cpu"]);
7484 assert!(cpu.contains("# CPU"), "{cpu}");
7485 assert!(cpu.contains("used_cpu_user:"), "{cpu}");
7487 assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
7488 assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
7489 assert!(!cpu.contains("redis_version"), "{cpu}");
7490
7491 let before = used_cpu_user(&cpu);
7495 let mut n = 0u64;
7496 let mut rounds = 0;
7497 while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
7498 for i in 0..1_000_000u64 {
7499 n = n.wrapping_add(i.wrapping_mul(i));
7500 }
7501 rounds += 1;
7502 assert!(rounds < 1_000, "cpu time never moved, n is {n}");
7506 }
7507 }
7508
7509 #[cfg(unix)]
7511 fn used_cpu_user(info: &str) -> f64 {
7512 info.lines()
7513 .find_map(|l| l.strip_prefix("used_cpu_user:"))
7514 .expect("no used_cpu_user in the reply")
7515 .trim()
7516 .parse()
7517 .expect("used_cpu_user is not a number")
7518 }
7519
7520 #[test]
7526 fn a_command_that_fails_leaves_nothing_half_written() {
7527 let mut f = Fixture::new();
7528 let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
7529 assert_eq!(reply, "-ERR offset is out of range\r\n");
7530 assert!(!reply.contains(':'), "no integer went out in front of it");
7531 }
7532
7533 #[test]
7534 fn quit_answers_first_and_closes_after() {
7535 let mut f = Fixture::new();
7536 let (flow, reply) = f.flow(&[b"QUIT"]);
7537 assert_eq!(reply, "+OK\r\n");
7538 assert_eq!(flow, Flow::Close);
7539 }
7540
7541 #[test]
7548 fn shutdown_writes_nothing_and_sets_the_flag() {
7549 let mut f = Fixture::new();
7550 assert!(!f.server.stopping(), "nobody has asked yet");
7551
7552 let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
7553 assert_eq!(reply, "");
7554 assert_eq!(flow, Flow::Close);
7555 assert!(f.server.stopping());
7556 }
7557
7558 #[test]
7566 fn shutdown_takes_the_flags_redis_takes() {
7567 for flags in [
7568 &[b"NOSAVE".as_slice()][..],
7569 &[b"SAVE"],
7570 &[b"NOW"],
7571 &[b"FORCE"],
7572 &[b"nosave"],
7573 &[b"NOW", b"NOW"],
7574 &[b"SAVE", b"SAVE"],
7575 &[b"NOSAVE", b"NOW", b"FORCE"],
7576 ] {
7577 let mut f = Fixture::new();
7578 let mut parts = vec![b"SHUTDOWN".as_slice()];
7579 parts.extend_from_slice(flags);
7580 let (flow, reply) = f.flow(&parts);
7581 assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
7582 assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
7583 assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
7584 }
7585
7586 for flags in [
7587 &[b"BOGUS".as_slice()][..],
7588 &[b"SAVE", b"NOSAVE"],
7589 &[b"NOSAVE", b"SAVE"],
7590 &[b"ABORT", b"NOW"],
7591 &[b"NOSAVE", b"ABORT"],
7592 &[b"NOW", b"FORCE", b"ABORT"],
7593 ] {
7594 let mut f = Fixture::new();
7595 let mut parts = vec![b"SHUTDOWN".as_slice()];
7596 parts.extend_from_slice(flags);
7597 assert_eq!(
7598 f.run(&parts),
7599 "-ERR syntax error\r\n",
7600 "SHUTDOWN {flags:?} was accepted"
7601 );
7602 assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
7603 }
7604 }
7605
7606 #[test]
7614 fn shutdown_abort_never_has_anything_to_abort() {
7615 let mut f = Fixture::new();
7616 for parts in [
7617 &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
7618 &[b"SHUTDOWN", b"ABORT", b"ABORT"],
7619 ] {
7620 assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
7621 assert!(!f.server.stopping(), "an abort stopped the server");
7622 }
7623 }
7624
7625 struct Backups {
7633 f: Fixture,
7634 dir: PathBuf,
7635 }
7636
7637 impl Backups {
7638 fn new(name: &str) -> Backups {
7639 let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
7640 let _ = std::fs::remove_dir_all(&dir);
7641 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
7642 let mut f = Fixture::new();
7643 f.server.set_dir(dir.clone());
7644 Backups { f, dir }
7645 }
7646
7647 fn run(&mut self, parts: &[&[u8]]) -> String {
7648 self.f.run(parts)
7649 }
7650
7651 fn files(&self) -> Vec<String> {
7653 let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
7654 Ok(entries) => entries
7655 .filter_map(|e| e.ok())
7656 .map(|e| e.file_name().to_string_lossy().into_owned())
7657 .collect(),
7658 Err(_) => Vec::new(),
7659 };
7660 names.sort();
7661 names
7662 }
7663
7664 fn read(&self, name: &str) -> Vec<u8> {
7665 std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
7666 }
7667 }
7668
7669 impl Drop for Backups {
7670 fn drop(&mut self) {
7671 let _ = std::fs::remove_dir_all(&self.dir);
7672 }
7673 }
7674
7675 #[test]
7678 fn backup_walks_the_states_the_reference_walks() {
7679 let mut b = Backups::new("states");
7680 let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
7681
7682 assert!(status(&mut b).contains("idle"));
7683 assert!(b.files().is_empty(), "an idle server has written a backup");
7684
7685 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7686 assert!(status(&mut b).contains("incrementing"));
7687 assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
7688
7689 assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
7690 assert!(status(&mut b).contains("sealed"));
7691 assert_eq!(
7692 b.files(),
7693 [
7694 "appendonly.aof.1.base.rdb",
7695 "appendonly.aof.1.incr.aof",
7696 "appendonly.aof.manifest",
7697 ]
7698 );
7699
7700 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7701 assert!(status(&mut b).contains("idle"));
7702 assert!(b.files().is_empty(), "cleanup left something behind");
7703 }
7704
7705 #[test]
7707 fn backup_refuses_the_moves_the_reference_refuses() {
7708 let mut b = Backups::new("refusals");
7709
7710 assert_eq!(
7711 b.run(&[b"BACKUP", b"SEAL"]),
7712 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7713 );
7714 assert_eq!(
7715 b.run(&[b"BACKUP", b"ABORT"]),
7716 "-ERR No backup in progress\r\n"
7717 );
7718 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7721
7722 b.run(&[b"BACKUP", b"START"]);
7723 assert_eq!(
7724 b.run(&[b"BACKUP", b"START"]),
7725 "-ERR A backup is already in progress, ABORT it first\r\n"
7726 );
7727 assert_eq!(
7728 b.run(&[b"BACKUP", b"CLEANUP"]),
7729 "-ERR Backup is in progress\r\n"
7730 );
7731
7732 b.run(&[b"BACKUP", b"SEAL"]);
7733 assert_eq!(
7734 b.run(&[b"BACKUP", b"START"]),
7735 "-ERR A sealed backup exists, CLEANUP it first\r\n"
7736 );
7737 assert_eq!(
7738 b.run(&[b"BACKUP", b"SEAL"]),
7739 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7740 );
7741 assert_eq!(
7742 b.run(&[b"BACKUP", b"ABORT"]),
7743 "-ERR No backup in progress\r\n"
7744 );
7745 }
7746
7747 #[test]
7754 fn backup_abort_removes_the_file_and_says_who_did_it() {
7755 let mut b = Backups::new("abort");
7756 b.run(&[b"BACKUP", b"START"]);
7757 assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
7758
7759 let status = b.run(&[b"BACKUP", b"STATUS"]);
7760 assert!(status.contains("failed"), "{status}");
7761 assert!(status.contains("aborted by user"), "{status}");
7762 assert!(b.files().is_empty(), "abort left the base file behind");
7763 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7764
7765 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7767 assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
7768 let status = b.run(&[b"BACKUP", b"STATUS"]);
7769 assert!(status.contains("incrementing"), "{status}");
7770 assert!(!status.contains("aborted"), "the old error was kept");
7771 }
7772
7773 #[test]
7775 fn backup_list_names_the_files_that_are_pinned_so_far() {
7776 let mut b = Backups::new("list");
7777 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7778
7779 b.run(&[b"BACKUP", b"START"]);
7780 let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
7781 let base = base.to_string_lossy().into_owned();
7782 assert_eq!(
7783 b.run(&[b"BACKUP", b"LIST"]),
7784 format!("*1\r\n${}\r\n{base}\r\n", base.len())
7785 );
7786
7787 b.run(&[b"BACKUP", b"SEAL"]);
7788 let listed = b.run(&[b"BACKUP", b"LIST"]);
7789 assert!(listed.starts_with("*3\r\n"), "{listed}");
7790 let names: Vec<&str> = listed
7793 .lines()
7794 .filter(|l| l.starts_with('/') || l.contains(":\\"))
7795 .collect();
7796 assert_eq!(names.len(), 3, "{listed}");
7797 assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
7798 assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
7799 assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
7800 }
7801
7802 #[test]
7810 fn a_backup_holds_the_dataset_as_it_was_at_start() {
7811 let mut b = Backups::new("contents");
7812 b.run(&[b"SET", b"bk", b"v1"]);
7813 b.run(&[b"BACKUP", b"START"]);
7814 b.run(&[b"SET", b"bk", b"v2"]);
7815 b.run(&[b"BACKUP", b"SEAL"]);
7816
7817 let base = b.read("appendonly.aof.1.base.rdb");
7818 assert!(base.starts_with(b"REDIS"), "not an RDB file");
7819 assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
7820 assert!(
7821 !base.windows(2).any(|w| w == b"v2"),
7822 "the base file moved on after START"
7823 );
7824 let at = base
7829 .windows(8)
7830 .position(|w| w == b"aof-base")
7831 .expect("no aof-base aux field");
7832 assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
7833
7834 assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
7835 assert_eq!(
7836 String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
7837 "file appendonly.aof.1.base.rdb seq 1 type b\n\
7838 file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
7839 );
7840 }
7841
7842 #[test]
7845 fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
7846 let mut b = Backups::new("status");
7847 b.f.server.set_clock_ms(1_700_000_000_000);
7848
7849 assert_eq!(
7850 b.run(&[b"BACKUP", b"STATUS"]),
7851 "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
7852 $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
7853 );
7854
7855 b.f.out = Out::new(Proto::Resp3);
7856 b.run(&[b"BACKUP", b"START"]);
7857 assert_eq!(
7858 b.run(&[b"BACKUP", b"STATUS"]),
7859 "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
7860 $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
7861 );
7862
7863 b.run(&[b"BACKUP", b"SEAL"]);
7864 let sealed = b.run(&[b"BACKUP", b"STATUS"]);
7865 assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
7866 }
7867
7868 #[test]
7871 fn a_sealed_backup_is_swept_away_after_the_timeout() {
7872 let mut b = Backups::new("ttl");
7873 b.f.server.set_clock_ms(1_000_000);
7874 assert_eq!(
7875 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
7876 "+OK\r\n"
7877 );
7878 b.run(&[b"BACKUP", b"START"]);
7879 b.run(&[b"BACKUP", b"SEAL"]);
7880
7881 b.f.server.set_clock_ms(1_000_000 + 59_000);
7883 b.f.server.backup_expire();
7884 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7885 assert_eq!(b.files().len(), 3);
7886
7887 b.f.server.set_clock_ms(1_000_000 + 60_000);
7888 b.f.server.backup_expire();
7889 let status = b.run(&[b"BACKUP", b"STATUS"]);
7890 assert!(status.contains("idle"), "{status}");
7891 assert!(b.files().is_empty(), "the timeout left the files behind");
7892
7893 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
7895 b.run(&[b"BACKUP", b"START"]);
7896 b.run(&[b"BACKUP", b"SEAL"]);
7897 b.f.server.set_clock_ms(9_000_000_000);
7898 b.f.server.backup_expire();
7899 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7900 }
7901
7902 #[test]
7905 fn the_backup_settings_behave_the_way_the_reference_does() {
7906 let mut b = Backups::new("config");
7907 let dir = b.dir.to_string_lossy().into_owned();
7908
7909 assert_eq!(
7910 b.run(&[b"CONFIG", b"GET", b"dir"]),
7911 format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
7912 );
7913 assert_eq!(
7914 b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
7915 "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
7916 );
7917 assert_eq!(
7918 b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
7919 "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
7920 );
7921
7922 assert_eq!(
7925 b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
7926 "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
7927 );
7928 assert_eq!(
7929 b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
7930 "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
7931 );
7932 assert!(
7933 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
7934 .contains("argument couldn't be parsed into an integer")
7935 );
7936 assert!(
7937 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
7938 .contains("argument must be between 0 and 9223372036854775807 inclusive")
7939 );
7940 }
7941
7942 #[test]
7944 fn backup_help_is_the_text_the_reference_sends() {
7945 let mut f = Fixture::new();
7946 let help = f.run(&[b"BACKUP", b"HELP"]);
7947 assert!(help.starts_with("*17\r\n"), "{help}");
7948 assert!(
7949 help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
7950 );
7951 assert!(help.contains("+ Start a new backup into the configured 'backupdirname'.\r\n"));
7952 assert!(help.contains("+ Freeze the current backup (BASE + INCR + manifest).\r\n"));
7953 assert!(help.contains("+ Return this help.\r\n+HELP\r\n+ Print this help.\r\n"));
7954 }
7955
7956 #[test]
7964 fn backup_refuses_what_it_cannot_read() {
7965 let mut f = Fixture::new();
7966 assert_eq!(
7967 f.run(&[b"BACKUP"]),
7968 "-ERR wrong number of arguments for 'backup' command\r\n"
7969 );
7970 assert_eq!(
7971 f.run(&[b"BACKUP", b"START", b"x"]),
7972 "-ERR wrong number of arguments for 'backup' command\r\n"
7973 );
7974 assert_eq!(
7975 f.run(&[b"BACKUP", b"NOPE"]),
7976 "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
7977 );
7978 }
7979
7980 #[test]
7981 fn the_command_counter_counts_every_command_including_the_bad_ones() {
7982 let mut f = Fixture::new();
7983 f.run(&[b"PING"]);
7984 f.run(&[b"NOPE"]);
7985 f.run(&[b"GET"]);
7986 assert_eq!(f.server.totals().commands, 3);
7987 }
7988
7989 #[test]
7990 fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
7991 let mut server = Server::new();
7992 server.set_threads(2);
7993 server.mine().turn.store(0, Relaxed);
7996 server.locals[1].mark(1 << 9);
7997 server.collect_marks();
7998 assert!(server.mine().wanted(9));
7999 assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
8001 }
8002
8003 #[test]
8004 fn what_two_threads_counted_is_added_up_when_info_asks() {
8005 let mut server = Server::new();
8006 server.set_threads(2);
8007 let ping = lookup(b"PING").expect("PING is a command");
8011 for (at, calls) in [(0, 2), (1, 3)] {
8012 let counters = &server.locals[at];
8013 for _ in 0..calls {
8014 counters.stats.commands.bump();
8015 counters.cmdstats.at(ping).calls.bump();
8016 }
8017 counters.stats.opened();
8018 }
8019 assert_eq!(server.totals().commands, 5);
8020 assert_eq!(server.totals().clients, 2);
8021 assert_eq!(server.totals().connections, 2);
8022 let rows: Vec<_> = server.command_stats().collect();
8023 assert_eq!(rows.len(), 1);
8024 assert_eq!(rows[0].0, "ping");
8025 assert_eq!(rows[0].1.calls, 5);
8026 server.reset_stats();
8029 assert_eq!(server.totals().commands, 0);
8030 assert_eq!(server.totals().connections, 0);
8031 assert_eq!(server.totals().clients, 2);
8032 }
8033
8034 #[test]
8035 fn the_parked_count_says_what_the_waiter_list_says() {
8036 let mut f = Fixture::new();
8037 assert_eq!(f.server.parked(), 0);
8038 for client in 1..=3u64 {
8039 f.session = Session::new(client);
8040 assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
8041 }
8042 assert_eq!(f.server.parked(), 3);
8043 assert_eq!(f.server.waiters().len(), 3);
8044
8045 f.server.forget_waiters(2);
8049 assert_eq!(f.server.parked(), f.server.waiters().len());
8050 f.server.forget_waiters(1);
8051 assert_eq!(f.server.parked(), f.server.waiters().len());
8052 f.run(&[b"RPUSH", b"q", b"v"]);
8053 let mut out = Out::new(Proto::Resp2);
8054 assert!(f.server.serve_waiter(3, 0, &mut out));
8055 f.server.forget_waiters(3);
8056 assert_eq!(f.server.parked(), 0);
8057 assert!(f.server.waiters().is_empty());
8058 }
8059
8060 #[test]
8061 fn a_set_goes_from_bytes_to_bytes() {
8062 let mut f = Fixture::new();
8063 assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
8064 assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
8065 assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
8066 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
8067 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
8068 assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
8069 assert_eq!(
8070 f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
8071 "*3\r\n:1\r\n:0\r\n:1\r\n"
8072 );
8073 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
8074 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8075 }
8076
8077 #[test]
8078 fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
8079 let mut f = Fixture::new();
8080 assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
8081 assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
8082 assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
8083 assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
8084 assert_eq!(
8085 f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
8086 "*2\r\n:0\r\n:0\r\n"
8087 );
8088 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
8089 }
8090
8091 #[test]
8092 fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
8093 let mut f = Fixture::new();
8097 f.run(&[b"SADD", b"s", b"one"]);
8098 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
8099
8100 f.run(&[b"HELLO", b"3"]);
8101 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
8102 }
8103
8104 #[test]
8105 fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
8106 let mut f = Fixture::new();
8109 f.run(&[b"SADD", b"s", b"42"]);
8110 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
8111 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
8112 assert_eq!(
8113 f.run(&[b"SISMEMBER", b"s", b"042"]),
8114 ":0\r\n",
8115 "the member is the bytes and not the number they parse to"
8116 );
8117 }
8118
8119 #[test]
8120 fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
8121 let mut f = Fixture::new();
8122 f.run(&[b"SET", b"str", b"v"]);
8123 f.run(&[b"SADD", b"set", b"a"]);
8124
8125 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8126 assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
8127 assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
8128 assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
8129 assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
8130 assert_eq!(f.run(&[b"GET", b"set"]), wrong);
8131 assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
8132 assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
8133 assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
8134
8135 assert_eq!(
8138 f.run(&[b"MGET", b"str", b"set", b"nope"]),
8139 "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
8140 );
8141 assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
8143 assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
8144 }
8145
8146 #[test]
8147 fn a_wrongtype_leaves_nothing_half_written() {
8148 let mut f = Fixture::new();
8152 f.run(&[b"SET", b"k", b"v"]);
8153 let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
8154 assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
8155 assert!(!reply.contains('*'), "an array header went out in front");
8156 }
8157
8158 #[test]
8159 fn emptying_a_set_takes_the_key_with_it() {
8160 let mut f = Fixture::new();
8161 f.run(&[b"SADD", b"s", b"a", b"b"]);
8162 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
8163 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
8164 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8165 assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
8166 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8167 }
8168
8169 fn split_scan(reply: &str) -> (String, Vec<String>) {
8175 let mut lines = reply.split("\r\n");
8176 assert_eq!(lines.next(), Some("*2"), "got {reply}");
8177 lines.next().expect("the cursor header");
8178 let cursor = lines.next().expect("the cursor").to_owned();
8179 let header = lines.next().expect("the member header");
8180 let n: usize = header[1..].parse().expect("a member count");
8181 let mut members = Vec::with_capacity(n);
8182 for _ in 0..n {
8183 lines.next().expect("a member header");
8184 members.push(lines.next().expect("a member").to_owned());
8185 }
8186 (cursor, members)
8187 }
8188
8189 #[test]
8190 fn popping_takes_a_member_off_the_set_and_hands_it_back() {
8191 let mut f = Fixture::new();
8192 f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
8193
8194 let one = f.run(&[b"SPOP", b"s"]);
8195 assert!(
8196 ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
8197 "got {one}"
8198 );
8199 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8200
8201 let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
8203 assert!(rest.starts_with("*3\r\n"), "got {rest}");
8204 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8205 assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
8207 assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
8208 }
8209
8210 #[test]
8211 fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
8212 let mut f = Fixture::new();
8217 f.run(&[b"HELLO", b"3"]);
8218 f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
8219
8220 assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
8221 assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
8223
8224 f.run(&[b"SADD", b"one", b"z"]);
8228 assert_eq!(
8229 f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
8230 "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
8231 );
8232 }
8233
8234 #[test]
8235 fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
8236 let mut f = Fixture::new();
8237 f.run(&[b"SADD", b"s", b"only"]);
8238 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8239 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8240 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
8241
8242 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
8243 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
8246 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
8247 assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
8249 }
8250
8251 #[test]
8252 fn a_pop_count_that_is_not_a_positive_number_says_so() {
8253 let mut f = Fixture::new();
8254 f.run(&[b"SADD", b"s", b"a"]);
8255 let bad = "-ERR value is out of range, must be positive\r\n";
8256 assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
8257 assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
8258 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
8259 assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
8261 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
8262 }
8263
8264 #[test]
8265 fn a_scan_walks_a_set_of_any_size_exactly_once() {
8266 let mut f = Fixture::new();
8267 let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
8268 let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
8269 .into_iter()
8270 .chain(members.iter().map(Vec::as_slice))
8271 .collect();
8272 f.run(&args);
8273
8274 let mut seen = Vec::new();
8275 let mut cursor = "0".to_owned();
8276 loop {
8277 let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
8278 let (next, got) = split_scan(&reply);
8279 seen.extend(got);
8280 cursor = next;
8281 if cursor == "0" {
8282 break;
8283 }
8284 }
8285 seen.sort();
8286 seen.dedup();
8287 assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
8288
8289 f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
8292 let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
8293 assert_eq!(cursor, "0");
8294 assert_eq!(got.len(), 3);
8295 assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
8297 }
8298
8299 #[test]
8300 fn a_scan_takes_match_and_count_and_refuses_anything_else() {
8301 let mut f = Fixture::new();
8302 f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
8303
8304 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
8305 let mut got = got;
8306 got.sort();
8307 assert_eq!(got, ["aa", "ab"]);
8308
8309 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
8312 let mut got = got;
8313 got.sort();
8314 assert_eq!(got, ["12", "13"]);
8315
8316 assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
8317 assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
8318 assert_eq!(
8319 f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
8320 "-ERR syntax error\r\n"
8321 );
8322 assert_eq!(
8325 f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
8326 "-ERR syntax error\r\n"
8327 );
8328 }
8329
8330 #[test]
8331 fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
8332 let mut f = Fixture::new();
8333 f.run(&[b"SADD", b"src", b"a", b"b"]);
8334 f.run(&[b"SADD", b"dst", b"c"]);
8335
8336 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
8337 assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
8338 assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
8339 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
8341 assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
8342
8343 assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
8346 assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
8347 assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
8348 }
8349
8350 #[test]
8351 fn moving_checks_the_types_in_the_order_redis_checks_them() {
8352 let mut f = Fixture::new();
8356 f.run(&[b"SET", b"str", b"v"]);
8357 f.run(&[b"SADD", b"set", b"a"]);
8358
8359 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8360 assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
8361 assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
8362 assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
8363 assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
8364 assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
8365 assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
8366 assert_eq!(
8367 f.run(&[b"SISMEMBER", b"set", b"a"]),
8368 ":1\r\n",
8369 "and none of that moved anything"
8370 );
8371 }
8372
8373 #[test]
8374 fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8375 let mut f = Fixture::new();
8378 f.run(&[b"SADD", b"s", b"a"]);
8379 for bad in [
8380 &[b"SSCAN".as_slice(), b"s", b"abc"][..],
8381 &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
8382 &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
8383 ] {
8384 let reply = f.run(bad);
8385 assert!(reply.starts_with("-ERR"), "got {reply}");
8386 assert!(!reply.contains('*'), "an array header went out in front");
8387 }
8388 }
8389
8390 #[test]
8391 fn a_hash_writes_reads_and_deletes_its_fields() {
8392 let mut f = Fixture::new();
8393 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
8394 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
8395 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8396 assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
8397 assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
8398 assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
8399 assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
8400 assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
8401 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
8402 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
8403
8404 assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
8407
8408 assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
8409 assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
8410 assert_eq!(
8411 f.run(&[b"EXISTS", b"h"]),
8412 ":0\r\n",
8413 "and losing the last field lost the key"
8414 );
8415 }
8416
8417 #[test]
8418 fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
8419 let mut f = Fixture::new();
8420 f.run(&[b"HSET", b"h", b"a", b"1"]);
8421 assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
8422 assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
8423 assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
8424 assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
8425 assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
8426
8427 f.run(&[b"HELLO", b"3"]);
8428 assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
8429 assert_eq!(
8430 f.run(&[b"HGETALL", b"nokey"]),
8431 "%0\r\n",
8432 "a missing key is the empty hash and never a nil"
8433 );
8434 assert_eq!(
8435 f.run(&[b"HKEYS", b"h"]),
8436 "*1\r\n$1\r\na\r\n",
8437 "and the two that answer one side stay arrays"
8438 );
8439 }
8440
8441 #[test]
8442 fn hmget_answers_once_per_field_and_hmset_answers_ok() {
8443 let mut f = Fixture::new();
8444 assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
8445 assert_eq!(
8446 f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
8447 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
8448 "the reply is positional, so b is a nil and not a gap"
8449 );
8450 assert_eq!(
8451 f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
8452 "*2\r\n$-1\r\n$-1\r\n",
8453 "and a missing key is all nils rather than an empty array"
8454 );
8455
8456 assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
8457 assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
8458 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8459 }
8460
8461 #[test]
8462 fn a_hash_counts_up_and_says_so_when_it_cannot() {
8463 let mut f = Fixture::new();
8464 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
8465 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
8466 assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
8467 assert_eq!(
8468 f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
8469 "$4\r\n10.5\r\n",
8470 "a bulk string and not a double, on both protocols"
8471 );
8472
8473 f.run(&[b"HSET", b"h", b"s", b"words"]);
8474 let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
8475 assert!(
8476 bad.starts_with("-ERR hash value is not an integer"),
8477 "{bad}"
8478 );
8479 let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
8480 assert!(
8481 bad.starts_with("-ERR value is not an integer"),
8482 "a bad argument is not yet a hash value, {bad}"
8483 );
8484 assert_eq!(
8485 f.run(&[b"HGET", b"h", b"s"]),
8486 "$5\r\nwords\r\n",
8487 "and neither of them wrote anything"
8488 );
8489 }
8490
8491 #[test]
8492 fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
8493 let fields = if cfg!(miri) { 96 } else { 500 };
8498 let mut f = Fixture::new();
8499 for i in 0..fields {
8500 let field = format!("field-{i}");
8501 let value = format!("value-{i}");
8502 f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
8503 }
8504
8505 let mut seen: Vec<String> = Vec::new();
8506 let mut cursor = "0".to_owned();
8507 loop {
8508 let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
8509 let (next, items) = scan_reply(&reply);
8510 assert_eq!(items.len() % 2, 0, "a pair went out half written");
8511 for pair in items.chunks(2) {
8512 assert_eq!(
8513 pair[0].strip_prefix("field-"),
8514 pair[1].strip_prefix("value-"),
8515 "a field came back with someone else's value"
8516 );
8517 seen.push(pair[0].clone());
8518 }
8519 cursor = next;
8520 if cursor == "0" {
8521 break;
8522 }
8523 }
8524 seen.sort();
8525 seen.dedup();
8526 assert_eq!(seen.len(), fields, "every field once and only once");
8527
8528 let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
8529 assert!(
8530 items.iter().all(|s| s.starts_with("field-")),
8531 "NOVALUES still sent the values"
8532 );
8533
8534 let last = fields - 1;
8535 let (_, one) = scan_reply(&f.run(&[
8536 b"HSCAN",
8537 b"h",
8538 b"0",
8539 b"MATCH",
8540 format!("field-{last}").as_bytes(),
8541 b"COUNT",
8542 b"1000",
8543 ]));
8544 assert_eq!(
8545 one,
8546 [format!("field-{last}"), format!("value-{last}")],
8547 "MATCH is on the field"
8548 );
8549 }
8550
8551 #[test]
8552 fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
8553 let mut f = Fixture::new();
8554 f.run(&[b"HSET", b"h", b"a", b"1"]);
8555 assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
8556 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
8557 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
8558 assert_eq!(
8559 f.run(&[b"HRANDFIELD", b"h", b"3"]),
8560 "*1\r\n$1\r\na\r\n",
8561 "a positive count is capped at the size of the hash"
8562 );
8563 assert_eq!(
8564 f.run(&[b"HRANDFIELD", b"h", b"-3"]),
8565 "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
8566 "and a negative one repeats itself"
8567 );
8568 assert_eq!(
8569 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8570 "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8571 "flat on RESP2"
8572 );
8573
8574 f.run(&[b"HELLO", b"3"]);
8575 assert_eq!(
8576 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8577 "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8578 "and nested on RESP3, but still an array and never a map"
8579 );
8580 }
8581
8582 #[test]
8583 fn every_hash_command_says_wrongtype_and_writes_nothing() {
8584 let mut f = Fixture::new();
8585 f.run(&[b"SET", b"str", b"v"]);
8586 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8587
8588 for cmd in [
8589 &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
8590 &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
8591 &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
8592 &[b"HGET".as_slice(), b"str", b"f"][..],
8593 &[b"HMGET".as_slice(), b"str", b"f"][..],
8594 &[b"HDEL".as_slice(), b"str", b"f"][..],
8595 &[b"HLEN".as_slice(), b"str"][..],
8596 &[b"HEXISTS".as_slice(), b"str", b"f"][..],
8597 &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
8598 &[b"HGETALL".as_slice(), b"str"][..],
8599 &[b"HKEYS".as_slice(), b"str"][..],
8600 &[b"HVALS".as_slice(), b"str"][..],
8601 &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
8602 &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
8603 &[b"HRANDFIELD".as_slice(), b"str"][..],
8604 &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
8605 &[b"HSCAN".as_slice(), b"str", b"0"][..],
8606 ] {
8607 let reply = f.run(cmd);
8608 assert_eq!(reply, wrong, "{:?}", cmd[0]);
8609 }
8610 assert_eq!(
8611 f.run(&[b"GET", b"str"]),
8612 "$1\r\nv\r\n",
8613 "and none of them touched the value"
8614 );
8615 }
8616
8617 #[test]
8618 fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8619 let mut f = Fixture::new();
8620 f.run(&[b"HSET", b"h", b"f", b"v"]);
8621 for bad in [
8622 &[b"HSCAN".as_slice(), b"h", b"abc"][..],
8623 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
8624 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
8625 &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
8626 ] {
8627 let reply = f.run(bad);
8628 assert!(reply.starts_with("-ERR"), "got {reply}");
8629 assert!(!reply.contains('*'), "an array header went out in front");
8630 }
8631 }
8632
8633 #[test]
8634 fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
8635 let mut f = Fixture::new();
8636 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8637 assert_eq!(
8638 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
8639 "*1\r\n:1\r\n"
8640 );
8641 assert_eq!(
8642 f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8643 "*3\r\n:100\r\n:-1\r\n:-2\r\n",
8644 "one answer per field, and the two sentinels are TTL's own"
8645 );
8646
8647 let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
8650 assert!((99_000..=100_000).contains(&ms), "got {ms}");
8651 let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8652 let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8653 assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
8654 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
8655
8656 assert_eq!(
8657 f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8658 "*3\r\n:1\r\n:-1\r\n:-2\r\n",
8659 "one for the deadline taken off, and it does not say what it was"
8660 );
8661 assert_eq!(
8662 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8663 "*1\r\n:-1\r\n"
8664 );
8665 assert_eq!(
8666 f.run(&[b"HGET", b"h", b"a"]),
8667 "$1\r\n1\r\n",
8668 "and the field is still there with the value it had"
8669 );
8670 }
8671
8672 #[test]
8673 fn a_deadline_that_has_already_gone_deletes_the_field_now() {
8674 let mut f = Fixture::new();
8675 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8676 assert_eq!(
8677 f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
8678 "*1\r\n:2\r\n",
8679 "two, and not one, because nothing was stored"
8680 );
8681 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8682 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8683
8684 assert_eq!(
8685 f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
8686 "*1\r\n:2\r\n"
8687 );
8688 assert_eq!(
8689 f.run(&[b"EXISTS", b"h"]),
8690 ":0\r\n",
8691 "and the last field going took the key with it"
8692 );
8693
8694 f.run(&[b"HSET", b"h", b"a", b"1"]);
8697 assert_eq!(
8698 f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
8699 "*1\r\n:2\r\n"
8700 );
8701 }
8702
8703 #[test]
8704 fn a_field_is_gone_once_its_moment_passes() {
8705 let mut f = Fixture::new();
8706 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8707 assert_eq!(
8708 f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
8709 "*1\r\n:1\r\n"
8710 );
8711 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
8712
8713 f.server.advance_clock_ms(60);
8717 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8718 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8719 assert_eq!(
8720 f.run(&[b"HGETALL", b"h"]),
8721 "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
8722 "and the walks do not hand back a field that has expired"
8723 );
8724 }
8725
8726 #[test]
8727 fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
8728 let mut f = Fixture::new();
8729 for cmd in [
8730 &[
8731 b"HEXPIRE".as_slice(),
8732 b"nokey",
8733 b"100",
8734 b"FIELDS",
8735 b"2",
8736 b"a",
8737 b"b",
8738 ][..],
8739 &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8740 &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8741 &[
8742 b"HEXPIRETIME".as_slice(),
8743 b"nokey",
8744 b"FIELDS",
8745 b"2",
8746 b"a",
8747 b"b",
8748 ][..],
8749 &[
8750 b"HPERSIST".as_slice(),
8751 b"nokey",
8752 b"FIELDS",
8753 b"2",
8754 b"a",
8755 b"b",
8756 ][..],
8757 ] {
8758 assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
8759 }
8760 }
8761
8762 #[test]
8763 fn writing_a_field_clears_the_deadline_that_was_on_it() {
8764 let mut f = Fixture::new();
8765 f.run(&[b"HSET", b"h", b"a", b"1"]);
8766 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
8767 f.run(&[b"HSET", b"h", b"a", b"2"]);
8768 assert_eq!(
8769 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8770 "*1\r\n:-1\r\n",
8771 "Redis has done this since 7.4, and it is why HGETEX exists"
8772 );
8773 }
8774
8775 #[test]
8776 fn the_four_conditions_reach_the_store_the_way_they_were_written() {
8777 let mut f = Fixture::new();
8778 f.run(&[b"HSET", b"h", b"a", b"1"]);
8779 assert_eq!(
8780 f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
8781 "*1\r\n:0\r\n",
8782 "XX on a field with no deadline changes nothing"
8783 );
8784 assert_eq!(
8785 f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
8786 "*1\r\n:1\r\n"
8787 );
8788 assert_eq!(
8789 f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
8790 "*1\r\n:0\r\n",
8791 "and NX will not move one that is already there"
8792 );
8793 assert_eq!(
8794 f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
8795 "*1\r\n:0\r\n"
8796 );
8797 assert_eq!(
8798 f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
8799 "*1\r\n:1\r\n"
8800 );
8801 assert_eq!(
8802 f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
8803 "*1\r\n:1\r\n"
8804 );
8805 assert_eq!(
8806 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8807 "*1\r\n:50\r\n"
8808 );
8809 }
8810
8811 #[test]
8812 fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
8813 let mut f = Fixture::new();
8814 f.run(&[b"HSET", b"h", b"a", b"1"]);
8815 for (bad, want) in [
8816 (
8817 &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
8818 "-ERR invalid expire time, must be >= 0",
8819 ),
8820 (
8821 &[
8822 b"HEXPIRE".as_slice(),
8823 b"h",
8824 b"9999999999999999",
8825 b"FIELDS",
8826 b"1",
8827 b"a",
8828 ][..],
8829 "-ERR invalid expire time in 'hexpire' command",
8830 ),
8831 (
8832 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
8833 "-ERR wrong number of arguments for 'hexpire' command",
8834 ),
8835 (
8836 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
8837 "-ERR Parameter `numFields` should be greater than 0",
8838 ),
8839 (
8840 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
8841 "-ERR wrong number of arguments",
8842 ),
8843 (
8844 &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
8845 "-ERR wrong number of arguments",
8846 ),
8847 ] {
8848 let reply = f.run(bad);
8849 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
8850 assert!(!reply.contains('*'), "an array header went out in front");
8851 }
8852 assert_eq!(
8853 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8854 "*1\r\n:-1\r\n",
8855 "and not one of them put a deadline on anything"
8856 );
8857 }
8858
8859 #[test]
8860 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
8861 let mut f = Fixture::new();
8862 f.run(&[b"SET", b"str", b"v"]);
8863 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8864
8865 for cmd in [
8866 &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
8867 &[
8868 b"HPEXPIRE".as_slice(),
8869 b"str",
8870 b"100",
8871 b"FIELDS",
8872 b"1",
8873 b"f",
8874 ][..],
8875 &[
8876 b"HEXPIREAT".as_slice(),
8877 b"str",
8878 b"9999999999",
8879 b"FIELDS",
8880 b"1",
8881 b"f",
8882 ][..],
8883 &[
8884 b"HPEXPIREAT".as_slice(),
8885 b"str",
8886 b"9999999999999",
8887 b"FIELDS",
8888 b"1",
8889 b"f",
8890 ][..],
8891 &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8892 &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8893 &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8894 &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8895 &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8896 ] {
8897 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
8898 }
8899 assert_eq!(
8900 f.run(&[b"GET", b"str"]),
8901 "$1\r\nv\r\n",
8902 "and none of them touched the value"
8903 );
8904 }
8905
8906 #[test]
8907 fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
8908 let mut f = Fixture::new();
8909 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8910 assert_eq!(
8911 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
8912 "*2\r\n$1\r\n1\r\n$-1\r\n",
8913 "positional, so the field that was not there is a nil in its place"
8914 );
8915 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8916 assert_eq!(
8917 f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
8918 "*1\r\n$-1\r\n"
8919 );
8920 assert_eq!(
8921 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
8922 "*1\r\n$1\r\n2\r\n"
8923 );
8924 assert_eq!(
8925 f.run(&[b"EXISTS", b"h"]),
8926 ":0\r\n",
8927 "and the last field took the key"
8928 );
8929 }
8930
8931 #[test]
8932 fn hgetex_reads_and_moves_the_deadline_in_one_command() {
8933 let mut f = Fixture::new();
8934 f.run(&[b"HSET", b"h", b"a", b"1"]);
8935 assert_eq!(
8936 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
8937 "*1\r\n$1\r\n1\r\n"
8938 );
8939 assert_eq!(
8940 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8941 "*1\r\n:-1\r\n",
8942 "no option means leave it alone, which is the one place this is not GETEX"
8943 );
8944
8945 f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
8946 assert_eq!(
8947 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8948 "*1\r\n:100\r\n"
8949 );
8950 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
8951 assert_eq!(
8952 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8953 "*1\r\n:100\r\n",
8954 "and a plain read really does leave it alone"
8955 );
8956 assert_eq!(
8957 f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
8958 "*1\r\n$1\r\n1\r\n"
8959 );
8960 assert_eq!(
8961 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8962 "*1\r\n:-1\r\n"
8963 );
8964
8965 assert_eq!(
8966 f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
8967 "*1\r\n$1\r\n1\r\n",
8968 "the value goes out before the deadline that has already gone is applied"
8969 );
8970 assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
8971 assert_eq!(
8972 f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
8973 "*1\r\n$-1\r\n"
8974 );
8975 }
8976
8977 #[test]
8978 fn hsetex_writes_all_of_it_or_none_of_it() {
8979 let mut f = Fixture::new();
8980 assert_eq!(
8981 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
8982 ":1\r\n"
8983 );
8984 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8985 assert_eq!(
8986 f.run(&[
8987 b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
8988 ]),
8989 ":0\r\n",
8990 "FNX wants every field named to be missing"
8991 );
8992 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8993 assert_eq!(
8994 f.run(&[b"HEXISTS", b"h", b"new"]),
8995 ":0\r\n",
8996 "and none of the list was written"
8997 );
8998 assert_eq!(
8999 f.run(&[
9000 b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
9001 ]),
9002 ":0\r\n",
9003 "and FXX wants every one of them to be there"
9004 );
9005 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
9006 assert_eq!(
9007 f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
9008 ":1\r\n"
9009 );
9010 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
9011
9012 assert_eq!(
9013 f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
9014 ":0\r\n"
9015 );
9016 assert_eq!(
9017 f.run(&[b"EXISTS", b"gone"]),
9018 ":0\r\n",
9019 "a key with no fields cannot meet FXX and is not created trying"
9020 );
9021 }
9022
9023 #[test]
9024 fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
9025 let mut f = Fixture::new();
9026 f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
9027 assert_eq!(
9028 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9029 "*1\r\n:100\r\n"
9030 );
9031
9032 f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
9033 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
9034 assert_eq!(
9035 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9036 "*1\r\n:100\r\n",
9037 "KEEPTTL put back what the write cleared"
9038 );
9039
9040 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
9041 assert_eq!(
9042 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9043 "*1\r\n:-1\r\n",
9044 "and without it a write clears the deadline the way HSET does"
9045 );
9046
9047 assert_eq!(
9050 f.run(&[
9051 b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
9052 ]),
9053 ":1\r\n"
9054 );
9055 assert_eq!(
9056 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9057 "*1\r\n:100\r\n"
9058 );
9059
9060 assert_eq!(
9061 f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
9062 ":1\r\n",
9063 "written, and not the separate code the HEXPIRE family has for this"
9064 );
9065 assert_eq!(
9066 f.run(&[b"EXISTS", b"h"]),
9067 ":0\r\n",
9068 "and storing it and then removing it emptied the hash"
9069 );
9070 }
9071
9072 #[test]
9073 fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
9074 let mut f = Fixture::new();
9075 f.run(&[b"HSET", b"h", b"a", b"1"]);
9076 for (bad, want) in [
9077 (
9079 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9080 "-ERR Number of fields must be a positive integer",
9081 ),
9082 (
9083 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9084 "-ERR The `numfields` parameter must match the number of arguments",
9085 ),
9086 (
9087 &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9088 "-ERR Mandatory argument FIELDS is missing or not at the right position",
9089 ),
9090 (
9092 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9093 "-ERR invalid number of fields",
9094 ),
9095 (
9096 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9097 "-ERR wrong number of arguments",
9098 ),
9099 (
9100 &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9101 "-ERR unknown argument: FIELD",
9102 ),
9103 (
9104 &[
9105 b"HGETEX".as_slice(),
9106 b"h",
9107 b"KEEPTTL",
9108 b"FIELDS",
9109 b"1",
9110 b"a",
9111 ][..],
9112 "-ERR unknown argument: KEEPTTL",
9113 ),
9114 (
9115 &[
9116 b"HGETEX".as_slice(),
9117 b"h",
9118 b"EX",
9119 b"100",
9120 b"PERSIST",
9121 b"FIELDS",
9122 b"1",
9123 b"a",
9124 ][..],
9125 "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
9126 ),
9127 (
9128 &[
9129 b"HSETEX".as_slice(),
9130 b"h",
9131 b"EX",
9132 b"1",
9133 b"KEEPTTL",
9134 b"FIELDS",
9135 b"1",
9136 b"a",
9137 b"1",
9138 ][..],
9139 "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
9140 ),
9141 (
9142 &[
9143 b"HSETEX".as_slice(),
9144 b"h",
9145 b"FNX",
9146 b"FXX",
9147 b"FIELDS",
9148 b"1",
9149 b"a",
9150 b"1",
9151 ][..],
9152 "-ERR Only one of FXX or FNX arguments can be specified",
9153 ),
9154 (
9155 &[
9156 b"HSETEX".as_slice(),
9157 b"h",
9158 b"FIELDS",
9159 b"2",
9160 b"a",
9161 b"1",
9162 b"b",
9163 ][..],
9164 "-ERR wrong number of arguments",
9165 ),
9166 (
9167 &[
9168 b"HGETEX".as_slice(),
9169 b"h",
9170 b"EX",
9171 b"-1",
9172 b"FIELDS",
9173 b"1",
9174 b"a",
9175 ][..],
9176 "-ERR invalid expire time, must be >= 0",
9177 ),
9178 (
9179 &[
9180 b"HGETEX".as_slice(),
9181 b"h",
9182 b"PXAT",
9183 b"99999999999999",
9184 b"FIELDS",
9185 b"1",
9186 b"a",
9187 ][..],
9188 "-ERR invalid expire time in 'hgetex' command",
9189 ),
9190 (
9191 &[
9192 b"HSETEX".as_slice(),
9193 b"h",
9194 b"EX",
9195 b"abc",
9196 b"FIELDS",
9197 b"1",
9198 b"a",
9199 b"1",
9200 ][..],
9201 "-ERR value is not an integer or out of range",
9202 ),
9203 ] {
9204 let reply = f.run(bad);
9205 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
9206 assert!(!reply.contains('*'), "an array header went out in front");
9207 }
9208 assert_eq!(
9209 f.run(&[b"HGET", b"h", b"a"]),
9210 "$1\r\n1\r\n",
9211 "and not one of them wrote anything"
9212 );
9213 assert_eq!(
9214 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9215 "*1\r\n:-1\r\n"
9216 );
9217 }
9218
9219 #[test]
9220 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
9221 let mut f = Fixture::new();
9222 f.run(&[b"SET", b"str", b"v"]);
9223 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9224 for cmd in [
9225 &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9226 &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9227 &[
9228 b"HGETEX".as_slice(),
9229 b"str",
9230 b"EX",
9231 b"100",
9232 b"FIELDS",
9233 b"1",
9234 b"f",
9235 ][..],
9236 &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
9237 ] {
9238 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9239 }
9240 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
9241 }
9242
9243 #[test]
9251 fn himport_writes_declared_values_into_sorted_fields() {
9252 let mut f = Fixture::new();
9253 assert_eq!(
9254 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
9255 "+OK\r\n"
9256 );
9257 assert_eq!(
9258 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
9259 "+OK\r\n"
9260 );
9261 assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
9262 assert_eq!(
9263 f.run(&[b"HGETALL", b"k"]),
9264 bulks(&["a", "3", "b", "1", "aa", "2"])
9265 );
9266 }
9267
9268 #[test]
9271 fn himport_set_replaces_the_whole_key() {
9272 let mut f = Fixture::new();
9273 f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
9274 f.run(&[b"EXPIRE", b"k", b"100"]);
9275 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9276 assert_eq!(
9277 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9278 "+OK\r\n"
9279 );
9280 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9281 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
9282 }
9283
9284 #[test]
9287 fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
9288 let mut f = Fixture::new();
9289 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
9290 f.run(&[b"SELECT", b"1"]);
9291 assert_eq!(
9292 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9293 "+OK\r\n"
9294 );
9295 f.run(&[b"SELECT", b"0"]);
9296 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
9297 assert_eq!(
9298 f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
9299 "-ERR no such fieldset\r\n"
9300 );
9301 }
9302
9303 #[test]
9310 fn himport_complains_in_the_order_a_real_server_does() {
9311 let mut f = Fixture::new();
9312 f.run(&[b"SET", b"str", b"v"]);
9313 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9314 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9315 assert_eq!(
9316 f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
9317 wrong,
9318 "the type beats a missing fieldset"
9319 );
9320 assert_eq!(
9321 f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
9322 wrong,
9323 "and it beats a value count that does not fit"
9324 );
9325 assert_eq!(
9326 f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
9327 "-ERR no such fieldset\r\n"
9328 );
9329 for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
9331 let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
9332 line.extend_from_slice(values);
9333 assert_eq!(
9334 f.run(&line),
9335 "-ERR value count does not match fieldset field count\r\n",
9336 "{} values into two fields",
9337 values.len()
9338 );
9339 }
9340 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9341 }
9342
9343 #[test]
9345 fn himport_checks_each_subcommand_count_under_its_own_name() {
9346 let mut f = Fixture::new();
9347 assert_eq!(
9348 f.run(&[b"HIMPORT"]),
9349 "-ERR wrong number of arguments for 'himport' command\r\n"
9350 );
9351 for (rest, name) in [
9352 (&["PREPARE"][..], "prepare"),
9353 (&["PREPARE", "fs"][..], "prepare"),
9354 (&["SET"][..], "set"),
9355 (&["SET", "k"][..], "set"),
9356 (&["SET", "k", "fs"][..], "set"),
9357 (&["DISCARD"][..], "discard"),
9358 (&["DISCARD", "a", "b"][..], "discard"),
9359 (&["DISCARDALL", "x"][..], "discardall"),
9360 ] {
9361 let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
9362 line.extend(rest.iter().map(|a| a.as_bytes()));
9363 assert_eq!(
9364 f.run(&line),
9365 format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
9366 "HIMPORT {}",
9367 rest.join(" ")
9368 );
9369 }
9370 assert_eq!(
9371 f.run(&[b"HIMPORT", b"NOPE", b"x"]),
9372 "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
9373 );
9374 }
9375
9376 #[test]
9379 fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
9380 let mut f = Fixture::new();
9381 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9382 assert_eq!(
9383 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
9384 "-ERR duplicate field name in fieldset\r\n"
9385 );
9386 assert_eq!(
9387 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9388 "+OK\r\n"
9389 );
9390 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9391 }
9392
9393 #[test]
9396 fn himport_prepare_replaces_and_the_discards_count() {
9397 let mut f = Fixture::new();
9398 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9399 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
9400 assert_eq!(
9401 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9402 "+OK\r\n"
9403 );
9404 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
9405
9406 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
9407 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
9408 f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
9409 f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
9410 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
9411 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
9412 }
9413
9414 fn int(reply: &str) -> i64 {
9420 let body = reply
9421 .strip_prefix(':')
9422 .and_then(|s| s.strip_suffix("\r\n"))
9423 .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
9424 body.parse().expect("an integer")
9425 }
9426
9427 fn int_reply(reply: &str) -> i64 {
9428 let body = reply
9429 .strip_prefix("*1\r\n:")
9430 .and_then(|s| s.strip_suffix("\r\n"))
9431 .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
9432 body.parse().expect("an integer")
9433 }
9434
9435 fn scan_reply(reply: &str) -> (String, Vec<String>) {
9437 let mut lines = reply.split("\r\n");
9438 assert_eq!(lines.next(), Some("*2"), "got {reply}");
9439 lines.next().expect("the cursor header");
9440 let cursor = lines.next().expect("a cursor").to_owned();
9441 let header = lines.next().expect("an item count");
9442 let n: usize = header[1..].parse().expect("a count");
9443 let mut items = Vec::with_capacity(n);
9444 for _ in 0..n {
9445 lines.next().expect("an item header");
9446 items.push(lines.next().expect("an item").to_owned());
9447 }
9448 (cursor, items)
9449 }
9450
9451 fn sorted(reply: &str) -> Vec<String> {
9454 let mut lines = reply.split("\r\n");
9455 let header = lines.next().expect("a header");
9456 assert!(
9457 header.starts_with('*') || header.starts_with('~'),
9458 "got {reply}"
9459 );
9460 let n: usize = header[1..].parse().expect("a member count");
9461 let mut got = Vec::with_capacity(n);
9462 for _ in 0..n {
9463 lines.next().expect("a member header");
9464 got.push(lines.next().expect("a member").to_owned());
9465 }
9466 got.sort();
9467 got
9468 }
9469
9470 #[test]
9471 fn the_algebra_answers_what_the_sets_share_and_do_not() {
9472 let mut f = Fixture::new();
9473 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9474 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9475 f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
9476
9477 assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
9478 assert_eq!(
9479 sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
9480 ["1", "2", "3", "4", "5"]
9481 );
9482 assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
9483 assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
9484
9485 assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
9488 assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
9489 assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
9490 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
9491 }
9492
9493 #[test]
9494 fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
9495 let mut f = Fixture::new();
9496 f.run(&[b"SADD", b"a", b"x"]);
9497 assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
9498 assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
9499 assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
9500
9501 f.run(&[b"HELLO", b"3"]);
9502 assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
9503 assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
9504 assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
9505 assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
9506 }
9507
9508 #[test]
9509 fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
9510 let mut f = Fixture::new();
9511 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9512 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9513
9514 assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
9515 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
9516 assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
9517 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
9518 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
9519 assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
9520
9521 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
9524 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
9525 assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
9526 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
9527
9528 f.run(&[b"SET", b"str", b"v"]);
9531 assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
9532 assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
9533 }
9534
9535 #[test]
9536 fn sintercard_counts_without_building_and_stops_at_a_limit() {
9537 let mut f = Fixture::new();
9538 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9539 f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
9540
9541 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
9542 assert_eq!(
9543 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9544 ":2\r\n"
9545 );
9546 assert_eq!(
9547 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9548 ":3\r\n",
9549 "a limit of zero is no limit"
9550 );
9551 assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
9552 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
9553
9554 assert_eq!(
9556 f.run(&[b"SINTERCARD", b"0", b"a"]),
9557 "-ERR numkeys should be greater than 0\r\n"
9558 );
9559 assert_eq!(
9560 f.run(&[b"SINTERCARD", b"abc", b"a"]),
9561 "-ERR numkeys should be greater than 0\r\n"
9562 );
9563 assert_eq!(
9564 f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
9565 "-ERR Number of keys can't be greater than number of args\r\n"
9566 );
9567 assert_eq!(
9568 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
9569 "-ERR LIMIT can't be negative\r\n"
9570 );
9571 assert_eq!(
9572 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
9573 "-ERR syntax error\r\n"
9574 );
9575 f.run(&[b"SADD", b"LIMIT", b"2"]);
9577 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
9578 }
9579
9580 #[test]
9583 fn sunioncard_and_sdiffcard_count_without_building() {
9584 let mut f = Fixture::new();
9585 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9586 f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
9587
9588 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
9589 assert_eq!(
9590 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9591 ":2\r\n"
9592 );
9593 assert_eq!(
9594 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9595 ":6\r\n",
9596 "a limit of zero is no limit"
9597 );
9598 assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
9599 assert_eq!(
9600 f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
9601 ":4\r\n",
9602 "a missing key adds nothing to a union"
9603 );
9604
9605 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
9606 assert_eq!(
9607 f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
9608 ":1\r\n"
9609 );
9610 assert_eq!(
9611 f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
9612 ":2\r\n",
9613 "a difference is not symmetric"
9614 );
9615 assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
9616 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
9617 assert_eq!(
9618 f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
9619 ":0\r\n",
9620 "nothing taken away from nothing"
9621 );
9622
9623 for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
9626 assert_eq!(
9627 f.run(&[name, b"0", b"a"]),
9628 "-ERR numkeys should be greater than 0\r\n"
9629 );
9630 assert_eq!(
9631 f.run(&[name, b"abc", b"a"]),
9632 "-ERR numkeys should be greater than 0\r\n"
9633 );
9634 assert_eq!(
9635 f.run(&[name, b"-1", b"a"]),
9636 "-ERR numkeys should be greater than 0\r\n"
9637 );
9638 assert_eq!(
9639 f.run(&[name, b"3", b"a", b"b"]),
9640 "-ERR Number of keys can't be greater than number of args\r\n"
9641 );
9642 assert_eq!(
9643 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
9644 "-ERR LIMIT can't be negative\r\n"
9645 );
9646 assert_eq!(
9647 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
9648 "-ERR LIMIT can't be negative\r\n",
9649 "a LIMIT that is not a number gets the negative message too"
9650 );
9651 assert_eq!(
9652 f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
9653 "-ERR syntax error\r\n"
9654 );
9655 assert_eq!(
9656 f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
9657 "-ERR syntax error\r\n"
9658 );
9659 assert_eq!(
9660 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
9661 "-ERR syntax error\r\n"
9662 );
9663 }
9664
9665 f.run(&[b"SADD", b"LIMIT", b"2"]);
9667 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
9668 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
9669 }
9670
9671 #[test]
9672 fn the_algebra_answers_wrongtype_before_it_writes_anything() {
9673 let mut f = Fixture::new();
9674 f.run(&[b"SADD", b"a", b"1"]);
9675 f.run(&[b"SADD", b"d", b"old"]);
9676 f.run(&[b"SET", b"str", b"v"]);
9677
9678 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9679 for bad in [
9680 &[b"SINTER".as_slice(), b"a", b"str"][..],
9681 &[b"SUNION".as_slice(), b"str"][..],
9682 &[b"SDIFF".as_slice(), b"a", b"str"][..],
9683 &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
9684 &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
9685 &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
9686 &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
9687 ] {
9688 let reply = f.run(bad);
9689 assert_eq!(reply, wrong, "for {:?}", bad[0]);
9690 }
9691 assert_eq!(
9692 f.run(&[b"SMEMBERS", b"d"]),
9693 "*1\r\n$3\r\nold\r\n",
9694 "and the destination was left alone every time"
9695 );
9696 }
9697
9698 #[cfg_attr(miri, ignore = "the volume is the claim")]
9710 #[test]
9711 fn churning_sets_does_not_grow_the_server() {
9712 let mut f = Fixture::new();
9713 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
9714 let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
9715 .chain(std::iter::once(&b"s"[..]))
9716 .chain(members.iter().map(Vec::as_slice))
9717 .collect();
9718
9719 f.run(&args);
9720 f.run(&[b"DEL", b"s"]);
9721 f.server.compact_step();
9722 let after_first = f.server.memory_bytes();
9723
9724 for _ in 0..200 {
9725 f.run(&args);
9726 f.run(&[b"DEL", b"s"]);
9727 f.server.compact_step();
9728 }
9729 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9730 assert!(
9731 f.server.memory_bytes() <= after_first * 2,
9732 "held {} after two hundred passes against {after_first} after one",
9733 f.server.memory_bytes()
9734 );
9735 }
9736
9737 #[test]
9745 fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
9746 let mut f = Fixture::new();
9747 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
9748 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
9749 assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
9750 assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
9751 assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
9752 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
9753
9754 assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
9756 assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
9757 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
9758
9759 f.run(&[b"SET", b"num", b"12345"]);
9760 assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
9761 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
9762 assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
9763 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
9764 assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
9765 }
9766
9767 #[test]
9772 fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
9773 let mut f = Fixture::new();
9774 f.run(&[b"SET", b"mykey", b"foobar"]);
9775 assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
9776 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
9777 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
9778 assert_eq!(
9779 f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
9780 ":6\r\n"
9781 );
9782 assert_eq!(
9783 f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
9784 ":25\r\n"
9785 );
9786 assert_eq!(
9787 f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
9788 ":17\r\n"
9789 );
9790 assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
9791
9792 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
9795
9796 assert_eq!(
9798 f.run(&[b"BITCOUNT", b"mykey", b"0"]),
9799 "-ERR syntax error\r\n"
9800 );
9801 assert_eq!(
9802 f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
9803 "-ERR syntax error\r\n"
9804 );
9805 }
9806
9807 #[test]
9813 fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
9814 let mut f = Fixture::new();
9815 f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
9816 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
9817 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
9818 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
9819 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
9820 assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
9821
9822 f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
9823 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
9824 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
9825 assert_eq!(
9826 f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
9827 ":8\r\n"
9828 );
9829
9830 assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
9833 assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
9834 }
9835
9836 #[test]
9838 fn the_eight_combinations_write_what_a_real_server_writes() {
9839 let mut f = Fixture::new();
9840 f.run(&[b"SET", b"a", b"abc"]);
9841 f.run(&[b"SET", b"b", b"abd"]);
9842 let cases: &[(&[u8], &str)] = &[
9843 (b"AND", "ab`"),
9844 (b"OR", "abg"),
9845 (b"XOR", "\u{0}\u{0}\u{7}"),
9846 (b"DIFF", "\u{0}\u{0}\u{3}"),
9847 (b"DIFF1", "\u{0}\u{0}\u{4}"),
9848 (b"ANDOR", "ab`"),
9849 (b"ONE", "\u{0}\u{0}\u{7}"),
9850 ];
9851 for (op, want) in cases {
9852 assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
9853 assert_eq!(
9854 f.run(&[b"GET", b"d"]),
9855 format!("$3\r\n{want}\r\n"),
9856 "{op:?}"
9857 );
9858 }
9859 assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
9861 assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
9862
9863 assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
9866 assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
9867
9868 f.run(&[b"SET", b"dest", b"x"]);
9871 assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
9872 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
9873 }
9874
9875 #[test]
9877 fn bitop_names_the_operation_in_its_own_complaints() {
9878 let mut f = Fixture::new();
9879 f.run(&[b"SET", b"a", b"abc"]);
9880 assert_eq!(
9881 f.run(&[b"BITOP", b"nope", b"d", b"a"]),
9882 "-ERR syntax error\r\n"
9883 );
9884 assert_eq!(
9885 f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
9886 "-ERR BITOP NOT must be called with a single source key.\r\n"
9887 );
9888 for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
9889 assert_eq!(
9890 f.run(&[b"BITOP", op, b"d", b"a"]),
9891 format!(
9892 "-ERR BITOP {} must be called with at least two source keys.\r\n",
9893 String::from_utf8_lossy(op)
9894 )
9895 );
9896 }
9897 f.run(&[b"LPUSH", b"l", b"x"]);
9898 assert_eq!(
9899 f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
9900 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9901 );
9902 }
9903
9904 #[test]
9906 fn bitfield_reads_and_writes_packed_fields() {
9907 let mut f = Fixture::new();
9908 assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
9909 assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
9910
9911 assert_eq!(
9912 f.run(&[
9913 b"BITFIELD",
9914 b"bf",
9915 b"INCRBY",
9916 b"u2",
9917 b"100",
9918 b"1",
9919 b"GET",
9920 b"u4",
9921 b"0"
9922 ]),
9923 "*2\r\n:1\r\n:0\r\n"
9924 );
9925 assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
9928
9929 assert_eq!(
9931 f.run(&[
9932 b"BITFIELD",
9933 b"bf",
9934 b"SET",
9935 b"u8",
9936 b"#0",
9937 b"255",
9938 b"GET",
9939 b"u8",
9940 b"#0"
9941 ]),
9942 "*2\r\n:0\r\n:255\r\n"
9943 );
9944
9945 assert_eq!(
9946 f.run(&[
9947 b"BITFIELD",
9948 b"bf",
9949 b"OVERFLOW",
9950 b"SAT",
9951 b"INCRBY",
9952 b"i8",
9953 b"0",
9954 b"120",
9955 b"INCRBY",
9956 b"i8",
9957 b"0",
9958 b"120"
9959 ]),
9960 "*2\r\n:119\r\n:127\r\n"
9961 );
9962 assert_eq!(
9963 f.run(&[
9964 b"BITFIELD",
9965 b"bf2",
9966 b"OVERFLOW",
9967 b"FAIL",
9968 b"INCRBY",
9969 b"u2",
9970 b"0",
9971 b"5"
9972 ]),
9973 "*1\r\n$-1\r\n"
9974 );
9975 assert_eq!(
9976 f.run(&[
9977 b"BITFIELD",
9978 b"bf3",
9979 b"OVERFLOW",
9980 b"WRAP",
9981 b"INCRBY",
9982 b"u2",
9983 b"0",
9984 b"5"
9985 ]),
9986 "*1\r\n:1\r\n"
9987 );
9988 assert_eq!(
9989 f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
9990 "*1\r\n:4611686018427387904\r\n"
9991 );
9992 }
9993
9994 #[test]
10000 fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
10001 let mut f = Fixture::new();
10002 let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
10003 assert_eq!(
10004 f.run(&[
10005 b"BITFIELD",
10006 b"bad",
10007 b"SET",
10008 b"u8",
10009 b"0",
10010 b"1",
10011 b"GET",
10012 b"u99",
10013 b"0"
10014 ]),
10015 bad_type
10016 );
10017 assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
10018 assert_eq!(
10019 f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
10020 bad_type
10021 );
10022 assert_eq!(
10023 f.run(&[b"BITFIELD", b"bad", b"GET"]),
10024 "-ERR syntax error\r\n"
10025 );
10026 assert_eq!(
10027 f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
10028 "-ERR syntax error\r\n"
10029 );
10030 assert_eq!(
10031 f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
10032 "-ERR syntax error\r\n"
10033 );
10034 assert_eq!(
10035 f.run(&[
10036 b"BITFIELD",
10037 b"bad",
10038 b"OVERFLOW",
10039 b"NOPE",
10040 b"GET",
10041 b"u8",
10042 b"0"
10043 ]),
10044 "-ERR Invalid OVERFLOW type specified\r\n"
10045 );
10046 assert_eq!(
10047 f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
10048 "-ERR value is not an integer or out of range\r\n"
10049 );
10050 for at in [&b"#-1"[..], b"abc"] {
10051 assert_eq!(
10052 f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
10053 "-ERR bit offset is not an integer or out of range\r\n"
10054 );
10055 }
10056 }
10057
10058 #[test]
10060 fn bitfield_ro_answers_gets_and_refuses_the_rest() {
10061 let mut f = Fixture::new();
10062 f.run(&[b"SET", b"n", b"123"]);
10063 assert_eq!(
10064 f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
10065 "*1\r\n:49\r\n"
10066 );
10067 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
10069
10070 assert_eq!(
10072 f.run(&[
10073 b"BITFIELD_RO",
10074 b"n",
10075 b"OVERFLOW",
10076 b"SAT",
10077 b"GET",
10078 b"u8",
10079 b"0"
10080 ]),
10081 "*1\r\n:49\r\n"
10082 );
10083 for sub in [&b"SET"[..], b"INCRBY"] {
10084 assert_eq!(
10085 f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
10086 "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
10087 );
10088 }
10089
10090 assert_eq!(
10091 f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
10092 "*1\r\n:0\r\n"
10093 );
10094 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10095 }
10096
10097 #[test]
10099 fn an_offset_off_the_end_of_the_world_is_refused() {
10100 let mut f = Fixture::new();
10101 let bad = "-ERR bit offset is not an integer or out of range\r\n";
10102 for arg in [&b"abc"[..], b"-1", b"4294967296"] {
10103 assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
10104 assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
10105 }
10106 for arg in [&b"2"[..], b"-1"] {
10107 assert_eq!(
10108 f.run(&[b"BITPOS", b"k", arg]),
10109 "-ERR The bit argument must be 1 or 0.\r\n"
10110 );
10111 }
10112 assert_eq!(
10113 f.run(&[b"BITPOS", b"k", b"abc"]),
10114 "-ERR value is not an integer or out of range\r\n"
10115 );
10116 assert_eq!(
10117 f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
10118 "-ERR value is not an integer or out of range\r\n"
10119 );
10120 let bad_bit = "-ERR bit is not an integer or out of range\r\n";
10121 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
10122 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
10123 }
10124
10125 #[test]
10127 fn every_bitmap_command_says_wrongtype() {
10128 let mut f = Fixture::new();
10129 f.run(&[b"LPUSH", b"l", b"x"]);
10130 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10131 let cases: &[&[&[u8]]] = &[
10132 &[b"SETBIT", b"l", b"0", b"1"],
10133 &[b"GETBIT", b"l", b"0"],
10134 &[b"BITCOUNT", b"l"],
10135 &[b"BITPOS", b"l", b"1"],
10136 &[b"BITOP", b"AND", b"d", b"l"],
10137 &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
10138 &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
10139 ];
10140 for case in cases {
10141 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
10142 }
10143 }
10144
10145 #[test]
10148 fn a_sketch_is_added_to_and_counted() {
10149 let mut f = Fixture::new();
10150 assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
10152 assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
10153 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
10154 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
10155 assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
10158 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
10159
10160 assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
10161 assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
10162 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10163 }
10164
10165 #[test]
10166 fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
10167 let mut f = Fixture::new();
10168 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10169 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";
10171 let mut reply = b"$27\r\n".to_vec();
10172 reply.extend_from_slice(want);
10173 reply.extend_from_slice(b"\r\n");
10174 assert_eq!(f.raw(&[b"GET", b"h"]), reply);
10175 }
10176
10177 #[test]
10178 fn counting_several_keys_counts_their_union() {
10179 let mut f = Fixture::new();
10180 f.run(&[b"PFADD", b"a", b"x", b"y"]);
10181 f.run(&[b"PFADD", b"b", b"y", b"z"]);
10182 assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
10183 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
10184 assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
10187 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
10188 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10189 }
10190
10191 #[test]
10192 fn a_merge_keeps_what_the_destination_had() {
10193 let mut f = Fixture::new();
10194 f.run(&[b"PFADD", b"a", b"x", b"y"]);
10195 f.run(&[b"PFADD", b"b", b"z"]);
10196 assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
10197 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
10198 f.run(&[b"PFADD", b"c", b"w"]);
10200 assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
10201 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
10202 assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
10205 assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
10206 }
10207
10208 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
10217 #[test]
10218 fn the_debug_forms_answer_four_different_shapes() {
10219 let mut f = Fixture::new();
10220 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10221 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
10222 assert_eq!(
10223 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10224 "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
10225 );
10226 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
10227 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
10228 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
10229 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
10230 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10231 assert_eq!(
10233 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10234 "-ERR HLL encoding is not sparse\r\n"
10235 );
10236
10237 let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
10239 assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
10240 assert_eq!(reply.matches(":0\r\n").count(), 16381);
10241 assert_eq!(reply.matches(":1\r\n").count(), 2);
10242 assert_eq!(reply.matches(":2\r\n").count(), 1);
10243
10244 assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
10245 }
10246
10247 #[test]
10248 fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
10249 let mut f = Fixture::new();
10250 f.run(&[b"SET", b"plain", b"not a sketch"]);
10251 let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
10252 assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
10253 assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
10254 assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
10255 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
10256
10257 f.run(&[b"RPUSH", b"l", b"x"]);
10260 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10261 assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
10262 assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
10263 assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
10264 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
10265 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
10266 }
10267
10268 #[test]
10269 fn pfdebug_has_its_own_complaints() {
10270 let mut f = Fixture::new();
10271 f.run(&[b"PFADD", b"h", b"a"]);
10272 assert_eq!(
10275 f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
10276 "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
10277 );
10278 let gone = "-ERR The specified key does not exist\r\n";
10280 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
10281 assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
10282 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
10283 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
10284 assert_eq!(
10285 f.run(&[b"PFDEBUG"]),
10286 "-ERR wrong number of arguments for 'pfdebug' command\r\n"
10287 );
10288 assert_eq!(
10289 f.run(&[b"PFSELFTEST", b"x"]),
10290 "-ERR wrong number of arguments for 'pfselftest' command\r\n"
10291 );
10292 }
10293
10294 #[test]
10295 fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
10296 let mut f = Fixture::new();
10297 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10298 let reply = f.raw(&[b"GET", b"h"]);
10301 let short = reply[5..reply.len() - 3].to_vec();
10302 f.run(&[b"SET", b"h", &short]);
10303 assert_eq!(
10304 f.run(&[b"PFCOUNT", b"h"]),
10305 "-INVALIDOBJ Corrupted HLL object detected\r\n"
10306 );
10307 }
10308
10309 #[test]
10310 fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
10311 let mut f = Fixture::new();
10312 f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
10315 if cfg!(miri) {
10322 f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
10323 f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
10324 } else {
10325 for i in 0..10_000u32 {
10326 let ele = format!("e{i}");
10327 f.run(&[b"PFADD", b"big", ele.as_bytes()]);
10328 }
10329 }
10330 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
10331 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
10332
10333 for key in [&b"small"[..], b"big"] {
10334 let mut copy = key.to_vec();
10335 copy.push(b'2');
10336 let bytes = payload(&f.raw(&[b"DUMP", key]));
10337 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
10338 assert_eq!(f.raw(&[b"GET", ©]), f.raw(&[b"GET", key]));
10341 assert_eq!(
10342 f.run(&[b"PFDEBUG", b"ENCODING", ©]),
10343 f.run(&[b"PFDEBUG", b"ENCODING", key])
10344 );
10345 assert_eq!(f.run(&[b"PFCOUNT", ©]), f.run(&[b"PFCOUNT", key]));
10346 }
10347 assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
10348 assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
10349 }
10350
10351 fn bulk(s: &str) -> String {
10355 format!("${}\r\n{s}\r\n", s.len())
10356 }
10357
10358 fn bulks(parts: &[&str]) -> String {
10361 let mut s = format!("*{}\r\n", parts.len());
10362 for p in parts {
10363 s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
10364 }
10365 s
10366 }
10367
10368 #[test]
10369 fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
10370 let mut f = Fixture::new();
10371 assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
10375 assert_eq!(
10376 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10377 bulks(&["c", "b", "a"])
10378 );
10379 assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
10380 assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
10381 assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
10382 assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
10383 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
10384 assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
10385 }
10386
10387 #[test]
10388 fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
10389 let mut f = Fixture::new();
10390 assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
10391 assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
10392 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10393 f.run(&[b"RPUSH", b"k", b"a"]);
10394 assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
10395 assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
10396 assert_eq!(
10397 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10398 bulks(&["z", "a", "y"])
10399 );
10400 }
10401
10402 #[test]
10405 fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
10406 let mut f = Fixture::new();
10407 assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
10408 assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
10409 assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
10410 assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
10411 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10412 assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
10415 assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
10416 assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
10418 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10419 }
10420
10421 #[test]
10422 fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
10423 let mut f = Fixture::new();
10424 f.run(&[b"RPUSH", b"k", b"a"]);
10425 let range = "-ERR value is out of range, must be positive\r\n";
10426 assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
10427 assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
10428 assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
10429 assert_eq!(
10432 f.run(&[b"LPOP", b"k", b"1", b"2"]),
10433 "-ERR wrong number of arguments for 'lpop' command\r\n"
10434 );
10435 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10436 }
10437
10438 #[test]
10439 fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
10440 let mut f = Fixture::new();
10441 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10442 assert_eq!(
10443 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10444 bulks(&["a", "b", "c"])
10445 );
10446 assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
10447 assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
10448 assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
10449 assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
10450 assert_eq!(
10451 f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
10452 bulks(&["a", "b", "c"])
10453 );
10454 assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
10457 assert_eq!(
10458 f.run(&[b"LRANGE", b"k", b"a", b"b"]),
10459 "-ERR value is not an integer or out of range\r\n"
10460 );
10461 }
10462
10463 #[test]
10464 fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
10465 let mut f = Fixture::new();
10466 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10467 assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
10468 assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
10469 assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
10470 assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
10471 assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
10472 assert_eq!(
10473 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10474 bulks(&["a", "b", "z"])
10475 );
10476 assert_eq!(
10479 f.run(&[b"LSET", b"k", b"99", b"z"]),
10480 "-ERR index out of range\r\n"
10481 );
10482 assert_eq!(
10483 f.run(&[b"LSET", b"nope", b"0", b"z"]),
10484 "-ERR no such key\r\n"
10485 );
10486 }
10487
10488 #[test]
10489 fn linsert_says_three_things_with_one_signed_number() {
10490 let mut f = Fixture::new();
10491 assert_eq!(
10494 f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
10495 ":0\r\n"
10496 );
10497 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10498 assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
10499 assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
10500 assert_eq!(
10501 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10502 bulks(&["X", "a", "b", "Y"])
10503 );
10504 assert_eq!(
10505 f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
10506 ":-1\r\n"
10507 );
10508 assert_eq!(
10509 f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
10510 "-ERR syntax error\r\n"
10511 );
10512 }
10513
10514 #[test]
10515 fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
10516 let mut f = Fixture::new();
10517 f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
10518 assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
10519 assert_eq!(
10520 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10521 bulks(&["b", "c", "a"])
10522 );
10523 assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
10524 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10525 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
10526 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
10527 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10528 assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
10529 }
10530
10531 #[test]
10532 fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
10533 let mut f = Fixture::new();
10534 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
10535 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
10536 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10537 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
10540 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10541 assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
10542 }
10543
10544 #[test]
10545 fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
10546 let mut f = Fixture::new();
10547 f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
10548 assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
10549 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
10550 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
10551 assert_eq!(
10552 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
10553 "*2\r\n:0\r\n:3\r\n"
10554 );
10555 assert_eq!(
10556 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
10557 "*3\r\n:6\r\n:3\r\n:0\r\n"
10558 );
10559 assert_eq!(
10562 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
10563 "*1\r\n:0\r\n"
10564 );
10565 assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
10568 assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
10569 assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
10570 assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
10571 }
10572
10573 #[test]
10574 fn lpos_words_its_three_mistakes_the_way_redis_does() {
10575 let mut f = Fixture::new();
10576 f.run(&[b"RPUSH", b"p", b"a"]);
10577 assert_eq!(
10580 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
10581 "-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"
10582 );
10583 assert_eq!(
10584 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
10585 "-ERR COUNT can't be negative\r\n"
10586 );
10587 assert_eq!(
10588 f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
10589 "-ERR MAXLEN can't be negative\r\n"
10590 );
10591 assert_eq!(
10592 f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
10593 "-ERR syntax error\r\n"
10594 );
10595 assert_eq!(
10596 f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
10597 "-ERR syntax error\r\n"
10598 );
10599 }
10600
10601 #[test]
10602 fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
10603 let mut f = Fixture::new();
10604 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10605 assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
10606 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10607 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
10608 assert_eq!(
10609 f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
10610 "$1\r\na\r\n"
10611 );
10612 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
10613 f.run(&[b"DEL", b"r"]);
10616 f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
10617 assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
10618 assert_eq!(
10619 f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
10620 bulks(&["3", "1", "2"])
10621 );
10622 assert_eq!(
10623 f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
10624 "$-1\r\n"
10625 );
10626 assert_eq!(
10627 f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
10628 "-ERR syntax error\r\n"
10629 );
10630 }
10631
10632 #[test]
10633 fn a_move_checks_the_destination_before_it_takes_anything() {
10634 let mut f = Fixture::new();
10635 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10636 f.run(&[b"SET", b"str", b"v"]);
10637 assert_eq!(
10638 f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
10639 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10640 );
10641 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10643 }
10644
10645 #[test]
10646 fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
10647 for (from, to, order, want) in [
10651 ("LEFT", "RIGHT", "OBO", ["a", "b"]),
10652 ("LEFT", "RIGHT", "BULK", ["a", "b"]),
10653 ("LEFT", "LEFT", "OBO", ["b", "a"]),
10654 ("LEFT", "LEFT", "BULK", ["a", "b"]),
10655 ("RIGHT", "LEFT", "OBO", ["d", "e"]),
10656 ("RIGHT", "LEFT", "BULK", ["d", "e"]),
10657 ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
10658 ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
10659 ] {
10660 let mut f = Fixture::new();
10661 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
10662 let how = format!("{from} {to} {order}");
10663 let reply = f.run(&[
10664 b"LMOVEM",
10665 b"s",
10666 b"d",
10667 from.as_bytes(),
10668 to.as_bytes(),
10669 b"COUNT",
10670 b"2",
10671 order.as_bytes(),
10672 ]);
10673 assert_eq!(reply, bulks(&want), "the reply for {how}");
10674 assert_eq!(
10675 f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
10676 bulks(&want),
10677 "the destination for {how}"
10678 );
10679 }
10680 }
10681
10682 #[test]
10683 fn a_block_move_of_one_needs_no_count_at_all() {
10684 let mut f = Fixture::new();
10685 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10686 assert_eq!(
10687 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
10688 bulks(&["a"])
10689 );
10690 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
10691 assert_eq!(
10694 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
10695 "-ERR syntax error\r\n"
10696 );
10697 assert_eq!(
10698 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
10699 "-ERR syntax error\r\n"
10700 );
10701 }
10702
10703 #[test]
10704 fn a_block_move_with_exactly_takes_all_of_them_or_none() {
10705 let mut f = Fixture::new();
10706 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10707 assert_eq!(
10711 f.run(&[
10712 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
10713 ]),
10714 "*-1\r\n"
10715 );
10716 assert_eq!(
10717 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10718 bulks(&["a", "b", "c"])
10719 );
10720 assert_eq!(
10722 f.run(&[
10723 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
10724 ]),
10725 bulks(&["a", "b", "c"])
10726 );
10727 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
10728 assert_eq!(
10729 f.run(&[
10730 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10731 ]),
10732 "*-1\r\n"
10733 );
10734 }
10735
10736 #[test]
10737 fn a_block_move_onto_itself_rotates_by_the_count() {
10738 let mut f = Fixture::new();
10739 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10740 assert_eq!(
10741 f.run(&[
10742 b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
10743 ]),
10744 bulks(&["a", "b"])
10745 );
10746 assert_eq!(
10747 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10748 bulks(&["c", "a", "b"])
10749 );
10750 }
10751
10752 #[test]
10753 fn a_block_move_reads_the_count_before_the_ordering_word() {
10754 let mut f = Fixture::new();
10755 f.run(&[b"RPUSH", b"s", b"a", b"b"]);
10756 f.run(&[b"SET", b"str", b"v"]);
10757 let count = "-ERR count should be greater than 0\r\n";
10758 assert_eq!(
10759 f.run(&[
10760 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
10761 ]),
10762 count
10763 );
10764 assert_eq!(
10765 f.run(&[
10766 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
10767 ]),
10768 count
10769 );
10770 assert_eq!(
10771 f.run(&[
10772 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
10773 ]),
10774 "-ERR syntax error\r\n"
10775 );
10776 assert_eq!(
10777 f.run(&[
10778 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
10779 ]),
10780 "-ERR syntax error\r\n"
10781 );
10782 assert_eq!(
10785 f.run(&[
10786 b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
10787 ]),
10788 count
10789 );
10790 assert_eq!(
10791 f.run(&[
10792 b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10793 ]),
10794 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10795 );
10796 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
10797 }
10798
10799 #[test]
10800 fn lmpop_answers_from_the_first_key_that_has_anything() {
10801 let mut f = Fixture::new();
10802 f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
10803 assert_eq!(
10806 f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
10807 "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
10808 );
10809 assert_eq!(
10810 f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
10811 "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
10812 );
10813 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
10814 assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
10817 }
10818
10819 #[test]
10820 fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
10821 let mut f = Fixture::new();
10822 f.run(&[b"RPUSH", b"k", b"a"]);
10823 assert_eq!(
10824 f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
10825 "-ERR numkeys should be greater than 0\r\n"
10826 );
10827 assert_eq!(
10828 f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
10829 "-ERR numkeys should be greater than 0\r\n"
10830 );
10831 assert_eq!(
10832 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
10833 "-ERR count should be greater than 0\r\n"
10834 );
10835 assert_eq!(
10838 f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
10839 "-ERR syntax error\r\n"
10840 );
10841 assert_eq!(
10842 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
10843 "-ERR syntax error\r\n"
10844 );
10845 assert_eq!(
10846 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
10847 "-ERR syntax error\r\n"
10848 );
10849 assert_eq!(
10850 f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
10851 "-ERR syntax error\r\n"
10852 );
10853 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10854 }
10855
10856 #[test]
10857 fn every_list_command_says_wrongtype_and_writes_nothing() {
10858 let mut f = Fixture::new();
10859 f.run(&[b"SET", b"str", b"v"]);
10860 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10861 for cmd in [
10862 &[b"LPUSH".as_slice(), b"str", b"a"][..],
10863 &[b"RPUSH", b"str", b"a"],
10864 &[b"LPUSHX", b"str", b"a"],
10865 &[b"RPUSHX", b"str", b"a"],
10866 &[b"LPOP", b"str"],
10867 &[b"LPOP", b"str", b"2"],
10868 &[b"RPOP", b"str"],
10869 &[b"LLEN", b"str"],
10870 &[b"LRANGE", b"str", b"0", b"-1"],
10871 &[b"LINDEX", b"str", b"0"],
10872 &[b"LSET", b"str", b"0", b"a"],
10873 &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
10874 &[b"LREM", b"str", b"0", b"a"],
10875 &[b"LTRIM", b"str", b"0", b"-1"],
10876 &[b"LPOS", b"str", b"a"],
10877 &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
10878 &[b"RPOPLPUSH", b"str", b"d"],
10879 &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
10880 &[b"LMPOP", b"1", b"str", b"LEFT"],
10881 ] {
10882 assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
10883 }
10884 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
10885 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10886 }
10887
10888 #[test]
10892 fn a_timeout_has_three_ways_of_being_wrong() {
10893 let mut f = Fixture::new();
10894 let not_float = "-ERR timeout is not a float or out of range\r\n";
10895 let range = "-ERR timeout is out of range\r\n";
10896 for (bad, want) in [
10897 (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
10898 (&[b"BLPOP", b"k", b"nan"], not_float),
10899 (&[b"BLPOP", b"k", b""], not_float),
10900 (&[b"BLPOP", b"k", b" 1"], not_float),
10903 (&[b"BLPOP", b"k", b"1 "], not_float),
10904 (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
10905 (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
10906 (&[b"BLPOP", b"k", b"1e400"], range),
10909 (&[b"BLPOP", b"k", b"inf"], range),
10910 (&[b"BLPOP", b"k", b"9999999999999999"], range),
10911 (&[b"BRPOP", b"k", b"abc"], not_float),
10912 (
10913 &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
10914 not_float,
10915 ),
10916 (
10917 &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
10918 "-ERR timeout is negative\r\n",
10919 ),
10920 (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
10921 ] {
10922 assert_eq!(f.run(bad), want, "for {bad:?}");
10923 }
10924 }
10925
10926 #[test]
10929 fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
10930 let mut f = Fixture::new();
10931 for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
10932 let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
10933 assert_eq!(flow, Flow::Block, "for {timeout:?}");
10934 assert!(out.is_empty(), "for {timeout:?}");
10935 }
10936 let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
10940 assert_eq!(flow, Flow::Block);
10941 assert!(out.is_empty());
10942 }
10943
10944 #[test]
10945 fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
10946 let mut f = Fixture::new();
10947 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10948
10949 assert_eq!(
10952 f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
10953 (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
10954 );
10955 assert_eq!(
10956 f.run(&[b"BRPOP", b"L", b"0"]),
10957 "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
10958 );
10959 assert_eq!(
10960 f.run(&[
10961 b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
10962 ]),
10963 "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10964 );
10965 assert_eq!(
10966 f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10967 "$1\r\nd\r\n"
10968 );
10969 assert_eq!(
10970 f.run(&[b"EXISTS", b"L"]),
10971 ":0\r\n",
10972 "and the key went with it"
10973 );
10974 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
10975 f.run(&[b"RPUSH", b"D", b"x"]);
10978 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
10979 assert_eq!(
10980 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
10981 "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
10982 );
10983 }
10984
10985 #[test]
10986 fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
10987 let mut f = Fixture::new();
10988 f.run(&[b"RPUSH", b"k", b"a"]);
10989 for (bad, want) in [
10990 (
10991 &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
10992 "-ERR numkeys should be greater than 0\r\n",
10993 ),
10994 (
10995 &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
10996 "-ERR numkeys should be greater than 0\r\n",
10997 ),
10998 (
11001 &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
11002 "-ERR syntax error\r\n",
11003 ),
11004 (
11005 &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
11006 "-ERR syntax error\r\n",
11007 ),
11008 (
11009 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
11010 "-ERR syntax error\r\n",
11011 ),
11012 (
11013 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
11014 "-ERR syntax error\r\n",
11015 ),
11016 (
11019 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
11020 "-ERR count should be greater than 0\r\n",
11021 ),
11022 (
11023 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
11024 "-ERR count should be greater than 0\r\n",
11025 ),
11026 ] {
11027 assert_eq!(f.run(bad), want, "for {bad:?}");
11028 }
11029 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
11030 }
11031
11032 #[test]
11033 fn a_blocking_move_reads_its_directions_before_its_timeout() {
11034 let mut f = Fixture::new();
11035 assert_eq!(
11038 f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
11039 "-ERR syntax error\r\n"
11040 );
11041 assert_eq!(
11042 f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
11043 "-ERR syntax error\r\n"
11044 );
11045 }
11046
11047 #[test]
11051 fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
11052 let mut f = Fixture::new();
11053 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
11054 assert_eq!(
11055 f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
11056 (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
11057 );
11058 assert_eq!(
11059 f.run(&[
11060 b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
11061 ]),
11062 bulks(&["e", "d"])
11063 );
11064 assert_eq!(
11065 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
11066 bulks(&["a", "e", "d"])
11067 );
11068 assert_eq!(
11070 f.run(&[
11071 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
11072 ]),
11073 bulks(&["b", "c"])
11074 );
11075 assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
11076 }
11077
11078 #[test]
11082 fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
11083 let mut f = Fixture::new();
11084 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11085 assert_eq!(
11087 f.flow(&[
11088 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
11089 ]),
11090 (Flow::Continue, bulks(&["a", "b"]))
11091 );
11092
11093 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11094 assert_eq!(
11097 f.flow(&[
11098 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
11099 ])
11100 .0,
11101 Flow::Block
11102 );
11103 assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
11104 }
11105
11106 #[test]
11107 fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
11108 let mut f = Fixture::new();
11109 let syntax = "-ERR syntax error\r\n";
11110 assert_eq!(
11112 f.run(&[
11113 b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
11114 ]),
11115 syntax
11116 );
11117 assert_eq!(
11119 f.run(&[
11120 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
11121 ]),
11122 "-ERR timeout is not a float or out of range\r\n"
11123 );
11124 assert_eq!(
11125 f.run(&[
11126 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
11127 ]),
11128 "-ERR timeout is negative\r\n"
11129 );
11130 assert_eq!(
11132 f.run(&[
11133 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
11134 ]),
11135 "-ERR count should be greater than 0\r\n"
11136 );
11137 assert_eq!(
11138 f.run(&[
11139 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
11140 ]),
11141 syntax
11142 );
11143 assert_eq!(
11146 f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
11147 syntax
11148 );
11149 assert_eq!(
11150 f.run(&[
11151 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
11152 ]),
11153 syntax
11154 );
11155 }
11156
11157 #[test]
11160 fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
11161 let mut f = Fixture::new();
11162 f.run(&[b"SET", b"S", b"v"]);
11163 f.run(&[b"RPUSH", b"D", b"x"]);
11164 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11165
11166 assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
11167 assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
11170 assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
11171 assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
11172 assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
11173 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
11176 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
11177 assert_eq!(
11178 f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
11179 wrong
11180 );
11181 assert_eq!(
11182 f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
11183 wrong
11184 );
11185
11186 assert_eq!(
11190 f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11191 .0,
11192 Flow::Block
11193 );
11194 assert_eq!(
11198 f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11199 .0,
11200 Flow::Block
11201 );
11202 f.run(&[b"RPUSH", b"E", b"1", b"2"]);
11203 assert_eq!(
11204 f.flow(&[
11205 b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
11206 ])
11207 .0,
11208 Flow::Block
11209 );
11210 }
11211
11212 #[cfg_attr(miri, ignore = "the volume is the claim")]
11217 #[test]
11218 fn churning_lists_does_not_grow_the_server() {
11219 let mut f = Fixture::new();
11220 let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
11221 let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
11222 .into_iter()
11223 .chain(vals.iter().map(Vec::as_slice))
11224 .collect();
11225
11226 f.run(&args);
11227 f.run(&[b"DEL", b"k"]);
11228 f.server.compact_step();
11229 let after_first = f.server.memory_bytes();
11230
11231 for _ in 0..200 {
11232 f.run(&args);
11233 f.run(&[b"LTRIM", b"k", b"1", b"0"]);
11234 f.server.compact_step();
11235 }
11236 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11237 assert!(
11238 f.server.memory_bytes() <= after_first * 2,
11239 "held {} after two hundred passes against {after_first} after one",
11240 f.server.memory_bytes()
11241 );
11242 }
11243
11244 #[test]
11247 fn a_sorted_set_takes_scores_and_gives_them_back() {
11248 let mut f = Fixture::new();
11249 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
11250 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
11251 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
11252 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
11253 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
11254 assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
11255 assert_eq!(
11256 f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
11257 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
11258 );
11259 assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
11260 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
11261 assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
11263 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11264 }
11265
11266 #[test]
11267 fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
11268 let mut f = Fixture::new();
11269 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
11270 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
11271 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
11272 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
11273
11274 f.out = Out::new(Proto::Resp3);
11275 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
11276 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
11277 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
11278 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
11279 }
11280
11281 #[test]
11282 fn the_zadd_options_gate_what_gets_written() {
11283 let mut f = Fixture::new();
11284 f.run(&[b"ZADD", b"z", b"5", b"a"]);
11285 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
11287 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
11288 assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
11289 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
11290 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
11292 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
11293 assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
11294 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
11296 assert_eq!(
11297 f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
11298 ":2\r\n"
11299 );
11300 }
11301
11302 #[test]
11303 fn zadd_incr_answers_a_score_or_nothing_at_all() {
11304 let mut f = Fixture::new();
11305 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
11306 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
11307 assert_eq!(
11310 f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
11311 "$-1\r\n"
11312 );
11313 assert_eq!(
11314 f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
11315 "$-1\r\n"
11316 );
11317 assert_eq!(
11318 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
11319 "$-1\r\n"
11320 );
11321 assert_eq!(
11322 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
11323 "$1\r\n8\r\n"
11324 );
11325 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
11326 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
11327 }
11328
11329 #[test]
11330 fn the_two_infinities_will_not_be_added_together() {
11331 let mut f = Fixture::new();
11332 f.run(&[b"ZADD", b"z", b"inf", b"m"]);
11333 let nan = "-ERR resulting score is not a number (NaN)\r\n";
11334 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
11335 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
11336 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
11337 assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
11339 }
11340
11341 #[test]
11342 fn zadd_says_its_mistakes_the_way_redis_says_them() {
11343 let mut f = Fixture::new();
11344 assert_eq!(
11347 f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
11348 "-ERR syntax error\r\n"
11349 );
11350 assert_eq!(
11351 f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
11352 "-ERR XX and NX options at the same time are not compatible\r\n"
11353 );
11354 let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
11355 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
11356 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
11357 assert_eq!(
11358 f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
11359 "-ERR INCR option supports a single increment-element pair\r\n"
11360 );
11361 assert_eq!(
11363 f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
11364 "-ERR syntax error\r\n"
11365 );
11366 assert_eq!(
11368 f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
11369 "-ERR value is not a valid float\r\n"
11370 );
11371 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11372 }
11373
11374 #[test]
11375 fn a_rank_says_where_a_member_sits_from_either_end() {
11376 let mut f = Fixture::new();
11377 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11378 assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
11379 assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
11380 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
11381 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
11382 assert_eq!(
11384 f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
11385 "*2\r\n:1\r\n$1\r\n2\r\n"
11386 );
11387 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
11388 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
11389 assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
11390 assert_eq!(
11393 f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
11394 "-ERR syntax error\r\n"
11395 );
11396 assert_eq!(
11397 f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
11398 "-ERR wrong number of arguments for 'zrevrank' command\r\n"
11399 );
11400 }
11401
11402 #[test]
11403 fn the_two_counts_read_their_two_kinds_of_bound() {
11404 let mut f = Fixture::new();
11405 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11406 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
11407 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
11408 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
11409 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
11410 assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
11411 assert_eq!(
11412 f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
11413 "-ERR min or max is not a float\r\n"
11414 );
11415
11416 f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
11417 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
11418 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
11419 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
11420 assert_eq!(
11423 f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
11424 "-ERR min or max not valid string range item\r\n"
11425 );
11426 }
11427
11428 #[test]
11434 fn one_range_command_selects_by_rank_or_score_or_name() {
11435 let mut f = Fixture::new();
11436 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11437 assert_eq!(
11438 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11439 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11440 );
11441 assert_eq!(
11442 f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
11443 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11444 );
11445 assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
11446 assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
11447 assert_eq!(
11450 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
11451 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11452 );
11453 assert_eq!(
11454 f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
11455 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11456 );
11457 assert_eq!(
11460 f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
11461 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11462 );
11463 assert_eq!(
11464 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
11465 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11466 );
11467 assert_eq!(
11468 f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
11469 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11470 );
11471 }
11472
11473 #[test]
11476 fn the_older_range_spellings_name_their_high_end_first() {
11477 let mut f = Fixture::new();
11478 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11479 assert_eq!(
11480 f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
11481 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11482 );
11483 assert_eq!(
11484 f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
11485 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11486 );
11487 assert_eq!(
11488 f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
11489 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11490 );
11491 assert_eq!(
11492 f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
11493 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11494 );
11495 assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
11499 assert_eq!(
11500 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
11501 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
11502 );
11503 assert_eq!(
11504 f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
11505 "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
11506 );
11507 for cmd in [
11510 &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
11511 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
11512 &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
11513 ] {
11514 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
11515 }
11516 }
11517
11518 #[test]
11521 fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
11522 let mut f = Fixture::new();
11523 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11524 assert_eq!(
11525 f.run(&[
11526 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
11527 ]),
11528 "*1\r\n$1\r\nb\r\n"
11529 );
11530 assert_eq!(
11532 f.run(&[
11533 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
11534 ]),
11535 "*0\r\n"
11536 );
11537 assert_eq!(
11538 f.run(&[
11539 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
11540 ]),
11541 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11542 );
11543 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";
11545 assert_eq!(
11546 f.run(&[
11547 b"ZRANGEBYSCORE",
11548 b"z",
11549 b"1",
11550 b"3",
11551 b"WITHSCORES",
11552 b"LIMIT",
11553 b"0",
11554 b"2"
11555 ]),
11556 both
11557 );
11558 assert_eq!(
11559 f.run(&[
11560 b"ZRANGEBYSCORE",
11561 b"z",
11562 b"1",
11563 b"3",
11564 b"LIMIT",
11565 b"0",
11566 b"2",
11567 b"WITHSCORES"
11568 ]),
11569 both
11570 );
11571 let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
11574 assert_eq!(
11575 f.run(&[
11576 b"ZREVRANGE",
11577 b"z",
11578 b"0",
11579 b"-1",
11580 b"WITHSCORES",
11581 b"LIMIT",
11582 b"0",
11583 b"1"
11584 ]),
11585 needs_by
11586 );
11587 assert_eq!(
11588 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
11589 needs_by
11590 );
11591 let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
11592 assert_eq!(
11593 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
11594 not_bylex
11595 );
11596 assert_eq!(
11597 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
11598 not_bylex
11599 );
11600 for cmd in [
11603 &[
11604 b"ZRANGE".as_slice(),
11605 b"z",
11606 b"0",
11607 b"-1",
11608 b"BYSCORE",
11609 b"BYLEX",
11610 ][..],
11611 &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
11612 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
11613 ] {
11614 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11615 }
11616 assert_eq!(
11617 f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
11618 "-ERR min or max is not a float\r\n"
11619 );
11620 assert_eq!(
11621 f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
11622 "-ERR min or max not valid string range item\r\n"
11623 );
11624 assert_eq!(
11625 f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
11626 "-ERR value is not an integer or out of range\r\n"
11627 );
11628 }
11629
11630 #[test]
11633 fn withscores_nests_on_resp3_and_flattens_on_resp2() {
11634 let mut f = Fixture::new();
11635 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11636 assert_eq!(
11637 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11638 "*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"
11639 );
11640 f.out = Out::new(Proto::Resp3);
11641 assert_eq!(
11642 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11643 "*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"
11644 );
11645 assert_eq!(
11646 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11647 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11648 );
11649 }
11650
11651 #[test]
11653 fn a_range_store_writes_the_window_into_another_key() {
11654 let mut f = Fixture::new();
11655 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11656 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
11657 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
11660 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11661 assert_eq!(
11662 f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
11663 ":2\r\n"
11664 );
11665 assert_eq!(
11666 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11667 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11668 );
11669 assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
11672 assert_eq!(
11673 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11674 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11675 );
11676 assert_eq!(
11679 f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
11680 "-ERR syntax error\r\n"
11681 );
11682 }
11683
11684 #[test]
11687 fn the_three_removals_share_their_window_with_the_reads() {
11688 let mut f = Fixture::new();
11689 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11690 assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
11691 assert_eq!(
11692 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11693 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11694 );
11695 assert_eq!(
11696 f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
11697 ":1\r\n"
11698 );
11699 assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
11700 assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
11702 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11703 assert_eq!(
11704 f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
11705 ":0\r\n"
11706 );
11707 assert_eq!(
11708 f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
11709 "-ERR value is not an integer or out of range\r\n"
11710 );
11711 }
11712
11713 #[test]
11715 fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
11716 let mut f = Fixture::new();
11717 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11718 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11719 assert_eq!(
11720 f.run(&[b"ZUNION", b"2", b"z", b"y"]),
11721 "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
11722 );
11723 assert_eq!(
11726 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
11727 "*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"
11728 );
11729 assert_eq!(
11730 f.run(&[
11731 b"ZUNION",
11732 b"2",
11733 b"z",
11734 b"y",
11735 b"WEIGHTS",
11736 b"2",
11737 b"3",
11738 b"WITHSCORES"
11739 ]),
11740 "*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"
11741 );
11742 assert_eq!(
11743 f.run(&[
11744 b"ZUNION",
11745 b"2",
11746 b"z",
11747 b"y",
11748 b"AGGREGATE",
11749 b"MIN",
11750 b"WITHSCORES"
11751 ]),
11752 "*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"
11753 );
11754 assert_eq!(
11755 f.run(&[
11756 b"ZUNION",
11757 b"2",
11758 b"z",
11759 b"y",
11760 b"AGGREGATE",
11761 b"MAX",
11762 b"WITHSCORES"
11763 ]),
11764 "*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"
11765 );
11766 assert_eq!(
11767 f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
11768 "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
11769 );
11770 assert_eq!(
11771 f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
11772 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
11773 );
11774 assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
11775 f.run(&[b"SADD", b"p", b"a", b"d"]);
11778 assert_eq!(
11779 f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
11780 "*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"
11781 );
11782 for cmd in [
11785 &[
11786 b"ZDIFF".as_slice(),
11787 b"2",
11788 b"z",
11789 b"y",
11790 b"WEIGHTS",
11791 b"1",
11792 b"1",
11793 ][..],
11794 &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
11795 ] {
11796 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11797 }
11798 }
11799
11800 #[test]
11802 fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
11803 let mut f = Fixture::new();
11804 f.run(&[b"ZADD", b"z", b"1", b"a"]);
11805 f.run(&[b"ZADD", b"y", b"2", b"b"]);
11806 assert_eq!(
11808 f.run(&[b"ZUNION", b"0", b"z"]),
11809 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11810 );
11811 assert_eq!(
11812 f.run(&[b"ZUNION", b"-1", b"z"]),
11813 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11814 );
11815 assert_eq!(
11816 f.run(&[b"ZINTERCARD", b"0", b"z"]),
11817 "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
11818 );
11819 assert_eq!(
11822 f.run(&[b"ZUNION", b"3", b"z", b"y"]),
11823 "-ERR syntax error\r\n"
11824 );
11825 assert_eq!(
11826 f.run(&[b"ZUNION", b"x", b"z"]),
11827 "-ERR value is not an integer or out of range\r\n"
11828 );
11829 assert_eq!(
11832 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
11833 "-ERR syntax error\r\n"
11834 );
11835 assert_eq!(
11836 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
11837 "-ERR weight value is not a float\r\n"
11838 );
11839 assert_eq!(
11840 f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
11841 "-ERR syntax error\r\n"
11842 );
11843 }
11844
11845 #[test]
11847 fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
11848 let mut f = Fixture::new();
11849 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11850 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11851 assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
11852 assert_eq!(
11853 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11854 "*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"
11855 );
11856 assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
11857 assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
11858 assert_eq!(
11861 f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
11862 ":0\r\n"
11863 );
11864 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11865 assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
11867 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
11868 for cmd in [
11869 &[
11870 b"ZUNIONSTORE".as_slice(),
11871 b"d",
11872 b"2",
11873 b"z",
11874 b"y",
11875 b"WITHSCORES",
11876 ][..],
11877 &[
11878 b"ZDIFFSTORE",
11879 b"d",
11880 b"2",
11881 b"z",
11882 b"y",
11883 b"WEIGHTS",
11884 b"1",
11885 b"1",
11886 ],
11887 ] {
11888 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11889 }
11890 }
11891
11892 #[test]
11894 fn intercard_counts_and_stops_at_its_limit() {
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 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
11898 assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
11899 assert_eq!(
11901 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
11902 ":2\r\n"
11903 );
11904 assert_eq!(
11905 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
11906 ":1\r\n"
11907 );
11908 let bad = "-ERR LIMIT can't be negative\r\n";
11911 assert_eq!(
11912 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
11913 bad
11914 );
11915 assert_eq!(
11916 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
11917 bad
11918 );
11919 for cmd in [
11920 &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
11921 &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
11922 &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
11923 ] {
11924 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11925 }
11926 }
11927
11928 #[test]
11930 fn a_draw_answers_one_member_or_an_array_of_them() {
11931 let mut f = Fixture::new();
11932 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11933 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
11936 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
11937 assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
11938 assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
11939 let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
11942 assert!(all.starts_with("*3\r\n"), "{all}");
11943 for m in ["a", "b", "c"] {
11944 assert!(all.contains(m), "{all}");
11945 }
11946 assert!(
11949 f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
11950 "five draws with replacement"
11951 );
11952 assert!(
11953 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
11954 .starts_with("*4\r\n"),
11955 "two pairs, flat on RESP2"
11956 );
11957 f.out = Out::new(Proto::Resp3);
11958 let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
11959 assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
11960 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
11961 f.out = Out::new(Proto::Resp2);
11962 assert_eq!(
11963 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
11964 "-ERR syntax error\r\n"
11965 );
11966 assert_eq!(
11967 f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
11968 "-ERR value is not an integer or out of range\r\n"
11969 );
11970 }
11971
11972 #[test]
11974 fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
11975 let mut f = Fixture::new();
11976 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11977 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";
11978 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11979 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
11980 assert_eq!(
11981 f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
11982 "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11983 );
11984 assert_eq!(
11985 f.run(&[b"ZSCAN", b"nokey", b"0"]),
11986 "*2\r\n$1\r\n0\r\n*0\r\n"
11987 );
11988 f.out = Out::new(Proto::Resp3);
11991 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11992 f.out = Out::new(Proto::Resp2);
11993 assert_eq!(
11994 f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
11995 "-ERR NOVALUES option can only be used in HSCAN\r\n"
11996 );
11997 assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
11998 assert_eq!(
11999 f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
12000 "-ERR syntax error\r\n"
12001 );
12002 }
12003
12004 #[test]
12006 fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
12007 let mut f = Fixture::new();
12008 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12009 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
12011 assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
12012 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12013 assert_eq!(
12015 f.run(&[b"ZPOPMIN", b"z", b"2"]),
12016 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
12017 );
12018 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
12021 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
12022 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
12023 assert_eq!(
12025 f.run(&[b"ZPOPMIN", b"z", b"9"]),
12026 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
12027 );
12028 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12029
12030 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
12031 f.out = Out::new(Proto::Resp3);
12032 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
12033 assert_eq!(
12034 f.run(&[b"ZPOPMIN", b"z", b"1"]),
12035 "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
12036 );
12037 f.out = Out::new(Proto::Resp2);
12038 let bad = "-ERR value is out of range, must be positive\r\n";
12041 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
12042 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
12043 assert_eq!(
12044 f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
12045 "-ERR syntax error\r\n"
12046 );
12047 }
12048
12049 #[test]
12051 fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
12052 let mut f = Fixture::new();
12053 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12054 assert_eq!(
12055 f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
12056 "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
12057 );
12058 assert_eq!(
12061 f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
12062 "*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"
12063 );
12064 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
12066 f.out = Out::new(Proto::Resp3);
12067 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
12068 f.out = Out::new(Proto::Resp2);
12069 let numkeys = "-ERR numkeys should be greater than 0\r\n";
12070 for bad in [
12071 &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
12072 &[b"ZMPOP", b"-1", b"z", b"MIN"],
12073 &[b"ZMPOP", b"x", b"z", b"MIN"],
12074 ] {
12075 assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
12076 }
12077 let count = "-ERR count should be greater than 0\r\n";
12078 for bad in [
12079 &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
12080 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
12081 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
12082 ] {
12083 assert_eq!(f.run(bad), count, "{:?}", bad[5]);
12084 }
12085 let syntax = "-ERR syntax error\r\n";
12086 for bad in [
12087 &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
12090 &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
12091 &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
12092 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
12093 ] {
12094 assert_eq!(f.run(bad), syntax, "{bad:?}");
12095 }
12096 }
12097
12098 #[test]
12101 fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
12102 let mut f = Fixture::new();
12103 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12104 assert_eq!(
12105 f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
12106 (
12107 Flow::Continue,
12108 "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
12109 )
12110 );
12111 assert_eq!(
12112 f.run(&[b"BZPOPMAX", b"z", b"0"]),
12113 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
12114 );
12115 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12116 assert_eq!(
12117 f.run(&[
12118 b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
12119 ]),
12120 "*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"
12121 );
12122 f.out = Out::new(Proto::Resp3);
12123 assert_eq!(
12124 f.run(&[b"BZPOPMIN", b"z", b"0"]),
12125 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
12126 );
12127 f.out = Out::new(Proto::Resp2);
12128 assert_eq!(
12130 f.flow(&[b"BZPOPMIN", b"z", b"0"]),
12131 (Flow::Block, String::new())
12132 );
12133 assert_eq!(
12134 f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
12135 (Flow::Block, String::new())
12136 );
12137 assert_eq!(
12140 f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
12141 "-ERR timeout is not a float or out of range\r\n"
12142 );
12143 assert_eq!(
12144 f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
12145 "-ERR numkeys should be greater than 0\r\n"
12146 );
12147 assert_eq!(
12148 f.run(&[b"BZPOPMIN", b"z", b"-1"]),
12149 "-ERR timeout is negative\r\n"
12150 );
12151 }
12152
12153 #[test]
12157 fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
12158 let mut f = Fixture::new();
12159 assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
12160 assert_eq!(f.server.parked(), 1);
12161 f.run(&[b"SET", b"z", b"v"]);
12164 let mut out = Out::new(Proto::Resp2);
12165 assert!(!f.server.serve_waiter(7, 0, &mut out));
12166 assert!(out.as_slice().is_empty());
12167 f.run(&[b"DEL", b"z"]);
12168 f.run(&[b"ZADD", b"z", b"5", b"m"]);
12169 assert!(f.server.serve_waiter(7, 0, &mut out));
12170 assert_eq!(
12171 core::str::from_utf8(out.as_slice()).expect("ascii"),
12172 "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
12173 );
12174 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12177 }
12178
12179 #[test]
12180 fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
12181 let mut f = Fixture::new();
12182 f.run(&[b"SET", b"s", b"v"]);
12183 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12184 for cmd in [
12185 &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
12186 &[b"ZINCRBY", b"s", b"1", b"a"],
12187 &[b"ZCARD", b"s"],
12188 &[b"ZSCORE", b"s", b"a"],
12189 &[b"ZMSCORE", b"s", b"a"],
12190 &[b"ZREM", b"s", b"a"],
12191 &[b"ZRANK", b"s", b"a"],
12192 &[b"ZREVRANK", b"s", b"a"],
12193 &[b"ZCOUNT", b"s", b"1", b"2"],
12194 &[b"ZLEXCOUNT", b"s", b"-", b"+"],
12195 &[b"ZRANGE", b"s", b"0", b"-1"],
12196 &[b"ZREVRANGE", b"s", b"0", b"-1"],
12197 &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
12198 &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
12199 &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
12200 &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
12201 &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
12202 &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
12203 &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
12204 &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
12205 &[b"ZUNION", b"1", b"s"],
12206 &[b"ZINTER", b"1", b"s"],
12207 &[b"ZDIFF", b"1", b"s"],
12208 &[b"ZUNIONSTORE", b"d", b"1", b"s"],
12209 &[b"ZINTERSTORE", b"d", b"1", b"s"],
12210 &[b"ZDIFFSTORE", b"d", b"1", b"s"],
12211 &[b"ZINTERCARD", b"1", b"s"],
12212 &[b"ZRANDMEMBER", b"s"],
12213 &[b"ZSCAN", b"s", b"0"],
12214 &[b"ZPOPMIN", b"s"],
12215 &[b"ZPOPMAX", b"s", b"2"],
12216 &[b"ZMPOP", b"1", b"s", b"MIN"],
12217 &[b"BZPOPMIN", b"s", b"0"],
12218 &[b"BZPOPMAX", b"s", b"0"],
12219 &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
12220 ] {
12221 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12222 }
12223 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
12224 }
12225
12226 #[cfg_attr(miri, ignore = "the volume is the claim")]
12231 #[test]
12232 fn churning_sorted_sets_does_not_grow_the_server() {
12233 let mut f = Fixture::new();
12234 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
12235 let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
12236 let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
12237 for i in 0..200 {
12238 args.push(&scores[i]);
12239 args.push(&members[i]);
12240 }
12241
12242 f.run(&args);
12243 f.run(&[b"DEL", b"z"]);
12244 f.server.compact_step();
12245 let after_first = f.server.memory_bytes();
12246
12247 for _ in 0..200 {
12248 f.run(&args);
12249 f.run(&[b"DEL", b"z"]);
12250 f.server.compact_step();
12251 }
12252 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
12253 assert!(
12254 f.server.memory_bytes() <= after_first * 2,
12255 "held {} after two hundred passes against {after_first} after one",
12256 f.server.memory_bytes()
12257 );
12258 }
12259
12260 fn sicily(f: &mut Fixture) {
12268 f.run(&[
12269 b"GEOADD",
12270 b"Sicily",
12271 b"13.361389",
12272 b"38.115556",
12273 b"Palermo",
12274 b"15.087269",
12275 b"37.502669",
12276 b"Catania",
12277 ]);
12278 f.run(&[
12279 b"GEOADD",
12280 b"Sicily",
12281 b"13.583333",
12282 b"37.316667",
12283 b"Agrigento",
12284 ]);
12285 }
12286
12287 #[test]
12288 fn places_go_in_as_scores_and_come_back_as_positions() {
12289 let mut f = Fixture::new();
12290 assert_eq!(
12291 f.run(&[
12292 b"GEOADD",
12293 b"Sicily",
12294 b"13.361389",
12295 b"38.115556",
12296 b"Palermo",
12297 b"15.087269",
12298 b"37.502669",
12299 b"Catania"
12300 ]),
12301 ":2\r\n"
12302 );
12303 assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
12307 assert_eq!(
12308 f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
12309 "$16\r\n3479099956230698\r\n"
12310 );
12311 assert_eq!(
12312 f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
12313 "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
12314 );
12315 assert_eq!(
12316 f.run(&[
12317 b"GEOHASH",
12318 b"Sicily",
12319 b"Palermo",
12320 b"Catania",
12321 b"NonExisting"
12322 ]),
12323 "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
12324 );
12325 assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
12329 assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
12330 }
12331
12332 #[test]
12333 fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
12334 let mut f = Fixture::new();
12335 sicily(&mut f);
12336 assert_eq!(
12337 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
12338 "$11\r\n166274.1516\r\n"
12339 );
12340 assert_eq!(
12341 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
12342 "$8\r\n166.2742\r\n"
12343 );
12344 assert_eq!(
12345 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
12346 "$8\r\n103.3182\r\n"
12347 );
12348 assert_eq!(
12352 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
12353 "$-1\r\n"
12354 );
12355 assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
12356 assert_eq!(
12357 f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
12358 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
12359 );
12360 assert_eq!(
12361 f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
12362 "-ERR syntax error\r\n"
12363 );
12364 }
12365
12366 #[test]
12367 fn a_search_finds_what_is_inside_it_nearest_first() {
12368 let mut f = Fixture::new();
12369 sicily(&mut f);
12370 let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
12371 assert_eq!(
12372 f.run(&[
12373 b"GEOSEARCH",
12374 b"Sicily",
12375 b"FROMLONLAT",
12376 b"15",
12377 b"37",
12378 b"BYRADIUS",
12379 b"200",
12380 b"km",
12381 b"ASC"
12382 ]),
12383 all
12384 );
12385 assert_eq!(
12388 f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
12389 all
12390 );
12391 assert_eq!(
12392 f.run(&[
12393 b"GEORADIUS_RO",
12394 b"Sicily",
12395 b"15",
12396 b"37",
12397 b"200",
12398 b"km",
12399 b"ASC"
12400 ]),
12401 all
12402 );
12403 assert_eq!(
12406 f.run(&[
12407 b"GEORADIUS",
12408 b"Sicily",
12409 b"15",
12410 b"37",
12411 b"200",
12412 b"km",
12413 b"DESC",
12414 b"COUNT",
12415 b"1"
12416 ]),
12417 "*1\r\n$7\r\nPalermo\r\n"
12418 );
12419 assert_eq!(
12420 f.run(&[
12421 b"GEORADIUS",
12422 b"Sicily",
12423 b"15",
12424 b"37",
12425 b"200",
12426 b"km",
12427 b"COUNT",
12428 b"1"
12429 ]),
12430 "*1\r\n$7\r\nCatania\r\n"
12431 );
12432 let empty = "*0\r\n";
12435 assert_eq!(
12436 f.run(&[
12437 b"GEOSEARCH",
12438 b"Sicily",
12439 b"FROMLONLAT",
12440 b"15",
12441 b"37",
12442 b"BYRADIUS",
12443 b"1",
12444 b"km"
12445 ]),
12446 empty
12447 );
12448 assert_eq!(
12449 f.run(&[
12450 b"GEOSEARCH",
12451 b"nokey",
12452 b"FROMLONLAT",
12453 b"15",
12454 b"37",
12455 b"BYRADIUS",
12456 b"1",
12457 b"km"
12458 ]),
12459 empty
12460 );
12461 assert_eq!(
12462 f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
12463 empty
12464 );
12465 }
12466
12467 #[test]
12468 fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
12469 let mut f = Fixture::new();
12470 sicily(&mut f);
12471 assert_eq!(
12472 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
12473 "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12474 );
12475 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";
12478 assert_eq!(
12479 f.run(&[
12480 b"GEORADIUSBYMEMBER_RO",
12481 b"Sicily",
12482 b"Agrigento",
12483 b"100",
12484 b"km",
12485 b"WITHDIST"
12486 ]),
12487 with_dist
12488 );
12489 assert_eq!(
12490 f.run(&[
12491 b"GEOSEARCH",
12492 b"Sicily",
12493 b"FROMMEMBER",
12494 b"Agrigento",
12495 b"BYRADIUS",
12496 b"100",
12497 b"km",
12498 b"ASC",
12499 b"WITHDIST"
12500 ]),
12501 with_dist
12502 );
12503 assert_eq!(
12504 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
12505 "-ERR could not decode requested zset member\r\n"
12506 );
12507 }
12508
12509 #[test]
12510 fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
12511 let mut f = Fixture::new();
12512 sicily(&mut f);
12513 assert_eq!(
12517 f.run(&[
12518 b"GEOSEARCH",
12519 b"Sicily",
12520 b"FROMLONLAT",
12521 b"15",
12522 b"37",
12523 b"BYBOX",
12524 b"400",
12525 b"400",
12526 b"km",
12527 b"ASC",
12528 b"WITHCOORD",
12529 b"WITHDIST",
12530 b"WITHHASH"
12531 ]),
12532 "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
12533 $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
12534 *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
12535 $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
12536 *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
12537 $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
12538 );
12539 }
12540
12541 #[test]
12542 fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
12543 let mut f = Fixture::new();
12544 sicily(&mut f);
12545 let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
12546 $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
12547 $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
12548 assert_eq!(
12549 f.run(&[
12550 b"GEOSEARCHSTORE",
12551 b"dst",
12552 b"Sicily",
12553 b"FROMLONLAT",
12554 b"15",
12555 b"37",
12556 b"BYRADIUS",
12557 b"200",
12558 b"km",
12559 b"ASC"
12560 ]),
12561 ":3\r\n"
12562 );
12563 assert_eq!(
12564 f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
12565 hashes
12566 );
12567 assert_eq!(
12570 f.run(&[
12571 b"GEORADIUS",
12572 b"Sicily",
12573 b"15",
12574 b"37",
12575 b"200",
12576 b"km",
12577 b"STORE",
12578 b"dst3"
12579 ]),
12580 ":3\r\n"
12581 );
12582 assert_eq!(
12583 f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
12584 hashes
12585 );
12586 assert_eq!(
12595 f.run(&[
12596 b"GEOSEARCHSTORE",
12597 b"dst2",
12598 b"Sicily",
12599 b"FROMLONLAT",
12600 b"15",
12601 b"37",
12602 b"BYRADIUS",
12603 b"200",
12604 b"km",
12605 b"ASC",
12606 b"STOREDIST"
12607 ]),
12608 ":3\r\n"
12609 );
12610 for (member, want) in [
12611 ("Catania", 56.441_257_870_158_19),
12612 ("Agrigento", 130.423_487_067_147_14),
12613 ("Palermo", 190.442_429_847_757_92),
12614 ] {
12615 let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
12616 let got: f64 = reply
12617 .trim_start_matches(|c: char| c != '\n')
12618 .trim()
12619 .parse()
12620 .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
12621 assert!(
12622 (got - want).abs() < 1e-9,
12623 "{member} scored {got} not {want}"
12624 );
12625 }
12626 assert_eq!(
12629 f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
12630 "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12631 );
12632 assert_eq!(
12636 f.run(&[
12637 b"GEOSEARCHSTORE",
12638 b"dst",
12639 b"nokey",
12640 b"FROMLONLAT",
12641 b"15",
12642 b"37",
12643 b"BYRADIUS",
12644 b"200",
12645 b"km"
12646 ]),
12647 ":0\r\n"
12648 );
12649 assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
12650 }
12651
12652 #[test]
12653 fn the_gates_on_geoadd_are_the_ones_zadd_has() {
12654 let mut f = Fixture::new();
12655 sicily(&mut f);
12656 assert_eq!(
12659 f.run(&[
12660 b"GEOADD",
12661 b"Sicily",
12662 b"XX",
12663 b"CH",
12664 b"13.361389",
12665 b"38.115556",
12666 b"Palermo"
12667 ]),
12668 ":0\r\n"
12669 );
12670 assert_eq!(
12671 f.run(&[
12672 b"GEOADD",
12673 b"Sicily",
12674 b"NX",
12675 b"13.361389",
12676 b"38.9",
12677 b"Palermo"
12678 ]),
12679 ":0\r\n"
12680 );
12681 assert_eq!(
12682 f.run(&[
12683 b"GEOADD",
12684 b"Sicily",
12685 b"CH",
12686 b"13.361389",
12687 b"38.9",
12688 b"Palermo"
12689 ]),
12690 ":1\r\n"
12691 );
12692 assert_eq!(
12695 f.run(&[
12696 b"GEOADD",
12697 b"new",
12698 b"13.361389",
12699 b"38.115556",
12700 b"here",
12701 b"181",
12702 b"38",
12703 b"there"
12704 ]),
12705 "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
12706 );
12707 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
12708 assert_eq!(
12709 f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
12710 "-ERR value is not a valid float\r\n"
12711 );
12712 assert_eq!(
12715 f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
12716 "-ERR syntax error\r\n"
12717 );
12718 assert_eq!(
12719 f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
12720 "-ERR syntax error\r\n"
12721 );
12722 assert_eq!(
12723 f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
12724 "-ERR syntax error\r\n"
12725 );
12726 assert_eq!(
12727 f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
12728 "-ERR wrong number of arguments for 'geoadd' command\r\n"
12729 );
12730 }
12731
12732 #[test]
12735 fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
12736 let mut f = Fixture::new();
12737 sicily(&mut f);
12738 let cases: &[(&[&[u8]], &str)] = &[
12739 (
12740 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
12741 "-ERR need numeric radius\r\n",
12742 ),
12743 (
12744 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
12745 "-ERR radius cannot be negative\r\n",
12746 ),
12747 (
12748 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
12749 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
12750 ),
12751 (
12752 &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
12753 "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
12754 ),
12755 (
12756 &[
12757 b"GEOSEARCH",
12758 b"Sicily",
12759 b"FROMLONLAT",
12760 b"15",
12761 b"37",
12762 b"BYBOX",
12763 b"x",
12764 b"1",
12765 b"km",
12766 ],
12767 "-ERR need numeric width\r\n",
12768 ),
12769 (
12770 &[
12771 b"GEOSEARCH",
12772 b"Sicily",
12773 b"FROMLONLAT",
12774 b"15",
12775 b"37",
12776 b"BYBOX",
12777 b"1",
12778 b"y",
12779 b"km",
12780 ],
12781 "-ERR need numeric height\r\n",
12782 ),
12783 (
12784 &[
12785 b"GEOSEARCH",
12786 b"Sicily",
12787 b"FROMLONLAT",
12788 b"15",
12789 b"37",
12790 b"BYBOX",
12791 b"-1",
12792 b"1",
12793 b"km",
12794 ],
12795 "-ERR height or width cannot be negative\r\n",
12796 ),
12797 (
12798 &[
12799 b"GEOSEARCH",
12800 b"Sicily",
12801 b"FROMLONLAT",
12802 b"15",
12803 b"37",
12804 b"BYRADIUS",
12805 b"1",
12806 b"km",
12807 b"ANY",
12808 ],
12809 "-ERR the ANY argument requires COUNT argument\r\n",
12810 ),
12811 (
12812 &[
12813 b"GEOSEARCH",
12814 b"Sicily",
12815 b"FROMLONLAT",
12816 b"15",
12817 b"37",
12818 b"BYRADIUS",
12819 b"1",
12820 b"km",
12821 b"COUNT",
12822 b"0",
12823 ],
12824 "-ERR COUNT must be > 0\r\n",
12825 ),
12826 (
12827 &[
12828 b"GEOSEARCH",
12829 b"Sicily",
12830 b"BYRADIUS",
12831 b"1",
12832 b"km",
12833 b"BYBOX",
12834 b"1",
12835 b"1",
12836 b"km",
12837 ],
12838 "-ERR syntax error\r\n",
12839 ),
12840 (
12841 &[
12842 b"GEOSEARCH",
12843 b"Sicily",
12844 b"FROMMEMBER",
12845 b"Palermo",
12846 b"FROMLONLAT",
12847 b"1",
12848 b"2",
12849 b"BYRADIUS",
12850 b"1",
12851 b"km",
12852 ],
12853 "-ERR syntax error\r\n",
12854 ),
12855 (
12858 &[
12859 b"geosearch",
12860 b"Sicily",
12861 b"BYRADIUS",
12862 b"1",
12863 b"km",
12864 b"ASC",
12865 b"WITHDIST",
12866 ],
12867 "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
12868 ),
12869 (
12870 &[
12871 b"GEOSEARCH",
12872 b"Sicily",
12873 b"FROMLONLAT",
12874 b"15",
12875 b"37",
12876 b"ASC",
12877 b"WITHDIST",
12878 ],
12879 "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
12880 ),
12881 (
12884 &[
12885 b"GEOSEARCHSTORE",
12886 b"d",
12887 b"Sicily",
12888 b"FROMLONLAT",
12889 b"15",
12890 b"37",
12891 b"BYRADIUS",
12892 b"1",
12893 b"km",
12894 b"WITHCOORD",
12895 ],
12896 "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12897 ),
12898 (
12899 &[
12900 b"GEORADIUS",
12901 b"Sicily",
12902 b"15",
12903 b"37",
12904 b"1",
12905 b"km",
12906 b"WITHDIST",
12907 b"STORE",
12908 b"d",
12909 ],
12910 "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12911 ),
12912 (
12915 &[
12916 b"GEORADIUS_RO",
12917 b"Sicily",
12918 b"15",
12919 b"37",
12920 b"1",
12921 b"km",
12922 b"STORE",
12923 b"d",
12924 ],
12925 "-ERR syntax error\r\n",
12926 ),
12927 (
12928 &[
12929 b"GEOSEARCH",
12930 b"Sicily",
12931 b"FROMLONLAT",
12932 b"15",
12933 b"37",
12934 b"BYRADIUS",
12935 b"1",
12936 b"km",
12937 b"STOREDIST",
12938 ],
12939 "-ERR syntax error\r\n",
12940 ),
12941 ];
12942 for (parts, want) in cases {
12943 assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
12944 }
12945 }
12946
12947 #[test]
12950 fn every_geo_command_says_wrongtype() {
12951 let mut f = Fixture::new();
12952 f.run(&[b"SET", b"s", b"v"]);
12953 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12954 let cases: &[&[&[u8]]] = &[
12955 &[b"GEOADD", b"s", b"13", b"38", b"m"],
12956 &[b"GEOPOS", b"s", b"m"],
12957 &[b"GEOHASH", b"s", b"m"],
12958 &[b"GEODIST", b"s", b"a", b"b"],
12959 &[
12960 b"GEOSEARCH",
12961 b"s",
12962 b"FROMLONLAT",
12963 b"15",
12964 b"37",
12965 b"BYRADIUS",
12966 b"1",
12967 b"km",
12968 ],
12969 &[
12970 b"GEOSEARCHSTORE",
12971 b"d",
12972 b"s",
12973 b"FROMLONLAT",
12974 b"15",
12975 b"37",
12976 b"BYRADIUS",
12977 b"1",
12978 b"km",
12979 ],
12980 &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
12981 &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
12982 &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
12983 &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
12984 ];
12985 for case in cases {
12986 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
12987 }
12988 assert_eq!(
12991 f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
12992 wrong
12993 );
12994 }
12995
12996 #[test]
12999 fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
13000 let mut f = Fixture::new();
13001 assert_eq!(
13004 f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
13005 ":3\r\n"
13006 );
13007 assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
13008 assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
13009 assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
13010 assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
13012 assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
13013 assert_eq!(
13014 f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
13015 "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
13016 );
13017 assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
13019 assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
13020 }
13021
13022 #[test]
13025 fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
13026 let mut f = Fixture::new();
13027 assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
13028 assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
13029 f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
13030 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13031 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13032 assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
13034 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13035 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
13036
13037 f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
13041 assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
13042 assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
13043 assert_eq!(
13046 f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
13047 "-ERR array index overflow\r\n"
13048 );
13049 assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
13050 }
13051
13052 #[test]
13055 fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
13056 let mut f = Fixture::new();
13057 f.run(&[b"ARSET", b"a", b"1", b"x"]);
13058 assert_eq!(
13059 f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
13060 "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
13061 );
13062 assert_eq!(
13065 f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
13066 "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
13067 );
13068 assert_eq!(
13070 f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
13071 "*2\r\n$-1\r\n$-1\r\n"
13072 );
13073 assert_eq!(
13077 f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
13078 "-ERR range exceeds maximum of 1000000 items\r\n"
13079 );
13080 }
13081
13082 #[test]
13085 fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
13086 let mut f = Fixture::new();
13087 assert_eq!(
13088 f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
13089 "-ERR invalid array index\r\n"
13090 );
13091 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13092 f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
13093 assert_eq!(
13094 f.run(&[b"ARDEL", b"a", b"0", b"01"]),
13095 "-ERR invalid array index\r\n"
13096 );
13097 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
13098 assert_eq!(
13101 f.run(&[b"ARGET", b"a", b"-1"]),
13102 "-ERR invalid array index\r\n"
13103 );
13104 assert_eq!(
13107 f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
13108 "-ERR wrong number of arguments for 'armset' command\r\n"
13109 );
13110 assert_eq!(
13111 f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
13112 "-ERR wrong number of arguments for 'ardelrange' command\r\n"
13113 );
13114 }
13115
13116 #[test]
13117 fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
13118 let mut f = Fixture::new();
13119 f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
13120 assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
13121 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13122 assert_eq!(
13125 f.run(&[
13126 b"ARDELRANGE",
13127 b"a",
13128 b"100",
13129 b"200",
13130 b"0",
13131 b"18446744073709551614"
13132 ]),
13133 ":2\r\n"
13134 );
13135 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13136 assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
13137 assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
13138 }
13139
13140 #[test]
13143 fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
13144 let mut f = Fixture::new();
13145 let long = vec![b'v'; 200];
13146 f.run(&[
13147 b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
13148 b"short", b"5", &long, b"6", b"-0",
13149 ]);
13150 assert_eq!(
13154 f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
13155 format!(
13156 "*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",
13157 String::from_utf8_lossy(&long)
13158 )
13159 );
13160 }
13161
13162 #[test]
13163 fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
13164 let mut f = Fixture::new();
13165 f.run(&[b"ARSET", b"a", b"0", b"x"]);
13166 assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
13167 assert_eq!(
13168 f.run(&[b"OBJECT", b"ENCODING", b"a"]),
13169 "$12\r\nsliced-array\r\n"
13170 );
13171 assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
13173 assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
13174 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
13175 assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
13176 assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
13177 assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
13178 }
13179
13180 #[test]
13181 fn every_array_command_refuses_a_key_holding_something_else() {
13182 let mut f = Fixture::new();
13183 f.run(&[b"SET", b"s", b"v"]);
13184 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13185 for cmd in [
13186 &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
13187 &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
13188 &[b"ARGET".as_ref(), b"s", b"0"][..],
13189 &[b"ARMGET".as_ref(), b"s", b"0"][..],
13190 &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
13191 &[b"ARLEN".as_ref(), b"s"][..],
13192 &[b"ARCOUNT".as_ref(), b"s"][..],
13193 &[b"ARDEL".as_ref(), b"s", b"0"][..],
13194 &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
13195 &[b"ARINSERT".as_ref(), b"s", b"x"][..],
13196 &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
13197 &[b"ARNEXT".as_ref(), b"s"][..],
13198 &[b"ARSEEK".as_ref(), b"s", b"1"][..],
13199 &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
13200 &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
13201 &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
13202 &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
13203 &[b"ARINFO".as_ref(), b"s"][..],
13204 ] {
13205 assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
13206 }
13207 }
13208
13209 #[test]
13213 fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
13214 let mut f = Fixture::new();
13215 f.run(&[b"SET", b"s", b"v"]);
13216 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13217 let bad = "-ERR invalid array index\r\n";
13218 assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
13219 assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
13220 assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
13221 assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
13222 assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
13223 assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
13224 f.run(&[b"ARSET", b"a", b"0", b"x"]);
13226 assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
13227 assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
13228 }
13229
13230 #[test]
13231 fn an_append_follows_a_cursor_the_client_can_move() {
13232 let mut f = Fixture::new();
13233 assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
13234 assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
13235 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
13236 assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
13237 assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
13238
13239 assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
13242 assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
13243 assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
13244 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
13245 assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
13246 assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
13247 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
13248
13249 assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
13252 assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
13253 assert_eq!(
13254 f.run(&[b"ARINSERT", b"a", b"x"]),
13255 "-ERR insert index overflow\r\n"
13256 );
13257 assert_eq!(
13258 f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
13259 "-ERR invalid array index\r\n"
13260 );
13261 }
13262
13263 #[test]
13264 fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
13265 let mut f = Fixture::new();
13266 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
13267 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
13268 assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
13269 assert_eq!(
13270 f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
13271 "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
13272 );
13273 assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
13276 assert_eq!(
13277 f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
13278 "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
13279 );
13280 assert_eq!(
13283 f.run(&[b"ARRING", b"r", b"0", b"x"]),
13284 "-ERR size must be positive\r\n"
13285 );
13286 assert_eq!(
13287 f.run(&[b"ARRING", b"r", b"big", b"x"]),
13288 "-ERR invalid size\r\n"
13289 );
13290 }
13291
13292 #[test]
13293 fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
13294 let mut f = Fixture::new();
13295 assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
13296 f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
13297 assert_eq!(
13298 f.run(&[b"ARLASTITEMS", b"r", b"3"]),
13299 "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
13300 );
13301 assert_eq!(
13302 f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
13303 "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
13304 );
13305 assert_eq!(
13306 f.run(&[b"ARLASTITEMS", b"r", b"99"]),
13307 "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
13308 "more than there is gets what there is"
13309 );
13310 assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
13313 assert_eq!(
13314 f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
13315 "-ERR syntax error\r\n"
13316 );
13317 assert_eq!(
13318 f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
13319 "-ERR invalid COUNT\r\n"
13320 );
13321
13322 f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
13325 assert_eq!(
13326 f.run(&[b"ARLASTITEMS", b"h", b"5"]),
13327 "*2\r\n$-1\r\n$1\r\nz\r\n"
13328 );
13329 }
13330
13331 #[test]
13332 fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
13333 let mut f = Fixture::new();
13334 assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
13335 f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
13336 assert_eq!(
13339 f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
13340 "*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"
13341 );
13342 assert_eq!(
13343 f.run(&[
13344 b"ARSCAN",
13345 b"a",
13346 b"18446744073709551614",
13347 b"0",
13348 b"LIMIT",
13349 b"1"
13350 ]),
13351 "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
13352 );
13353 assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
13354 assert_eq!(
13355 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
13356 "-ERR LIMIT must be positive\r\n"
13357 );
13358 assert_eq!(
13359 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
13360 "-ERR syntax error\r\n"
13361 );
13362 assert_eq!(
13363 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
13364 "-ERR wrong number of arguments for 'arscan' command\r\n"
13365 );
13366 }
13367
13368 #[test]
13369 fn a_grep_answers_the_indexes_whose_elements_match() {
13370 let mut f = Fixture::new();
13371 assert_eq!(
13372 f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
13373 "*0\r\n"
13374 );
13375 f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
13376
13377 assert_eq!(
13380 f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
13381 "*3\r\n:0\r\n:1\r\n:2\r\n"
13382 );
13383 assert_eq!(
13384 f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
13385 "*3\r\n:2\r\n:1\r\n:0\r\n"
13386 );
13387 assert_eq!(
13388 f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
13389 "*2\r\n:1\r\n:2\r\n"
13390 );
13391
13392 assert_eq!(
13395 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
13396 "*1\r\n:0\r\n"
13397 );
13398 assert_eq!(
13399 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
13400 "*2\r\n:0\r\n:3\r\n"
13401 );
13402 assert_eq!(
13403 f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
13404 "*1\r\n:2\r\n"
13405 );
13406 assert_eq!(
13407 f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
13408 "*2\r\n:1\r\n:2\r\n"
13409 );
13410
13411 let both: &[&[u8]] = &[
13414 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
13415 ];
13416 assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
13417 assert_eq!(
13418 f.run(&[
13419 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
13420 ]),
13421 "*0\r\n"
13422 );
13423 assert_eq!(
13424 f.run(&[
13425 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
13426 ]),
13427 "*2\r\n:0\r\n:1\r\n"
13428 );
13429
13430 assert_eq!(
13433 f.run(&[
13434 b"ARGREP",
13435 b"a",
13436 b"-",
13437 b"+",
13438 b"MATCH",
13439 b"a",
13440 b"WITHVALUES",
13441 b"LIMIT",
13442 b"2"
13443 ]),
13444 "*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"
13445 );
13446 assert_eq!(
13447 f.run(&[
13448 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
13449 ]),
13450 "*1\r\n:3\r\n"
13451 );
13452 }
13453
13454 #[test]
13456 fn a_grep_reports_a_broken_command_the_way_redis_does() {
13457 let mut f = Fixture::new();
13458 f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
13459 let syntax = "-ERR syntax error\r\n";
13460
13461 assert_eq!(
13464 f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
13465 "-ERR invalid array index\r\n"
13466 );
13467 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
13468 assert_eq!(
13470 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
13471 syntax
13472 );
13473 assert_eq!(
13474 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
13475 syntax
13476 );
13477 assert_eq!(
13478 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
13479 syntax,
13480 "a command with no predicate in it at all"
13481 );
13482 assert_eq!(
13483 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
13484 "-ERR LIMIT must be positive\r\n"
13485 );
13486 assert_eq!(
13487 f.run(&[
13488 b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
13489 ]),
13490 "-ERR value is not an integer or out of range\r\n"
13491 );
13492 assert_eq!(
13493 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
13494 "-ERR regular expression is empty\r\n"
13495 );
13496 assert_eq!(
13497 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
13498 "-ERR invalid regular expression: Missing ')'\r\n"
13499 );
13500 assert_eq!(
13501 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
13502 "-ERR regular expression backreferences are not supported\r\n"
13503 );
13504 let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
13507 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
13508 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
13509 }
13510
13511 #[test]
13512 fn an_op_reduces_a_range_to_one_number() {
13513 let mut f = Fixture::new();
13514 f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
13515 assert_eq!(
13516 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
13517 "$4\r\n-0.5\r\n"
13518 );
13519 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
13520 assert_eq!(
13521 f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
13522 "$3\r\n2.5\r\n"
13523 );
13524 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
13525 assert_eq!(
13526 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
13527 ":1\r\n"
13528 );
13529 f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
13532 assert_eq!(
13533 f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
13534 "$19\r\n0.30000000000000004\r\n"
13535 );
13536 assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
13537 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
13538
13539 f.run(&[b"ARSET", b"w", b"0", b"word"]);
13542 assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
13543 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
13544 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
13545
13546 assert_eq!(
13547 f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
13548 "-ERR unknown operation\r\n"
13549 );
13550 assert_eq!(
13551 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
13552 "-ERR MATCH requires a value argument\r\n"
13553 );
13554 assert_eq!(
13555 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
13556 "-ERR wrong number of arguments for 'arop' command\r\n"
13557 );
13558 }
13559
13560 #[test]
13561 fn the_info_is_a_map_and_a_missing_key_is_an_error() {
13562 let mut f = Fixture::new();
13563 assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
13564 f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
13565 let short = f.run(&[b"ARINFO", b"a"]);
13566 assert!(
13567 short.starts_with("*14\r\n"),
13568 "seven pairs on RESP2: {short}"
13569 );
13570 assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
13571 assert!(
13572 short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
13573 "{short}"
13574 );
13575 assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
13576 let full = f.run(&[b"ARINFO", b"a", b"full"]);
13577 assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
13578 assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
13581 assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
13582 assert!(
13583 full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
13584 "{full}"
13585 );
13586 assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
13587
13588 let mut g = Fixture::new();
13590 g.run(&[b"HELLO", b"3"]);
13591 g.run(&[b"ARINSERT", b"a", b"x"]);
13592 let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
13593 assert!(map.starts_with("%12\r\n"), "{map}");
13594 assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
13595 assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
13596 }
13597
13598 #[test]
13599 fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
13600 let mut f = Fixture::new();
13601 for (score, want) in [
13604 ("3", "3"),
13605 ("3.5", "3.5"),
13606 ("0.3", "0.3"),
13607 ("1e30", "1e+30"),
13608 ("1e19", "1e+19"),
13609 ("1e-7", "1e-7"),
13610 ("0.000001", "0.000001"),
13611 ("4611686018427387904", "4611686018427387904"),
13612 ("-0", "-0"),
13613 ] {
13614 f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
13615 assert_eq!(
13616 f.run(&[b"ZSCORE", b"z", b"m"]),
13617 format!("${}\r\n{want}\r\n", want.len()),
13618 "score {score}"
13619 );
13620 }
13621
13622 let mut g = Fixture::new();
13625 g.run(&[b"HELLO", b"3"]);
13626 g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
13627 assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
13628 assert_eq!(
13633 g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
13634 "$31\r\n1000000000000000000000000000000\r\n"
13635 );
13636 assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
13637 assert_eq!(
13638 g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
13639 "$20\r\n10000000000000000000\r\n"
13640 );
13641 }
13642
13643 #[test]
13646 fn a_node_comes_back_with_the_fields_it_went_in_with() {
13647 let mut f = Fixture::new();
13648 assert_eq!(
13649 f.run(&[
13650 b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
13651 ]),
13652 ":1\r\n"
13653 );
13654 assert_eq!(
13660 f.run(&[b"G.NGET", b"social", b"ada"]),
13661 "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13662 );
13663 assert_eq!(
13666 f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
13667 ":0\r\n"
13668 );
13669 assert_eq!(
13670 f.run(&[b"G.NGET", b"social", b"ada"]),
13671 "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
13672 );
13673 assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
13676 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13677 assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
13678 assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
13679
13680 assert_eq!(
13683 f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
13684 "-ERR syntax error\r\n"
13685 );
13686 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
13687
13688 let mut g = Fixture::new();
13690 g.run(&[b"HELLO", b"3"]);
13691 g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
13692 assert_eq!(
13693 g.run(&[b"G.NGET", b"social", b"ada"]),
13694 "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13695 );
13696 }
13697
13698 #[test]
13699 fn an_edge_creates_the_ends_it_needs() {
13700 let mut f = Fixture::new();
13701 assert_eq!(
13702 f.run(&[
13703 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
13704 ]),
13705 ":1\r\n"
13706 );
13707 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
13709 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13710 assert_eq!(
13711 f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
13712 "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
13713 );
13714 assert_eq!(
13715 f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
13716 "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
13717 );
13718 assert_eq!(
13721 f.run(&[
13722 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
13723 ]),
13724 ":0\r\n"
13725 );
13726 assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
13727 assert_eq!(
13729 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
13730 ":1\r\n"
13731 );
13732 assert_eq!(
13733 f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
13734 ":1\r\n"
13735 );
13736
13737 assert_eq!(
13738 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13739 ":1\r\n"
13740 );
13741 assert_eq!(
13742 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13743 ":0\r\n"
13744 );
13745 assert_eq!(
13748 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
13749 ":0\r\n"
13750 );
13751 assert_eq!(
13752 f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
13753 ":0\r\n"
13754 );
13755 assert_eq!(
13756 f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
13757 ":0\r\n"
13758 );
13759 }
13760
13761 #[test]
13764 fn a_hop_answers_a_cursor_and_a_page() {
13765 let mut f = Fixture::new();
13766 for i in 0..25u32 {
13767 let dst = format!("n{i}");
13768 f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
13769 }
13770 let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
13772 assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
13773
13774 let mut seen = 0;
13775 let mut cursor = String::from("0");
13776 loop {
13777 let page = f.run(&[
13778 b"G.OUT",
13779 b"social",
13780 b"hub",
13781 b"FOLLOWS",
13782 b"COUNT",
13783 b"7",
13784 b"CURSOR",
13785 cursor.as_bytes(),
13786 ]);
13787 let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
13788 cursor = head
13789 .rsplit("\r\n")
13790 .next()
13791 .expect("the cursor line")
13792 .to_string();
13793 seen += rest
13794 .split_once("\r\n")
13795 .expect("the page length")
13796 .0
13797 .parse::<usize>()
13798 .expect("a length");
13799 if cursor == "0" {
13800 break;
13801 }
13802 }
13803 assert_eq!(seen, 25, "every neighbour once across the pages");
13804
13805 assert_eq!(
13808 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
13809 "*2\r\n$1\r\n0\r\n*0\r\n"
13810 );
13811 assert_eq!(
13812 f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
13813 "*2\r\n$1\r\n0\r\n*0\r\n"
13814 );
13815 assert_eq!(
13816 f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
13817 "*2\r\n$1\r\n0\r\n*0\r\n"
13818 );
13819 assert_eq!(
13820 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
13821 "-ERR COUNT must be a positive integer\r\n"
13822 );
13823 assert_eq!(
13824 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
13825 "-ERR syntax error\r\n"
13826 );
13827 }
13828
13829 #[test]
13830 fn a_degree_counts_one_way_or_both() {
13831 let mut f = Fixture::new();
13832 f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
13833 f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
13834 f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
13835 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
13836 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
13837 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
13838 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
13839 assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
13840 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
13841 assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
13842 assert_eq!(
13843 f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
13844 "-ERR syntax error\r\n"
13845 );
13846 }
13847
13848 #[test]
13851 fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
13852 let mut f = Fixture::new();
13853 for (src, dst) in [
13854 ("ada", "grace"),
13855 ("ada", "alan"),
13856 ("grace", "edsger"),
13857 ("alan", "edsger"),
13858 ("edsger", "barbara"),
13859 ] {
13860 f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
13861 }
13862 assert_eq!(
13865 f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
13866 "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
13867 );
13868 assert_eq!(
13869 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
13870 "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
13871 );
13872 let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
13873 assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
13874 assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
13875 assert_eq!(
13877 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
13878 "*1\r\n$5\r\ngrace\r\n"
13879 );
13880 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
13882 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
13883 assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
13884 assert_eq!(
13885 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
13886 "-ERR DEPTH must be a positive integer\r\n"
13887 );
13888 assert_eq!(
13889 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
13890 "-ERR syntax error\r\n"
13891 );
13892 }
13893
13894 #[test]
13897 fn a_path_is_the_shortest_one_and_goes_over_any_label() {
13898 let mut f = Fixture::new();
13899 for i in 0..6u32 {
13902 let src = format!("n{i}");
13903 let dst = format!("n{}", i + 1);
13904 f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
13905 }
13906 assert_eq!(
13907 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13908 "*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"
13909 );
13910 f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
13911 assert_eq!(
13912 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13913 "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
13914 );
13915 assert_eq!(
13918 f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
13919 "*1\r\n$2\r\nn2\r\n"
13920 );
13921 assert_eq!(
13922 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
13923 "*0\r\n"
13924 );
13925 assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
13927 f.run(&[b"G.NADD", b"road", b"island"]);
13930 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
13931 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
13932 assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
13933 assert_eq!(
13934 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
13935 "-ERR syntax error\r\n"
13936 );
13937 }
13938
13939 #[test]
13943 fn the_keyspace_sees_a_graph_key_like_any_other() {
13944 let mut f = Fixture::new();
13945 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
13946 assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
13947 assert_eq!(
13948 f.run(&[b"OBJECT", b"ENCODING", b"social"]),
13949 "$9\r\nadjacency\r\n"
13950 );
13951 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13952 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
13953 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
13954 let held = f.server.memory_bytes();
13958 for i in 0..200u32 {
13959 let dst = format!("n{i}");
13960 f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
13961 }
13962 assert!(
13963 f.server.memory_bytes() > held,
13964 "two hundred edges cost something: {held} then {}",
13965 f.server.memory_bytes()
13966 );
13967 f.run(&[b"DEL", b"big"]);
13968
13969 assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
13972 assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
13973 assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
13974 assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
13975 assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
13976 f.run(&[b"SELECT", b"1"]);
13977 assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
13978
13979 assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
13980 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13981 f.run(&[b"G.NADD", b"g", b"n"]);
13982 assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
13983 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13984 }
13985
13986 #[test]
13989 fn a_graph_cannot_be_copied_or_dumped() {
13990 let mut f = Fixture::new();
13991 f.run(&[b"G.NADD", b"social", b"ada"]);
13992 assert_eq!(
13993 f.run(&[b"COPY", b"social", b"other"]),
13994 "-ERR COPY is not supported for a graph\r\n"
13995 );
13996 assert_eq!(
13997 f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
13998 "-ERR COPY is not supported for a graph\r\n"
13999 );
14000 assert_eq!(
14001 f.run(&[b"DUMP", b"social"]),
14002 "-ERR DUMP is not supported for a graph\r\n"
14003 );
14004 assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
14006 }
14007
14008 #[test]
14011 fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
14012 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14013 let mut f = Fixture::new();
14014 f.run(&[b"G.NADD", b"social", b"ada"]);
14015 assert_eq!(f.run(&[b"GET", b"social"]), wrong);
14016 assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
14017 assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
14018
14019 f.run(&[b"SET", b"str", b"v"]);
14020 for cmd in [
14021 vec![b"G.NADD".as_ref(), b"str", b"n"],
14022 vec![b"G.NGET".as_ref(), b"str", b"n"],
14023 vec![b"G.NDEL".as_ref(), b"str", b"n"],
14024 vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
14025 vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
14026 vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
14027 vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
14028 vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
14029 vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
14030 vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
14031 ] {
14032 assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
14033 }
14034 }
14035
14036 #[test]
14039 fn a_graph_goes_when_its_last_node_does() {
14040 let mut f = Fixture::new();
14041 f.run(&[
14042 b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
14043 ]);
14044 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
14045 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
14047 assert_eq!(
14048 f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
14049 ":0\r\n"
14050 );
14051 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
14052 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
14053
14054 assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
14055 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
14056 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14057 assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
14058
14059 f.run(&[b"G.NADD", b"social", b"first"]);
14062 f.run(&[b"G.NADD", b"social", b"second"]);
14063 f.run(&[b"G.NDEL", b"social", b"first"]);
14064 f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
14065 assert_eq!(
14066 f.run(&[b"G.OUT", b"social", b"third", b"F"]),
14067 "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
14068 );
14069 }
14070
14071 #[test]
14076 fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
14077 let mut f = Fixture::new();
14078 let doc = br#"{"a":1,"b":{"c":true}}"#;
14079 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
14080 assert_eq!(
14083 f.run(&[b"JSON.GET", b"doc"]),
14084 bulk(r#"{"a":1,"b":{"c":true}}"#)
14085 );
14086 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
14087 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
14088 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
14089 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
14092 assert_eq!(
14093 f.run(&[b"JSON.GET", b"doc", b".nope"]),
14094 "-ERR Path does not exist\r\n"
14095 );
14096 assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
14097 assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
14100 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
14101 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
14102 assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
14103 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14104 }
14105
14106 #[test]
14112 fn the_two_json_errors_that_carry_no_prefix() {
14113 let mut f = Fixture::new();
14114 f.run(&[b"SET", b"plain", b"x"]);
14115 let wrong = "-Existing key has wrong Redis type\r\n";
14116 assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
14117 assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
14118 assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
14119 assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
14120 assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
14121
14122 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
14123 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
14127 assert_eq!(
14128 f.run(&[b"JSON.GET", b"doc"]),
14129 bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
14130 );
14131 assert_eq!(
14132 f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
14133 "-Err wrong static path\r\n"
14134 );
14135 }
14136
14137 #[test]
14139 fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
14140 let mut f = Fixture::new();
14141 assert_eq!(
14143 f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
14144 "-ERR new objects must be created at the root\r\n"
14145 );
14146 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14147 assert_eq!(
14150 f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
14151 "-ERR new objects must be created at the root\r\n"
14152 );
14153 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
14154 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
14155
14156 f.run(&[
14157 b"JSON.SET",
14158 b"doc",
14159 b"$",
14160 br#"{"o":{},"arr":[1,2],"s":"x"}"#,
14161 ]);
14162 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
14164 assert_eq!(
14167 f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
14168 "$-1\r\n"
14169 );
14170 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
14171 assert_eq!(
14173 f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
14174 "-ERR array index out of range\r\n"
14175 );
14176 assert_eq!(
14177 f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
14178 "-ERR array index out of range\r\n"
14179 );
14180 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
14181 assert_eq!(
14184 f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
14185 "$-1\r\n"
14186 );
14187 assert_eq!(
14188 f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
14189 "$-1\r\n"
14190 );
14191 assert_eq!(
14192 f.run(&[b"JSON.GET", b"doc"]),
14193 bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
14194 );
14195 assert!(
14199 f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
14200 .starts_with("-this is not the start of a value")
14201 );
14202 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
14203 }
14204
14205 #[test]
14208 fn the_json_commands_that_do_not_answer_text() {
14209 let mut f = Fixture::new();
14210 let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
14211 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14212
14213 assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
14214 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
14215 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
14216 assert_eq!(
14217 f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
14218 format!("*1\r\n{}", bulk("integer"))
14219 );
14220 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
14223 assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
14224
14225 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
14228 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
14229 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
14232 assert_eq!(
14233 f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
14234 "-ERR Path does not exist or not a bool\r\n"
14235 );
14236 assert_eq!(
14237 f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
14238 "-ERR Path does not exist or not a bool\r\n"
14239 );
14240 assert_eq!(
14241 f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
14242 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14243 );
14244
14245 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
14248 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
14249 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
14250 assert_eq!(
14251 f.run(&[b"JSON.GET", b"doc"]),
14252 bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
14253 );
14254
14255 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
14258 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
14259 assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
14264 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
14265 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14266 assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
14267 assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
14268 assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
14269 assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
14270 assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
14271 }
14272
14273 #[test]
14280 fn json_get_lays_out_the_wrapper_it_builds() {
14281 let mut f = Fixture::new();
14282 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
14283
14284 assert_eq!(
14285 f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
14286 bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
14287 );
14288 assert_eq!(
14290 f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
14291 bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
14292 );
14293 let fmt: &[&[u8]] = &[b"INDENT", b" ", b"NEWLINE", b"\n", b"SPACE", b" "];
14294 let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
14295 one.extend_from_slice(fmt);
14296 one.push(b"$.b");
14297 assert_eq!(
14298 f.run(&one),
14299 bulk("[\n [\n 1,\n {\n \"c\": 2\n }\n ]\n]")
14300 );
14301 let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
14302 two.extend_from_slice(fmt);
14303 two.push(b"$.a");
14304 two.push(b"$.nope");
14305 assert_eq!(
14306 f.run(&two),
14307 bulk("{\n \"$.a\": [\n 1\n ],\n \"$.nope\": []\n}")
14308 );
14309 let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
14312 root.push(b".a");
14313 assert_eq!(f.run(&root), bulk("1"));
14314 }
14315
14316 #[test]
14319 fn json_mget_answers_once_per_key_whatever_is_under_them() {
14320 let mut f = Fixture::new();
14321 f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
14322 f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
14323 f.run(&[b"SET", b"plain", b"x"]);
14324 assert_eq!(
14325 f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
14326 format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
14327 );
14328 assert_eq!(
14331 f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
14332 format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
14333 );
14334 assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
14337 }
14338
14339 #[test]
14347 fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
14348 let mut f = Fixture::new();
14349 let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
14350 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14351
14352 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
14353 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
14354 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
14355 assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
14356 assert_eq!(
14357 f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
14358 format!("*2\r\n{}{}", bulk("x"), bulk("y"))
14359 );
14360 assert_eq!(
14363 f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
14364 "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
14365 );
14366
14367 assert_eq!(
14370 f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
14371 "-ERR Path does not exist\r\n"
14372 );
14373 assert_eq!(
14374 f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
14375 "-ERR Path does not exist\r\n"
14376 );
14377 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
14378 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
14381 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
14384
14385 assert_eq!(
14389 f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
14390 "-ERR Path does not exist or not an array\r\n"
14391 );
14392 assert_eq!(
14393 f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
14394 "-ERR Path does not exist or not an object\r\n"
14395 );
14396 assert_eq!(
14397 f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
14398 "-WRONGTYPE wrong type of path value - expected object\r\n"
14399 );
14400 assert_eq!(
14401 f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
14402 "-WRONGTYPE wrong type of path value - expected string\r\n"
14403 );
14404
14405 assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
14408 assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
14409 assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
14410 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
14411 assert_eq!(
14412 f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
14413 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14414 );
14415 assert_eq!(
14417 f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
14418 "-ERR Path does not exist or not an object\r\n"
14419 );
14420 }
14421
14422 #[test]
14428 fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
14429 let mut f = Fixture::new();
14430 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
14431
14432 assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
14433 assert_eq!(
14434 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
14435 "*1\r\n:6\r\n"
14436 );
14437 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
14438
14439 assert_eq!(
14442 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
14443 ":7\r\n"
14444 );
14445 assert_eq!(
14446 f.run(&[b"JSON.GET", b"doc", b".a"]),
14447 bulk("[1,2,3,4,5,0,6]")
14448 );
14449 assert_eq!(
14450 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
14451 ":8\r\n"
14452 );
14453 assert_eq!(
14455 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
14456 "-ERR index out of bounds\r\n"
14457 );
14458 assert_eq!(
14459 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
14460 "-ERR index out of bounds\r\n"
14461 );
14462
14463 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
14466 assert_eq!(
14467 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
14468 ":3\r\n"
14469 );
14470 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
14471 assert_eq!(
14472 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
14473 ":2\r\n"
14474 );
14475 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
14476 assert_eq!(
14477 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
14478 ":0\r\n"
14479 );
14480 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14481
14482 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
14485 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
14486 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
14487 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
14488 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
14489
14490 for call in [
14493 &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
14494 &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
14495 &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
14496 &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
14497 ] {
14498 for path in [&b".n"[..], &b".nope"[..]] {
14499 let args: Vec<&[u8]> = call
14500 .iter()
14501 .map(|a| if *a == b"PATH" { path } else { *a })
14502 .collect();
14503 assert_eq!(
14504 f.run(&args),
14505 "-ERR Path does not exist or not an array\r\n",
14506 "{} {}",
14507 String::from_utf8_lossy(call[0]),
14508 String::from_utf8_lossy(path)
14509 );
14510 }
14511 }
14512
14513 assert_eq!(
14516 f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
14517 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14518 );
14519 assert_eq!(
14520 f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
14521 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14522 );
14523
14524 assert!(
14529 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
14530 .starts_with("-this is not the start of a value")
14531 );
14532 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14533 }
14534
14535 #[test]
14543 fn json_arrinsert_is_all_or_nothing_across_the_matches() {
14544 let mut f = Fixture::new();
14545 let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
14546 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14547 assert_eq!(
14548 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
14549 "-ERR index out of bounds\r\n"
14550 );
14551 assert_eq!(
14552 f.run(&[b"JSON.GET", b"doc"]),
14553 bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
14554 );
14555 assert_eq!(
14557 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
14558 "*3\r\n:4\r\n:3\r\n:2\r\n"
14559 );
14560 assert_eq!(
14561 f.run(&[b"JSON.GET", b"doc"]),
14562 bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
14563 );
14564 }
14565
14566 #[test]
14574 fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
14575 let mut f = Fixture::new();
14576 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
14577
14578 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
14579 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
14580 assert_eq!(
14581 f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
14582 "*1\r\n:1\r\n"
14583 );
14584
14585 assert_eq!(
14588 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
14589 ":3\r\n"
14590 );
14591 assert_eq!(
14594 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
14595 ":-1\r\n"
14596 );
14597
14598 for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
14601 assert_eq!(
14602 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
14603 ":3\r\n",
14604 "{}",
14605 String::from_utf8_lossy(start)
14606 );
14607 }
14608 assert_eq!(
14609 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
14610 ":0\r\n"
14611 );
14612 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
14615 assert_eq!(
14616 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
14617 ":-1\r\n"
14618 );
14619
14620 f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
14624 assert_eq!(
14625 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
14626 ":0\r\n"
14627 );
14628 assert_eq!(
14629 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
14630 ":1\r\n"
14631 );
14632 assert_eq!(
14633 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
14634 ":-1\r\n"
14635 );
14636
14637 assert_eq!(
14641 f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
14642 "-ERR Path does not exist\r\n"
14643 );
14644 assert_eq!(
14645 f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
14646 "-WRONGTYPE wrong type of path value - expected array\r\n"
14647 );
14648 assert_eq!(
14649 f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
14650 "-ERR Path does not exist\r\n"
14651 );
14652 assert_eq!(
14653 f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
14654 "-ERR Path does not exist\r\n"
14655 );
14656 }
14657
14658 #[test]
14661 fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
14662 let mut f = Fixture::new();
14663 let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
14664 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14665
14666 assert_eq!(
14669 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
14670 bulk("9").as_str()
14671 );
14672 assert_eq!(
14674 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
14675 bulk("[11]").as_str()
14676 );
14677 assert_eq!(
14680 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
14681 bulk("13.0").as_str()
14682 );
14683 assert_eq!(
14684 f.run(&[b"JSON.TYPE", b"doc", b".i"]),
14685 bulk("number").as_str()
14686 );
14687 assert_eq!(
14688 f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
14689 bulk("3.0").as_str()
14690 );
14691 assert_eq!(
14692 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
14693 bulk("-8").as_str()
14694 );
14695 f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
14698 assert_eq!(
14699 f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
14700 bulk("1.224744871391589").as_str()
14701 );
14702 assert_eq!(
14703 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
14704 "-ERR result is not a number\r\n"
14705 );
14706 f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
14710 assert_eq!(
14711 f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
14712 "-ERR numeric overflow\r\n"
14713 );
14714 f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
14715 assert_eq!(
14716 f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
14717 "-ERR numeric overflow\r\n"
14718 );
14719 f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
14721 assert_eq!(
14722 f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
14723 "-ERR result is not a number\r\n"
14724 );
14725
14726 assert_eq!(
14730 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
14731 bulk("[null]").as_str()
14732 );
14733 assert_eq!(
14734 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
14735 bulk("[]").as_str()
14736 );
14737 assert_eq!(
14738 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
14739 "-ERR Path does not exist or does not contains a number\r\n"
14740 );
14741 assert_eq!(
14742 f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
14743 "-ERR Path does not exist or does not contains a number\r\n"
14744 );
14745 assert_eq!(
14748 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
14749 "-bad input number\r\n"
14750 );
14751 assert_eq!(
14752 f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
14753 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14754 );
14755 assert_eq!(
14756 f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
14757 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14758 );
14759 }
14760
14761 #[test]
14764 fn json_strappend_reads_its_shape_off_the_argument_count() {
14765 let mut f = Fixture::new();
14766 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
14767
14768 assert_eq!(
14769 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
14770 ":3\r\n"
14771 );
14772 assert_eq!(
14773 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
14774 "*1\r\n:4\r\n"
14775 );
14776 assert_eq!(
14779 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
14780 ":6\r\n"
14781 );
14782 f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
14785 assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
14786 assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
14787
14788 assert_eq!(
14792 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
14793 "-WRONGTYPE wrong type of path value - expected string\r\n"
14794 );
14795 assert_eq!(
14796 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
14797 "*1\r\n$-1\r\n"
14798 );
14799 assert_eq!(
14800 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
14801 "-ERR Path does not exist or not a string\r\n"
14802 );
14803 assert_eq!(
14804 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
14805 "*0\r\n"
14806 );
14807 assert_eq!(
14808 f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
14809 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14810 );
14811 }
14812
14813 #[test]
14816 fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
14817 let mut f = Fixture::new();
14818 let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
14821
14822 f.run(&[b"JSON.SET", b"doc", b"$", three]);
14823 assert_eq!(
14824 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14825 ":4\r\n"
14826 );
14827 f.run(&[b"JSON.SET", b"doc", b"$", three]);
14828 assert_eq!(
14829 f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
14830 ":2\r\n"
14831 );
14832 f.run(&[b"JSON.SET", b"doc", b"$", three]);
14833 assert_eq!(
14834 f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
14835 ":1\r\n"
14836 );
14837 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
14838 assert_eq!(
14839 f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
14840 bulk("1").as_str()
14841 );
14842 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
14843 assert_eq!(
14844 f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
14845 bulk("13").as_str()
14846 );
14847 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
14848 assert_eq!(
14849 f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
14850 ":4\r\n"
14851 );
14852 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
14853 assert_eq!(
14854 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14855 bulk("false").as_str()
14856 );
14857 assert_eq!(
14860 f.run(&[b"JSON.GET", b"doc", b".a"]),
14861 bulk("[false,true,false]").as_str()
14862 );
14863
14864 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
14867 assert_eq!(
14868 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14869 ":3\r\n"
14870 );
14871 assert_eq!(
14872 f.run(&[b"JSON.GET", b"doc", b".a"]),
14873 bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
14874 );
14875 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
14878 assert_eq!(
14879 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14880 "-ERR Path does not exist or not an array\r\n"
14881 );
14882 assert_eq!(
14883 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14884 "-ERR Path does not exist or not a bool\r\n"
14885 );
14886 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
14890 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
14891 assert_eq!(
14892 f.run(&[b"JSON.GET", b"doc", b".a"]),
14893 bulk("[[],[2]]").as_str()
14894 );
14895 }
14896
14897 #[test]
14900 fn a_write_reaches_a_match_that_sits_inside_another_match() {
14901 let mut f = Fixture::new();
14902 let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
14903
14904 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14905 assert_eq!(
14906 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
14907 "*3\r\n:3\r\n:2\r\n:3\r\n"
14908 );
14909 assert_eq!(
14910 f.run(&[b"JSON.GET", b"doc", b"$"]),
14911 bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
14912 );
14913
14914 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14917 assert_eq!(
14918 f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
14919 "*3\r\n:1\r\n:1\r\n:1\r\n"
14920 );
14921 assert_eq!(
14922 f.run(&[b"JSON.GET", b"doc", b"$"]),
14923 bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
14924 );
14925
14926 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14929 assert_eq!(
14930 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
14931 bulk("[null,8,8]").as_str()
14932 );
14933 }
14934
14935 #[test]
14938 fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
14939 let mut f = Fixture::new();
14940 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
14941
14942 assert_eq!(
14945 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
14946 bulk("[null]").as_str()
14947 );
14948 assert_eq!(
14949 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
14950 bulk("[null]").as_str()
14951 );
14952 assert_eq!(
14953 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
14954 bulk("[]").as_str()
14955 );
14956 assert_eq!(
14957 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
14958 "-ERR Path does not exist or does not contains a number\r\n"
14959 );
14960 assert_eq!(
14962 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
14963 "-bad input number\r\n"
14964 );
14965
14966 assert_eq!(
14968 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
14969 "*1\r\n$-1\r\n"
14970 );
14971 assert_eq!(
14972 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
14973 "-ERR Path does not exist or not a string\r\n"
14974 );
14975 assert_eq!(
14976 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
14977 "-WRONGTYPE wrong type of path value - expected string\r\n"
14978 );
14979
14980 assert_eq!(
14982 f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
14983 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14984 );
14985 assert_eq!(
14986 f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
14987 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14988 );
14989 }
14990
14991 #[test]
14994 fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
14995 let mut f = Fixture::new();
14996
14997 assert_eq!(
15001 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
15002 "+OK\r\n"
15003 );
15004 assert_eq!(
15005 f.run(&[b"JSON.GET", b"doc", b"$"]),
15006 bulk(r#"[{"x":null,"y":1}]"#).as_str()
15007 );
15008
15009 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
15012 assert_eq!(
15013 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
15014 "+OK\r\n"
15015 );
15016 assert_eq!(
15017 f.run(&[b"JSON.GET", b"doc", b"$"]),
15018 bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
15019 );
15020
15021 assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
15023 assert_eq!(
15024 f.run(&[b"JSON.GET", b"doc", b"$"]),
15025 bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
15026 );
15027
15028 assert_eq!(
15032 f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
15033 "+OK\r\n"
15034 );
15035 assert_eq!(
15036 f.run(&[b"JSON.GET", b"doc", b"$"]),
15037 bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
15038 );
15039
15040 assert_eq!(
15044 f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
15045 "+OK\r\n"
15046 );
15047 assert_eq!(
15048 f.run(&[b"JSON.GET", b"doc", b"$.new"]),
15049 bulk(r#"[{"z":null}]"#).as_str()
15050 );
15051 assert_eq!(
15052 f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
15053 "$-1\r\n"
15054 );
15055 assert_eq!(
15056 f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
15057 "-Err wrong static path\r\n"
15058 );
15059
15060 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
15062 assert_eq!(
15063 f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
15064 "+OK\r\n"
15065 );
15066 assert_eq!(
15067 f.run(&[b"JSON.GET", b"doc", b"$"]),
15068 bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
15069 );
15070
15071 assert_eq!(
15073 f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
15074 "-ERR syntax error\r\n"
15075 );
15076 assert_eq!(
15077 f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
15078 "-ERR new objects must be created at the root\r\n"
15079 );
15080 f.run(&[b"SET", b"str", b"x"]);
15081 assert_eq!(
15082 f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
15083 "-Existing key has wrong Redis type\r\n"
15084 );
15085 }
15086
15087 #[test]
15090 fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
15091 let mut f = Fixture::new();
15092 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15093 assert_eq!(
15094 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
15095 "+OK\r\n"
15096 );
15097 assert_eq!(
15101 f.run(&[b"JSON.GET", b"doc", b"$"]),
15102 bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
15103 );
15104
15105 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15108 assert_eq!(
15109 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
15110 "+OK\r\n"
15111 );
15112 assert_eq!(
15113 f.run(&[b"JSON.GET", b"doc", b"$"]),
15114 bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
15115 );
15116 }
15117
15118 #[test]
15121 fn a_filter_path_reads_and_writes_the_members_it_keeps() {
15122 let mut f = Fixture::new();
15123 let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
15124 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15125
15126 assert_eq!(
15127 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
15128 bulk(r#"["a","c"]"#).as_str()
15129 );
15130 assert_eq!(
15133 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
15134 bulk(r#"["a","c"]"#).as_str()
15135 );
15136 assert_eq!(
15138 f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
15139 bulk(r#""a""#).as_str()
15140 );
15141 assert_eq!(
15142 f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
15143 "*1\r\n$6\r\nobject\r\n"
15144 );
15145
15146 assert_eq!(
15150 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
15151 bulk("[9,10]").as_str()
15152 );
15153 assert_eq!(
15154 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
15155 "+OK\r\n"
15156 );
15157 assert_eq!(
15158 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
15159 "-Err wrong static path\r\n"
15160 );
15161 assert_eq!(
15162 f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
15163 ":2\r\n"
15164 );
15165 assert_eq!(
15166 f.run(&[b"JSON.GET", b"doc", b"$"]),
15167 bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
15168 );
15169
15170 assert!(
15173 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
15174 .starts_with("-ERR")
15175 );
15176 assert!(
15177 f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
15178 .starts_with("-ERR")
15179 );
15180 }
15181
15182 #[test]
15185 fn a_filter_takes_the_membership_operators_and_the_methods_too() {
15186 let mut f = Fixture::new();
15187 let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
15188 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15189
15190 for (path, want) in [
15191 (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
15192 (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
15193 (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
15194 (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
15195 (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
15196 (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
15197 (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
15198 (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
15199 (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
15200 (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
15201 (b"$.box[?(@.n~)].t", "[]"),
15202 (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
15203 (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
15204 (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
15205 (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
15206 ] {
15207 assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
15208 }
15209
15210 assert_eq!(
15213 f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
15214 "+OK\r\n"
15215 );
15216 assert_eq!(
15217 f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
15218 bulk(r#"["b"]"#).as_str()
15219 );
15220 }
15221
15222 #[test]
15225 fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
15226 let mut f = Fixture::new();
15227 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
15228 assert_eq!(
15229 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15230 "+OK\r\n"
15231 );
15232 assert_eq!(
15233 f.run(&[b"JSON.GET", b"doc", b"$"]),
15234 bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
15235 );
15236 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
15239 assert_eq!(
15240 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15241 "+OK\r\n"
15242 );
15243 assert_eq!(
15244 f.run(&[b"JSON.GET", b"doc", b"$"]),
15245 bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
15246 );
15247 }
15248
15249 #[test]
15252 fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
15253 let mut f = Fixture::new();
15254 assert_eq!(
15255 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
15256 "+OK\r\n"
15257 );
15258 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
15259 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
15260
15261 assert_eq!(
15263 f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
15264 "+OK\r\n"
15265 );
15266 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
15267
15268 f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
15273 assert_eq!(
15274 f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
15275 "$-1\r\n"
15276 );
15277 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
15278 assert_eq!(
15279 f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
15280 "$-1\r\n"
15281 );
15282 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15283
15284 f.run(&[b"SET", b"str", b"x"]);
15289 assert_eq!(
15290 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
15291 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
15292 );
15293 assert_eq!(
15294 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
15295 "-Existing key has wrong Redis type\r\n"
15296 );
15297 assert_eq!(
15298 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
15299 "-ERR new objects must be created at the root\r\n"
15300 );
15301 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
15302
15303 assert_eq!(
15308 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
15309 "-Err wrong static path\r\n"
15310 );
15311 assert_eq!(
15312 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
15313 "-ERR array index out of range\r\n"
15314 );
15315 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15316
15317 f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
15321 assert_eq!(
15322 f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
15323 "+OK\r\n"
15324 );
15325 assert_eq!(
15326 f.run(&[b"JSON.GET", b"c", b"$"]),
15327 bulk(r#"[{"n":3}]"#).as_str()
15328 );
15329
15330 assert_eq!(
15333 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
15334 "-ERR wrong number of arguments for 'json.mset' command\r\n"
15335 );
15336 }
15337
15338 #[test]
15341 fn json_resp_answers_the_document_as_resp_types() {
15342 let mut f = Fixture::new();
15343 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
15344 assert_eq!(
15345 f.run(&[b"JSON.RESP", b"doc"]),
15346 "*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"
15347 );
15348 assert_eq!(
15350 f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
15351 "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
15352 );
15353
15354 f.run(&[
15355 b"JSON.SET",
15356 b"doc",
15357 b"$",
15358 br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
15359 ]);
15360 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
15361 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
15362 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
15365 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
15366 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
15367
15368 assert_eq!(
15371 f.run(&[b"JSON.RESP", b"doc", b".nope"]),
15372 "-ERR Path does not exist\r\n"
15373 );
15374 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
15375 assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
15376 assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
15377 }
15378
15379 #[test]
15383 fn json_debug_answers_a_byte_count_and_its_own_help() {
15384 let mut f = Fixture::new();
15385 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
15386 let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
15387 assert!(one.starts_with(':'), "{one}");
15388 assert_eq!(
15389 f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
15390 format!("*1\r\n{one}")
15391 );
15392 let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
15393 assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
15394
15395 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
15399 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
15400 assert_eq!(
15401 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
15402 "-ERR Path does not exist\r\n"
15403 );
15404 assert_eq!(
15405 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
15406 "*0\r\n"
15407 );
15408
15409 assert_eq!(
15410 f.run(&[b"JSON.DEBUG", b"HELP"]),
15411 "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
15412 $34\r\nHELP - this message\r\n"
15413 );
15414 assert_eq!(
15415 f.run(&[b"JSON.DEBUG", b"NOPE"]),
15416 "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
15417 );
15418 assert_eq!(
15419 f.run(&[b"JSON.DEBUG", b"MEMORY"]),
15420 "-ERR wrong number of arguments for 'json.debug' command\r\n"
15421 );
15422 }
15423
15424 #[test]
15429 fn the_first_vadd_decides_how_wide_the_set_is() {
15430 let mut f = Fixture::new();
15431 assert_eq!(
15432 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
15433 ":1\r\n"
15434 );
15435 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
15436 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15437 assert_eq!(
15440 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
15441 ":0\r\n"
15442 );
15443 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15444 assert_eq!(
15447 f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
15448 "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
15449 );
15450 assert_eq!(
15453 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
15454 ":1\r\n"
15455 );
15456 assert_eq!(
15457 f.run(&[b"VEMB", b"v", b"nowhere"]),
15458 "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
15459 );
15460 assert_eq!(
15464 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
15465 "-ERR asked quantization mismatch with existing vector set\r\n"
15466 );
15467 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
15470 assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
15471 assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
15472 }
15473
15474 #[test]
15477 fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
15478 let mut f = Fixture::new();
15479 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
15480 assert_eq!(
15486 f.run(&[b"VEMB", b"v", b"a"]),
15487 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
15488 );
15489 f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
15491 assert_eq!(
15492 f.run(&[b"VEMB", b"n", b"a"]),
15493 "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
15494 );
15495 f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
15498 assert_eq!(
15499 f.run(&[b"VEMB", b"b", b"a"]),
15500 "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
15501 );
15502 assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
15503 assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
15504
15505 let mut f = Fixture::new();
15509 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
15510 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
15511 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
15512 assert_eq!(
15513 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
15514 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
15515 $8\r\nopposite\r\n$1\r\n0\r\n"
15516 );
15517 assert_eq!(
15520 f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
15521 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15522 );
15523 assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
15526 assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
15527 assert_eq!(
15530 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
15531 "*1\r\n$6\r\nacross\r\n"
15532 );
15533 assert_eq!(
15534 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
15535 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15536 );
15537 assert_eq!(
15541 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
15542 "*1\r\n$6\r\nacross\r\n"
15543 );
15544
15545 let mut g = Fixture::new();
15548 g.run(&[b"HELLO", b"3"]);
15549 g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15550 assert_eq!(
15551 g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
15552 "%1\r\n$4\r\neast\r\n,1\r\n"
15553 );
15554 }
15555
15556 #[test]
15558 fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
15559 let mut f = Fixture::new();
15560 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15561 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15562 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
15563 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
15564 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
15567 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
15568 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
15570 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15571 assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
15574 assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
15575 assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
15576 assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
15579 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
15580 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
15581 assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
15582
15583 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15585 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15586 assert_eq!(
15587 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
15588 "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
15589 );
15590 }
15591
15592 #[test]
15595 fn vrem_takes_the_attribute_with_it() {
15596 let mut f = Fixture::new();
15597 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15598 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15599 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
15600 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
15601 assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
15602 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15605
15606 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15609 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15610 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15611 f.run(&[b"VREM", b"v", b"east"]);
15612 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
15613 assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
15614 }
15615
15616 #[test]
15619 fn vinfo_says_partition_first() {
15620 let mut f = Fixture::new();
15621 f.run(&[
15622 b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
15623 ]);
15624 f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
15625 let info = f.run(&[b"VINFO", b"v"]);
15626 assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
15627 assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
15630 assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
15631 assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
15632 assert!(
15635 info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
15636 "{info}"
15637 );
15638 let mut f = Fixture::new();
15639 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
15640 assert!(
15641 f.run(&[b"VINFO", b"v"])
15642 .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
15643 );
15644 assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
15645 }
15646
15647 fn named() -> Fixture {
15649 let mut f = Fixture::new();
15650 for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
15651 .iter()
15652 .enumerate()
15653 {
15654 let x = (i + 1).to_string();
15655 f.run(&[
15656 b"VADD",
15657 b"r",
15658 b"VALUES",
15659 b"2",
15660 x.as_bytes(),
15661 b"1",
15662 name.as_bytes(),
15663 ]);
15664 }
15665 f
15666 }
15667
15668 #[test]
15671 fn vrange_walks_the_names_and_not_the_vectors() {
15672 let mut f = named();
15673 assert_eq!(
15674 f.run(&[b"VRANGE", b"r", b"-", b"+"]),
15675 "*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"
15676 );
15677 assert_eq!(
15678 f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
15679 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
15680 "the high end is a name and not a prefix, so delta is past it"
15681 );
15682 assert_eq!(
15683 f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
15684 "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
15685 );
15686 assert_eq!(
15687 f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
15688 "*1\r\n$4\r\nbeta\r\n"
15689 );
15690 assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
15691 f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
15694 assert_eq!(
15695 f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
15696 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15697 );
15698 assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
15699 }
15700
15701 #[test]
15704 fn a_vrange_count_of_zero_asks_for_nothing() {
15705 let mut f = named();
15706 assert_eq!(
15707 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
15708 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15709 );
15710 assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
15711 assert!(
15712 f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
15713 .starts_with("*5\r\n"),
15714 "a negative count is no limit at all"
15715 );
15716 }
15717
15718 #[test]
15721 fn vrange_says_which_end_it_could_not_read() {
15722 let mut f = named();
15723 assert_eq!(
15724 f.run(&[b"VRANGE", b"r", b"x", b"y"]),
15725 "-ERR invalid start range format\r\n"
15726 );
15727 assert_eq!(
15728 f.run(&[b"VRANGE", b"r", b"+", b"x"]),
15729 "-ERR invalid end range format\r\n",
15730 "the high end is spelled wrong, which is worth saying before the \
15731 low end being on the wrong side"
15732 );
15733 assert_eq!(
15734 f.run(&[b"VRANGE", b"r", b"+", b"-"]),
15735 "-ERR '-' can only be used as first argument, '+' only as second\r\n"
15736 );
15737 assert_eq!(
15740 f.run(&[b"VRANGE", b"r", b"[", b"+"]),
15741 "-ERR invalid start range format\r\n"
15742 );
15743 assert_eq!(
15744 f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
15745 "-ERR invalid COUNT value\r\n"
15746 );
15747 assert_eq!(
15748 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
15749 "-ERR wrong number of arguments for 'VRANGE' command\r\n"
15750 );
15751 f.run(&[b"SET", b"s", b"x"]);
15752 assert!(
15753 f.run(&[b"VRANGE", b"s", b"-", b"+"])
15754 .starts_with("-WRONGTYPE")
15755 );
15756 }
15757
15758 #[test]
15761 fn reduce_is_refused_and_not_ignored() {
15762 let mut f = Fixture::new();
15763 let reduce = f.run(&[
15764 b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
15765 ]);
15766 assert!(
15767 reduce.starts_with("-ERR REDUCE is not supported."),
15768 "{reduce}"
15769 );
15770 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15771 }
15772
15773 #[test]
15776 fn vsim_filter_reads_the_attributes() {
15777 let mut f = Fixture::new();
15778 for (name, x, y, attr) in [
15779 ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
15780 ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
15781 ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
15782 ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
15783 ] {
15784 f.run(&[
15785 b"VADD",
15786 b"v",
15787 b"VALUES",
15788 b"2",
15789 x.as_bytes(),
15790 y.as_bytes(),
15791 name.as_bytes(),
15792 b"SETATTR",
15793 attr.as_bytes(),
15794 ]);
15795 }
15796 assert_eq!(
15799 f.run(&[
15800 b"VSIM",
15801 b"v",
15802 b"VALUES",
15803 b"2",
15804 b"9",
15805 b"1",
15806 b"COUNT",
15807 b"2",
15808 b"FILTER",
15809 b".lang == \"en\"",
15810 ]),
15811 "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
15812 );
15813 assert_eq!(
15816 f.run(&[
15817 b"VSIM",
15818 b"v",
15819 b"VALUES",
15820 b"2",
15821 b"9",
15822 b"1",
15823 b"FILTER",
15824 b".lang == 'en' and .year > 1980",
15825 ]),
15826 "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
15827 );
15828 assert_eq!(
15830 f.run(&[
15831 b"VSIM",
15832 b"v",
15833 b"VALUES",
15834 b"2",
15835 b"9",
15836 b"1",
15837 b"FILTER",
15838 b".lang in ['fr', 'de']",
15839 ]),
15840 "*1\r\n$1\r\nb\r\n"
15841 );
15842 assert_eq!(
15843 f.run(&[
15844 b"VSIM",
15845 b"v",
15846 b"VALUES",
15847 b"2",
15848 b"9",
15849 b"1",
15850 b"FILTER",
15851 b".rating > 3"
15852 ]),
15853 "*0\r\n"
15854 );
15855 assert_eq!(
15858 f.run(&[
15859 b"VSIM",
15860 b"v",
15861 b"VALUES",
15862 b"2",
15863 b"9",
15864 b"1",
15865 b"TRUTH",
15866 b"FILTER",
15867 b".year < 1980",
15868 ]),
15869 "*1\r\n$1\r\nc\r\n"
15870 );
15871 f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
15874 assert_eq!(
15875 f.run(&[
15876 b"VSIM",
15877 b"v",
15878 b"VALUES",
15879 b"2",
15880 b"9",
15881 b"1",
15882 b"COUNT",
15883 b"1",
15884 b"FILTER",
15885 b".lang == \"en\"",
15886 ]),
15887 "*1\r\n$1\r\nb\r\n"
15888 );
15889 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
15892 assert_eq!(
15893 f.run(&[
15894 b"VSIM",
15895 b"v",
15896 b"VALUES",
15897 b"2",
15898 b"9",
15899 b"1",
15900 b"COUNT",
15901 b"1",
15902 b"FILTER",
15903 b".lang == \"en\"",
15904 ]),
15905 "*1\r\n$1\r\nb\r\n"
15906 );
15907
15908 let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
15911 assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
15912 assert_eq!(
15913 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
15914 "-ERR invalid FILTER expression\r\n"
15915 );
15916 assert_eq!(
15919 f.run(&[
15920 b"VSIM",
15921 b"v",
15922 b"VALUES",
15923 b"2",
15924 b"9",
15925 b"1",
15926 b"COUNT",
15927 b"1",
15928 b"FILTER-EF",
15929 b"500",
15930 b"FILTER",
15931 b".lang == 'en'",
15932 ]),
15933 "*1\r\n$1\r\nb\r\n"
15934 );
15935 assert_eq!(
15936 f.run(&[
15937 b"VSIM",
15938 b"v",
15939 b"VALUES",
15940 b"2",
15941 b"9",
15942 b"1",
15943 b"COUNT",
15944 b"1",
15945 b"FILTER-EF",
15946 b"0"
15947 ]),
15948 "*1\r\n$1\r\nb\r\n"
15949 );
15950 assert_eq!(
15951 f.run(&[
15952 b"VSIM",
15953 b"v",
15954 b"VALUES",
15955 b"2",
15956 b"9",
15957 b"1",
15958 b"FILTER-EF",
15959 b"lots"
15960 ]),
15961 "-ERR EF must be a positive integer\r\n"
15962 );
15963 }
15964
15965 #[test]
15968 fn the_keyspace_sees_a_vector_set_key_like_any_other() {
15969 let mut f = Fixture::new();
15970 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15971 assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
15972 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
15973 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
15974 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
15975 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
15976 assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
15977 assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
15978 assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
15979 assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
15980 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15981
15982 f.run(&[b"SET", b"s", b"1"]);
15984 assert_eq!(
15985 f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
15986 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15987 );
15988 assert_eq!(
15989 f.run(&[b"VCARD", b"s"]),
15990 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15991 );
15992 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15993 assert_eq!(
15994 f.run(&[b"GET", b"v"]),
15995 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15996 );
15997 f.run(&[b"G.NADD", b"social", b"ada"]);
16001 assert_eq!(
16002 f.run(&[b"VCARD", b"social"]),
16003 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16004 );
16005 assert_eq!(
16006 f.run(&[b"G.NGET", b"v", b"ada"]),
16007 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16008 );
16009 }
16010
16011 #[test]
16014 fn vrandmember_has_the_two_shapes_srandmember_has() {
16015 let mut f = Fixture::new();
16016 for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
16017 let x = (i + 1).to_string();
16018 f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
16019 }
16020 let one = f.run(&[b"VRANDMEMBER", b"v"]);
16022 assert!(one.starts_with("$1\r\n"), "{one}");
16023 let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
16025 assert!(all.starts_with("*3\r\n"), "{all}");
16026 for name in ["a", "b", "c"] {
16027 assert!(all.contains(name), "{all} is missing {name}");
16028 }
16029 all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
16030 assert!(all.starts_with("*2\r\n"), "{all}");
16031 let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
16033 assert!(many.starts_with("*5\r\n"), "{many}");
16034 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
16036 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
16037 }
16038
16039 #[test]
16042 fn vlinks_reports_one_layer_of_partition_neighbours() {
16043 let mut f = Fixture::new();
16044 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16045 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
16046 assert_eq!(
16049 f.run(&[b"VLINKS", b"v", b"east"]),
16050 "*1\r\n*1\r\n$5\r\nnorth\r\n"
16051 );
16052 assert_eq!(
16053 f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
16054 "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
16055 );
16056 assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
16057 assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
16058 }
16059
16060 #[test]
16063 fn fp32_and_values_are_the_same_vector() {
16064 let mut f = Fixture::new();
16065 let mut blob = Vec::new();
16066 for x in [3.0f32, 4.0] {
16067 blob.extend_from_slice(&x.to_le_bytes());
16068 }
16069 assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
16070 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
16071 assert_eq!(
16072 f.run(&[b"VEMB", b"v", b"a"]),
16073 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
16074 );
16075 assert_eq!(
16081 f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
16082 "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
16083 );
16084 assert_eq!(
16086 f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
16087 "-ERR invalid vector specification\r\n"
16088 );
16089 assert_eq!(
16091 f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
16092 "-ERR syntax error\r\n"
16093 );
16094 assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
16095 }
16096
16097 #[test]
16102 fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
16103 let mut f = Fixture::new();
16104 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
16105 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
16106 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
16107 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
16108 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
16109 assert_eq!(
16112 f.run(&[b"BF.INFO", b"b"]),
16113 "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16114 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16115 +Expansion rate\r\n:2\r\n"
16116 );
16117 assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
16118 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
16119 assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
16122 assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
16123 }
16124
16125 #[test]
16131 fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
16132 let mut f = Fixture::new();
16133 f.run(&[b"SET", b"s", b"text"]);
16134 assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
16135 assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16136 for cmd in [
16137 vec![&b"BF.ADD"[..], b"s", b"x"],
16138 vec![&b"BF.MADD"[..], b"s", b"x"],
16139 vec![&b"BF.CARD"[..], b"s"],
16140 vec![&b"BF.INFO"[..], b"s"],
16141 vec![&b"BF.DEBUG"[..], b"s"],
16142 vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
16143 ] {
16144 let name = String::from_utf8_lossy(cmd[0]).into_owned();
16145 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16146 }
16147 assert_eq!(
16150 f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
16151 "-ERR bad error rate\r\n"
16152 );
16153 assert!(
16154 f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
16155 .starts_with("-WRONGTYPE")
16156 );
16157 }
16158
16159 #[test]
16162 fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
16163 let mut f = Fixture::new();
16164 assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
16165 for i in 0..10u32 {
16166 assert_eq!(
16167 f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
16168 ":1\r\n"
16169 );
16170 }
16171 assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
16172 assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
16173 assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
16174 assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
16177 assert_eq!(
16178 f.run(&[b"BF.DEBUG", b"g"]),
16179 "*3\r\n$7\r\nsize:11\r\n\
16180 $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
16181 $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
16182 );
16183
16184 assert_eq!(
16186 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
16187 "+OK\r\n"
16188 );
16189 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
16190 assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
16191 assert_eq!(
16192 f.run(&[b"BF.ADD", b"n", b"c"]),
16193 "-ERR non scaling filter is full\r\n"
16194 );
16195 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
16198 assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
16201 f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
16202 assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
16203 assert_eq!(
16206 f.run(&[
16207 b"BF.RESERVE",
16208 b"q",
16209 b"0.01",
16210 b"2",
16211 b"NONSCALING",
16212 b"EXPANSION",
16213 b"2"
16214 ]),
16215 "-Nonscaling filters cannot expand\r\n"
16216 );
16217 }
16218
16219 #[test]
16222 fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
16223 let mut f = Fixture::new();
16224 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
16225 assert_eq!(
16226 f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
16227 "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
16228 );
16229 assert_eq!(
16230 f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
16231 "*2\r\n:1\r\n:0\r\n"
16232 );
16233 }
16234
16235 #[test]
16238 fn insert_is_a_reserve_and_a_madd_with_different_errors() {
16239 let mut f = Fixture::new();
16240 assert_eq!(
16241 f.run(&[
16242 b"BF.INSERT",
16243 b"i",
16244 b"CAPACITY",
16245 b"50",
16246 b"ERROR",
16247 b"0.001",
16248 b"ITEMS",
16249 b"a",
16250 b"b"
16251 ]),
16252 "*2\r\n:1\r\n:1\r\n"
16253 );
16254 assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
16255 assert_eq!(
16257 f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16258 "-ERR not found\r\n"
16259 );
16260 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16261 assert_eq!(
16264 f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16265 "-Bad capacity\r\n"
16266 );
16267 assert_eq!(
16268 f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
16269 "-Bad error rate\r\n"
16270 );
16271 assert_eq!(
16272 f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
16273 "-Bad expansion\r\n"
16274 );
16275 assert_eq!(
16280 f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
16281 "*1\r\n:1\r\n"
16282 );
16283 assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
16284 assert_eq!(
16288 f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
16289 "*1\r\n:1\r\n"
16290 );
16291 assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
16292 assert_eq!(
16293 f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
16294 "*1\r\n:1\r\n"
16295 );
16296 assert_eq!(
16297 f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
16298 "-ERR not found\r\n"
16299 );
16300 assert_eq!(
16302 f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16303 "-Unknown argument received\r\n"
16304 );
16305 assert_eq!(
16307 f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16308 "*1\r\n:1\r\n"
16309 );
16310 assert!(
16312 f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
16313 .contains("wrong number of arguments")
16314 );
16315 }
16316
16317 #[test]
16320 fn a_dump_replays_into_a_filter_that_answers_the_same() {
16321 let mut f = Fixture::new();
16322 f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
16323 for i in 0..25u32 {
16324 f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
16325 }
16326 assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
16327
16328 let mut iter = b"0".to_vec();
16331 let mut chunks = 0;
16332 loop {
16333 let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
16334 let text = String::from_utf8_lossy(&raw).into_owned();
16335 let next = text
16336 .split("\r\n")
16337 .nth(1)
16338 .and_then(|n| n.strip_prefix(':'))
16339 .expect("a two element reply of an iterator and a chunk")
16340 .to_owned();
16341 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16342 let data = &body[body
16343 .windows(2)
16344 .position(|w| w == b"\r\n")
16345 .expect("a length line")
16346 + 2..body.len() - 2];
16347 if next == "0" {
16348 assert!(data.is_empty(), "the last chunk is empty");
16349 break;
16350 }
16351 let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
16352 assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
16353 iter = next.into_bytes();
16354 chunks += 1;
16355 }
16356 assert_eq!(chunks, 3, "a header and one chunk per link");
16357
16358 assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
16359 assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
16360 for i in 0..25u32 {
16361 assert_eq!(
16362 f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
16363 ":1\r\n"
16364 );
16365 }
16366
16367 assert_eq!(
16370 f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
16371 "-ERR received bad data\r\n"
16372 );
16373 assert_eq!(
16374 f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
16375 "-ERR received bad data\r\n"
16376 );
16377 assert_eq!(
16379 f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
16380 "-ERR invalid offset - no link found\r\n"
16381 );
16382 assert_eq!(
16383 f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
16384 "-ERR Second argument must be numeric\r\n"
16385 );
16386 assert_eq!(
16389 f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
16390 "-Second argument must be numeric\r\n"
16391 );
16392 }
16393
16394 #[test]
16397 fn reserve_reads_its_numbers_the_way_string2ll_does() {
16398 let mut f = Fixture::new();
16399 for (args, want) in [
16400 (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
16401 (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
16402 (
16403 vec![&b"0"[..], b"10"],
16404 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16405 ),
16406 (
16407 vec![&b"1"[..], b"10"],
16408 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16409 ),
16410 (
16411 vec![&b"inf"[..], b"10"],
16412 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16413 ),
16414 (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
16415 (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
16416 (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
16417 (
16418 vec![&b"0.01"[..], b"0"],
16419 "-ERR capacity must be in the range [1, 1073741824]\r\n",
16420 ),
16421 (
16422 vec![&b"0.01"[..], b"1073741825"],
16423 "-ERR capacity must be in the range [1, 1073741824]\r\n",
16424 ),
16425 ] {
16426 let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
16427 cmd.extend(args.iter().copied());
16428 assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
16429 }
16430 assert_eq!(
16431 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
16432 "-ERR no expansion\r\n"
16433 );
16434 assert_eq!(
16435 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
16436 "-ERR bad expansion\r\n"
16437 );
16438 assert_eq!(
16439 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
16440 "-ERR expansion must be in the range [0, 32768]\r\n"
16441 );
16442 assert_eq!(
16444 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
16445 "+OK\r\n"
16446 );
16447 assert_eq!(
16448 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
16449 "-ERR item exists\r\n"
16450 );
16451 assert_eq!(
16452 f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
16453 "-Invalid information value\r\n"
16454 );
16455 assert!(
16456 f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
16457 .contains("wrong number of arguments")
16458 );
16459 }
16460
16461 #[test]
16463 fn the_bloom_family_answers_in_resp3_spelling_too() {
16464 let mut f = Fixture::new();
16465 f.out.set_proto(Proto::Resp3);
16466 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
16467 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
16468 assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
16469 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
16470 assert_eq!(
16471 f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
16472 "*2\r\n#t\r\n#f\r\n"
16473 );
16474 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
16476 assert_eq!(
16477 f.run(&[b"BF.INFO", b"b"]),
16478 "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16479 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
16480 +Expansion rate\r\n:2\r\n"
16481 );
16482 assert_eq!(
16485 f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
16486 "%1\r\n+Capacity\r\n:100\r\n"
16487 );
16488 }
16489
16490 fn cf_header(
16499 items: u64,
16500 buckets: u64,
16501 deletes: u64,
16502 filters: u64,
16503 geometry: [u16; 3],
16504 ) -> Vec<u8> {
16505 let mut out = Vec::with_capacity(38);
16506 for n in [items, buckets, deletes, filters] {
16507 out.extend_from_slice(&n.to_le_bytes());
16508 }
16509 for n in geometry {
16510 out.extend_from_slice(&n.to_le_bytes());
16511 }
16512 out
16513 }
16514
16515 #[test]
16519 fn cf_add_makes_the_filter_and_counts_the_copies() {
16520 let mut f = Fixture::new();
16521 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16522 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16523 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
16524 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
16527 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
16528 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
16529 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
16530 assert_eq!(
16531 f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
16532 "*2\r\n:1\r\n:0\r\n"
16533 );
16534 assert_eq!(
16537 f.run(&[b"CF.INFO", b"d"]),
16538 "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16539 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
16540 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
16541 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16542 );
16543 assert_eq!(
16544 f.run(&[b"CF.DEBUG", b"d"]),
16545 "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
16546 max_iterations:20 expansion:1\r\n"
16547 );
16548 assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
16549 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
16550
16551 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16553 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
16554 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16555 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
16556 assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
16557
16558 assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
16561 assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
16562 assert_eq!(
16563 f.run(&[b"CF.COMPACT", b"gone"]),
16564 "-Cuckoo filter was not found\r\n"
16565 );
16566 assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
16567 assert!(
16571 f.run(&[b"CF.COMPACT", b"a", b"b"])
16572 .contains("wrong number of arguments")
16573 );
16574 }
16575
16576 #[test]
16579 fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
16580 let mut f = Fixture::new();
16581 f.run(&[b"SET", b"s", b"text"]);
16582 assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
16583 assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16584 assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
16585 assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
16589 assert_eq!(
16590 f.run(&[b"CF.COMPACT", b"s"]),
16591 "-Cuckoo filter was not found\r\n"
16592 );
16593 for cmd in [
16594 vec![&b"CF.ADD"[..], b"s", b"x"],
16595 vec![&b"CF.ADDNX"[..], b"s", b"x"],
16596 vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
16597 vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
16598 vec![&b"CF.INFO"[..], b"s"],
16599 vec![&b"CF.DEBUG"[..], b"s"],
16600 vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
16601 vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
16602 vec![&b"CF.RESERVE"[..], b"s", b"64"],
16603 ] {
16604 let name = String::from_utf8_lossy(cmd[0]).into_owned();
16605 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16606 }
16607 }
16608
16609 #[test]
16612 fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
16613 let mut f = Fixture::new();
16614 assert_eq!(
16615 f.run(&[
16616 b"CF.RESERVE",
16617 b"r",
16618 b"64",
16619 b"BUCKETSIZE",
16620 b"1",
16621 b"MAXITERATIONS",
16622 b"7",
16623 b"EXPANSION",
16624 b"4"
16625 ]),
16626 "+OK\r\n"
16627 );
16628 assert_eq!(
16629 f.run(&[b"CF.DEBUG", b"r"]),
16630 "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
16631 max_iterations:7 expansion:4\r\n"
16632 );
16633 assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
16634
16635 assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
16636 assert_eq!(
16637 f.run(&[b"CF.RESERVE", b"q", b"1"]),
16638 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16639 );
16640 assert_eq!(
16643 f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
16644 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16645 );
16646 assert_eq!(
16647 f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
16648 "+OK\r\n"
16649 );
16650
16651 assert_eq!(
16656 f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
16657 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16658 );
16659 assert_eq!(
16660 f.run(&[
16661 b"CF.RESERVE",
16662 b"q2",
16663 b"64",
16664 b"EXPANSION",
16665 b"xx",
16666 b"BUCKETSIZE",
16667 b"0"
16668 ]),
16669 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16670 );
16671 assert_eq!(
16672 f.run(&[
16673 b"CF.RESERVE",
16674 b"q2",
16675 b"64",
16676 b"MAXITERATIONS",
16677 b"0",
16678 b"BUCKETSIZE",
16679 b"0"
16680 ]),
16681 "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
16682 );
16683 assert_eq!(
16687 f.run(&[
16688 b"CF.RESERVE",
16689 b"q3",
16690 b"64",
16691 b"BUCKETSIZE",
16692 b"1",
16693 b"BUCKETSIZE",
16694 b"0"
16695 ]),
16696 "+OK\r\n"
16697 );
16698 assert_eq!(
16701 f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
16702 "+OK\r\n"
16703 );
16704 assert_eq!(
16705 f.run(&[b"CF.DEBUG", b"q4"]),
16706 "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
16707 max_iterations:20 expansion:1\r\n"
16708 );
16709 assert!(
16712 f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
16713 .contains("wrong number of arguments")
16714 );
16715 }
16716
16717 #[test]
16720 fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
16721 let mut f = Fixture::new();
16722 assert_eq!(
16723 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
16724 "*2\r\n:1\r\n:1\r\n"
16725 );
16726 assert_eq!(
16727 f.run(&[b"CF.DEBUG", b"i"]),
16728 "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
16729 max_iterations:20 expansion:1\r\n"
16730 );
16731 assert_eq!(
16734 f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
16735 "*2\r\n:0\r\n:1\r\n"
16736 );
16737 assert_eq!(
16738 f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16739 "-ERR not found\r\n"
16740 );
16741 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16742
16743 assert_eq!(
16744 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16745 "-Bad capacity\r\n"
16746 );
16747 assert_eq!(
16750 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
16751 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16752 );
16753 assert_eq!(
16757 f.run(&[
16758 b"CF.INSERT",
16759 b"i",
16760 b"CAPACITY",
16761 b"8",
16762 b"CAPACITY",
16763 b"2",
16764 b"ITEMS",
16765 b"a"
16766 ]),
16767 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16768 );
16769 assert_eq!(
16773 f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
16774 "*1\r\n:1\r\n"
16775 );
16776 assert_eq!(
16777 f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
16778 "*1\r\n:1\r\n"
16779 );
16780 assert_eq!(
16781 f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16782 "-Unknown argument received\r\n"
16783 );
16784 assert_eq!(
16786 f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16787 "*1\r\n:1\r\n"
16788 );
16789 assert!(
16791 f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
16792 .contains("wrong number of arguments")
16793 );
16794 assert!(
16795 f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
16796 .contains("wrong number of arguments")
16797 );
16798 }
16799
16800 #[test]
16803 fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
16804 let mut f = Fixture::new();
16805 f.run(&[
16806 b"CF.RESERVE",
16807 b"s",
16808 b"4",
16809 b"BUCKETSIZE",
16810 b"1",
16811 b"EXPANSION",
16812 b"0",
16813 ]);
16814 for i in 0..4u32 {
16815 assert_eq!(
16816 f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
16817 ":1\r\n"
16818 );
16819 }
16820 assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
16821 assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
16822 assert_eq!(
16825 f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
16826 "*2\r\n:-1\r\n:-1\r\n"
16827 );
16828 assert_eq!(
16829 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
16830 "*2\r\n:0\r\n:-1\r\n"
16831 );
16832
16833 let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
16839 assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
16840 assert_eq!(
16841 f.run(&[b"CF.ADD", b"g", b"q"]),
16842 "-Maximum expansions reached\r\n"
16843 );
16844 assert_eq!(
16845 f.run(&[b"CF.INFO", b"g"]),
16846 "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
16847 +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
16848 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
16849 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16850 );
16851 }
16852
16853 #[test]
16857 fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
16858 let mut f = Fixture::new();
16859 f.run(&[
16860 b"CF.RESERVE",
16861 b"src",
16862 b"8",
16863 b"BUCKETSIZE",
16864 b"2",
16865 b"EXPANSION",
16866 b"2",
16867 ]);
16868 for i in 0..40u32 {
16869 f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
16870 }
16871 let mut pos = b"0".to_vec();
16875 let mut chunks = 0;
16876 loop {
16877 let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
16878 let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
16879 let next = head
16880 .split("\r\n")
16881 .nth(1)
16882 .and_then(|n| n.strip_prefix(':'))
16883 .expect("a two element reply of a position and a chunk")
16884 .to_owned();
16885 if next == "0" {
16886 assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
16887 break;
16888 }
16889 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16890 let at = body
16891 .windows(2)
16892 .position(|w| w == b"\r\n")
16893 .expect("a length line")
16894 + 2;
16895 let data = &body[at..body.len() - 2];
16896 assert_eq!(
16897 f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
16898 "+OK\r\n",
16899 "loading chunk {chunks}"
16900 );
16901 pos = next.into_bytes();
16902 chunks += 1;
16903 }
16904 assert!(chunks >= 2, "a header and at least one chunk");
16905
16906 assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
16907 assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
16908 for i in 0..40u32 {
16909 assert_eq!(
16910 f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
16911 ":1\r\n"
16912 );
16913 }
16914
16915 f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
16918 assert_eq!(
16919 f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
16920 "*2\r\n:0\r\n$-1\r\n"
16921 );
16922
16923 assert_eq!(
16927 f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
16928 "-Invalid position\r\n"
16929 );
16930 assert_eq!(
16931 f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
16932 "-Invalid position\r\n"
16933 );
16934 assert_eq!(
16935 f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
16936 "-Invalid position\r\n"
16937 );
16938 assert_eq!(
16939 f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
16940 "-Couldn't load chunk!\r\n"
16941 );
16942 let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
16944 assert_eq!(
16945 f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
16946 "-ERR item exists\r\n"
16947 );
16948 assert_eq!(
16952 f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
16953 "-Invalid header\r\n"
16954 );
16955 for (why, bad) in [
16956 ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
16957 ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
16958 (
16959 "a bucket count that is not a power of two",
16960 cf_header(0, 3, 0, 1, [2, 20, 1]),
16961 ),
16962 ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
16963 ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
16964 (
16965 "a growth nobody could reach",
16966 cf_header(0, 8, 0, 1, [2, 20, 32769]),
16967 ),
16968 (
16969 "a chain that cannot grow and did",
16970 cf_header(0, 8, 0, 2, [2, 20, 0]),
16971 ),
16972 (
16975 "a filter count that wraps",
16976 cf_header(0, 8, 0, 65_536, [2, 20, 1]),
16977 ),
16978 ] {
16979 assert_eq!(
16980 f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
16981 "-Couldn't create filter!\r\n",
16982 "{why}"
16983 );
16984 }
16985 }
16986
16987 #[test]
16990 fn the_cuckoo_family_answers_in_resp3_spelling_too() {
16991 let mut f = Fixture::new();
16992 f.out.set_proto(Proto::Resp3);
16993 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16994 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16995 assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
16996 assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
16997 assert_eq!(
16998 f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
16999 "*2\r\n#t\r\n#f\r\n"
17000 );
17001 assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
17002 assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
17003 assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
17005 assert_eq!(
17006 f.run(&[b"CF.INFO", b"c"]),
17007 "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
17008 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
17009 +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
17010 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
17011 );
17012
17013 f.run(&[
17018 b"CF.RESERVE",
17019 b"s",
17020 b"4",
17021 b"BUCKETSIZE",
17022 b"1",
17023 b"EXPANSION",
17024 b"0",
17025 ]);
17026 assert_eq!(
17027 f.run(&[
17028 b"CF.INSERT",
17029 b"s",
17030 b"ITEMS",
17031 b"a",
17032 b"b",
17033 b"c",
17034 b"d",
17035 b"e",
17036 b"f"
17037 ]),
17038 "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
17039 );
17040 assert_eq!(
17041 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
17042 "*2\r\n:0\r\n:-1\r\n"
17043 );
17044 assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
17045 assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
17048 }
17049
17050 #[test]
17055 fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
17056 let mut f = Fixture::new();
17057 assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
17058 assert_eq!(
17059 f.run(&[b"CMS.INFO", b"d"]),
17060 "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
17061 );
17062 assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
17063 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
17064 assert_eq!(
17067 f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
17068 "+OK\r\n"
17069 );
17070 assert_eq!(
17071 f.run(&[b"CMS.INFO", b"p"]),
17072 "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
17073 );
17074 assert_eq!(
17077 f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
17078 "-CMS: key already exists\r\n"
17079 );
17080 assert_eq!(
17081 f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
17082 "-CMS: invalid width\r\n"
17083 );
17084 assert_eq!(
17085 f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
17086 "-CMS: invalid depth\r\n"
17087 );
17088 assert_eq!(
17089 f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
17090 "-CMS: invalid overestimation value\r\n"
17091 );
17092 assert_eq!(
17093 f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
17094 "-CMS: invalid prob value\r\n"
17095 );
17096 assert_eq!(
17100 f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
17101 "-CMS: invalid init arguments\r\n"
17102 );
17103 assert_eq!(
17107 f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
17108 "-CMS: Insufficient memory to create the key\r\n"
17109 );
17110 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17111 }
17112
17113 #[test]
17116 fn increments_are_parsed_whole_and_the_counters_saturate() {
17117 let mut f = Fixture::new();
17118 f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
17119 assert_eq!(
17120 f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
17121 "*2\r\n:3\r\n:4\r\n"
17122 );
17123 assert_eq!(
17126 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
17127 "*2\r\n:4\r\n:5\r\n"
17128 );
17129 assert_eq!(
17131 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
17132 "-CMS: Cannot parse number\r\n"
17133 );
17134 assert_eq!(
17135 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
17136 "-CMS: Number cannot be negative\r\n"
17137 );
17138 assert_eq!(
17139 f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
17140 "*2\r\n:5\r\n:4\r\n"
17141 );
17142 f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
17145 assert_eq!(
17146 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
17147 "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
17148 );
17149 assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
17150 f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
17153 f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
17154 f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
17155 assert_eq!(
17156 f.run(&[b"CMS.INFO", b"w"]),
17157 "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
17158 );
17159 assert!(
17162 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
17163 .contains("wrong number of arguments")
17164 );
17165 assert_eq!(
17166 f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
17167 "-CMS: key does not exist\r\n"
17168 );
17169 assert_eq!(
17170 f.run(&[b"CMS.QUERY", b"nope", b"a"]),
17171 "-CMS: key does not exist\r\n"
17172 );
17173 }
17174
17175 #[test]
17178 fn a_merge_lands_whole_or_not_at_all() {
17179 let mut f = Fixture::new();
17180 for name in [&b"m1"[..], b"m2", b"dst"] {
17181 f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
17182 }
17183 f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
17184 f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
17185 assert_eq!(
17186 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17187 "+OK\r\n"
17188 );
17189 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17190 assert_eq!(
17193 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17194 "+OK\r\n"
17195 );
17196 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17197 assert_eq!(
17198 f.run(&[
17199 b"CMS.MERGE",
17200 b"dst",
17201 b"2",
17202 b"m1",
17203 b"m2",
17204 b"WEIGHTS",
17205 b"2",
17206 b"3"
17207 ]),
17208 "+OK\r\n"
17209 );
17210 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17211 assert_eq!(
17214 f.run(&[
17215 b"CMS.MERGE",
17216 b"dst",
17217 b"1",
17218 b"m1",
17219 b"WEIGHTS",
17220 b"4611686018427387904"
17221 ]),
17222 "-CMS: MERGE overflow\r\n"
17223 );
17224 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17225 f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
17228 assert_eq!(
17229 f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
17230 "-CMS: key does not exist\r\n"
17231 );
17232 assert_eq!(
17233 f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
17234 "-CMS: Number of keys must be positive\r\n"
17235 );
17236 assert_eq!(
17237 f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
17238 "-CMS: wrong number of keys\r\n"
17239 );
17240 assert_eq!(
17241 f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
17242 "-CMS: wrong number of keys/weights\r\n"
17243 );
17244 assert_eq!(
17245 f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
17246 "-CMS: width/depth is not equal\r\n"
17247 );
17248 assert_eq!(
17249 f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
17250 "-CMS: key does not exist\r\n"
17251 );
17252 }
17253
17254 #[test]
17257 fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17258 let mut f = Fixture::new();
17259 f.run(&[b"SET", b"s", b"text"]);
17260 for cmd in [
17261 vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
17262 vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
17263 vec![&b"CMS.QUERY"[..], b"s", b"a"],
17264 vec![&b"CMS.INFO"[..], b"s"],
17265 vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
17266 ] {
17267 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17268 let reply = f.run(&cmd);
17269 assert!(
17272 reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
17273 "{name}: {reply}"
17274 );
17275 }
17276 f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
17277 assert_eq!(
17281 f.run(&[b"COPY", b"c", b"c2"]),
17282 "-ERR not supported for this module key\r\n"
17283 );
17284 assert_eq!(
17285 f.run(&[b"DUMP", b"c"]),
17286 "-ERR DUMP is not supported for this module key\r\n"
17287 );
17288 f.run(&[b"G.NADD", b"g", b"a"]);
17290 assert_eq!(
17291 f.run(&[b"COPY", b"g", b"g2"]),
17292 "-ERR COPY is not supported for a graph\r\n"
17293 );
17294 assert_eq!(
17295 f.run(&[b"DUMP", b"g"]),
17296 "-ERR DUMP is not supported for a graph\r\n"
17297 );
17298 assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
17301 assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
17302 assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
17303 assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
17304 assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
17305 }
17306
17307 #[test]
17312 fn a_reserve_takes_three_arguments_or_six() {
17313 let mut f = Fixture::new();
17314 assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
17315 assert_eq!(
17316 f.run(&[b"TOPK.INFO", b"t"]),
17317 "*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"
17318 );
17319 for cmd in [
17322 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
17323 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
17324 ] {
17325 assert!(f.run(&cmd).contains("wrong number of arguments"));
17326 }
17327 assert_eq!(
17328 f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
17329 "+OK\r\n"
17330 );
17331 assert_eq!(
17334 f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
17335 "-TopK: key already exists\r\n"
17336 );
17337 assert_eq!(
17338 f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
17339 "-TopK: invalid k\r\n"
17340 );
17341 assert_eq!(
17342 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
17343 "-TopK: invalid width\r\n"
17344 );
17345 assert_eq!(
17346 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
17347 "-TopK: invalid depth\r\n"
17348 );
17349 assert_eq!(
17351 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
17352 "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
17353 );
17354 assert_eq!(
17355 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
17356 "+OK\r\n"
17357 );
17358 assert_eq!(
17360 f.run(&[
17361 b"TOPK.RESERVE",
17362 b"w",
17363 b"1",
17364 b"4294967295",
17365 b"4294967295",
17366 b"0.9"
17367 ]),
17368 "-ERR Insufficient memory to create topk data structure\r\n"
17369 );
17370 }
17371
17372 #[test]
17374 fn the_kept_set_is_what_query_and_list_answer_from() {
17375 let mut f = Fixture::new();
17376 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
17377 assert_eq!(
17380 f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
17381 "*2\r\n$-1\r\n$-1\r\n"
17382 );
17383 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
17384 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
17387 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
17388 assert_eq!(
17389 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
17390 "*3\r\n:1\r\n:0\r\n:1\r\n"
17391 );
17392 assert_eq!(
17394 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17395 "*3\r\n:11\r\n:1\r\n:6\r\n"
17396 );
17397 assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
17398 assert_eq!(
17399 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
17400 "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
17401 );
17402 assert_eq!(
17405 f.run(&[b"TOPK.LIST", b"t", b"w"]),
17406 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17407 );
17408 assert_eq!(
17409 f.run(&[b"TOPK.LIST", b"t", b""]),
17410 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17411 );
17412 assert_eq!(
17413 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
17414 "-WITHCOUNT keyword expected\r\n"
17415 );
17416 assert_eq!(
17419 f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
17420 "-WITHCOUNT keyword expected\r\n"
17421 );
17422 assert_eq!(
17423 f.run(&[b"TOPK.LIST", b"missing"]),
17424 "-TopK: key does not exist\r\n"
17425 );
17426 f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
17428 assert_eq!(
17429 f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
17430 "*1\r\n$-1\r\n"
17431 );
17432 assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
17433 assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
17434 }
17435
17436 #[test]
17439 fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
17440 let mut f = Fixture::new();
17441 f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
17442 assert_eq!(
17446 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
17447 format!(
17448 "*2\r\n$-1\r\n-{}\r\n",
17449 "TopK: increment must be an integer greater or equal to 0 and smaller or equal to 100,000"
17450 )
17451 );
17452 assert_eq!(
17453 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17454 "*3\r\n:3\r\n:0\r\n:0\r\n"
17455 );
17456 assert_eq!(
17458 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
17459 "*1\r\n$-1\r\n"
17460 );
17461 assert!(
17462 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
17463 .contains("smaller or equal to 100,000")
17464 );
17465 assert!(
17467 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
17468 .contains("wrong number of arguments")
17469 );
17470 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
17471 }
17472
17473 #[test]
17475 fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
17476 let mut f = Fixture::new();
17477 f.run(&[b"HELLO", b"3"]);
17478 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
17479 f.run(&[b"TOPK.ADD", b"t", b"a"]);
17480 assert_eq!(
17481 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
17482 "*2\r\n#t\r\n#f\r\n"
17483 );
17484 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
17486 assert_eq!(
17487 f.run(&[b"TOPK.INFO", b"t"]),
17488 "%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"
17489 );
17490 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
17491 }
17492
17493 #[test]
17496 fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17497 let mut f = Fixture::new();
17498 f.run(&[b"SET", b"s", b"text"]);
17499 for cmd in [
17500 vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
17501 vec![&b"TOPK.ADD"[..], b"s", b"a"],
17502 vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
17503 vec![&b"TOPK.QUERY"[..], b"s", b"a"],
17504 vec![&b"TOPK.COUNT"[..], b"s", b"a"],
17505 vec![&b"TOPK.LIST"[..], b"s"],
17506 vec![&b"TOPK.INFO"[..], b"s"],
17507 ] {
17508 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17509 let reply = f.run(&cmd);
17510 assert!(
17511 reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
17512 "{name}: {reply}"
17513 );
17514 }
17515 f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
17516 assert_eq!(
17517 f.run(&[b"COPY", b"t", b"t2"]),
17518 "-ERR not supported for this module key\r\n"
17519 );
17520 assert_eq!(
17521 f.run(&[b"DUMP", b"t"]),
17522 "-ERR DUMP is not supported for this module key\r\n"
17523 );
17524 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17525 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17526 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17527 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
17528 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17529 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17530 assert_eq!(
17533 f.run(&[b"TOPK.INFO", b"t3"]),
17534 "-TopK: key does not exist\r\n"
17535 );
17536 }
17537
17538 #[test]
17543 fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
17544 let mut f = Fixture::new();
17545 assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
17546 assert_eq!(
17548 f.run(&[b"TDIGEST.INFO", b"t"]),
17549 "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
17550 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
17551 +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
17552 );
17553 assert_eq!(
17554 f.run(&[b"TDIGEST.CREATE", b"t"]),
17555 "-ERR T-Digest: key already exists\r\n"
17556 );
17557 assert!(
17559 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
17560 .contains("wrong number of arguments")
17561 );
17562 assert_eq!(
17563 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
17564 "+OK\r\n"
17565 );
17566 assert_eq!(
17567 f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
17568 "+OK\r\n"
17569 );
17570 assert_eq!(
17574 f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
17575 "-ERR T-Digest: error parsing compression parameter\r\n"
17576 );
17577 assert_eq!(
17578 f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
17579 "-ERR T-Digest: wrong keyword\r\n"
17580 );
17581 assert_eq!(
17582 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
17583 "-ERR T-Digest: error parsing compression parameter\r\n"
17584 );
17585 assert_eq!(
17586 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
17587 "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
17588 );
17589 assert_eq!(
17592 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
17593 "-ERR T-Digest: allocation failed\r\n"
17594 );
17595 assert_eq!(
17597 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
17598 "-ERR T-Digest: allocation failed\r\n"
17599 );
17600 assert_eq!(
17603 f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
17604 "-ERR T-Digest: key already exists\r\n"
17605 );
17606 }
17607
17608 #[test]
17611 fn the_quantile_family_answers_what_the_module_answers() {
17612 let mut f = Fixture::new();
17613 f.run(&[b"TDIGEST.CREATE", b"s"]);
17614 assert_eq!(
17615 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
17616 "+OK\r\n"
17617 );
17618 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
17619 assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
17620 assert_eq!(
17622 f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
17623 "*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"
17624 );
17625 assert_eq!(
17626 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
17627 "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
17628 );
17629 assert_eq!(
17632 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
17633 "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
17634 );
17635 assert_eq!(
17636 f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17637 "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
17638 );
17639 assert_eq!(
17640 f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17641 "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
17642 );
17643 assert_eq!(
17644 f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
17645 "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
17646 );
17647 assert_eq!(
17648 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
17649 "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
17650 );
17651 assert_eq!(
17652 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
17653 "$3\r\n2.5\r\n"
17654 );
17655 assert_eq!(
17656 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
17657 "$3\r\n2.5\r\n"
17658 );
17659 assert_eq!(
17661 f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
17662 "-ERR T-Digest: quantile should be in [0,1]\r\n"
17663 );
17664 assert_eq!(
17665 f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
17666 "-ERR T-Digest: error parsing quantile\r\n"
17667 );
17668 assert_eq!(
17669 f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
17670 "-ERR T-Digest: error parsing cdf\r\n"
17671 );
17672 assert_eq!(
17673 f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
17674 "-ERR T-Digest: error parsing value\r\n"
17675 );
17676 assert_eq!(
17677 f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
17678 "-ERR T-Digest: rank needs to be non negative\r\n"
17679 );
17680 assert_eq!(
17681 f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
17682 "-ERR T-Digest: error parsing rank\r\n"
17683 );
17684 assert_eq!(
17687 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
17688 "-ERR T-Digest: error parsing low_cut_percentile\r\n"
17689 );
17690 assert_eq!(
17691 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
17692 "-ERR T-Digest: error parsing high_cut_percentile\r\n"
17693 );
17694 assert_eq!(
17695 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
17696 "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
17697 );
17698 assert_eq!(
17699 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
17700 "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
17701 );
17702 }
17703
17704 #[test]
17707 fn an_empty_digest_has_an_answer_for_everything() {
17708 let mut f = Fixture::new();
17709 f.run(&[b"TDIGEST.CREATE", b"e"]);
17710 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17711 assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
17712 assert_eq!(
17713 f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
17714 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17715 );
17716 assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
17717 assert_eq!(
17718 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
17719 "$3\r\nnan\r\n"
17720 );
17721 assert_eq!(
17724 f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
17725 "*2\r\n:-2\r\n:-2\r\n"
17726 );
17727 assert_eq!(
17728 f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
17729 "*2\r\n:-2\r\n:-2\r\n"
17730 );
17731 assert_eq!(
17732 f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
17733 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17734 );
17735 f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
17737 assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
17738 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17739 f.run(&[b"TDIGEST.CREATE", b"e2"]);
17742 assert_eq!(
17743 f.run(&[b"TDIGEST.INFO", b"e"]),
17744 f.run(&[b"TDIGEST.INFO", b"e2"])
17745 );
17746 }
17747
17748 #[test]
17751 fn a_sample_is_read_the_way_redis_reads_a_double() {
17752 let mut f = Fixture::new();
17753 f.run(&[b"TDIGEST.CREATE", b"a"]);
17754 for bad in [
17757 &b"nan"[..],
17758 b"1e400",
17759 b"-1e400",
17760 b"1e309",
17761 b"1e-400",
17762 b"",
17763 b" 1",
17764 b"1 ",
17765 b"1e",
17766 b"--1",
17767 ] {
17768 assert_eq!(
17769 f.run(&[b"TDIGEST.ADD", b"a", bad]),
17770 "-ERR T-Digest: error parsing val parameter\r\n",
17771 "{}",
17772 String::from_utf8_lossy(bad)
17773 );
17774 }
17775 for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
17778 assert_eq!(
17779 f.run(&[b"TDIGEST.ADD", b"a", word]),
17780 "-ERR T-Digest: val parameter needs to be a finite number\r\n",
17781 "{}",
17782 String::from_utf8_lossy(word)
17783 );
17784 }
17785 for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
17788 assert_eq!(
17789 f.run(&[b"TDIGEST.ADD", b"a", good]),
17790 "+OK\r\n",
17791 "{}",
17792 String::from_utf8_lossy(good)
17793 );
17794 }
17795 assert!(
17797 f.run(&[b"TDIGEST.INFO", b"a"])
17798 .contains("Observations\r\n:6\r\n")
17799 );
17800 assert_eq!(
17803 f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
17804 "-ERR T-Digest: error parsing val parameter\r\n"
17805 );
17806 assert!(
17807 f.run(&[b"TDIGEST.INFO", b"a"])
17808 .contains("Observations\r\n:6\r\n")
17809 );
17810 }
17811
17812 #[test]
17815 fn a_merge_sweeps_the_destination_between_its_inputs() {
17816 let mut f = Fixture::new();
17817 f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
17818 f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
17819 f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
17820 f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
17821 assert_eq!(
17822 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
17823 "+OK\r\n"
17824 );
17825 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17830 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17831 assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
17832 assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
17833 assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
17834 assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
17835 assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
17836 assert!(
17839 f.run(&[b"TDIGEST.INFO", b"m1"])
17840 .contains("Merged nodes\r\n:3\r\n")
17841 );
17842 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
17845 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17846 assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
17847 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17848 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
17851 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17852 assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
17853 assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
17854 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
17856 assert!(
17857 f.run(&[b"TDIGEST.INFO", b"d"])
17858 .contains("Compression\r\n:500\r\n")
17859 );
17860 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
17862 assert!(
17863 f.run(&[b"TDIGEST.INFO", b"d"])
17864 .contains("Observations\r\n:12\r\n")
17865 );
17866 assert_eq!(
17868 f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
17869 "-ERR T-Digest: error parsing numkeys\r\n"
17870 );
17871 assert_eq!(
17872 f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
17873 "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
17874 );
17875 assert!(
17876 f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
17877 .contains("wrong number of arguments")
17878 );
17879 assert!(
17880 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
17881 .contains("wrong number of arguments")
17882 );
17883 assert_eq!(
17884 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
17885 "-ERR T-Digest: wrong keyword\r\n"
17886 );
17887 assert_eq!(
17890 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
17891 "-ERR T-Digest: key does not exist\r\n"
17892 );
17893 assert!(
17894 f.run(&[b"TDIGEST.INFO", b"d"])
17895 .contains("Observations\r\n:12\r\n")
17896 );
17897 assert_eq!(
17900 f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
17901 "-ERR T-Digest: key does not exist\r\n"
17902 );
17903 }
17904
17905 #[test]
17907 fn a_digest_answers_doubles_and_a_map_on_resp3() {
17908 let mut f = Fixture::new();
17909 f.run(&[b"HELLO", b"3"]);
17910 f.run(&[b"TDIGEST.CREATE", b"s"]);
17911 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
17912 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
17913 assert_eq!(
17914 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
17915 "*2\r\n,1\r\n,4\r\n"
17916 );
17917 assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
17918 assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
17920 assert_eq!(
17921 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
17922 "*1\r\n,-inf\r\n"
17923 );
17924 f.run(&[b"TDIGEST.CREATE", b"e"]);
17925 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
17926 assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
17928 assert_eq!(
17931 f.run(&[b"TDIGEST.INFO", b"s"]),
17932 "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
17933 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
17934 +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
17935 );
17936 }
17937
17938 #[test]
17941 fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
17942 let mut f = Fixture::new();
17943 f.run(&[b"SET", b"s", b"text"]);
17944 for cmd in [
17945 vec![&b"TDIGEST.CREATE"[..], b"s"],
17946 vec![&b"TDIGEST.RESET"[..], b"s"],
17947 vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
17948 vec![&b"TDIGEST.MIN"[..], b"s"],
17949 vec![&b"TDIGEST.MAX"[..], b"s"],
17950 vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
17951 vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
17952 vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
17953 vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
17954 vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
17955 vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
17956 vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
17957 vec![&b"TDIGEST.INFO"[..], b"s"],
17958 ] {
17959 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17960 let reply = f.run(&cmd);
17961 assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
17962 }
17963 f.run(&[b"TDIGEST.CREATE", b"t"]);
17965 assert!(
17966 f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
17967 .starts_with("-WRONGTYPE")
17968 );
17969 assert!(
17970 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
17971 .starts_with("-WRONGTYPE")
17972 );
17973 assert_eq!(
17974 f.run(&[b"COPY", b"t", b"t2"]),
17975 "-ERR not supported for this module key\r\n"
17976 );
17977 assert_eq!(
17978 f.run(&[b"DUMP", b"t"]),
17979 "-ERR DUMP is not supported for this module key\r\n"
17980 );
17981 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17982 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17983 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17984 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
17985 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17986 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17987 assert_eq!(
17990 f.run(&[b"TDIGEST.INFO", b"t3"]),
17991 "-ERR T-Digest: key does not exist\r\n"
17992 );
17993 assert_eq!(
17996 f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
17997 "-ERR T-Digest: key does not exist\r\n"
17998 );
17999 }
18000
18001 fn without_memory(reply: &str) -> String {
18011 let head = "+memoryUsage\r\n:";
18012 let at = reply.find(head).expect("every TS.INFO reports memory");
18013 let rest = &reply[at + head.len()..];
18014 let end = rest.find("\r\n").expect("and it is a whole number");
18015 format!("{}{}", &reply[..at + head.len()], &rest[end..])
18016 }
18017
18018 #[test]
18021 fn a_series_is_made_empty_and_reports_on_itself() {
18022 let mut f = Fixture::new();
18023 assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
18024 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18025 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
18026 assert_eq!(
18030 without_memory(&f.run(&[b"TS.INFO", b"t"])),
18031 "*28\r\n\
18032 +totalSamples\r\n:0\r\n\
18033 +memoryUsage\r\n:\r\n\
18034 +firstTimestamp\r\n:0\r\n\
18035 +lastTimestamp\r\n:0\r\n\
18036 +retentionTime\r\n:0\r\n\
18037 +chunkCount\r\n:1\r\n\
18038 +chunkSize\r\n:4096\r\n\
18039 +chunkType\r\n+compressed\r\n\
18040 +duplicatePolicy\r\n+block\r\n\
18041 +labels\r\n*0\r\n\
18042 +sourceKey\r\n$-1\r\n\
18043 +rules\r\n*0\r\n\
18044 +ignoreMaxTimeDiff\r\n:0\r\n\
18045 +ignoreMaxValDiff\r\n$1\r\n0\r\n"
18046 );
18047 assert_eq!(
18050 f.run(&[b"TS.CREATE", b"t"]),
18051 "-ERR TSDB: key already exists\r\n"
18052 );
18053 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18054 assert_eq!(
18055 f.run(&[b"TS.CREATE", b"str"]),
18056 "-ERR TSDB: key already exists\r\n"
18057 );
18058 assert_eq!(
18061 f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
18062 "-ERR TSDB: Couldn't parse RETENTION\r\n"
18063 );
18064 let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
18070 assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
18071 assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
18072 assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
18073 assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
18074 assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
18075 assert_eq!(
18076 f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
18077 "-ERR TSDB: the key is not a TSDB key\r\n"
18078 );
18079 assert_eq!(
18081 f.run(&[b"TS.INFO", b"nope"]),
18082 "-ERR TSDB: the key does not exist\r\n"
18083 );
18084 assert_eq!(
18085 f.run(&[b"TS.GET", b"nope"]),
18086 "-ERR TSDB: the key does not exist\r\n"
18087 );
18088 assert_eq!(
18089 f.run(&[b"TS.ALTER", b"nope"]),
18090 "-ERR TSDB: the key does not exist\r\n"
18091 );
18092 assert_eq!(
18093 f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
18094 "-ERR TSDB: the key does not exist\r\n"
18095 );
18096 }
18097
18098 #[test]
18101 fn the_options_are_a_keyword_scan_and_not_a_grammar() {
18102 let mut f = Fixture::new();
18103 assert_eq!(
18104 f.run(&[
18105 b"TS.CREATE",
18106 b"t",
18107 b"RETENTION",
18108 b"5000",
18109 b"ENCODING",
18110 b"UNCOMPRESSED",
18111 b"CHUNK_SIZE",
18112 b"128",
18113 b"DUPLICATE_POLICY",
18114 b"LAST",
18115 b"IGNORE",
18116 b"10",
18117 b"0.5",
18118 b"LABELS",
18119 b"room",
18120 b"kitchen"
18121 ]),
18122 "+OK\r\n"
18123 );
18124 let info = f.run(&[b"TS.INFO", b"t"]);
18125 assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
18126 assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
18127 assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
18128 assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
18129 assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
18130 assert!(
18133 info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
18134 "{info}"
18135 );
18136 assert!(
18137 info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
18138 "{info}"
18139 );
18140
18141 assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
18143 assert_eq!(
18147 f.run(&[
18148 b"TS.CREATE",
18149 b"g",
18150 b"LABELS",
18151 b"a",
18152 b"b",
18153 b"RETENTION",
18154 b"5"
18155 ]),
18156 "+OK\r\n"
18157 );
18158 let greedy = f.run(&[b"TS.INFO", b"g"]);
18159 assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
18160 assert!(
18161 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"),
18162 "{greedy}"
18163 );
18164
18165 assert_eq!(
18167 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
18168 "-ERR TSDB: Couldn't parse LABELS\r\n"
18169 );
18170 assert_eq!(
18171 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
18172 "-ERR TSDB: Couldn't parse LABELS\r\n"
18173 );
18174 assert_eq!(
18175 f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
18176 "-ERR TSDB: Couldn't parse RETENTION\r\n"
18177 );
18178 assert_eq!(
18181 f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
18182 "-TSDB: Couldn't parse RETENTION\r\n"
18183 );
18184 assert_eq!(
18185 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
18186 "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
18187 );
18188 assert_eq!(
18189 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
18190 "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
18191 );
18192 assert_eq!(
18193 f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
18194 "-ERR TSDB: unknown ENCODING parameter\r\n"
18195 );
18196 assert!(
18199 f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
18200 .contains("wrong number of arguments for 'ts.create' command")
18201 );
18202 assert_eq!(
18203 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
18204 "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
18205 );
18206 assert_eq!(
18207 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
18208 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18209 );
18210 assert_eq!(
18211 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
18212 "-ERR TSDB: Couldn't parse IGNORE\r\n"
18213 );
18214 assert_eq!(
18215 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
18216 "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
18217 );
18218 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
18219
18220 assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
18223 let after = f.run(&[b"TS.INFO", b"t"]);
18224 assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
18225 assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
18226 assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
18227 assert_eq!(
18228 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
18229 "-ERR TSDB: unknown ENCODING parameter\r\n"
18230 );
18231 assert_eq!(
18233 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
18234 "+OK\r\n"
18235 );
18236 assert!(
18237 f.run(&[b"TS.INFO", b"t"])
18238 .contains("+chunkType\r\n+uncompressed\r\n")
18239 );
18240 }
18241
18242 #[test]
18245 fn samples_land_where_they_are_put_and_the_newest_comes_back() {
18246 let mut f = Fixture::new();
18247 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
18248 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18250 assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
18251 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
18254 assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
18255 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
18256 assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
18259 assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
18260
18261 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18264 assert_eq!(
18265 f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
18266 "-ERR TSDB: invalid value\r\n"
18267 );
18268 for bad in [
18272 &b".5"[..],
18273 b"1.",
18274 b"+1",
18275 b" 1",
18276 b"0x10",
18277 b"inf",
18278 b"1e400",
18279 b"--1",
18280 b"1e",
18281 ] {
18282 assert_eq!(
18283 f.run(&[b"TS.ADD", b"v", b"1", bad]),
18284 "-ERR TSDB: invalid value\r\n",
18285 "{}",
18286 String::from_utf8_lossy(bad)
18287 );
18288 }
18289 assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
18291
18292 assert_eq!(
18295 f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
18296 "-ERR TSDB: invalid timestamp\r\n"
18297 );
18298 assert_eq!(
18299 f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
18300 "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
18301 );
18302
18303 assert_eq!(
18306 f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
18307 "-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"
18308 );
18309 assert_eq!(
18310 f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
18311 ":300\r\n"
18312 );
18313 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
18314 assert_eq!(
18317 f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
18318 ":1\r\n"
18319 );
18320 assert_eq!(
18321 f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
18322 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18323 );
18324
18325 assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
18328 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
18329 assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
18330 assert_eq!(
18331 f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
18332 "-ERR TSDB: Timestamp is older than retention\r\n"
18333 );
18334 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
18336 assert!(
18337 f.run(&[b"TS.INFO", b"r"])
18338 .contains("+totalSamples\r\n:1\r\n")
18339 );
18340
18341 assert_eq!(
18344 f.run(&[
18345 b"TS.CREATE",
18346 b"i",
18347 b"DUPLICATE_POLICY",
18348 b"LAST",
18349 b"IGNORE",
18350 b"10",
18351 b"0.5"
18352 ]),
18353 "+OK\r\n"
18354 );
18355 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
18356 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
18357 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
18358 }
18359
18360 #[test]
18363 fn a_madd_answers_each_triple_and_creates_nothing() {
18364 let mut f = Fixture::new();
18365 assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
18366 assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
18367 assert_eq!(
18368 f.run(&[
18369 b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
18370 ]),
18371 "*3\r\n:100\r\n:100\r\n:200\r\n"
18372 );
18373 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18376 assert_eq!(
18377 f.run(&[
18378 b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
18379 ]),
18380 "*3\r\n\
18381 -ERR TSDB: the key is not a TSDB key\r\n\
18382 -ERR TSDB: the key is not a TSDB key\r\n\
18383 :300\r\n"
18384 );
18385 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
18386 assert_eq!(
18388 f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
18389 "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
18390 );
18391 assert!(
18393 f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
18394 .contains("wrong number of arguments for 'ts.madd' command")
18395 );
18396 }
18397
18398 #[test]
18400 fn an_increment_walks_the_newest_value_up_and_down() {
18401 let mut f = Fixture::new();
18402 assert_eq!(
18403 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18404 ":100\r\n"
18405 );
18406 assert_eq!(
18407 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18408 ":100\r\n"
18409 );
18410 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
18413 assert_eq!(
18414 f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
18415 ":200\r\n"
18416 );
18417 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
18418 assert_eq!(
18421 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
18422 "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
18423 );
18424 assert_eq!(
18428 f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
18429 ":1\r\n"
18430 );
18431 assert_eq!(
18432 f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
18433 ":1\r\n"
18434 );
18435 assert_eq!(
18436 f.run(&[b"TS.INCRBY", b"t", b"nan"]),
18437 "-ERR TSDB: invalid increase/decrease value\r\n"
18438 );
18439 assert_eq!(
18440 f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
18441 "-ERR TSDB: invalid increase/decrease value\r\n"
18442 );
18443 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18446 assert_eq!(
18447 f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
18448 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
18449 );
18450 assert_eq!(
18455 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
18456 "-ERR TSDB: invalid timestamp\r\n"
18457 );
18458 assert_eq!(
18461 f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
18462 format!(":{}\r\n", f.server.now_ms())
18463 );
18464 assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
18466 assert_eq!(
18467 f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
18468 "-ERR TSDB: cannot increment/decrement NaN value\r\n"
18469 );
18470 }
18471
18472 #[test]
18474 fn deleting_takes_out_a_span_and_answers_how_many_went() {
18475 let mut f = Fixture::new();
18476 for at in [b"100".as_slice(), b"200", b"300", b"400"] {
18477 f.run(&[b"TS.ADD", b"t", at, b"1"]);
18478 }
18479 assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
18480 assert!(
18481 f.run(&[b"TS.INFO", b"t"])
18482 .contains("+totalSamples\r\n:2\r\n")
18483 );
18484 assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
18486 assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
18488 let empty = f.run(&[b"TS.INFO", b"t"]);
18491 assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
18492 assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
18493 assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
18494 assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
18495 assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
18496 assert_eq!(
18498 f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
18499 "-ERR TSDB: wrong fromTimestamp\r\n"
18500 );
18501 assert_eq!(
18502 f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
18503 "-ERR TSDB: wrong toTimestamp\r\n"
18504 );
18505 assert_eq!(
18506 f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
18507 "-ERR TSDB: wrong fromTimestamp\r\n"
18508 );
18509 }
18510
18511 #[test]
18514 fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
18515 let mut f = Fixture::new();
18516 f.out = Out::new(Proto::Resp3);
18517 assert_eq!(
18518 f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
18519 "+OK\r\n"
18520 );
18521 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
18522 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
18524 assert_eq!(
18525 without_memory(&f.run(&[b"TS.INFO", b"t"])),
18526 "%14\r\n\
18527 +totalSamples\r\n:1\r\n\
18528 +memoryUsage\r\n:\r\n\
18529 +firstTimestamp\r\n:100\r\n\
18530 +lastTimestamp\r\n:100\r\n\
18531 +retentionTime\r\n:0\r\n\
18532 +chunkCount\r\n:1\r\n\
18533 +chunkSize\r\n:4096\r\n\
18534 +chunkType\r\n+compressed\r\n\
18535 +duplicatePolicy\r\n+block\r\n\
18536 +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18537 +sourceKey\r\n_\r\n\
18538 +rules\r\n%0\r\n\
18539 +ignoreMaxTimeDiff\r\n:0\r\n\
18540 +ignoreMaxValDiff\r\n,0\r\n"
18541 );
18542 }
18543
18544 #[test]
18547 fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
18548 let mut f = Fixture::new();
18549 for (at, v) in [
18550 (b"100".as_slice(), b"1".as_slice()),
18551 (b"200", b"2"),
18552 (b"300", b"3"),
18553 (b"400", b"4"),
18554 ] {
18555 f.run(&[b"TS.ADD", b"t", at, v]);
18556 }
18557 assert_eq!(
18558 f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
18559 "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
18560 *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
18561 );
18562 assert_eq!(
18564 f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
18565 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18566 );
18567 assert_eq!(
18570 f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
18571 "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
18572 );
18573 assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
18575 assert_eq!(
18577 f.run(&[
18578 b"TS.RANGE",
18579 b"t",
18580 b"-",
18581 b"+",
18582 b"FILTER_BY_VALUE",
18583 b"2",
18584 b"3"
18585 ]),
18586 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18587 );
18588 assert_eq!(
18589 f.run(&[
18590 b"TS.RANGE",
18591 b"t",
18592 b"-",
18593 b"+",
18594 b"FILTER_BY_TS",
18595 b"100",
18596 b"400"
18597 ]),
18598 "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
18599 );
18600 assert_eq!(
18602 f.run(&[
18603 b"TS.RANGE",
18604 b"t",
18605 b"-",
18606 b"+",
18607 b"ZZZ",
18608 b"FILTER_BY_TS",
18609 b"400"
18610 ]),
18611 "*1\r\n*2\r\n:400\r\n+4\r\n"
18612 );
18613 assert_eq!(
18615 f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
18616 "*1\r\n*2\r\n:100\r\n+1\r\n"
18617 );
18618 }
18619
18620 #[test]
18622 fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
18623 let mut f = Fixture::new();
18624 for (at, v) in [
18625 (b"100".as_slice(), b"1".as_slice()),
18626 (b"200", b"2"),
18627 (b"300", b"3"),
18628 (b"400", b"4"),
18629 ] {
18630 f.run(&[b"TS.ADD", b"t", at, v]);
18631 }
18632 assert_eq!(
18633 f.run(&[
18634 b"TS.RANGE",
18635 b"t",
18636 b"-",
18637 b"+",
18638 b"AGGREGATION",
18639 b"avg",
18640 b"200"
18641 ]),
18642 "*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"
18643 );
18644 assert_eq!(
18647 f.run(&[
18648 b"TS.RANGE",
18649 b"t",
18650 b"-",
18651 b"+",
18652 b"AGGREGATION",
18653 b"min,max,count",
18654 b"200"
18655 ]),
18656 "*3\r\n\
18657 *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
18658 *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
18659 *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
18660 );
18661 assert_eq!(
18663 f.run(&[
18664 b"TS.RANGE",
18665 b"t",
18666 b"-",
18667 b"+",
18668 b"AGGREGATION",
18669 b"avg",
18670 b"200",
18671 b"BUCKETTIMESTAMP",
18672 b"+"
18673 ]),
18674 "*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"
18675 );
18676 assert_eq!(
18678 f.run(&[
18679 b"TS.RANGE",
18680 b"t",
18681 b"100",
18682 b"400",
18683 b"ALIGN",
18684 b"100",
18685 b"AGGREGATION",
18686 b"sum",
18687 b"200"
18688 ]),
18689 "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
18690 );
18691 assert_eq!(
18694 f.run(&[
18695 b"TS.RANGE",
18696 b"t",
18697 b"-",
18698 b"+",
18699 b"AGGREGATION",
18700 b"count",
18701 b"200"
18702 ]),
18703 "*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"
18704 );
18705 assert_eq!(
18706 f.run(&[
18707 b"TS.RANGE",
18708 b"t",
18709 b"-",
18710 b"+",
18711 b"AGGREGATION",
18712 b"count",
18713 b"200",
18714 b"COUNT",
18715 b"1"
18716 ]),
18717 "*1\r\n*2\r\n:0\r\n+1\r\n"
18718 );
18719 }
18720
18721 #[test]
18724 fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
18725 let mut f = Fixture::new();
18726 for (at, v) in [
18727 (b"0".as_slice(), b"1".as_slice()),
18728 (b"100", b"2"),
18729 (b"500", b"nan"),
18730 (b"600", b"3"),
18731 ] {
18732 f.run(&[b"TS.ADD", b"g", at, v]);
18733 }
18734 assert_eq!(
18737 f.run(&[
18738 b"TS.RANGE",
18739 b"g",
18740 b"-",
18741 b"+",
18742 b"AGGREGATION",
18743 b"avg",
18744 b"100"
18745 ]),
18746 "*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"
18747 );
18748 assert_eq!(
18750 f.run(&[
18751 b"TS.RANGE",
18752 b"g",
18753 b"-",
18754 b"+",
18755 b"AGGREGATION",
18756 b"sum",
18757 b"100",
18758 b"EMPTY"
18759 ]),
18760 "*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\
18761 *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\
18762 *2\r\n:600\r\n+3\r\n"
18763 );
18764 assert_eq!(
18769 f.run(&[
18770 b"TS.RANGE",
18771 b"g",
18772 b"-",
18773 b"+",
18774 b"AGGREGATION",
18775 b"last",
18776 b"100",
18777 b"EMPTY"
18778 ]),
18779 "*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\
18780 *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\
18781 *2\r\n:600\r\n+3\r\n"
18782 );
18783 assert_eq!(
18784 f.run(&[
18785 b"TS.REVRANGE",
18786 b"g",
18787 b"-",
18788 b"+",
18789 b"AGGREGATION",
18790 b"last",
18791 b"100",
18792 b"EMPTY"
18793 ]),
18794 "*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\
18795 *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\
18796 *2\r\n:0\r\n+1\r\n"
18797 );
18798 assert_eq!(
18801 f.run(&[
18802 b"TS.RANGE",
18803 b"g",
18804 b"500",
18805 b"600",
18806 b"AGGREGATION",
18807 b"last",
18808 b"100",
18809 b"EMPTY"
18810 ]),
18811 "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
18812 );
18813 }
18814
18815 #[test]
18818 fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
18819 let mut f = Fixture::new();
18820 f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
18821 f.run(&[b"SET", b"str", b"x"]);
18822 let cases: &[(&[&[u8]], &str)] = &[
18823 (
18824 &[b"TS.RANGE", b"t"],
18825 "-ERR wrong number of arguments for 'ts.range' command\r\n",
18826 ),
18827 (
18829 &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
18830 "-ERR TSDB: the key does not exist\r\n",
18831 ),
18832 (
18833 &[b"TS.RANGE", b"str", b"-", b"+"],
18834 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
18835 ),
18836 (
18837 &[b"TS.RANGE", b"t", b"abc", b"+"],
18838 "-ERR TSDB: wrong fromTimestamp\r\n",
18839 ),
18840 (
18841 &[b"TS.RANGE", b"t", b"-", b"abc"],
18842 "-ERR TSDB: wrong toTimestamp\r\n",
18843 ),
18844 (
18845 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
18846 "-ERR TSDB: COUNT argument is missing\r\n",
18847 ),
18848 (
18849 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
18850 "-ERR TSDB: Couldn't parse COUNT\r\n",
18851 ),
18852 (
18853 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
18854 "-ERR TSDB: Invalid COUNT value\r\n",
18855 ),
18856 (
18857 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
18858 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18859 ),
18860 (
18861 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
18862 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18863 ),
18864 (
18865 &[
18866 b"TS.RANGE",
18867 b"t",
18868 b"-",
18869 b"+",
18870 b"AGGREGATION",
18871 b"nope",
18872 b"100",
18873 ],
18874 "-ERR TSDB: Unknown aggregation type\r\n",
18875 ),
18876 (
18877 &[
18878 b"TS.RANGE",
18879 b"t",
18880 b"-",
18881 b"+",
18882 b"AGGREGATION",
18883 b"avg,,min",
18884 b"100",
18885 ],
18886 "-ERR TSDB: Empty aggregation type in list\r\n",
18887 ),
18888 (
18890 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
18891 "-ERR TSDB: Unknown aggregation type\r\n",
18892 ),
18893 (
18894 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
18895 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
18896 ),
18897 (
18898 &[
18899 b"TS.RANGE",
18900 b"t",
18901 b"-",
18902 b"+",
18903 b"AGGREGATION",
18904 b"avg",
18905 b"100",
18906 b"X",
18907 b"EMPTY",
18908 ],
18909 "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
18910 ),
18911 (
18912 &[
18913 b"TS.RANGE",
18914 b"t",
18915 b"-",
18916 b"+",
18917 b"AGGREGATION",
18918 b"avg",
18919 b"100",
18920 b"BUCKETTIMESTAMP",
18921 b"z",
18922 ],
18923 "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
18924 ),
18925 (
18926 &[
18927 b"TS.RANGE",
18928 b"t",
18929 b"-",
18930 b"+",
18931 b"AGGREGATION",
18932 b"avg",
18933 b"100",
18934 b"X",
18935 b"Y",
18936 b"BUCKETTIMESTAMP",
18937 b"-",
18938 ],
18939 "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
18940 AGGREGATION flag\r\n",
18941 ),
18942 (
18943 &[
18944 b"TS.RANGE",
18945 b"t",
18946 b"-",
18947 b"+",
18948 b"ALIGN",
18949 b"z",
18950 b"AGGREGATION",
18951 b"avg",
18952 b"100",
18953 ],
18954 "-ERR TSDB: unknown ALIGN parameter\r\n",
18955 ),
18956 (
18957 &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
18958 "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
18959 ),
18960 (
18961 &[
18962 b"TS.RANGE",
18963 b"t",
18964 b"-",
18965 b"+",
18966 b"ALIGN",
18967 b"-",
18968 b"AGGREGATION",
18969 b"avg",
18970 b"100",
18971 ],
18972 "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
18973 ),
18974 (
18975 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
18976 "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
18977 ),
18978 (
18979 &[
18980 b"TS.RANGE",
18981 b"t",
18982 b"-",
18983 b"+",
18984 b"FILTER_BY_VALUE",
18985 b"x",
18986 b"2",
18987 ],
18988 "-ERR TSDB: Couldn't parse MIN\r\n",
18989 ),
18990 (
18991 &[
18992 b"TS.RANGE",
18993 b"t",
18994 b"-",
18995 b"+",
18996 b"FILTER_BY_VALUE",
18997 b"1",
18998 b"y",
18999 ],
19000 "-ERR TSDB: Couldn't parse MAX\r\n",
19001 ),
19002 (
19003 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
19004 "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
19005 ),
19006 ];
19007 for (argv, want) in cases {
19008 let got = f.run(argv);
19009 assert_eq!(&got, want, "{:?}", argv.last());
19010 }
19011 f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
19015 f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
19016 assert_eq!(
19017 f.run(&[
19018 b"TS.RANGE",
19019 b"wide",
19020 b"-",
19021 b"+",
19022 b"AGGREGATION",
19023 b"avg",
19024 b"1",
19025 b"EMPTY"
19026 ]),
19027 "-ERR TSDB: the requested range holds too many empty buckets\r\n"
19028 );
19029 }
19030
19031 #[test]
19033 fn resp3_writes_a_read_value_as_a_double() {
19034 let mut f = Fixture::new();
19035 f.out = Out::new(Proto::Resp3);
19036 for (at, v) in [
19037 (b"0".as_slice(), b"1".as_slice()),
19038 (b"100", b"2"),
19039 (b"500", b"nan"),
19040 (b"600", b"3"),
19041 ] {
19042 f.run(&[b"TS.ADD", b"g", at, v]);
19043 }
19044 assert_eq!(
19045 f.run(&[
19046 b"TS.RANGE",
19047 b"g",
19048 b"0",
19049 b"100",
19050 b"AGGREGATION",
19051 b"avg,min",
19052 b"200"
19053 ]),
19054 "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
19055 );
19056 assert_eq!(
19057 f.run(&[
19058 b"TS.RANGE",
19059 b"g",
19060 b"500",
19061 b"600",
19062 b"AGGREGATION",
19063 b"last",
19064 b"100",
19065 b"EMPTY"
19066 ]),
19067 "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
19068 );
19069 }
19070
19071 fn joined() -> Fixture {
19074 let mut f = Fixture::new();
19075 f.run(&[b"TS.CREATE", b"z"]);
19076 for (at, v) in [
19077 (b"10".as_slice(), b"1".as_slice()),
19078 (b"20", b"2"),
19079 (b"40", b"4"),
19080 (b"50", b"5"),
19081 ] {
19082 f.run(&[b"TS.ADD", b"x", at, v]);
19083 }
19084 for (at, v) in [
19085 (b"20".as_slice(), b"20".as_slice()),
19086 (b"30", b"30"),
19087 (b"50", b"50"),
19088 (b"60", b"60"),
19089 ] {
19090 f.run(&[b"TS.ADD", b"y", at, v]);
19091 }
19092 f
19093 }
19094
19095 #[test]
19099 fn an_nrange_joins_its_keys_on_the_timestamp() {
19100 let mut f = joined();
19101 assert_eq!(
19103 f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
19104 "*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\
19105 *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
19106 );
19107 assert_eq!(
19109 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
19110 "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
19111 *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19112 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19113 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19114 *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
19115 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19116 );
19117 assert_eq!(
19120 f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
19121 "*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"
19122 );
19123 assert_eq!(
19124 f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
19125 "*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"
19126 );
19127 assert_eq!(
19130 f.run(&[
19131 b"TS.NREVRANGE",
19132 b"2",
19133 b"x",
19134 b"y",
19135 b"-",
19136 b"+",
19137 b"COUNT",
19138 b"1"
19139 ]),
19140 "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19141 );
19142 assert_eq!(
19143 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
19144 "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
19145 );
19146 assert_eq!(
19148 f.run(&[
19149 b"TS.NRANGE",
19150 b"2",
19151 b"x",
19152 b"y",
19153 b"-",
19154 b"+",
19155 b"FILTER_BY_VALUE",
19156 b"2",
19157 b"30"
19158 ]),
19159 "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19160 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19161 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19162 *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
19163 );
19164 }
19165
19166 #[test]
19170 fn an_nrange_aggregation_names_one_reduction_a_key() {
19171 let mut f = joined();
19172 assert_eq!(
19173 f.run(&[
19174 b"TS.NRANGE",
19175 b"2",
19176 b"x",
19177 b"y",
19178 b"-",
19179 b"+",
19180 b"AGGREGATION",
19181 b"sum",
19182 b"sum",
19183 b"20"
19184 ]),
19185 "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
19186 *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
19187 *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
19188 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19189 );
19190 assert_eq!(
19192 f.run(&[
19193 b"TS.NRANGE",
19194 b"2",
19195 b"x",
19196 b"y",
19197 b"-",
19198 b"+",
19199 b"AGGREGATION",
19200 b"sum,count",
19201 b"avg",
19202 b"20"
19203 ]),
19204 "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
19205 *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
19206 *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
19207 *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
19208 );
19209 assert_eq!(
19212 f.run(&[
19213 b"TS.NRANGE",
19214 b"2",
19215 b"x",
19216 b"y",
19217 b"-",
19218 b"+",
19219 b"AGGREGATION",
19220 b"avg",
19221 b"sum",
19222 b"100",
19223 b"EMPTY",
19224 b"BUCKETTIMESTAMP",
19225 b"end"
19226 ]),
19227 "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
19228 );
19229 assert_eq!(
19232 f.run(&[
19233 b"TS.NRANGE",
19234 b"2",
19235 b"x",
19236 b"y",
19237 b"-",
19238 b"+",
19239 b"AGGREGATION",
19240 b"avg",
19241 b"COUNT",
19242 b"100"
19243 ]),
19244 "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
19245 );
19246 }
19247
19248 #[test]
19251 fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
19252 let mut f = joined();
19253 f.run(&[b"SET", b"str", b"hi"]);
19254 let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
19255 let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
19256 must be equal to numkeys\r\n";
19257 let cases: &[(&[&[u8]], &str)] = &[
19258 (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
19259 (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
19260 (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
19261 (
19264 &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
19265 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19266 ),
19267 (
19268 &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
19269 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19270 ),
19271 (
19274 &[
19275 b"TS.NRANGE",
19276 b"2",
19277 b"x",
19278 b"y",
19279 b"abc",
19280 b"+",
19281 b"AGGREGATION",
19282 b"nope",
19283 b"sum",
19284 b"100",
19285 ],
19286 "-ERR TSDB: Unknown aggregation type\r\n",
19287 ),
19288 (
19289 &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
19290 "-ERR TSDB: wrong fromTimestamp\r\n",
19291 ),
19292 (
19293 &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
19294 "-ERR TSDB: wrong toTimestamp\r\n",
19295 ),
19296 (
19300 &[
19301 b"TS.NRANGE",
19302 b"2",
19303 b"x",
19304 b"y",
19305 b"-",
19306 b"+",
19307 b"AGGREGATION",
19308 b"avg",
19309 ],
19310 numkeys,
19311 ),
19312 (
19313 &[
19314 b"TS.NRANGE",
19315 b"2",
19316 b"x",
19317 b"y",
19318 b"-",
19319 b"+",
19320 b"AGGREGATION",
19321 b"100",
19322 b"sum",
19323 b"100",
19324 ],
19325 numkeys,
19326 ),
19327 (
19328 &[
19329 b"TS.NRANGE",
19330 b"2",
19331 b"x",
19332 b"y",
19333 b"-",
19334 b"+",
19335 b"AGGREGATION",
19336 b"avg",
19337 b"sum",
19338 b"sum",
19339 b"100",
19340 ],
19341 numkeys,
19342 ),
19343 (
19344 &[
19345 b"TS.NRANGE",
19346 b"2",
19347 b"x",
19348 b"y",
19349 b"-",
19350 b"+",
19351 b"AGGREGATION",
19352 b"avg",
19353 b"sum",
19354 b"abc",
19355 ],
19356 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19357 ),
19358 (
19359 &[
19360 b"TS.NRANGE",
19361 b"2",
19362 b"x",
19363 b"y",
19364 b"-",
19365 b"+",
19366 b"AGGREGATION",
19367 b"avg",
19368 b"sum",
19369 b"0",
19370 ],
19371 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19372 ),
19373 (
19376 &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
19377 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19378 ),
19379 (
19380 &[
19381 b"TS.NRANGE",
19382 b"1",
19383 b"x",
19384 b"-",
19385 b"+",
19386 b"AGGREGATION",
19387 b"100",
19388 b"200",
19389 ],
19390 "-ERR TSDB: Unknown aggregation type\r\n",
19391 ),
19392 (
19394 &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
19395 "-ERR TSDB: the key does not exist\r\n",
19396 ),
19397 (
19398 &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
19399 "-ERR WRONGTYPE Operation against a key \
19400 holding the wrong kind of value\r\n",
19401 ),
19402 ];
19403 for (argv, want) in cases {
19404 let got = f.run(argv);
19405 assert_eq!(&got, want, "{argv:?}");
19406 }
19407 }
19408
19409 #[test]
19411 fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
19412 let mut f = joined();
19413 assert_eq!(
19414 f.run(&[b"TS.READ", b"x", b"-"]),
19415 "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
19416 *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19417 );
19418 assert_eq!(
19421 f.run(&[b"TS.READ", b"x", b"+"]),
19422 "*1\r\n*2\r\n:50\r\n+5\r\n"
19423 );
19424 assert_eq!(
19425 f.run(&[b"TS.READ", b"x", b"25"]),
19426 "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19427 );
19428 assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
19431 assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
19432 assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
19433 assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
19434 assert_eq!(
19438 f.run(&[b"TS.READ", b"x", b"abc"]),
19439 "-TSDB: invalid timestamp\r\n"
19440 );
19441 assert_eq!(
19442 f.run(&[b"TS.READ", b"x", b"-1"]),
19443 "-TSDB: invalid timestamp\r\n"
19444 );
19445 f.run(&[b"SET", b"str", b"hi"]);
19446 assert_eq!(
19447 f.run(&[b"TS.READ", b"str", b"-"]),
19448 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19449 );
19450 assert_eq!(
19453 f.run(&[b"TS.READ", b"x"]),
19454 "-ERR wrong number of arguments for 'ts.read' command\r\n"
19455 );
19456 assert_eq!(
19457 f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
19458 "-ERR wrong number of arguments for 'ts.read' command\r\n"
19459 );
19460 }
19461
19462 #[test]
19465 fn getkeys_reads_the_count_of_a_joined_read() {
19466 let mut f = Fixture::new();
19467 assert_eq!(
19468 f.run(&[
19469 b"COMMAND",
19470 b"GETKEYS",
19471 b"TS.NRANGE",
19472 b"2",
19473 b"a",
19474 b"b",
19475 b"-",
19476 b"+"
19477 ]),
19478 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
19479 );
19480 assert_eq!(
19481 f.run(&[
19482 b"COMMAND",
19483 b"GETKEYS",
19484 b"TS.NREVRANGE",
19485 b"1",
19486 b"a",
19487 b"-",
19488 b"+"
19489 ]),
19490 "*1\r\n$1\r\na\r\n"
19491 );
19492 for n in [b"0".as_slice(), b"9", b"abc"] {
19495 assert_eq!(
19496 f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
19497 "-ERR Invalid arguments specified for command\r\n"
19498 );
19499 }
19500 }
19501
19502 fn labelled() -> Fixture {
19504 let mut f = Fixture::new();
19505 f.run(&[
19506 b"TS.CREATE",
19507 b"a",
19508 b"LABELS",
19509 b"room",
19510 b"kitchen",
19511 b"x",
19512 b"1",
19513 ]);
19514 f.run(&[
19515 b"TS.CREATE",
19516 b"b",
19517 b"LABELS",
19518 b"room",
19519 b"bedroom",
19520 b"x",
19521 b"2",
19522 ]);
19523 f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
19524 f.run(&[b"TS.CREATE", b"d"]);
19525 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
19526 f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
19527 f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
19528 f
19529 }
19530
19531 #[test]
19534 fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
19535 let mut f = labelled();
19536 let cases: &[(&[&[u8]], &str)] = &[
19537 (
19540 &[b"TS.QUERYINDEX", b"room=kitchen"],
19541 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19542 ),
19543 (
19544 &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
19545 "*1\r\n$1\r\na\r\n",
19546 ),
19547 (
19548 &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
19549 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
19550 ),
19551 (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
19554 (
19556 &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
19557 "*1\r\n$1\r\nc\r\n",
19558 ),
19559 (
19560 &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
19561 "*1\r\n$1\r\na\r\n",
19562 ),
19563 (
19564 &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
19565 "-ERR TSDB: please provide at least one matcher\r\n",
19566 ),
19567 (
19570 &[b"TS.QUERYINDEX", b"room==kitchen"],
19571 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19572 ),
19573 (
19574 &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
19575 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19576 ),
19577 (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
19578 (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
19581 (
19582 &[b"TS.QUERYINDEX", b"=(1)"],
19583 "-ERR TSDB: failed parsing labels\r\n",
19584 ),
19585 (
19586 &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
19587 "-ERR TSDB: failed parsing labels\r\n",
19588 ),
19589 (
19590 &[b"TS.QUERYINDEX", b"room=(kitchen"],
19591 "-ERR TSDB: failed parsing labels\r\n",
19592 ),
19593 (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
19594 (
19595 &[b"TS.QUERYINDEX", b"nonsense"],
19596 "-ERR TSDB: failed parsing labels\r\n",
19597 ),
19598 (
19600 &[b"TS.QUERYINDEX", b"room!=kitchen"],
19601 "-ERR TSDB: please provide at least one matcher\r\n",
19602 ),
19603 (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
19605 (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
19606 (
19607 &[b"TS.QUERYINDEX"],
19608 "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
19609 ),
19610 ];
19611 for (argv, want) in cases {
19612 let got = f.run(argv);
19613 assert_eq!(&got, want, "{:?}", argv.last());
19614 }
19615 }
19616
19617 #[test]
19619 fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
19620 let mut f = labelled();
19621 let cases: &[(&[&[u8]], &str)] = &[
19622 (
19623 &[b"TS.QUERYLABELS", b"LABELS"],
19624 "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19625 ),
19626 (
19627 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
19628 "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19629 ),
19630 (
19631 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19632 "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19633 ),
19634 (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
19637 (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
19638 (
19639 &[b"TS.QUERYLABELS", b"VALUES"],
19640 "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
19641 ),
19642 (
19643 &[b"TS.QUERYLABELS", b"ZZZ"],
19644 "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
19645 ),
19646 (
19647 &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
19648 "-ERR TSDB: unknown argument, expected FILTER\r\n",
19649 ),
19650 (
19651 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
19652 "-ERR TSDB: FILTER given with no filter expressions\r\n",
19653 ),
19654 (
19658 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
19659 "-ERR TSDB: please provide at least one matcher\r\n",
19660 ),
19661 (
19662 &[
19663 b"TS.QUERYLABELS",
19664 b"LABELS",
19665 b"FILTER",
19666 b"room=kitchen",
19667 b"x=",
19668 ],
19669 "*1\r\n$4\r\nroom\r\n",
19670 ),
19671 ];
19672 for (argv, want) in cases {
19673 let got = f.run(argv);
19674 assert_eq!(&got, want, "{:?}", argv.last());
19675 }
19676 }
19677
19678 #[test]
19681 fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
19682 let mut f = labelled();
19683 let cases: &[(&[&[u8]], &str)] = &[
19684 (
19687 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19688 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
19689 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
19690 ),
19691 (
19692 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19693 "*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\
19694 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
19695 *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",
19696 ),
19697 (
19699 &[
19700 b"TS.MGET",
19701 b"SELECTED_LABELS",
19702 b"x",
19703 b"FILTER",
19704 b"room=kitchen",
19705 ],
19706 "*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\
19707 *2\r\n:100\r\n+1.5\r\n\
19708 *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",
19709 ),
19710 (
19713 &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
19714 "*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",
19715 ),
19716 (
19717 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19718 "*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\
19719 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19720 ),
19721 (
19724 &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
19725 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
19726 ),
19727 (
19728 &[b"TS.MGET", b"a", b"b", b"c"],
19729 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19730 ),
19731 (
19732 &[b"TS.MGET", b"FILTER"],
19733 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19734 ),
19735 (
19738 &[
19739 b"TS.MGET",
19740 b"WITHLABELS",
19741 b"SELECTED_LABELS",
19742 b"x",
19743 b"FILTER",
19744 b"bad",
19745 ],
19746 "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
19747 ),
19748 (
19749 &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
19750 "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
19751 ),
19752 ];
19753 for (argv, want) in cases {
19754 let got = f.run(argv);
19755 assert_eq!(&got, want, "{:?}", argv.last());
19756 }
19757 }
19758
19759 #[test]
19762 fn resp3_writes_the_label_surface_as_sets_and_maps() {
19763 let mut f = labelled();
19764 f.out = Out::new(Proto::Resp3);
19765 let cases: &[(&[&[u8]], &str)] = &[
19766 (
19767 &[b"TS.QUERYINDEX", b"room=kitchen"],
19768 "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
19769 ),
19770 (
19771 &[b"TS.QUERYLABELS", b"LABELS"],
19772 "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19773 ),
19774 (
19775 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19776 "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19777 ),
19778 (
19781 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19782 "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
19783 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
19784 ),
19785 (
19786 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19787 "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19788 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
19789 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
19790 ),
19791 (
19792 &[
19793 b"TS.MGET",
19794 b"SELECTED_LABELS",
19795 b"x",
19796 b"FILTER",
19797 b"room=kitchen",
19798 ],
19799 "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
19800 *2\r\n:100\r\n,1.5\r\n\
19801 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
19802 ),
19803 (
19806 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19807 "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
19808 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19809 ),
19810 ];
19811 for (argv, want) in cases {
19812 let got = f.run(argv);
19813 assert_eq!(&got, want, "{:?}", argv.last());
19814 }
19815 }
19816
19817 fn spanned() -> Fixture {
19820 let mut f = labelled();
19821 f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
19822 f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
19823 f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
19824 f
19825 }
19826
19827 #[test]
19830 fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
19831 let mut f = spanned();
19832 let cases: &[(&[&[u8]], &str)] = &[
19833 (
19834 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
19835 "*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\
19836 *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",
19837 ),
19838 (
19841 &[
19842 b"TS.MREVRANGE",
19843 b"-",
19844 b"+",
19845 b"WITHLABELS",
19846 b"FILTER",
19847 b"room=kitchen",
19848 ],
19849 "*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\
19850 *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\
19851 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19852 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
19853 ),
19854 (
19857 &[
19858 b"TS.MRANGE",
19859 b"-",
19860 b"+",
19861 b"SELECTED_LABELS",
19862 b"x",
19863 b"FILTER",
19864 b"room=kitchen",
19865 ],
19866 "*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\
19867 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
19868 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
19869 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
19870 ),
19871 (
19874 &[
19875 b"TS.MRANGE",
19876 b"-",
19877 b"+",
19878 b"FILTER",
19879 b"room=kitchen",
19880 b"GROUPBY",
19881 b"room",
19882 b"REDUCE",
19883 b"sum",
19884 ],
19885 "*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\
19886 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
19887 ),
19888 (
19891 &[
19892 b"TS.MRANGE",
19893 b"-",
19894 b"+",
19895 b"WITHLABELS",
19896 b"FILTER",
19897 b"room=kitchen",
19898 b"GROUPBY",
19899 b"room",
19900 b"REDUCE",
19901 b"max",
19902 ],
19903 "*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\
19904 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
19905 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
19906 *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",
19907 ),
19908 (
19910 &[
19911 b"TS.MREVRANGE",
19912 b"-",
19913 b"+",
19914 b"COUNT",
19915 b"1",
19916 b"FILTER",
19917 b"room=kitchen",
19918 b"GROUPBY",
19919 b"room",
19920 b"REDUCE",
19921 b"count",
19922 ],
19923 "*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",
19924 ),
19925 (
19927 &[
19928 b"TS.MRANGE",
19929 b"-",
19930 b"+",
19931 b"FILTER",
19932 b"room=kitchen",
19933 b"GROUPBY",
19934 b"nope",
19935 b"REDUCE",
19936 b"sum",
19937 ],
19938 "*0\r\n",
19939 ),
19940 (
19941 &[
19942 b"TS.MRANGE",
19943 b"-",
19944 b"+",
19945 b"AGGREGATION",
19946 b"sum,avg",
19947 b"100",
19948 b"FILTER",
19949 b"room=bedroom",
19950 ],
19951 "*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",
19952 ),
19953 (
19955 &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
19956 "-ERR TSDB: missing FILTER argument\r\n",
19957 ),
19958 (
19959 &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
19960 "-ERR TSDB: missing labels for filter argument\r\n",
19961 ),
19962 (
19963 &[
19964 b"TS.MRANGE",
19965 b"-",
19966 b"+",
19967 b"GROUPBY",
19968 b"room",
19969 b"REDUCE",
19970 b"sum",
19971 b"FILTER",
19972 b"room=kitchen",
19973 ],
19974 "-ERR TSDB: GROUPBY should always come after filter\r\n",
19975 ),
19976 (
19979 &[
19980 b"TS.MRANGE",
19981 b"-",
19982 b"+",
19983 b"FILTER",
19984 b"room=kitchen",
19985 b"GROUPBY",
19986 b"room",
19987 b"REDUCE",
19988 b"sum",
19989 b"x",
19990 ],
19991 "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
19992 ),
19993 (
19995 &[
19996 b"TS.MRANGE",
19997 b"-",
19998 b"+",
19999 b"FILTER",
20000 b"nope",
20001 b"GROUPBY",
20002 b"room",
20003 b"REDUCE",
20004 b"sum",
20005 b"x",
20006 ],
20007 "-ERR TSDB: failed parsing labels\r\n",
20008 ),
20009 (
20010 &[
20011 b"TS.MRANGE",
20012 b"-",
20013 b"+",
20014 b"FILTER",
20015 b"room=kitchen",
20016 b"GROUPBY",
20017 b"room",
20018 b"REDUCE",
20019 b"twa",
20020 ],
20021 "-ERR TSDB: Invalid reducer type\r\n",
20022 ),
20023 (
20024 &[
20025 b"TS.MRANGE",
20026 b"-",
20027 b"+",
20028 b"AGGREGATION",
20029 b"sum,avg",
20030 b"100",
20031 b"FILTER",
20032 b"room=kitchen",
20033 b"GROUPBY",
20034 b"room",
20035 b"REDUCE",
20036 b"sum",
20037 ],
20038 "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
20039 ),
20040 (
20043 &[
20044 b"TS.MRANGE",
20045 b"-",
20046 b"+",
20047 b"SELECTED_LABELS",
20048 b"COUNT",
20049 b"FILTER",
20050 b"room=kitchen",
20051 ],
20052 "-ERR TSDB: Couldn't parse COUNT\r\n",
20053 ),
20054 ];
20055 for (argv, want) in cases {
20056 let got = f.run(argv);
20057 assert_eq!(&got, want, "{argv:?}");
20058 }
20059 }
20060
20061 #[test]
20064 fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
20065 let mut f = spanned();
20066 f.out = Out::new(Proto::Resp3);
20067 let cases: &[(&[&[u8]], &str)] = &[
20068 (
20069 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
20070 "%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\
20071 *1\r\n*2\r\n:200\r\n,2\r\n",
20072 ),
20073 (
20076 &[
20077 b"TS.MRANGE",
20078 b"-",
20079 b"+",
20080 b"AGGREGATION",
20081 b"sum,avg",
20082 b"100",
20083 b"FILTER",
20084 b"room=bedroom",
20085 ],
20086 "%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\
20087 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
20088 ),
20089 (
20090 &[
20091 b"TS.MRANGE",
20092 b"-",
20093 b"+",
20094 b"FILTER",
20095 b"room=kitchen",
20096 b"GROUPBY",
20097 b"room",
20098 b"REDUCE",
20099 b"sum",
20100 ],
20101 "%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\
20102 $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\
20103 *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",
20104 ),
20105 (
20108 &[
20109 b"TS.MRANGE",
20110 b"-",
20111 b"+",
20112 b"WITHLABELS",
20113 b"FILTER",
20114 b"room=kitchen",
20115 b"GROUPBY",
20116 b"room",
20117 b"REDUCE",
20118 b"max",
20119 ],
20120 "%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\
20121 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
20122 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
20123 *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",
20124 ),
20125 (
20126 &[
20127 b"TS.MRANGE",
20128 b"-",
20129 b"+",
20130 b"FILTER",
20131 b"room=kitchen",
20132 b"GROUPBY",
20133 b"nope",
20134 b"REDUCE",
20135 b"sum",
20136 ],
20137 "%0\r\n",
20138 ),
20139 ];
20140 for (argv, want) in cases {
20141 let got = f.run(argv);
20142 assert_eq!(&got, want, "{argv:?}");
20143 }
20144 }
20145
20146 #[test]
20148 fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
20149 let mut f = Fixture::new();
20150 f.run(&[b"TS.CREATE", b"src"]);
20151 f.run(&[b"TS.CREATE", b"dst"]);
20152 f.run(&[b"SET", b"plain", b"v"]);
20153 let cases: &[(&[&[u8]], &str)] = &[
20154 (
20158 &[
20159 b"TS.CREATERULE",
20160 b"src",
20161 b"dst",
20162 b"AGGREGATION",
20163 b"nope",
20164 b"x",
20165 ],
20166 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
20167 ),
20168 (
20169 &[
20170 b"TS.CREATERULE",
20171 b"src",
20172 b"dst",
20173 b"AGGREGATION",
20174 b"nope",
20175 b"10",
20176 ],
20177 "-ERR TSDB: Unknown aggregation type\r\n",
20178 ),
20179 (
20180 &[
20181 b"TS.CREATERULE",
20182 b"src",
20183 b"dst",
20184 b"AGGREGATION",
20185 b"avg",
20186 b"0",
20187 ],
20188 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
20189 ),
20190 (
20191 &[
20192 b"TS.CREATERULE",
20193 b"src",
20194 b"dst",
20195 b"AGGREGATION",
20196 b"avg",
20197 b"10",
20198 b"x",
20199 ],
20200 "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
20201 ),
20202 (
20203 &[
20204 b"TS.CREATERULE",
20205 b"src",
20206 b"src",
20207 b"AGGREGATION",
20208 b"avg",
20209 b"10",
20210 ],
20211 "-ERR TSDB: the source key and destination key should be different\r\n",
20212 ),
20213 (
20217 &[
20218 b"TS.CREATERULE",
20219 b"nope",
20220 b"plain",
20221 b"AGGREGATION",
20222 b"avg",
20223 b"10",
20224 ],
20225 "-ERR TSDB: the key does not exist\r\n",
20226 ),
20227 (
20228 &[
20229 b"TS.CREATERULE",
20230 b"src",
20231 b"nope",
20232 b"AGGREGATION",
20233 b"avg",
20234 b"10",
20235 ],
20236 "-ERR TSDB: the key does not exist\r\n",
20237 ),
20238 (
20241 &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
20242 "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
20243 ),
20244 (
20245 &[
20246 b"TS.CREATERULE",
20247 b"src",
20248 b"dst",
20249 b"AGGREGATION",
20250 b"avg",
20251 b"10",
20252 ],
20253 "+OK\r\n",
20254 ),
20255 (
20258 &[
20259 b"TS.CREATERULE",
20260 b"src",
20261 b"dst",
20262 b"AGGREGATION",
20263 b"avg",
20264 b"10",
20265 ],
20266 "-ERR TSDB: the destination key already has a src rule\r\n",
20267 ),
20268 (
20271 &[
20272 b"TS.CREATERULE",
20273 b"dst",
20274 b"src",
20275 b"AGGREGATION",
20276 b"avg",
20277 b"10",
20278 ],
20279 "-ERR TSDB: the source key already has a source rule\r\n",
20280 ),
20281 (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
20282 (
20283 &[b"TS.DELETERULE", b"src", b"dst"],
20284 "-ERR TSDB: compaction rule does not exist\r\n",
20285 ),
20286 (
20290 &[b"TS.DELETERULE", b"src", b"nope"],
20291 "-ERR TSDB: compaction rule does not exist\r\n",
20292 ),
20293 (
20294 &[b"TS.DELETERULE", b"nope", b"dst"],
20295 "-ERR TSDB: the key does not exist\r\n",
20296 ),
20297 ];
20298 for (argv, want) in cases {
20299 let got = f.run(argv);
20300 assert_eq!(&got, want, "{argv:?}");
20301 }
20302 }
20303
20304 #[test]
20306 fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
20307 let mut f = Fixture::new();
20308 f.run(&[b"TS.CREATE", b"src"]);
20309 f.run(&[b"TS.CREATE", b"dst"]);
20310 f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
20313 f.run(&[
20314 b"TS.CREATERULE",
20315 b"src",
20316 b"dst",
20317 b"AGGREGATION",
20318 b"sum",
20319 b"100",
20320 ]);
20321 f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
20322 assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
20323 assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
20326 f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
20327 assert_eq!(
20328 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20329 "*1\r\n*2\r\n:0\r\n+2\r\n"
20330 );
20331 f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
20334 assert_eq!(
20335 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20336 "*1\r\n*2\r\n:0\r\n+11\r\n"
20337 );
20338 assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
20341 assert_eq!(
20342 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20343 "*1\r\n*2\r\n:0\r\n+8\r\n"
20344 );
20345 assert_eq!(
20346 f.run(&[b"TS.GET", b"dst", b"LATEST"]),
20347 "*2\r\n:100\r\n+4\r\n"
20348 );
20349 assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
20351 f.run(&[b"DEL", b"dst"]);
20352 assert_eq!(
20353 f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
20354 "-ERR TSDB: compaction rule does not exist\r\n"
20355 );
20356 }
20357
20358 #[test]
20361 fn xadd_ids_only_ever_go_up() {
20362 let mut f = Fixture::new();
20363 assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
20365 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
20367 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
20368 assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
20369 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20370
20371 assert!(
20372 f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
20373 .contains("equal or smaller")
20374 );
20375 assert!(
20376 f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
20377 .contains("must be greater than 0-0")
20378 );
20379 assert!(
20380 f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
20381 .contains("Invalid stream ID")
20382 );
20383 assert!(
20386 f.run(&[b"XADD", b"s", b"*", b"a"])
20387 .contains("wrong number of arguments")
20388 );
20389
20390 assert_eq!(
20393 f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
20394 "$-1\r\n"
20395 );
20396 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20397 assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
20398 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
20399 }
20400
20401 #[test]
20404 fn trimming_reads_its_options_the_way_redis_does() {
20405 let mut f = Fixture::new();
20406 for i in 1..=10u32 {
20407 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20408 }
20409 assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
20410 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20411 assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
20412 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20413
20414 assert!(
20418 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
20419 .contains("not an integer")
20420 );
20421 assert!(
20422 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
20423 .contains("MAXLEN argument must be >= 0")
20424 );
20425 assert!(
20428 f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
20429 .contains("without specifying a trimming strategy")
20430 );
20431 assert!(
20432 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
20433 .contains("without the special ~ option")
20434 );
20435 assert!(
20436 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
20437 .contains("at the same time are not compatible")
20438 );
20439 assert!(
20441 f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
20442 .contains("syntax error")
20443 );
20444 assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
20445 }
20446
20447 #[test]
20449 fn xrange_looks_the_key_up_before_it_reads_the_count() {
20450 let mut f = Fixture::new();
20451 f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
20452 f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
20453
20454 assert_eq!(
20455 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
20456 "*2\r\n*2\r\n$3\r\n5-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n\
20457 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20458 );
20459 assert_eq!(
20460 f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
20461 "*1\r\n*2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20462 );
20463 assert_eq!(
20467 f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
20468 "*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\
20469 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20470 );
20471 assert_eq!(
20472 f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
20473 "*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"
20474 );
20475 assert!(
20476 f.run(&[b"XRANGE", b"s", b"(-", b"+"])
20477 .contains("Invalid stream ID")
20478 );
20479
20480 assert_eq!(
20484 f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
20485 "*0\r\n"
20486 );
20487 assert_eq!(
20488 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
20489 "*-1\r\n"
20490 );
20491 f.run(&[b"SET", b"str", b"v"]);
20492 assert!(
20493 f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
20494 .starts_with("-WRONGTYPE")
20495 );
20496 assert_eq!(
20498 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
20499 "*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"
20500 );
20501 }
20502
20503 #[test]
20505 fn a_bad_id_late_in_the_list_stops_the_whole_command() {
20506 let mut f = Fixture::new();
20507 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20508 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20509 assert!(
20510 f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
20511 .contains("Invalid stream ID")
20512 );
20513 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20514 assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
20515 assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
20516 assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
20517 assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
20518 }
20519
20520 #[test]
20522 fn xgroup_has_an_arity_per_subcommand() {
20523 let mut f = Fixture::new();
20524 assert!(
20525 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20526 .contains("requires the key")
20527 );
20528 assert_eq!(
20529 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
20530 "+OK\r\n"
20531 );
20532 assert!(
20535 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20536 .starts_with("-BUSYGROUP")
20537 );
20538 assert_eq!(
20539 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20540 ":1\r\n"
20541 );
20542 assert_eq!(
20543 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20544 ":0\r\n"
20545 );
20546 assert_eq!(
20547 f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
20548 ":0\r\n"
20549 );
20550
20551 let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
20553 assert!(
20554 short.contains("wrong number of arguments for 'xgroup|destroy' command"),
20555 "{short}"
20556 );
20557 let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
20559 assert!(
20560 odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
20561 "{odd}"
20562 );
20563 assert!(
20564 f.run(&[b"XGROUP", b"NOSUCH", b"s"])
20565 .contains("Try XGROUP HELP")
20566 );
20567
20568 assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
20569 assert!(
20570 f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
20571 .starts_with("-NOGROUP")
20572 );
20573 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
20574 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
20575 assert!(
20576 f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
20577 .contains("requires the key")
20578 );
20579 }
20580
20581 #[test]
20583 fn xreadgroup_hands_out_and_xack_takes_back() {
20584 let mut f = Fixture::new();
20585 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20586 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20587 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20588
20589 let first = f.run(&[
20590 b"XREADGROUP",
20591 b"GROUP",
20592 b"g",
20593 b"c1",
20594 b"COUNT",
20595 b"1",
20596 b"STREAMS",
20597 b"s",
20598 b">",
20599 ]);
20600 assert_eq!(
20601 first,
20602 "*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"
20603 );
20604 assert_eq!(
20607 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
20608 "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
20609 );
20610 assert_eq!(
20611 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20612 "*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"
20613 );
20614
20615 assert_eq!(
20616 f.run(&[b"XPENDING", b"s", b"g"]),
20617 "*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"
20618 );
20619 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
20620 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
20621 assert_eq!(
20623 f.run(&[b"XPENDING", b"s", b"g"]),
20624 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
20625 );
20626
20627 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20630 f.run(&[b"XDEL", b"s", b"2-1"]);
20631 assert_eq!(
20632 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20633 "*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"
20634 );
20635
20636 assert!(
20639 f.run(&[
20640 b"XREADGROUP",
20641 b"GROUP",
20642 b"nope",
20643 b"c",
20644 b"STREAMS",
20645 b"s",
20646 b"+"
20647 ])
20648 .starts_with("-NOGROUP")
20649 );
20650 assert!(
20651 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
20652 .contains("meaningless in the context of XREADGROUP")
20653 );
20654 assert!(
20655 f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
20656 .contains("only supported by XREADGROUP")
20657 );
20658 assert!(
20659 f.run(&[
20660 b"XREADGROUP",
20661 b"GROUP",
20662 b"g",
20663 b"c",
20664 b"STREAMS",
20665 b"s",
20666 b"a",
20667 b"b"
20668 ])
20669 .contains("Unbalanced 'xreadgroup' list of streams")
20670 );
20671 }
20672
20673 #[test]
20676 fn xread_with_no_block_writes_the_null_itself() {
20677 let mut f = Fixture::new();
20678 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20679 assert_eq!(
20680 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
20681 "*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"
20682 );
20683 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
20686 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
20687 f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
20688 assert_eq!(
20689 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
20690 "*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"
20691 );
20692 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
20694 assert_eq!(
20696 f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
20697 "*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"
20698 );
20699 assert_eq!(
20702 f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
20703 "*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"
20704 );
20705 assert!(
20707 f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
20708 .contains("not an integer")
20709 );
20710 assert!(
20711 f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
20712 .contains("timeout is negative")
20713 );
20714 assert!(
20715 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
20716 .contains("Unbalanced 'xread' list of streams")
20717 );
20718 }
20719
20720 #[test]
20722 fn a_blocked_xread_wakes_on_the_next_entry() {
20723 let mut f = Fixture::new();
20724 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20725 let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20726 assert_eq!(flow, Flow::Block);
20727 assert!(reply.is_empty());
20728
20729 f.session = Session::new(8);
20734 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20735 assert_eq!(flow, Flow::Block);
20736 assert_eq!(f.server.parked(), 2);
20737
20738 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20739 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";
20740 for client in [7, 8] {
20741 let mut out = Out::new(Proto::Resp2);
20742 assert!(f.server.serve_waiter(client, 0, &mut out));
20743 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
20744 }
20745
20746 f.server.forget_waiters(7);
20749 f.server.forget_waiters(8);
20750 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
20751 assert_eq!(flow, Flow::Block);
20752 let mut out = Out::new(Proto::Resp2);
20753 assert!(!f.server.serve_waiter(8, 0, &mut out));
20754 assert!(out.as_slice().is_empty());
20755 assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
20756 assert_eq!(
20757 core::str::from_utf8(out.as_slice()).expect("ascii"),
20758 "*-1\r\n"
20759 );
20760 }
20761
20762 #[test]
20764 fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
20765 let mut f = Fixture::new();
20766 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20767 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
20768 let (flow, _) = f.flow(&[
20769 b"XREADGROUP",
20770 b"GROUP",
20771 b"g",
20772 b"c",
20773 b"BLOCK",
20774 b"0",
20775 b"STREAMS",
20776 b"s",
20777 b">",
20778 ]);
20779 assert_eq!(flow, Flow::Block);
20780
20781 f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
20782 let mut out = Out::new(Proto::Resp2);
20783 assert!(f.server.serve_waiter(7, 0, &mut out));
20784 assert_eq!(
20787 core::str::from_utf8(out.as_slice()).expect("ascii"),
20788 "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
20789 );
20790 }
20791
20792 #[test]
20794 fn xclaim_reads_ids_until_one_will_not_parse() {
20795 let mut f = Fixture::new();
20796 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20797 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20798 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20799 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20800
20801 assert!(
20804 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
20805 .contains("Unrecognized XCLAIM option '-'")
20806 );
20807 assert_eq!(
20808 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
20809 "*1\r\n$3\r\n1-1\r\n"
20810 );
20811 f.run(&[b"XDEL", b"s", b"2-1"]);
20814 assert_eq!(
20815 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
20816 "*0\r\n"
20817 );
20818 assert!(
20819 f.run(&[b"XPENDING", b"s", b"g"])
20820 .starts_with("*4\r\n:1\r\n")
20821 );
20822 assert!(
20823 f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
20824 .starts_with("-NOGROUP")
20825 );
20826 assert!(
20827 f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
20828 .contains("Invalid min-idle-time argument for XCLAIM")
20829 );
20830 }
20831
20832 #[test]
20834 fn xautoclaim_reports_what_it_dropped() {
20835 let mut f = Fixture::new();
20836 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20837 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20838 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20839 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20840 f.run(&[b"XDEL", b"s", b"1-1"]);
20841
20842 assert_eq!(
20845 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
20846 "*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"
20847 );
20848 assert!(
20849 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
20850 .contains("COUNT must be > 0")
20851 );
20852 assert!(
20853 f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
20854 .starts_with("-NOGROUP")
20855 );
20856 }
20857
20858 #[test]
20860 fn xdelex_answers_one_integer_an_id() {
20861 let mut f = Fixture::new();
20862 for i in 1..=4 {
20863 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20864 }
20865 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20866 f.run(&[
20867 b"XREADGROUP",
20868 b"GROUP",
20869 b"g",
20870 b"c",
20871 b"COUNT",
20872 b"2",
20873 b"STREAMS",
20874 b"s",
20875 b">",
20876 ]);
20877
20878 assert_eq!(
20880 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
20881 "*2\r\n:1\r\n:-1\r\n"
20882 );
20883 assert!(
20886 f.run(&[b"XPENDING", b"s", b"g"])
20887 .starts_with("*4\r\n:2\r\n")
20888 );
20889 assert_eq!(
20891 f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
20892 "*1\r\n:1\r\n"
20893 );
20894 assert_eq!(
20896 f.run(&[b"XPENDING", b"s", b"g"]),
20897 "*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"
20898 );
20899
20900 assert_eq!(
20904 f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
20905 "*2\r\n:2\r\n:2\r\n"
20906 );
20907
20908 assert_eq!(
20910 f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
20911 "*2\r\n:-1\r\n:-1\r\n"
20912 );
20913 assert!(
20915 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
20916 .starts_with("-ERR Invalid stream ID")
20917 );
20918 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20919
20920 assert!(
20921 f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
20922 .contains("Number of IDs must be a positive integer")
20923 );
20924 assert!(
20925 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
20926 .contains("The `numids` parameter must match the number of arguments")
20927 );
20928 assert!(
20931 f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
20932 .starts_with("-ERR syntax error")
20933 );
20934 assert!(
20935 f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
20936 .starts_with("-ERR syntax error")
20937 );
20938 f.run(&[b"SET", b"str", b"v"]);
20940 assert!(
20941 f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
20942 .starts_with("-WRONGTYPE")
20943 );
20944 }
20945
20946 #[test]
20948 fn xackdel_reports_what_the_group_was_holding() {
20949 let mut f = Fixture::new();
20950 for i in 1..=3 {
20951 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20952 }
20953 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20954 f.run(&[
20955 b"XREADGROUP",
20956 b"GROUP",
20957 b"g",
20958 b"c",
20959 b"COUNT",
20960 b"1",
20961 b"STREAMS",
20962 b"s",
20963 b">",
20964 ]);
20965
20966 assert_eq!(
20970 f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
20971 "*2\r\n:1\r\n:-1\r\n"
20972 );
20973 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20974
20975 assert_eq!(
20977 f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
20978 "*1\r\n:-1\r\n"
20979 );
20980 assert_eq!(
20981 f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
20982 "*1\r\n:-1\r\n"
20983 );
20984
20985 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
20988 f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
20989 assert_eq!(
20990 f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
20991 "*1\r\n:2\r\n"
20992 );
20993 assert_eq!(
20994 f.run(&[b"XPENDING", b"s", b"g"]),
20995 "*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"
20996 );
20997 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20998 }
20999
21000 #[test]
21002 fn xnack_releases_an_entry_for_the_next_claim() {
21003 let mut f = Fixture::new();
21004 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21005 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21006 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21007 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21008 f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
21011
21012 assert_eq!(
21013 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
21014 ":1\r\n"
21015 );
21016 assert_eq!(
21020 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
21021 "*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"
21022 );
21023 assert_eq!(
21025 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21026 "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
21027 );
21028 assert_eq!(
21030 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
21031 "*-1\r\n"
21032 );
21033 assert_eq!(
21035 f.run(&[
21036 b"XAUTOCLAIM",
21037 b"s",
21038 b"g",
21039 b"c2",
21040 b"99999999",
21041 b"-",
21042 b"JUSTID"
21043 ]),
21044 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
21045 );
21046
21047 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21051 assert!(
21052 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21053 .contains(":-1\r\n:1\r\n")
21054 );
21055 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21057 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21058 assert!(
21059 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21060 .contains(":-1\r\n:0\r\n")
21061 );
21062 f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
21064 assert!(
21065 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21066 .contains(":9223372036854775807\r\n")
21067 );
21068 f.run(&[
21069 b"XNACK",
21070 b"s",
21071 b"g",
21072 b"FATAL",
21073 b"IDS",
21074 b"1",
21075 b"1-1",
21076 b"RETRYCOUNT",
21077 b"3",
21078 ]);
21079 assert!(
21080 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21081 .contains(":-1\r\n:3\r\n")
21082 );
21083
21084 f.run(&[b"XACK", b"s", b"g", b"2-1"]);
21088 assert_eq!(
21089 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
21090 ":0\r\n"
21091 );
21092 assert_eq!(
21093 f.run(&[
21094 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
21095 ]),
21096 ":1\r\n"
21097 );
21098 assert!(
21099 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21100 .contains(":-1\r\n:0\r\n")
21101 );
21102 assert_eq!(
21104 f.run(&[
21105 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
21106 ]),
21107 ":0\r\n"
21108 );
21109
21110 assert_eq!(
21113 f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
21114 "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
21115 );
21116 assert!(
21117 f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
21118 .starts_with("-ERR")
21119 );
21120 assert!(
21122 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
21123 .contains("numids must be a positive integer")
21124 );
21125 assert!(
21126 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
21127 .contains("number of IDs doesn't match numids")
21128 );
21129 assert!(
21132 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
21133 .contains("Unrecognized XNACK option '2-1'")
21134 );
21135 }
21136
21137 #[test]
21139 fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
21140 let mut f = Fixture::new();
21141 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21142 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21143 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21144 f.run(&[
21145 b"XREADGROUP",
21146 b"GROUP",
21147 b"g",
21148 b"c1",
21149 b"COUNT",
21150 b"1",
21151 b"STREAMS",
21152 b"s",
21153 b">",
21154 ]);
21155
21156 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21157 assert!(info.starts_with("*20\r\n"), "{info}");
21160 assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
21161 assert!(
21162 info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
21163 "{info}"
21164 );
21165 assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
21166 assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
21167
21168 let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
21169 assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
21170 assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
21171 assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
21172 assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
21173
21174 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
21178 let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
21179 assert!(consumers.starts_with("*2\r\n"), "{consumers}");
21180 assert!(
21181 consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
21182 "{consumers}"
21183 );
21184 let c1 = consumers.find("c1").unwrap();
21186 let c2 = consumers.find("c2").unwrap();
21187 assert!(c1 < c2, "{consumers}");
21188
21189 let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
21190 assert!(full.starts_with("*18\r\n"), "{full}");
21191 assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
21192 assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
21193
21194 assert!(
21195 f.run(&[b"XINFO", b"STREAM", b"missing"])
21196 .contains("no such key")
21197 );
21198 assert!(
21199 f.run(&[b"XINFO", b"GROUPS", b"missing"])
21200 .contains("no such key")
21201 );
21202 assert!(
21203 f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
21204 .starts_with("-NOGROUP")
21205 );
21206 assert!(
21207 f.run(&[b"XINFO", b"NOSUCH", b"s"])
21208 .contains("Try XINFO HELP")
21209 );
21210 assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
21211 assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
21212 }
21213
21214 #[test]
21216 fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
21217 let mut f = Fixture::new();
21218 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21219 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21220 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21221
21222 let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
21223 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");
21224 assert_eq!(
21225 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21226 "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
21227 );
21228 assert_eq!(
21230 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
21231 "*0\r\n"
21232 );
21233 assert_eq!(
21234 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
21235 list
21236 );
21237 assert!(
21239 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
21240 .contains("syntax error")
21241 );
21242 assert!(
21243 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
21244 .contains("syntax error")
21245 );
21246 assert_eq!(
21247 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
21248 "*0\r\n"
21249 );
21250 assert!(
21251 f.run(&[b"XPENDING", b"missing", b"g"])
21252 .starts_with("-NOGROUP")
21253 );
21254 }
21255
21256 #[test]
21258 fn xsetid_will_not_go_below_what_is_there() {
21259 let mut f = Fixture::new();
21260 f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
21261 assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
21262 assert_eq!(
21263 f.run(&[
21264 b"XSETID",
21265 b"s",
21266 b"10-1",
21267 b"ENTRIESADDED",
21268 b"7",
21269 b"MAXDELETEDID",
21270 b"9-1"
21271 ]),
21272 "+OK\r\n"
21273 );
21274 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21275 assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
21276 assert!(
21277 info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
21278 "{info}"
21279 );
21280
21281 assert!(
21282 f.run(&[b"XSETID", b"s", b"1-1"])
21283 .contains("smaller than the target stream top item")
21284 );
21285 assert!(
21286 f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
21287 .contains("entries_added must be positive")
21288 );
21289 assert!(
21290 f.run(&[b"XSETID", b"missing", b"1-1"])
21291 .contains("no such key")
21292 );
21293 }
21294
21295 #[test]
21297 fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
21298 let mut f = Fixture::new();
21299 f.run(&[b"HELLO", b"3"]);
21300 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21301 assert_eq!(
21304 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
21305 "%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"
21306 );
21307 assert_eq!(
21310 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
21311 "*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"
21312 );
21313 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
21314 }
21315
21316 struct Mem {
21322 blobs: Vec<Vec<u8>>,
21323 }
21324
21325 impl yo_kv::cold::Blocks for Mem {
21326 fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
21327 self.blobs.push(bytes.to_vec());
21328 Ok(yo_common::Addr::new(
21329 yo_common::Space::Log,
21330 (self.blobs.len() - 1) as u64,
21331 ))
21332 }
21333
21334 fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
21335 self.blobs
21336 .get(at.offset() as usize)
21337 .map(Vec::as_slice)
21338 .ok_or_else(|| {
21339 yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
21340 })
21341 }
21342
21343 fn bytes(&self) -> u64 {
21344 self.blobs.iter().map(|b| b.len() as u64).sum()
21345 }
21346 }
21347
21348 fn filled(attach: bool) -> (Fixture, usize) {
21361 let mut f = Fixture::new();
21362 if attach {
21363 f.server
21364 .striped(0)
21365 .hold_stripe(0)
21366 .attach(Box::new(Mem { blobs: Vec::new() }));
21367 }
21368 let val = vec![b'v'; 256];
21369 for i in 0..24000u32 {
21370 let k = format!("key:{i:08}");
21371 f.run(&[b"SET", k.as_bytes(), &val]);
21372 }
21373 let full = f.server.memory_bytes();
21374 assert!(full > 3 * 1024 * 1024, "the arena is several segments");
21375 (f, full)
21376 }
21377
21378 fn press(f: &mut Fixture, limit: usize) {
21384 let val = vec![b'v'; 256];
21385 for i in 0..3000u32 {
21386 let k = format!("new:{i:08}");
21387 assert_eq!(
21388 f.run(&[b"SET", k.as_bytes(), &val]),
21389 "+OK\r\n",
21390 "write {i} was refused"
21391 );
21392 f.server.refresh_memory();
21393 if f.server.memory_bytes() <= limit {
21394 return;
21395 }
21396 }
21397 panic!(
21398 "it never got under: {} against {limit}",
21399 f.server.memory_bytes()
21400 );
21401 }
21402
21403 #[test]
21404 fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
21405 let mut f = Fixture::new();
21406 assert_eq!(
21407 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21408 "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
21409 "no limit is the default"
21410 );
21411 for (typed, bytes) in [
21414 (&b"0"[..], "0"),
21415 (b"1024", "1024"),
21416 (b"1k", "1000"),
21417 (b"1gb", "1073741824"),
21418 (b"-1", "-1"),
21419 ] {
21420 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
21421 assert_eq!(
21422 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21423 format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
21424 "set {}",
21425 String::from_utf8_lossy(typed)
21426 );
21427 }
21428 for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
21429 assert_eq!(
21430 f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
21431 "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
21432 "refused {}",
21433 String::from_utf8_lossy(bad)
21434 );
21435 }
21436 let info = f.run(&[b"INFO", b"memory"]);
21438 assert!(info.contains("maxstore:-1"), "{info}");
21439 assert!(info.contains("yo_memory_regime:evict"), "{info}");
21440 assert!(info.contains("yo_store_bytes:0"), "{info}");
21441 }
21442
21443 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21444 #[test]
21445 fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
21446 let (mut f, full) = filled(true);
21450 let keys = f.run(&[b"DBSIZE"]);
21451 assert!(
21452 f.run(&[b"INFO", b"memory"])
21453 .contains("yo_memory_regime:migrate"),
21454 "a database with somewhere to put values migrates"
21455 );
21456
21457 let limit = full - 2 * 1024 * 1024;
21458 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21459 f.run(&[
21460 b"CONFIG",
21461 b"SET",
21462 b"maxmemory",
21463 limit.to_string().as_bytes(),
21464 ]);
21465 press(&mut f, limit);
21466
21467 assert!(
21468 f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21469 "nothing was thrown away"
21470 );
21471 let after: usize = f.run(&[b"DBSIZE"])[1..]
21472 .trim_end()
21473 .parse()
21474 .expect("a count");
21475 let before: usize = keys[1..].trim_end().parse().expect("a count");
21476 assert!(after > before, "the keys that came in are all still here");
21477 assert!(
21478 f.server.store_bytes() > 0,
21479 "and what came out of memory went to the file"
21480 );
21481 let val = format!("$256\r\n{}\r\n", "v".repeat(256));
21484 assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
21485 assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
21486 }
21487
21488 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21489 #[test]
21490 fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
21491 let (mut f, full) = filled(true);
21495 f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
21496 assert!(
21497 f.run(&[b"INFO", b"memory"])
21498 .contains("yo_memory_regime:evict"),
21499 "nothing may go to the file"
21500 );
21501
21502 let limit = full - 2 * 1024 * 1024;
21503 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21504 f.run(&[
21505 b"CONFIG",
21506 b"SET",
21507 b"maxmemory",
21508 limit.to_string().as_bytes(),
21509 ]);
21510 press(&mut f, limit);
21511
21512 assert!(
21513 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21514 "keys were thrown away, which is what was asked for"
21515 );
21516 assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
21517 }
21518
21519 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21520 #[test]
21521 fn a_full_file_goes_back_to_evicting() {
21522 let (mut f, full) = filled(true);
21526 f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
21527 let limit = full - 2 * 1024 * 1024;
21528 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21529 f.run(&[
21530 b"CONFIG",
21531 b"SET",
21532 b"maxmemory",
21533 limit.to_string().as_bytes(),
21534 ]);
21535 press(&mut f, limit);
21536
21537 assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
21538 assert!(
21539 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21540 "and then it started evicting"
21541 );
21542 assert!(
21543 f.run(&[b"INFO", b"memory"])
21544 .contains("yo_memory_regime:evict"),
21545 "and it says so"
21546 );
21547 }
21548 #[test]
21564 fn the_string_group_answers_the_same_however_many_stripes_there_are() {
21565 let script: &[&[&[u8]]] = &[
21566 &[b"SET", b"k1", b"v1"],
21569 &[b"SET", b"k2", b"v2"],
21570 &[b"GET", b"k1"],
21571 &[b"GET", b"nothing"],
21572 &[b"GETSET", b"k1", b"v1b"],
21573 &[b"SETNX", b"k1", b"no"],
21574 &[b"SETNX", b"k3", b"yes"],
21575 &[b"APPEND", b"k3", b"!"],
21576 &[b"STRLEN", b"k3"],
21577 &[b"SETRANGE", b"k3", b"1", b"XY"],
21578 &[b"GETRANGE", b"k3", b"0", b"-1"],
21579 &[b"INCR", b"n1"],
21580 &[b"INCRBY", b"n1", b"41"],
21581 &[b"DECRBY", b"n1", b"2"],
21582 &[b"INCRBYFLOAT", b"f1", b"1.5"],
21583 &[b"SETEX", b"e1", b"100", b"v"],
21584 &[b"PSETEX", b"e2", b"100000", b"v"],
21585 &[b"GETEX", b"e1", b"PERSIST"],
21586 &[b"GETDEL", b"k2"],
21587 &[b"GET", b"k2"],
21588 &[b"DIGEST", b"k1"],
21589 &[b"DELEX", b"k3"],
21590 &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
21593 &[b"MGET", b"a", b"b", b"c", b"missing"],
21594 &[b"MSETNX", b"d", b"4", b"e", b"5"],
21595 &[b"MSETNX", b"e", b"6", b"f", b"7"],
21596 &[b"MGET", b"d", b"e", b"f"],
21597 &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
21598 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
21599 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
21600 &[b"MGET", b"g", b"h"],
21601 &[b"SET", b"s1", b"ohmytext"],
21602 &[b"SET", b"s2", b"mynewtext"],
21603 &[b"LCS", b"s1", b"s2"],
21604 &[b"LCS", b"s1", b"s2", b"LEN"],
21605 &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
21606 &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
21607 &[b"LCS", b"s1", b"gone"],
21608 &[b"MSET", b"odd"],
21610 &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
21611 &[b"MGET"],
21612 ];
21613
21614 let mut one = Fixture::new();
21615 let mut many = Fixture::striped(8);
21616 for parts in script {
21617 let a = one.run(parts);
21618 let b = many.run(parts);
21619 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21620 }
21621 }
21622
21623 #[test]
21628 fn a_striped_database_spreads_the_keys_it_is_given() {
21629 let mut f = Fixture::striped(8);
21630 for i in 0..256 {
21631 let key = format!("key:{i}");
21632 f.run(&[b"SET", key.as_bytes(), b"v"]);
21633 }
21634 assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
21635 }
21636
21637 #[test]
21640 fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
21641 let mut one = Fixture::new();
21642 let mut many = Fixture::striped(8);
21643 for f in [&mut one, &mut many] {
21644 f.run(&[b"SET", b"str", b"v"]);
21645 f.server
21651 .striped(0)
21652 .hold(b"list")
21653 .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
21654 .expect("a new list");
21655 }
21656 assert_eq!(
21657 one.run(&[b"MGET", b"str", b"list", b"gone"]),
21658 many.run(&[b"MGET", b"str", b"list", b"gone"])
21659 );
21660 }
21661
21662 #[test]
21670 fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
21671 let script: &[&[&[u8]]] = &[
21672 &[b"SET", b"k1", b"v1"],
21673 &[b"SET", b"k2", b"v2"],
21674 &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
21675 &[b"TYPE", b"k1"],
21676 &[b"TYPE", b"gone"],
21677 &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
21678 &[b"EXPIRE", b"k1", b"100"],
21679 &[b"TTL", b"k1"],
21680 &[b"EXPIRE", b"k1", b"200", b"NX"],
21681 &[b"PERSIST", b"k1"],
21682 &[b"TTL", b"k1"],
21683 &[b"PEXPIREAT", b"k2", b"1900000000000"],
21684 &[b"EXPIRETIME", b"k2"],
21685 &[b"PEXPIRETIME", b"k2"],
21686 &[b"PERSIST", b"k2"],
21687 &[b"OBJECT", b"ENCODING", b"k1"],
21688 &[b"OBJECT", b"REFCOUNT", b"k1"],
21689 &[b"OBJECT", b"IDLETIME", b"k1"],
21690 &[b"OBJECT", b"FREQ", b"k1"],
21691 &[b"OBJECT", b"ENCODING", b"gone"],
21692 &[b"OBJECT", b"HELP"],
21693 &[b"RENAME", b"k1", b"k9"],
21694 &[b"GET", b"k9"],
21695 &[b"RENAME", b"gone", b"x"],
21696 &[b"RENAMENX", b"k9", b"k2"],
21697 &[b"RENAMENX", b"k9", b"k8"],
21698 &[b"GET", b"k8"],
21699 &[b"COPY", b"k8", b"c1"],
21700 &[b"COPY", b"k8", b"c1"],
21701 &[b"COPY", b"k8", b"c1", b"REPLACE"],
21702 &[b"COPY", b"k8", b"k8"],
21703 &[b"COPY", b"gone", b"c2"],
21704 &[b"COPY", b"k8", b"k8", b"DB", b"1"],
21705 &[b"COPY", b"k8", b"c9", b"DB", b"9"],
21706 &[b"MOVE", b"c1", b"1"],
21707 &[b"MOVE", b"c1", b"1"],
21708 &[b"MOVE", b"k8", b"0"],
21709 &[b"DEL", b"k2", b"gone"],
21710 &[b"UNLINK", b"k8", b"k8"],
21711 &[b"DBSIZE"],
21712 ];
21713
21714 let mut one = Fixture::new();
21715 let mut many = Fixture::striped(8);
21716 for parts in script {
21717 let a = one.run(parts);
21718 let b = many.run(parts);
21719 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21720 }
21721
21722 for f in [&mut one, &mut many] {
21728 f.run(&[b"SET", b"d1", b"payload"]);
21729 let payload = f
21730 .server
21731 .striped(0)
21732 .hold(b"d1")
21733 .dump(b"d1")
21734 .expect("a key that is there");
21735 assert!(
21736 f.run(&[b"DUMP", b"d1"])
21737 .starts_with(&format!("${}", payload.len())),
21738 "a payload of the length the store gave"
21739 );
21740 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
21741 assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
21742 assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
21743 assert_eq!(
21744 f.run(&[b"RESTORE", b"d2", b"0", &payload]),
21745 "-BUSYKEY Target key name already exists.\r\n"
21746 );
21747 assert_eq!(
21748 f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
21749 "-ERR DUMP payload version or checksum are wrong\r\n"
21750 );
21751 }
21752 }
21753
21754 #[test]
21761 fn a_scan_of_a_striped_database_walks_all_of_it() {
21762 let n = if cfg!(miri) { 80 } else { 500 };
21766 let mut f = Fixture::striped(8);
21767 for i in 0..n {
21768 let key = format!("key:{i}");
21769 f.run(&[b"SET", key.as_bytes(), b"v"]);
21770 }
21771
21772 let mut seen = Vec::new();
21773 let mut cursor = "0".to_owned();
21774 let mut calls = 0;
21775 loop {
21776 let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
21777 let (next, keys) = scan_reply(&reply);
21778 seen.extend(keys);
21779 cursor = next;
21780 calls += 1;
21781 assert!(calls < 5_000, "a scan that will not finish");
21782 if cursor == "0" {
21783 break;
21784 }
21785 }
21786 seen.sort();
21787 assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
21788 assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
21789
21790 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
21794 let (_, keys) = scan_reply(&reply);
21795 assert_eq!(keys.len(), 10, "key:40 through key:49");
21796 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
21797 let (_, keys) = scan_reply(&reply);
21798 assert!(keys.is_empty(), "nothing here is a list");
21799 }
21800
21801 #[test]
21807 fn a_random_key_can_come_from_any_stripe() {
21808 let mut f = Fixture::striped(8);
21809 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
21810 for i in 0..200 {
21811 let key = format!("key:{i}");
21812 f.run(&[b"SET", key.as_bytes(), b"v"]);
21813 }
21814 let mut homes = std::collections::HashSet::new();
21815 for _ in 0..200 {
21816 let got = f.run(&[b"RANDOMKEY"]);
21817 let key = got.split("\r\n").nth(1).expect("a key").to_owned();
21818 assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
21819 homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
21820 }
21821 assert_eq!(homes.len(), 8, "some stripe was never drawn from");
21822 }
21823
21824 fn apart(f: &mut Fixture, src: &str) -> String {
21828 let home = f.server.striped(0).stripe_of(src.as_bytes());
21829 for i in 0..1_000 {
21830 let dst = format!("dst:{i}");
21831 if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
21832 return dst;
21833 }
21834 }
21835 panic!("eight stripes and a thousand keys all landed in one place");
21836 }
21837
21838 #[test]
21841 fn a_rename_across_stripes_takes_everything_with_it() {
21842 let mut f = Fixture::striped(8);
21843 let dst = apart(&mut f, "src");
21844 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21845
21846 f.run(&[b"SET", src, b"v"]);
21847 f.run(&[b"EXPIRE", src, b"100"]);
21848 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21849 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
21850 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
21851 assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
21852
21853 f.server
21858 .striped(0)
21859 .hold(src)
21860 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21861 .expect("a new list");
21862 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21863 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
21864 assert_eq!(
21865 f.server.striped(0).hold(dst).llen(dst).expect("a list"),
21866 2,
21867 "the members are on the stripe the key moved to"
21868 );
21869
21870 f.run(&[b"SET", src, b"v"]);
21873 assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
21874 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
21875 assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
21876 }
21877
21878 #[test]
21880 fn a_copy_across_stripes_leaves_the_source_where_it_was() {
21881 let mut f = Fixture::striped(8);
21882 let dst = apart(&mut f, "src");
21883 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21884
21885 f.run(&[b"SET", src, b"v"]);
21886 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21887 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
21888 assert_eq!(
21889 f.run(&[b"COPY", src, dst]),
21890 ":0\r\n",
21891 "the destination is taken"
21892 );
21893 f.run(&[b"SET", src, b"w"]);
21894 assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
21895 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
21896
21897 f.run(&[b"DEL", src, dst]);
21901 f.server
21902 .striped(0)
21903 .hold(src)
21904 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21905 .expect("a new list");
21906 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21907 f.server
21908 .striped(0)
21909 .hold(src)
21910 .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
21911 .expect("a list that is there");
21912 assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
21913 assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
21914 }
21915
21916 #[test]
21924 fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
21925 let script: &[&[&[u8]]] = &[
21926 &[b"SET", b"k1", b"foobar"],
21927 &[b"SETBIT", b"b1", b"7", b"1"],
21928 &[b"SETBIT", b"b1", b"7", b"0"],
21929 &[b"GETBIT", b"k1", b"6"],
21930 &[b"GETBIT", b"k1", b"100"],
21931 &[b"BITCOUNT", b"k1"],
21932 &[b"BITCOUNT", b"k1", b"0", b"0"],
21933 &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
21934 &[b"BITPOS", b"k1", b"1"],
21935 &[b"BITPOS", b"k1", b"0", b"2"],
21936 &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
21937 &[
21938 b"BITFIELD",
21939 b"bf",
21940 b"SET",
21941 b"u8",
21942 b"0",
21943 b"255",
21944 b"GET",
21945 b"u8",
21946 b"0",
21947 ],
21948 &[
21949 b"BITFIELD",
21950 b"bf",
21951 b"OVERFLOW",
21952 b"SAT",
21953 b"INCRBY",
21954 b"u8",
21955 b"0",
21956 b"10",
21957 ],
21958 &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
21959 &[b"SET", b"s1", b"abc"],
21962 &[b"SET", b"s2", b"abd"],
21963 &[b"SET", b"s3", b"a"],
21964 &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
21965 &[b"GET", b"d1"],
21966 &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
21967 &[b"GET", b"d2"],
21968 &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
21969 &[b"STRLEN", b"d3"],
21970 &[b"BITOP", b"NOT", b"d4", b"s1"],
21971 &[b"STRLEN", b"d4"],
21972 &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
21973 &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
21974 &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
21975 &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
21976 &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
21979 &[b"EXISTS", b"d1"],
21980 &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
21981 &[b"GET", b"d9"],
21982 &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
21986 &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
21987 &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
21988 &[b"BITOP", b"DIFF", b"d1", b"s1"],
21989 &[b"BITOP", b"NOPE", b"d1", b"s1"],
21990 &[b"BITCOUNT", b"list"],
21991 &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
21992 ];
21993
21994 let mut one = Fixture::new();
21995 let mut many = Fixture::striped(8);
21996 for f in [&mut one, &mut many] {
21997 plant_list(f, b"list");
21998 }
21999 for parts in script {
22000 let a = one.run(parts);
22001 let b = many.run(parts);
22002 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22003 }
22004 }
22005
22006 fn plant_list(f: &mut Fixture, key: &[u8]) {
22012 f.server
22013 .striped(0)
22014 .hold(key)
22015 .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
22016 .expect("a new list");
22017 }
22018
22019 #[test]
22026 fn a_bitop_across_stripes_reads_every_source() {
22027 let mut f = Fixture::striped(8);
22028 let other = apart(&mut f, "src");
22029 let (src, far) = (b"src".as_slice(), other.as_bytes());
22030 assert_ne!(
22031 f.server.striped(0).stripe_of(src),
22032 f.server.striped(0).stripe_of(far),
22033 "the two keys are the point of the test"
22034 );
22035
22036 f.run(&[b"SET", src, b"abc"]);
22037 f.run(&[b"SET", far, b"abd"]);
22038 assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
22039 assert_eq!(
22040 f.run(&[b"GET", far]),
22041 "$3\r\nab`\r\n",
22042 "a destination that is also a source"
22043 );
22044 f.run(&[b"SET", far, b"abd"]);
22045 assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
22046 assert_eq!(
22047 f.run(&[b"GET", src]),
22048 "$3\r\n\0\0\x07\r\n",
22049 "and the other way round"
22050 );
22051
22052 f.run(&[b"SET", src, b"abc"]);
22056 f.run(&[b"DEL", far]);
22057 assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
22058 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
22059 f.run(&[b"SET", src, b"abc"]);
22060 f.run(&[b"DEL", far]);
22061 plant_list(&mut f, far);
22062 assert_eq!(
22063 f.run(&[b"BITOP", b"OR", b"out", src, far]),
22064 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22065 );
22066 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
22067 }
22068
22069 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
22075 #[test]
22076 fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
22077 let script: &[&[&[u8]]] = &[
22078 &[b"PFADD", b"h1", b"a", b"b", b"c"],
22079 &[b"PFADD", b"h1", b"a"],
22080 &[b"PFADD", b"h2"],
22081 &[b"PFADD", b"h2", b"c", b"d", b"e"],
22082 &[b"PFCOUNT", b"h1"],
22083 &[b"PFCOUNT", b"h2"],
22084 &[b"PFCOUNT", b"missing"],
22085 &[b"PFCOUNT", b"h1", b"h2"],
22087 &[b"PFCOUNT", b"h1", b"missing"],
22088 &[b"PFMERGE", b"m", b"h1", b"h2"],
22089 &[b"PFCOUNT", b"m"],
22090 &[b"STRLEN", b"m"],
22091 &[b"PFMERGE", b"m"],
22092 &[b"PFCOUNT", b"m"],
22093 &[b"PFMERGE", b"m2", b"missing"],
22094 &[b"PFCOUNT", b"m2"],
22095 &[b"PFDEBUG", b"ENCODING", b"h1"],
22098 &[b"PFDEBUG", b"DECODE", b"h1"],
22099 &[b"PFDEBUG", b"TODENSE", b"h1"],
22100 &[b"PFDEBUG", b"ENCODING", b"h1"],
22101 &[b"PFDEBUG", b"TODENSE", b"h1"],
22102 &[b"PFCOUNT", b"h1", b"h2"],
22103 &[b"PFSELFTEST"],
22104 &[b"SET", b"plain", b"not a sketch at all"],
22106 &[b"PFADD", b"plain", b"a"],
22107 &[b"PFCOUNT", b"plain"],
22108 &[b"PFCOUNT", b"h1", b"plain"],
22109 &[b"PFMERGE", b"plain", b"h1"],
22110 &[b"PFMERGE", b"m", b"plain"],
22111 &[b"PFDEBUG", b"ENCODING", b"gone"],
22112 &[b"PFDEBUG", b"NOPE", b"h1"],
22113 ];
22114
22115 let mut one = Fixture::new();
22116 let mut many = Fixture::striped(8);
22117 for parts in script {
22118 let a = one.run(parts);
22119 let b = many.run(parts);
22120 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22121 }
22122 }
22123
22124 #[test]
22133 fn the_set_group_answers_the_same_however_many_stripes_there_are() {
22134 const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
22135 let script: &[&[&[u8]]] = &[
22136 &[b"SADD", b"s1", b"a", b"b", b"c"],
22137 &[b"SADD", b"s1", b"a"],
22138 &[b"SADD", b"s2", b"b", b"c", b"d"],
22139 &[b"SADD", b"ints", b"1", b"2", b"3"],
22140 &[b"SCARD", b"s1"],
22141 &[b"SISMEMBER", b"s1", b"a"],
22142 &[b"SISMEMBER", b"s1", b"z"],
22143 &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
22144 &[b"SMEMBERS", b"s1"],
22145 &[b"SREM", b"s1", b"c"],
22146 &[b"SADD", b"s1", b"c"],
22147 &[b"SSCAN", b"s1", b"0"],
22148 &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
22149 &[b"SADD", b"one", b"m"],
22152 &[b"SRANDMEMBER", b"one"],
22153 &[b"SRANDMEMBER", b"one", b"-3"],
22154 &[b"SRANDMEMBER", b"gone"],
22155 &[b"SPOP", b"one"],
22156 &[b"SPOP", b"one"],
22157 &[b"SPOP", b"gone", b"2"],
22158 &[b"SMOVE", b"s1", b"s2", b"a"],
22160 &[b"SMOVE", b"s1", b"s2", b"zzz"],
22161 &[b"SMOVE", b"gone", b"s2", b"a"],
22162 &[b"SMEMBERS", b"s1"],
22163 &[b"SMEMBERS", b"s2"],
22164 &[b"SINTER", b"s1", b"s2"],
22166 &[b"SUNION", b"s1", b"s2"],
22167 &[b"SDIFF", b"s2", b"s1"],
22168 &[b"SINTER", b"s1", b"gone"],
22169 &[b"SUNION", b"s1", b"gone"],
22170 &[b"SDIFF", b"gone", b"s1"],
22171 &[b"SINTER", b"ints", b"s1"],
22172 &[b"SINTERCARD", b"2", b"s1", b"s2"],
22173 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
22174 &[b"SUNIONCARD", b"2", b"s1", b"s2"],
22175 &[b"SDIFFCARD", b"2", b"s2", b"s1"],
22176 &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
22177 &[b"SMEMBERS", b"d1"],
22178 &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
22179 &[b"SCARD", b"d2"],
22180 &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
22181 &[b"SCARD", b"d3"],
22182 &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
22185 &[b"EXISTS", b"d4"],
22186 &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
22188 &[b"SCARD", b"s2"],
22189 &[b"SET", b"str", b"v"],
22191 &[b"SADD", b"str", b"a"],
22192 &[b"SINTER", b"s1", b"str"],
22193 &[b"SINTERSTORE", b"d5", b"s1", b"str"],
22194 &[b"EXISTS", b"d5"],
22195 &[b"SMOVE", b"str", b"s2", b"a"],
22196 &[b"SMOVE", b"s1", b"str", b"b"],
22197 &[b"SMOVE", b"gone", b"str", b"b"],
22198 &[b"SINTERCARD", b"0", b"s1"],
22199 &[b"SINTERCARD", b"3", b"s1", b"s2"],
22200 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
22201 &[b"SPOP", b"s1", b"-1"],
22202 ];
22203
22204 let mut one = Fixture::new();
22205 let mut many = Fixture::striped(8);
22206 for parts in script {
22207 let a = one.run(parts);
22208 let b = many.run(parts);
22209 let name = String::from_utf8_lossy(parts[0]).to_uppercase();
22210 if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
22211 assert_eq!(sorted(&a), sorted(&b), "{name}");
22212 } else {
22213 assert_eq!(a, b, "{name}");
22214 }
22215 }
22216 }
22217
22218 #[test]
22220 fn a_set_operation_across_stripes_reads_every_set() {
22221 let mut f = Fixture::striped(8);
22222 let second = apart(&mut f, "s1");
22223 let third = apart(&mut f, &second);
22224 let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
22225
22226 f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
22227 f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
22228 assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
22229 assert_eq!(
22230 sorted(&f.run(&[b"SUNION", s1, s2])),
22231 ["a", "b", "c", "d"],
22232 "a union of two stripes is both of them"
22233 );
22234 assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
22235 assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
22236 assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
22237 assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
22238
22239 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
22241 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
22242 assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
22243 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
22244 assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
22245 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
22246
22247 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
22250 assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
22251 f.run(&[b"SET", s3, b"v"]);
22252 assert_eq!(
22253 f.run(&[b"SINTER", s1, s3]),
22254 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22255 );
22256 assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
22257 }
22258
22259 #[test]
22261 fn a_move_across_stripes_takes_the_member_with_it() {
22262 let mut f = Fixture::striped(8);
22263 let other = apart(&mut f, "src");
22264 let (src, dst) = (b"src".as_slice(), other.as_bytes());
22265
22266 f.run(&[b"SADD", src, b"a", b"b"]);
22267 f.run(&[b"SADD", dst, b"c"]);
22268 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
22269 assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
22270 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
22271 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
22272
22273 f.run(&[b"DEL", dst]);
22276 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
22277 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
22278 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
22279
22280 f.run(&[b"SET", dst, b"v"]);
22284 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
22285 f.run(&[b"SADD", src, b"b"]);
22286 assert_eq!(
22287 f.run(&[b"SMOVE", src, dst, b"b"]),
22288 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22289 );
22290 }
22291
22292 #[test]
22294 fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
22295 let mut f = Fixture::striped(8);
22296 let other = apart(&mut f, "src");
22297 let (src, far) = (b"src".as_slice(), other.as_bytes());
22298
22299 for i in 0..150 {
22300 let ele = format!("e:{i}");
22301 f.run(&[b"PFADD", src, ele.as_bytes()]);
22302 }
22303 for i in 150..200 {
22304 let ele = format!("e:{i}");
22305 f.run(&[b"PFADD", far, ele.as_bytes()]);
22306 }
22307 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
22310 assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
22311 assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
22312
22313 let dest = apart(&mut f, &other);
22316 assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
22317 assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
22318 assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
22319 assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
22320 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
22321 }
22322
22323 #[test]
22330 fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
22331 let script: &[&[&[u8]]] = &[
22332 &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
22333 &[b"ZADD", b"z1", b"NX", b"9", b"a"],
22334 &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
22335 &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
22336 &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
22337 &[b"ZINCRBY", b"z1", b"1.5", b"b"],
22338 &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
22339 &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
22340 &[b"ZADD", b"one", b"1", b"m"],
22341 &[b"ZCARD", b"z1"],
22342 &[b"ZCARD", b"gone"],
22343 &[b"ZSCORE", b"z1", b"a"],
22344 &[b"ZSCORE", b"z1", b"zz"],
22345 &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
22346 &[b"ZRANK", b"z1", b"c"],
22347 &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
22348 &[b"ZREVRANK", b"z1", b"c"],
22349 &[b"ZRANK", b"z1", b"gone"],
22350 &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
22351 &[b"ZCOUNT", b"z1", b"(1", b"3"],
22352 &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
22353 &[b"ZRANGE", b"z1", b"0", b"-1"],
22355 &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
22356 &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
22357 &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
22358 &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
22359 &[b"ZREVRANGE", b"z1", b"0", b"-1"],
22360 &[
22361 b"ZRANGEBYSCORE",
22362 b"z1",
22363 b"-inf",
22364 b"+inf",
22365 b"LIMIT",
22366 b"1",
22367 b"1",
22368 ],
22369 &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
22370 &[b"ZSCAN", b"z1", b"0"],
22371 &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
22372 &[b"ZRANDMEMBER", b"one"],
22375 &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
22376 &[b"ZRANDMEMBER", b"gone"],
22377 &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
22379 &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
22380 &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
22381 &[b"EXISTS", b"d0"],
22382 &[b"ZUNION", b"2", b"z1", b"z2"],
22384 &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
22385 &[
22386 b"ZUNION",
22387 b"2",
22388 b"z1",
22389 b"z2",
22390 b"WEIGHTS",
22391 b"2",
22392 b"3",
22393 b"AGGREGATE",
22394 b"MAX",
22395 b"WITHSCORES",
22396 ],
22397 &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
22398 &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
22399 &[b"ZDIFF", b"2", b"gone", b"z1"],
22400 &[b"ZINTERCARD", b"2", b"z1", b"z2"],
22401 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
22402 &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
22403 &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
22404 &[
22405 b"ZINTERSTORE",
22406 b"d2",
22407 b"2",
22408 b"z1",
22409 b"z2",
22410 b"AGGREGATE",
22411 b"MIN",
22412 ],
22413 &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
22414 &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
22415 &[b"ZCARD", b"d3"],
22416 &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
22419 &[b"EXISTS", b"d4"],
22420 &[b"SADD", b"plain", b"a", b"x"],
22423 &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
22424 &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
22425 &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
22427 &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
22428 &[b"ZREM", b"d5", b"x", b"nothere"],
22430 &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
22431 &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
22432 &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
22433 &[b"ZPOPMIN", b"z1"],
22434 &[b"ZPOPMAX", b"z1", b"2"],
22435 &[b"ZPOPMIN", b"gone"],
22436 &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
22437 &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
22438 &[b"SET", b"str", b"v"],
22440 &[b"ZADD", b"str", b"1", b"a"],
22441 &[b"ZSCORE", b"str", b"a"],
22442 &[b"ZADD", b"z1", b"nan", b"a"],
22443 &[b"ZUNION", b"2", b"z1", b"str"],
22444 &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
22445 &[b"EXISTS", b"d6"],
22446 &[b"ZINTERCARD", b"0", b"z1"],
22447 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
22448 &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
22449 &[b"ZMPOP", b"1", b"str", b"MIN"],
22450 &[b"ZPOPMIN", b"z1", b"-1"],
22451 ];
22452
22453 let mut one = Fixture::new();
22454 let mut many = Fixture::striped(8);
22455 for parts in script {
22456 let a = one.run(parts);
22457 let b = many.run(parts);
22458 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22459 }
22460 }
22461
22462 #[test]
22464 fn a_sorted_set_operation_across_stripes_reads_every_input() {
22465 let mut f = Fixture::striped(8);
22466 let second = apart(&mut f, "z1");
22467 let third = apart(&mut f, &second);
22468 let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
22469
22470 f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
22471 f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
22472 assert_eq!(
22475 f.run(&[b"ZUNION", b"2", z1, z2]),
22476 "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
22477 );
22478 assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
22479 assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
22480 assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
22481 assert_eq!(
22482 f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
22483 ":1\r\n"
22484 );
22485
22486 assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
22489 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
22490 assert_eq!(
22491 f.run(&[
22492 b"ZUNIONSTORE",
22493 z3,
22494 b"2",
22495 z1,
22496 z2,
22497 b"WEIGHTS",
22498 b"2",
22499 b"3",
22500 b"AGGREGATE",
22501 b"MAX"
22502 ]),
22503 ":3\r\n"
22504 );
22505 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
22506 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
22507 assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
22508 assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
22509 assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
22510
22511 let popped = format!(
22514 "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
22515 second.len()
22516 );
22517 assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
22518 f.run(&[b"ZADD", z2, b"3", b"b"]);
22519
22520 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
22523 assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
22524 f.run(&[b"SET", z3, b"v"]);
22525 assert_eq!(
22526 f.run(&[b"ZUNION", b"2", z1, z3]),
22527 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22528 );
22529 assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
22530
22531 assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
22535 assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
22536 assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
22537 }
22538
22539 #[test]
22541 fn a_range_store_across_stripes_copies_the_window() {
22542 let mut f = Fixture::striped(8);
22543 let other = apart(&mut f, "src");
22544 let third = apart(&mut f, &other);
22545 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22546
22547 f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
22548 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
22549 assert_eq!(
22550 f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
22551 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
22552 );
22553 assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
22554
22555 assert_eq!(
22558 f.run(&[
22559 b"ZRANGESTORE",
22560 dst,
22561 src,
22562 b"+inf",
22563 b"-inf",
22564 b"BYSCORE",
22565 b"REV",
22566 b"LIMIT",
22567 b"0",
22568 b"2"
22569 ]),
22570 ":2\r\n"
22571 );
22572 assert_eq!(
22573 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
22574 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22575 );
22576
22577 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
22580 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
22581 f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
22582 f.run(&[b"SET", plain, b"v"]);
22583 assert_eq!(
22584 f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
22585 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22586 );
22587 assert_eq!(
22588 f.run(&[b"ZCARD", dst]),
22589 ":3\r\n",
22590 "and left the destination"
22591 );
22592 }
22593
22594 #[test]
22601 fn the_list_group_answers_the_same_however_many_stripes_there_are() {
22602 let script: &[&[&[u8]]] = &[
22603 &[b"RPUSH", b"l1", b"a", b"b", b"c"],
22604 &[b"LPUSH", b"l1", b"z"],
22605 &[b"RPUSHX", b"l1", b"d"],
22606 &[b"LPUSHX", b"gone", b"x"],
22607 &[b"RPUSHX", b"gone", b"x"],
22608 &[b"LLEN", b"l1"],
22609 &[b"LLEN", b"gone"],
22610 &[b"LRANGE", b"l1", b"0", b"-1"],
22611 &[b"LRANGE", b"l1", b"1", b"2"],
22612 &[b"LRANGE", b"l1", b"5", b"9"],
22613 &[b"LINDEX", b"l1", b"0"],
22614 &[b"LINDEX", b"l1", b"-1"],
22615 &[b"LINDEX", b"l1", b"99"],
22616 &[b"LSET", b"l1", b"0", b"y"],
22617 &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
22618 &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
22619 &[b"LPOS", b"l1", b"b"],
22620 &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
22621 &[b"LPOS", b"l1", b"nothere"],
22622 &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
22623 &[b"LREM", b"l1", b"1", b"aa"],
22624 &[b"LTRIM", b"l1", b"0", b"3"],
22625 &[b"LRANGE", b"l1", b"0", b"-1"],
22626 &[b"LPOP", b"l1"],
22627 &[b"RPOP", b"l1"],
22628 &[b"LPOP", b"l1", b"2"],
22629 &[b"LPOP", b"gone"],
22630 &[b"LPOP", b"gone", b"2"],
22631 &[b"EXISTS", b"l1"],
22632 &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
22635 &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
22636 &[b"RPOPLPUSH", b"src", b"dst"],
22637 &[b"LRANGE", b"dst", b"0", b"-1"],
22638 &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
22639 &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
22640 &[
22641 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
22642 ],
22643 &[
22644 b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
22645 ],
22646 &[b"LRANGE", b"dst", b"0", b"-1"],
22647 &[
22648 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
22649 ],
22650 &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
22651 &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
22652 &[b"LMPOP", b"1", b"gone", b"LEFT"],
22653 &[b"RPUSH", b"q", b"a", b"b", b"c"],
22656 &[b"BLPOP", b"gone", b"q", b"0"],
22657 &[b"BRPOP", b"q", b"0"],
22658 &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
22659 &[b"RPUSH", b"q", b"x", b"y", b"z"],
22660 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22661 &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
22662 &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22663 &[b"BLPOP", b"q", b"0"],
22664 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22665 &[b"SET", b"plain", b"v"],
22667 &[b"LPUSH", b"plain", b"a"],
22668 &[b"LLEN", b"plain"],
22669 &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
22670 &[b"LRANGE", b"dst", b"0", b"-1"],
22671 &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
22672 &[b"LSET", b"gone", b"0", b"v"],
22673 &[b"LSET", b"dst", b"99", b"v"],
22674 &[b"LPOP", b"dst", b"-1"],
22675 &[b"LMPOP", b"0", b"dst", b"LEFT"],
22676 &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
22677 ];
22678
22679 let mut one = Fixture::new();
22680 let mut many = Fixture::striped(8);
22681 for parts in script {
22682 let a = one.run(parts);
22683 let b = many.run(parts);
22684 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22685 }
22686 }
22687
22688 #[test]
22690 fn a_list_move_across_stripes_takes_the_elements_with_it() {
22691 let mut f = Fixture::striped(8);
22692 let other = apart(&mut f, "src");
22693 let third = apart(&mut f, &other);
22694 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22695
22696 f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
22697 assert_eq!(
22698 f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
22699 "$1\r\na\r\n"
22700 );
22701 assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
22702 assert_eq!(
22703 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22704 "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
22705 "one went on each end of the destination"
22706 );
22707 assert_eq!(
22708 f.run(&[b"LRANGE", src, b"0", b"-1"]),
22709 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22710 );
22711
22712 assert_eq!(
22714 f.run(&[
22715 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
22716 ]),
22717 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22718 );
22719 assert_eq!(
22720 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22721 "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
22722 );
22723 assert_eq!(
22724 f.run(&[b"EXISTS", src]),
22725 ":0\r\n",
22726 "and the source is gone with its last element"
22727 );
22728
22729 f.run(&[b"RPUSH", src, b"e", b"f"]);
22732 assert_eq!(
22733 f.run(&[
22734 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
22735 ]),
22736 "*-1\r\n"
22737 );
22738 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
22739 assert_eq!(
22740 f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
22741 "$-1\r\n"
22742 );
22743 assert_eq!(
22744 f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
22745 "*-1\r\n"
22746 );
22747
22748 f.run(&[b"SET", plain, b"v"]);
22752 assert_eq!(
22753 f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
22754 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22755 );
22756 assert_eq!(
22757 f.run(&[b"LLEN", src]),
22758 ":2\r\n",
22759 "and left the source alone"
22760 );
22761 assert_eq!(
22762 f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
22763 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22764 );
22765 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
22766 }
22767
22768 #[test]
22774 fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
22775 let mut f = Fixture::striped(8);
22776 let other = apart(&mut f, "q");
22777 let (q, far) = (b"q".as_slice(), other.as_bytes());
22778
22779 assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
22780 assert_eq!(f.server.parked(), 1);
22781 f.run(&[b"RPUSH", far, b"v"]);
22782 let mut out = Out::new(Proto::Resp2);
22783 assert!(f.server.serve_waiter(7, 0, &mut out));
22784 let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
22785 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22786 assert_eq!(
22787 f.run(&[b"EXISTS", far]),
22788 ":0\r\n",
22789 "and it took the element with it"
22790 );
22791
22792 f.server.forget_waiters(7);
22795 assert_eq!(
22796 f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
22797 Flow::Block
22798 );
22799 f.run(&[b"RPUSH", q, b"w"]);
22800 let mut out = Out::new(Proto::Resp2);
22801 assert!(f.server.serve_waiter(7, 0, &mut out));
22802 assert_eq!(
22803 core::str::from_utf8(out.as_slice()).expect("ascii"),
22804 "$1\r\nw\r\n"
22805 );
22806 assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
22807 }
22808
22809 #[test]
22815 fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
22816 let script: &[&[&[u8]]] = &[
22817 &[b"XADD", b"s", b"1-1", b"a", b"1"],
22818 &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
22819 &[b"XADD", b"s", b"3-1", b"d", b"4"],
22820 &[b"XADD", b"s", b"1-1", b"e", b"5"],
22821 &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
22822 &[b"XLEN", b"s"],
22823 &[b"XLEN", b"gone"],
22824 &[b"XRANGE", b"s", b"-", b"+"],
22825 &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
22826 &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
22827 &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
22828 &[b"XREVRANGE", b"s", b"+", b"-"],
22829 &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
22830 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
22831 &[b"XREAD", b"STREAMS", b"s", b"$"],
22832 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22834 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22835 &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
22836 &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
22837 &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
22838 &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
22839 &[
22840 b"XREADGROUP",
22841 b"GROUP",
22842 b"g",
22843 b"c1",
22844 b"COUNT",
22845 b"1",
22846 b"STREAMS",
22847 b"s",
22848 b"0",
22849 ],
22850 &[
22851 b"XREADGROUP",
22852 b"GROUP",
22853 b"nope",
22854 b"c1",
22855 b"STREAMS",
22856 b"s",
22857 b">",
22858 ],
22859 &[b"XPENDING", b"s", b"g"],
22860 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
22861 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
22862 &[b"XPENDING", b"s", b"nope"],
22863 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
22864 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
22865 &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
22866 &[b"XACK", b"s", b"g", b"1-1"],
22867 &[b"XACK", b"s", b"g", b"1-1"],
22868 &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
22869 &[b"XPENDING", b"s", b"g"],
22870 &[b"XINFO", b"STREAM", b"s"],
22871 &[b"XINFO", b"GROUPS", b"s"],
22872 &[b"XINFO", b"CONSUMERS", b"s", b"g"],
22873 &[b"XINFO", b"STREAM", b"gone"],
22874 &[b"XDEL", b"s", b"3-1"],
22876 &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
22877 &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
22878 &[b"XADD", b"s", b"9-1", b"z", b"9"],
22879 &[b"XTRIM", b"s", b"MAXLEN", b"1"],
22880 &[b"XTRIM", b"s", b"MINID", b"9"],
22881 &[b"XSETID", b"s", b"99-1"],
22882 &[b"XSETID", b"s", b"1-1"],
22883 &[b"XLEN", b"s"],
22884 &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
22885 &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
22886 &[b"XGROUP", b"DESTROY", b"s", b"g"],
22887 &[b"XGROUP", b"DESTROY", b"s", b"g"],
22888 &[b"SET", b"plain", b"v"],
22890 &[b"XADD", b"plain", b"1-1", b"a", b"1"],
22891 &[b"XLEN", b"plain"],
22892 &[b"XREAD", b"STREAMS", b"plain", b"0"],
22893 &[b"XRANGE", b"s", b"bogus", b"+"],
22894 &[b"XADD", b"s", b"1-1", b"a"],
22895 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
22896 &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
22897 ];
22898
22899 let mut one = Fixture::new();
22900 let mut many = Fixture::striped(8);
22901 for parts in script {
22902 let a = one.run(parts);
22903 let b = many.run(parts);
22904 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22905 }
22906 }
22907
22908 #[test]
22914 fn a_stream_read_across_stripes_reads_every_key() {
22915 let mut f = Fixture::striped(8);
22916 let other = apart(&mut f, "s1");
22917 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
22918
22919 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22920 f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
22921 let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
22922 assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
22923 assert!(got.contains("1-1"), "the first one is in there: {got}");
22924 assert!(got.contains("2-1"), "and so is the second: {got}");
22925
22926 f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
22929 let got = f.run(&[
22930 b"XREADGROUP",
22931 b"GROUP",
22932 b"g",
22933 b"c",
22934 b"STREAMS",
22935 s1,
22936 s2,
22937 b">",
22938 b">",
22939 ]);
22940 assert!(got.starts_with("-NOGROUP"), "{got}");
22941 assert_eq!(
22942 f.run(&[b"XPENDING", s1, b"g"]),
22943 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
22944 "and read nothing from the key that did have the group"
22945 );
22946
22947 f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
22948 let got = f.run(&[
22949 b"XREADGROUP",
22950 b"GROUP",
22951 b"g",
22952 b"c",
22953 b"STREAMS",
22954 s1,
22955 s2,
22956 b">",
22957 b">",
22958 ]);
22959 assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
22960 }
22961
22962 #[test]
22964 fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
22965 let mut f = Fixture::striped(8);
22966 let other = apart(&mut f, "s1");
22967 let (s1, far) = (b"s1".as_slice(), other.as_bytes());
22968 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22969 f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
22970
22971 assert_eq!(
22972 f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
22973 .0,
22974 Flow::Block
22975 );
22976 f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
22977 let mut out = Out::new(Proto::Resp2);
22978 assert!(f.server.serve_waiter(7, 0, &mut out));
22979 let want = format!(
22980 "*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",
22981 other.len()
22982 );
22983 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22984 }
22985
22986 #[test]
22988 fn the_json_group_answers_the_same_however_many_stripes_there_are() {
22989 let script: &[&[&[u8]]] = &[
22990 &[
22991 b"JSON.SET",
22992 b"d",
22993 b"$",
22994 br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
22995 ],
22996 &[b"JSON.SET", b"d", b"$.a", b"2"],
22997 &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
22998 &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
22999 &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
23000 &[b"JSON.GET", b"d"],
23001 &[b"JSON.GET", b"d", b"$.b"],
23002 &[b"JSON.GET", b"gone", b"$"],
23003 &[b"JSON.TYPE", b"d", b"$.b"],
23004 &[b"JSON.TYPE", b"d", b"$.s"],
23005 &[b"JSON.TOGGLE", b"d", b"$.t"],
23006 &[b"JSON.ARRLEN", b"d", b"$.b"],
23007 &[b"JSON.OBJLEN", b"d", b"$"],
23008 &[b"JSON.OBJKEYS", b"d", b"$"],
23009 &[b"JSON.STRLEN", b"d", b"$.s"],
23010 &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
23011 &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
23012 &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
23013 &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
23014 &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
23015 &[b"JSON.ARRPOP", b"d", b"$.b"],
23016 &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
23017 &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
23018 &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
23019 &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
23020 &[b"JSON.RESP", b"d", b"$.b"],
23021 &[b"JSON.DEBUG", b"MEMORY", b"d"],
23022 &[b"JSON.CLEAR", b"d", b"$.b"],
23023 &[b"JSON.DEL", b"d", b"$.m"],
23024 &[b"JSON.FORGET", b"d", b"$.nothere"],
23025 &[
23027 b"JSON.MSET",
23028 b"m1",
23029 b"$",
23030 b"1",
23031 b"m2",
23032 b"$",
23033 b"2",
23034 b"m3",
23035 b"$",
23036 b"3",
23037 ],
23038 &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
23039 &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
23040 &[b"JSON.GET", b"m1", b"$"],
23041 &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
23042 &[b"JSON.GET", b"m2", b"$"],
23043 &[b"SET", b"plain", b"v"],
23045 &[b"JSON.GET", b"plain", b"$"],
23046 &[b"JSON.SET", b"plain", b"$", b"1"],
23047 &[b"JSON.MGET", b"m1", b"plain", b"$"],
23048 &[b"JSON.SET", b"d", b"$.b", b"["],
23049 &[b"JSON.DEL", b"plain"],
23050 ];
23051
23052 let mut one = Fixture::new();
23053 let mut many = Fixture::striped(8);
23054 for parts in script {
23055 let a = one.run(parts);
23056 let b = many.run(parts);
23057 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23058 }
23059 }
23060
23061 #[test]
23068 fn a_json_multi_write_across_stripes_reaches_every_key() {
23069 let mut f = Fixture::striped(8);
23070 let second = apart(&mut f, "m1");
23071 let third = apart(&mut f, &second);
23072 let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
23073
23074 assert_eq!(
23075 f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
23076 "+OK\r\n"
23077 );
23078 assert_eq!(
23079 f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
23080 "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
23081 );
23082
23083 assert_eq!(
23086 f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
23087 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
23088 );
23089 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
23090
23091 assert_eq!(
23095 f.run(&[
23096 b"JSON.MSET",
23097 m1,
23098 b"$",
23099 b"9",
23100 m2,
23101 b"$.deep",
23102 b"9",
23103 m3,
23104 b"$",
23105 b"7"
23106 ]),
23107 "$-1\r\n"
23108 );
23109 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
23110 assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
23111 assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
23112 }
23113
23114 #[test]
23116 fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
23117 let script: &[&[&[u8]]] = &[
23118 &[
23119 b"GEOADD",
23120 b"g",
23121 b"13.361389",
23122 b"38.115556",
23123 b"palermo",
23124 b"15.087269",
23125 b"37.502669",
23126 b"catania",
23127 ],
23128 &[
23129 b"GEOADD",
23130 b"g",
23131 b"NX",
23132 b"13.361389",
23133 b"38.115556",
23134 b"palermo",
23135 ],
23136 &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
23137 &[b"GEOPOS", b"g", b"palermo", b"nothere"],
23138 &[b"GEOHASH", b"g", b"palermo", b"catania"],
23139 &[b"GEODIST", b"g", b"palermo", b"catania"],
23140 &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
23141 &[b"GEODIST", b"g", b"palermo", b"nothere"],
23142 &[
23143 b"GEOSEARCH",
23144 b"g",
23145 b"FROMLONLAT",
23146 b"15",
23147 b"37",
23148 b"BYRADIUS",
23149 b"200",
23150 b"KM",
23151 b"ASC",
23152 b"WITHCOORD",
23153 b"WITHDIST",
23154 b"WITHHASH",
23155 ],
23156 &[
23157 b"GEOSEARCH",
23158 b"g",
23159 b"FROMMEMBER",
23160 b"palermo",
23161 b"BYBOX",
23162 b"400",
23163 b"400",
23164 b"KM",
23165 b"DESC",
23166 ],
23167 &[
23168 b"GEORADIUS",
23169 b"g",
23170 b"15",
23171 b"37",
23172 b"200",
23173 b"KM",
23174 b"COUNT",
23175 b"1",
23176 ],
23177 &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
23178 &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
23179 &[
23180 b"GEOSEARCHSTORE",
23181 b"dst",
23182 b"g",
23183 b"FROMLONLAT",
23184 b"15",
23185 b"37",
23186 b"BYRADIUS",
23187 b"200",
23188 b"KM",
23189 ],
23190 &[b"ZRANGE", b"dst", b"0", b"-1"],
23191 &[
23192 b"GEOSEARCHSTORE",
23193 b"dst",
23194 b"g",
23195 b"FROMLONLAT",
23196 b"15",
23197 b"37",
23198 b"BYRADIUS",
23199 b"1",
23200 b"M",
23201 b"STOREDIST",
23202 ],
23203 &[b"EXISTS", b"dst"],
23204 &[
23205 b"GEORADIUS",
23206 b"g",
23207 b"15",
23208 b"37",
23209 b"200",
23210 b"KM",
23211 b"STORE",
23212 b"dst",
23213 ],
23214 &[b"ZCARD", b"dst"],
23215 &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
23217 &[b"SET", b"plain", b"v"],
23218 &[b"GEOPOS", b"plain", b"a"],
23219 &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
23220 &[
23221 b"GEOSEARCHSTORE",
23222 b"dst",
23223 b"g",
23224 b"FROMLONLAT",
23225 b"15",
23226 b"37",
23227 b"BYRADIUS",
23228 b"200",
23229 b"KM",
23230 b"WITHCOORD",
23231 ],
23232 ];
23233
23234 let mut one = Fixture::new();
23235 let mut many = Fixture::striped(8);
23236 for parts in script {
23237 let a = one.run(parts);
23238 let b = many.run(parts);
23239 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23240 }
23241 }
23242
23243 #[test]
23245 fn a_geo_search_store_across_stripes_writes_what_it_found() {
23246 let mut f = Fixture::striped(8);
23247 let other = apart(&mut f, "g");
23248 let third = apart(&mut f, &other);
23249 let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
23250
23251 f.run(&[
23252 b"GEOADD",
23253 g,
23254 b"13.361389",
23255 b"38.115556",
23256 b"palermo",
23257 b"15.087269",
23258 b"37.502669",
23259 b"catania",
23260 ]);
23261 assert_eq!(
23262 f.run(&[
23263 b"GEOSEARCHSTORE",
23264 dst,
23265 g,
23266 b"FROMLONLAT",
23267 b"15",
23268 b"37",
23269 b"BYRADIUS",
23270 b"200",
23271 b"KM",
23272 b"ASC",
23273 ]),
23274 ":2\r\n"
23275 );
23276 assert_eq!(
23277 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
23278 "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
23279 "the geohash is the score, so the order is not the search order"
23280 );
23281 assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
23282
23283 assert_eq!(
23286 f.run(&[
23287 b"GEOSEARCHSTORE",
23288 dst,
23289 g,
23290 b"FROMMEMBER",
23291 b"palermo",
23292 b"BYRADIUS",
23293 b"200",
23294 b"KM",
23295 b"STOREDIST",
23296 ]),
23297 ":2\r\n"
23298 );
23299 assert_eq!(
23300 f.run(&[b"ZSCORE", dst, b"palermo"]),
23301 "$1\r\n0\r\n",
23302 "the centre is nought away from itself"
23303 );
23304
23305 assert_eq!(
23309 f.run(&[
23310 b"GEOSEARCHSTORE",
23311 dst,
23312 g,
23313 b"FROMLONLAT",
23314 b"0",
23315 b"0",
23316 b"BYRADIUS",
23317 b"1",
23318 b"M",
23319 ]),
23320 ":0\r\n"
23321 );
23322 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
23323 f.run(&[
23324 b"GEOSEARCHSTORE",
23325 dst,
23326 g,
23327 b"FROMLONLAT",
23328 b"15",
23329 b"37",
23330 b"BYRADIUS",
23331 b"200",
23332 b"KM",
23333 ]);
23334 f.run(&[b"SET", plain, b"v"]);
23335 assert_eq!(
23336 f.run(&[
23337 b"GEOSEARCHSTORE",
23338 dst,
23339 plain,
23340 b"FROMLONLAT",
23341 b"15",
23342 b"37",
23343 b"BYRADIUS",
23344 b"200",
23345 b"KM",
23346 ]),
23347 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23348 );
23349 assert_eq!(
23350 f.run(&[b"ZCARD", dst]),
23351 ":2\r\n",
23352 "and left the destination"
23353 );
23354 }
23355
23356 #[test]
23362 fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
23363 let script: &[&[&[u8]]] = &[
23364 &[
23365 b"TS.CREATE",
23366 b"ts:a",
23367 b"LABELS",
23368 b"sensor",
23369 b"a",
23370 b"room",
23371 b"1",
23372 ],
23373 &[b"TS.CREATE", b"ts:a"],
23374 &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
23375 &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
23376 &[
23377 b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
23378 ],
23379 &[
23380 b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
23381 ],
23382 &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
23383 &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
23384 &[b"TS.GET", b"ts:a"],
23385 &[b"TS.GET", b"gone"],
23386 &[b"TS.RANGE", b"ts:a", b"-", b"+"],
23387 &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
23388 &[
23389 b"TS.RANGE",
23390 b"ts:a",
23391 b"-",
23392 b"+",
23393 b"AGGREGATION",
23394 b"avg",
23395 b"2000",
23396 ],
23397 &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
23398 &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23399 &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23400 &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
23401 &[b"TS.READ", b"ts:a", b"0"],
23402 &[b"TS.READ", b"ts:a", b"+"],
23403 &[b"TS.QUERYINDEX", b"sensor=a"],
23405 &[b"TS.QUERYINDEX", b"room=1"],
23406 &[b"TS.QUERYINDEX", b"room=9"],
23407 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
23408 &[
23409 b"TS.QUERYLABELS",
23410 b"VALUES",
23411 b"sensor",
23412 b"FILTER",
23413 b"room=1",
23414 ],
23415 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
23416 &[
23417 b"TS.MGET",
23418 b"SELECTED_LABELS",
23419 b"sensor",
23420 b"FILTER",
23421 b"sensor=a",
23422 ],
23423 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
23424 &[
23425 b"TS.MREVRANGE",
23426 b"-",
23427 b"+",
23428 b"WITHLABELS",
23429 b"FILTER",
23430 b"sensor=a",
23431 ],
23432 &[
23433 b"TS.MRANGE",
23434 b"-",
23435 b"+",
23436 b"FILTER",
23437 b"room=1",
23438 b"GROUPBY",
23439 b"room",
23440 b"REDUCE",
23441 b"max",
23442 ],
23443 &[b"TS.INFO", b"ts:a"],
23444 &[
23446 b"TS.CREATERULE",
23447 b"ts:a",
23448 b"ts:down",
23449 b"AGGREGATION",
23450 b"avg",
23451 b"1000",
23452 ],
23453 &[b"TS.CREATE", b"ts:down"],
23454 &[
23455 b"TS.CREATERULE",
23456 b"ts:a",
23457 b"ts:down",
23458 b"AGGREGATION",
23459 b"avg",
23460 b"1000",
23461 ],
23462 &[b"TS.ADD", b"ts:a", b"5000", b"4"],
23463 &[b"TS.ADD", b"ts:a", b"6000", b"5"],
23464 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23465 &[b"TS.GET", b"ts:down", b"LATEST"],
23466 &[b"TS.INFO", b"ts:down"],
23467 &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
23468 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23469 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23470 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23471 &[b"TS.DEL", b"ts:a", b"0", b"1000"],
23472 &[b"SET", b"plain", b"v"],
23474 &[b"TS.ADD", b"plain", b"1", b"1"],
23475 &[b"TS.GET", b"plain"],
23476 &[b"TS.READ", b"plain", b"0"],
23477 &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
23478 &[b"TS.RANGE", b"gone", b"-", b"+"],
23479 &[b"TS.INFO", b"gone"],
23480 ];
23481
23482 let mut one = Fixture::new();
23483 let mut many = Fixture::striped(8);
23484 for parts in script {
23485 let a = one.run(parts);
23486 let b = many.run(parts);
23487 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23488 }
23489 }
23490
23491 #[test]
23501 fn a_compaction_rule_across_stripes_reaches_both_ends() {
23502 let mut many = Fixture::striped(8);
23503 let other = apart(&mut many, "src");
23504 let (src, dst) = (b"src".as_slice(), other.as_bytes());
23505 let mut one = Fixture::new();
23506 let mut both = |parts: &[&[u8]]| {
23507 let a = one.run(parts);
23508 let b = many.run(parts);
23509 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23510 a
23511 };
23512
23513 both(&[b"TS.CREATE", src]);
23514 both(&[b"TS.CREATE", dst]);
23515 assert_eq!(
23516 both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
23517 "+OK\r\n"
23518 );
23519 both(&[b"TS.ADD", src, b"1000", b"1"]);
23520 both(&[b"TS.ADD", src, b"1500", b"3"]);
23521 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23524 let open = both(&[b"TS.GET", dst, b"LATEST"]);
23525 assert!(open.contains(":1000"), "the open bucket is folded: {open}");
23526
23527 both(&[b"TS.ADD", src, b"2000", b"5"]);
23530 let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
23531 assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
23532 assert!(got.contains(":1000"), "{got}");
23533
23534 both(&[b"TS.DEL", src, b"1000", b"1999"]);
23536 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23537
23538 assert!(
23541 both(&[b"TS.INFO", dst]).contains("src"),
23542 "the source is named"
23543 );
23544 assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
23545 assert_eq!(
23546 both(&[b"TS.DELETERULE", src, dst]),
23547 "-ERR TSDB: compaction rule does not exist\r\n"
23548 );
23549 }
23550
23551 #[test]
23553 fn a_label_query_across_stripes_finds_every_series() {
23554 let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
23555 let mut many = Fixture::striped(8);
23556 let mut homes: Vec<usize> = names
23557 .iter()
23558 .map(|name| many.server.striped(0).stripe_of(name))
23559 .collect();
23560 homes.sort_unstable();
23561 homes.dedup();
23562 assert!(homes.len() > 1, "the six keys are not all on one stripe");
23563
23564 let mut one = Fixture::new();
23565 let mut both = |parts: &[&[u8]]| {
23566 let a = one.run(parts);
23567 let b = many.run(parts);
23568 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23569 a
23570 };
23571 for name in &names {
23572 both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
23573 both(&[b"TS.ADD", name, b"1000", b"1"]);
23574 }
23575
23576 let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
23577 assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
23578 assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23579 assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23580 assert_eq!(
23581 both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
23582 "*1\r\n$4\r\nroom\r\n"
23583 );
23584 }
23585
23586 #[test]
23593 fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
23594 let script: &[&[&[u8]]] = &[
23595 &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
23596 &[b"HMSET", b"h", b"c", b"3"],
23597 &[b"HSETNX", b"h", b"a", b"9"],
23598 &[b"HSETNX", b"h", b"d", b"4"],
23599 &[b"HGET", b"h", b"a"],
23600 &[b"HGET", b"h", b"nope"],
23601 &[b"HMGET", b"h", b"a", b"nope"],
23602 &[b"HLEN", b"h"],
23603 &[b"HEXISTS", b"h", b"a"],
23604 &[b"HSTRLEN", b"h", b"a"],
23605 &[b"HGETALL", b"h"],
23606 &[b"HKEYS", b"h"],
23607 &[b"HVALS", b"h"],
23608 &[b"HINCRBY", b"h", b"a", b"5"],
23609 &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
23610 &[b"HSCAN", b"h", b"0"],
23611 &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
23612 &[b"HSCAN", b"h", b"0", b"NOVALUES"],
23613 &[b"HDEL", b"h", b"d"],
23614 &[b"HSET", b"one", b"f", b"v"],
23615 &[b"HRANDFIELD", b"one"],
23616 &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
23617 &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
23619 &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
23620 &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
23621 &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23622 &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23623 &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
23624 &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
23625 &[b"HGET", b"h", b"b"],
23626 &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
23628 &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
23629 &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
23630 &[b"HGET", b"h", b"e"],
23631 &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
23633 &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
23634 &[b"HGETALL", b"imp"],
23635 &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
23636 &[b"HIMPORT", b"DISCARD", b"fs"],
23637 &[b"SET", b"plain", b"v"],
23639 &[b"HSET", b"plain", b"a", b"1"],
23640 &[b"HGETALL", b"plain"],
23641 &[b"HGET", b"gone", b"a"],
23642 &[b"HINCRBY", b"h", b"a", b"nan"],
23643 ];
23644
23645 let mut one = Fixture::new();
23646 let mut many = Fixture::striped(8);
23647 one.server.set_clock_ms(1_700_000_000_000);
23651 many.server.set_clock_ms(1_700_000_000_000);
23652 for parts in script {
23653 let a = one.run(parts);
23654 let b = many.run(parts);
23655 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23656 }
23657 }
23658
23659 #[test]
23661 fn the_array_group_answers_the_same_however_many_stripes_there_are() {
23662 let script: &[&[&[u8]]] = &[
23663 &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
23664 &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
23665 &[b"ARGET", b"a", b"1"],
23666 &[b"ARGET", b"a", b"99"],
23667 &[b"ARMGET", b"a", b"0", b"5", b"99"],
23668 &[b"ARGETRANGE", b"a", b"0", b"7"],
23669 &[b"ARLEN", b"a"],
23670 &[b"ARCOUNT", b"a"],
23671 &[b"ARINSERT", b"a", b"m", b"n"],
23672 &[b"ARSCAN", b"a", b"0", b"20"],
23673 &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
23674 &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
23675 &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
23676 &[b"ARLASTITEMS", b"a", b"2"],
23677 &[b"ARLASTITEMS", b"a", b"2", b"REV"],
23678 &[b"ARNEXT", b"a"],
23679 &[b"ARSEEK", b"a", b"3"],
23680 &[b"AROP", b"a", b"0", b"20", b"USED"],
23681 &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
23682 &[b"ARINFO", b"a"],
23683 &[b"ARINFO", b"a", b"FULL"],
23684 &[b"ARDEL", b"a", b"0"],
23685 &[b"ARDELRANGE", b"a", b"1", b"2"],
23686 &[b"ARCOUNT", b"a"],
23687 &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
23688 &[b"ARGETRANGE", b"r", b"0", b"9"],
23689 &[b"SET", b"plain", b"v"],
23691 &[b"ARGET", b"plain", b"0"],
23692 &[b"ARSET", b"plain", b"0", b"v"],
23693 &[b"ARGET", b"gone", b"0"],
23694 &[b"ARSET", b"a", b"bad", b"v"],
23695 ];
23696
23697 let mut one = Fixture::new();
23698 let mut many = Fixture::striped(8);
23699 for parts in script {
23700 let a = one.run(parts);
23701 let b = many.run(parts);
23702 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23703 }
23704 }
23705
23706 #[test]
23712 fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
23713 let script: &[&[&[u8]]] = &[
23714 &[b"G.NADD", b"g", b"n1", b"name", b"one"],
23715 &[b"G.NADD", b"g", b"n2", b"name", b"two"],
23716 &[b"G.NADD", b"g", b"n3"],
23717 &[b"G.NGET", b"g", b"n1"],
23718 &[b"G.NGET", b"g", b"gone"],
23719 &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
23720 &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
23721 &[b"G.OUT", b"g", b"n1", b"knows"],
23722 &[b"G.IN", b"g", b"n2", b"knows"],
23723 &[b"G.DEG", b"g", b"n1", b"knows"],
23724 &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
23725 &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
23726 &[b"G.PATH", b"g", b"n1", b"n3"],
23727 &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
23728 &[b"G.NDEL", b"g", b"n3"],
23729 &[b"G.NGET", b"g", b"n3"],
23730 &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
23732 &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
23733 &[b"VCARD", b"v"],
23734 &[b"VDIM", b"v"],
23735 &[b"VEMB", b"v", b"e1"],
23736 &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
23737 &[b"VSIM", b"v", b"ELE", b"e1"],
23738 &[b"VISMEMBER", b"v", b"e1"],
23739 &[b"VISMEMBER", b"v", b"gone"],
23740 &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
23741 &[b"VGETATTR", b"v", b"e1"],
23742 &[b"VRANGE", b"v", b"-", b"+"],
23743 &[b"VLINKS", b"v", b"e1"],
23744 &[b"VINFO", b"v"],
23745 &[b"VREM", b"v", b"e2"],
23746 &[b"VCARD", b"v"],
23747 &[b"SET", b"plain", b"v"],
23749 &[b"G.NGET", b"plain", b"n1"],
23750 &[b"VCARD", b"plain"],
23751 &[b"G.NADD", b"gone2", b"n"],
23752 &[b"VEMB", b"gone3", b"e"],
23753 ];
23754
23755 let mut one = Fixture::new();
23756 let mut many = Fixture::striped(8);
23757 for parts in script {
23758 let a = one.run(parts);
23759 let b = many.run(parts);
23760 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23761 }
23762 }
23763
23764 #[test]
23767 fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
23768 let script: &[&[&[u8]]] = &[
23769 &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
23771 &[b"BF.ADD", b"bf", b"a"],
23772 &[b"BF.ADD", b"bf", b"a"],
23773 &[b"BF.MADD", b"bf", b"b", b"c"],
23774 &[b"BF.EXISTS", b"bf", b"a"],
23775 &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
23776 &[b"BF.CARD", b"bf"],
23777 &[b"BF.INFO", b"bf"],
23778 &[b"BF.INFO", b"bf", b"CAPACITY"],
23779 &[b"BF.DEBUG", b"bf"],
23780 &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
23781 &[b"BF.EXISTS", b"made", b"x"],
23782 &[b"BF.SCANDUMP", b"bf", b"0"],
23783 &[b"CF.RESERVE", b"cf", b"100"],
23785 &[b"CF.ADD", b"cf", b"a"],
23786 &[b"CF.ADDNX", b"cf", b"a"],
23787 &[b"CF.COUNT", b"cf", b"a"],
23788 &[b"CF.EXISTS", b"cf", b"a"],
23789 &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
23790 &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
23791 &[b"CF.DEL", b"cf", b"a"],
23792 &[b"CF.COMPACT", b"cf"],
23793 &[b"CF.INFO", b"cf"],
23794 &[b"CF.DEBUG", b"cf"],
23795 &[b"CF.SCANDUMP", b"cf", b"0"],
23796 &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
23798 &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
23799 &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
23800 &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
23801 &[b"CMS.INFO", b"cms"],
23802 &[b"TOPK.RESERVE", b"tk", b"3"],
23804 &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
23805 &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
23806 &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
23807 &[b"TOPK.COUNT", b"tk", b"a", b"c"],
23808 &[b"TOPK.LIST", b"tk"],
23809 &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
23810 &[b"TOPK.INFO", b"tk"],
23811 &[b"TDIGEST.CREATE", b"td"],
23813 &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
23814 &[b"TDIGEST.MIN", b"td"],
23815 &[b"TDIGEST.MAX", b"td"],
23816 &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
23817 &[b"TDIGEST.CDF", b"td", b"3"],
23818 &[b"TDIGEST.RANK", b"td", b"3"],
23819 &[b"TDIGEST.REVRANK", b"td", b"3"],
23820 &[b"TDIGEST.BYRANK", b"td", b"0"],
23821 &[b"TDIGEST.BYREVRANK", b"td", b"0"],
23822 &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
23823 &[b"TDIGEST.INFO", b"td"],
23824 &[b"TDIGEST.RESET", b"td"],
23825 &[b"TDIGEST.MIN", b"td"],
23826 &[b"SET", b"plain", b"v"],
23828 &[b"BF.ADD", b"plain", b"a"],
23829 &[b"CF.ADD", b"plain", b"a"],
23830 &[b"CMS.QUERY", b"plain", b"a"],
23831 &[b"TOPK.ADD", b"plain", b"a"],
23832 &[b"TDIGEST.ADD", b"plain", b"1"],
23833 &[b"CMS.INFO", b"gone"],
23834 &[b"TOPK.INFO", b"gone"],
23835 &[b"TDIGEST.INFO", b"gone"],
23836 ];
23837
23838 let mut one = Fixture::new();
23839 let mut many = Fixture::striped(8);
23840 for parts in script {
23841 let a = one.run(parts);
23842 let b = many.run(parts);
23843 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23844 }
23845 }
23846
23847 #[test]
23854 fn a_sketch_merge_across_stripes_reads_every_source() {
23855 let mut many = Fixture::striped(8);
23856 let other = apart(&mut many, "s1");
23857 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
23858 let mut one = Fixture::new();
23859 let mut both = |parts: &[&[u8]]| {
23860 let a = one.run(parts);
23861 let b = many.run(parts);
23862 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23863 a
23864 };
23865
23866 for key in [b"cd".as_slice(), s1, s2] {
23870 both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
23871 }
23872 both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
23873 both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
23874 assert_eq!(
23875 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
23876 "+OK\r\n",
23877 "the merge took both sources"
23878 );
23879 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
23880 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
23882 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23883 both(&[b"SET", b"plain", b"v"]);
23885 assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
23886 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23887
23888 both(&[b"FLUSHALL"]);
23891 both(&[b"TDIGEST.CREATE", b"td"]);
23892 both(&[b"TDIGEST.CREATE", s1]);
23893 both(&[b"TDIGEST.CREATE", s2]);
23894 both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
23895 both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
23896 assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
23897 assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
23898 assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
23899 }
23900
23901 #[test]
23908 fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
23909 let script: &[&[&[u8]]] = &[
23910 &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
23911 &[b"SORT", b"l"],
23912 &[b"SORT", b"l", b"DESC"],
23913 &[b"SORT", b"l", b"ALPHA"],
23914 &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
23915 &[b"SORT_RO", b"l"],
23916 &[
23919 b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
23920 ],
23921 &[b"SORT", b"l", b"BY", b"w_*"],
23922 &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
23923 &[b"DEL", b"w_2"],
23924 &[b"SORT", b"l", b"BY", b"w_*"],
23925 &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
23928 &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
23929 &[b"HSET", b"h_1", b"f", b"9"],
23931 &[b"HSET", b"h_2", b"f", b"8"],
23932 &[b"HSET", b"h_3", b"f", b"7"],
23933 &[b"HSET", b"h_10", b"f", b"6"],
23934 &[b"SORT", b"l", b"BY", b"h_*->f"],
23935 &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
23936 &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
23938 &[b"LRANGE", b"out", b"0", b"-1"],
23939 &[b"SORT", b"l", b"STORE", b"l"],
23940 &[b"LRANGE", b"l", b"0", b"-1"],
23941 &[b"SORT", b"missing", b"STORE", b"out"],
23944 &[b"EXISTS", b"out"],
23945 &[b"SADD", b"s", b"c", b"a", b"b"],
23948 &[b"SORT", b"s", b"ALPHA"],
23949 &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
23950 &[b"LRANGE", b"out", b"0", b"-1"],
23951 &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
23952 &[b"SORT", b"z", b"BY", b"nosort"],
23953 &[b"SORT", b"z", b"ALPHA", b"DESC"],
23954 &[b"SET", b"str", b"v"],
23957 &[b"SORT", b"str"],
23958 &[b"RPUSH", b"words", b"one", b"two"],
23959 &[b"SORT", b"words"],
23960 &[b"SORT_RO", b"l", b"STORE", b"out"],
23961 ];
23962
23963 let mut one = Fixture::new();
23964 let mut many = Fixture::striped(8);
23965 for parts in script {
23966 let a = one.run(parts);
23967 let b = many.run(parts);
23968 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23969 }
23970 }
23971
23972 #[test]
23979 fn a_sort_across_stripes_reads_every_pattern_key() {
23980 let mut f = Fixture::striped(8);
23981 let out = apart(&mut f, "l");
23982 let (list, dest) = (b"l".as_slice(), out.as_bytes());
23983
23984 f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
23985 f.run(&[
23986 b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
23987 ]);
23988 f.run(&[
23989 b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
23990 ]);
23991
23992 let db = f.server.striped(0);
23995 let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
23996 .iter()
23997 .map(|k| db.stripe_of(k.as_slice()))
23998 .collect();
23999 assert!(
24000 weights.iter().any(|s| *s != weights[0]),
24001 "the four weight keys all landed on one stripe, so this proves nothing"
24002 );
24003
24004 assert_eq!(
24005 f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
24006 "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
24007 "the order came off the weights and the answer off the data keys"
24008 );
24009 assert_eq!(
24010 f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
24011 ":4\r\n"
24012 );
24013 assert_eq!(
24014 f.run(&[b"LRANGE", dest, b"0", b"-1"]),
24015 "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
24016 "the destination is on a stripe of its own and got the whole answer"
24017 );
24018 }
24019
24020 #[test]
24028 fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
24029 let mut f = Fixture::striped(8);
24030 let other = apart(&mut f, "h");
24031 let (first, second) = (b"h".as_slice(), other.as_bytes());
24032
24033 assert_eq!(
24034 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
24035 "+OK\r\n"
24036 );
24037 assert_eq!(
24038 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
24039 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
24040 "the read comes off one stripe and has to answer for all of them"
24041 );
24042 for key in [first, second] {
24043 f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
24044 assert_eq!(
24045 f.run(&[b"OBJECT", b"ENCODING", key]),
24046 "$8\r\nlistpack\r\n",
24047 "two fields is still under the ladder"
24048 );
24049 f.run(&[b"HSET", key, b"c", b"3"]);
24050 assert_eq!(
24051 f.run(&[b"OBJECT", b"ENCODING", key]),
24052 "$9\r\nhashtable\r\n",
24053 "three fields is over it, on whichever stripe the key is on"
24054 );
24055 }
24056
24057 assert_eq!(
24060 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
24061 "+OK\r\n"
24062 );
24063 let db = f.server.striped(0);
24064 assert!(
24065 (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
24066 "a stripe kept the old policy"
24067 );
24068 }
24069
24070 fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
24076 let search = f.server.search.lock();
24077 let index = search.named(name).expect("the index is there");
24078 (index.held.docs.len(), index.held.docs.last())
24079 }
24080
24081 #[test]
24084 fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
24085 let mut f = Fixture::new();
24086 f.run(&[
24087 b"FT.CREATE",
24088 b"ix",
24089 b"PREFIX",
24090 b"1",
24091 b"p:",
24092 b"SCHEMA",
24093 b"t",
24094 b"TEXT",
24095 ]);
24096 f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
24097 assert_eq!(held(&f, b"ix"), (1, 1));
24098 f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
24099 assert_eq!(held(&f, b"ix"), (1, 1));
24100
24101 f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
24104 f.run(&[b"HDEL", b"p:1", b"u"]);
24105 assert_eq!(held(&f, b"ix"), (1, 3));
24106 let search = f.server.search.lock();
24107 let index = search.named(b"ix").expect("there");
24108 assert_eq!(index.held.docs.id(b"p:1"), Some(3));
24109 }
24110
24111 #[test]
24114 fn a_fresh_index_reads_the_keys_that_were_already_there() {
24115 let mut f = Fixture::new();
24116 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24117 f.run(&[b"SET", b"p:str", b"not a hash"]);
24118 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24119 f.run(&[
24120 b"FT.CREATE",
24121 b"ix",
24122 b"PREFIX",
24123 b"1",
24124 b"p:",
24125 b"SCHEMA",
24126 b"t",
24127 b"TEXT",
24128 ]);
24129
24130 assert_eq!(held(&f, b"ix"), (1, 1));
24131 let search = f.server.search.lock();
24132 let index = search.named(b"ix").expect("there");
24133 assert_eq!(index.trouble.whole().failures(), 0);
24134 }
24135
24136 #[test]
24139 fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
24140 let mut f = Fixture::new();
24141 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24142 f.run(&[
24143 b"FT.CREATE",
24144 b"ix",
24145 b"PREFIX",
24146 b"1",
24147 b"p:",
24148 b"SKIPINITIALSCAN",
24149 b"SCHEMA",
24150 b"t",
24151 b"TEXT",
24152 ]);
24153 assert_eq!(held(&f, b"ix"), (0, 0));
24154 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24155 assert_eq!(held(&f, b"ix"), (1, 1));
24156 }
24157
24158 #[test]
24166 fn only_a_real_change_gives_the_document_a_new_number() {
24167 let mut f = Fixture::new();
24168 f.run(&[
24169 b"FT.CREATE",
24170 b"ix",
24171 b"PREFIX",
24172 b"1",
24173 b"p:",
24174 b"SCHEMA",
24175 b"t",
24176 b"TEXT",
24177 ]);
24178 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24179 assert_eq!(held(&f, b"ix"), (1, 1));
24180
24181 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24182 assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
24183
24184 for quiet in [
24185 vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
24186 vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
24187 vec![b"HGET".as_slice(), b"p:1", b"t"],
24188 vec![b"HGETALL".as_slice(), b"p:1"],
24189 vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
24190 vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
24191 vec![
24192 b"HGETEX".as_slice(),
24193 b"p:1",
24194 b"EX",
24195 b"100",
24196 b"FIELDS",
24197 b"1",
24198 b"t",
24199 ],
24200 vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
24201 ] {
24202 f.run(&quiet);
24203 assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
24204 }
24205
24206 f.run(&[b"HSET", b"p:2", b"n", b"1"]);
24208 f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
24209 assert_eq!(held(&f, b"ix"), (2, 4));
24210 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
24216 assert_eq!(held(&f, b"ix"), (1, 5));
24217 }
24218
24219 #[test]
24225 fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
24226 fn refused(f: &Fixture, name: &[u8]) -> u64 {
24228 let search = f.server.search.lock();
24229 let index = search.named(name).expect("the index is there");
24230 index.trouble.whole().failures()
24231 }
24232
24233 let mut f = Fixture::new();
24234 f.run(&[
24235 b"FT.CREATE",
24236 b"ix",
24237 b"PREFIX",
24238 b"1",
24239 b"p:",
24240 b"SCHEMA",
24241 b"t",
24242 b"TEXT",
24243 ]);
24244 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24245 assert_eq!(held(&f, b"ix"), (1, 1));
24246 f.run(&[b"HDEL", b"p:1", b"t"]);
24247 assert_eq!(
24248 held(&f, b"ix"),
24249 (0, 1),
24250 "HDEL of the last field spends none"
24251 );
24252 assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
24253
24254 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24255 assert_eq!(held(&f, b"ix"), (1, 2));
24256 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
24257 assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
24258 assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
24259
24260 f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
24261 assert_eq!(held(&f, b"ix"), (1, 4));
24262 f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
24263 assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
24264
24265 f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
24268 assert_eq!(held(&f, b"ix"), (1, 6));
24269 f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
24270 assert_eq!(held(&f, b"ix"), (0, 7));
24271 assert_eq!(refused(&f, b"ix"), 1);
24272 }
24273
24274 #[test]
24278 fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
24279 let mut f = Fixture::new();
24280 f.run(&[
24281 b"FT.CREATE",
24282 b"ix",
24283 b"PREFIX",
24284 b"1",
24285 b"p:",
24286 b"SCHEMA",
24287 b"t",
24288 b"TEXT",
24289 b"u",
24290 b"TEXT",
24291 ]);
24292 f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
24293 assert_eq!(held(&f, b"ix"), (1, 1));
24294 f.run(&[
24295 b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24296 ]);
24297 assert_eq!(
24298 held(&f, b"ix"),
24299 (1, 3),
24300 "the key lived and the field did not"
24301 );
24302
24303 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24305 assert_eq!(held(&f, b"ix"), (2, 4));
24306 f.run(&[
24307 b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24308 ]);
24309 assert_eq!(held(&f, b"ix"), (1, 6));
24310 }
24311
24312 fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
24315 let search = f.server.search.lock();
24316 let index = search.named(name).expect("the index is there");
24317 index.held.docs.id(key)
24318 }
24319
24320 fn indexed() -> Fixture {
24323 let mut f = Fixture::new();
24324 f.run(&[
24325 b"FT.CREATE",
24326 b"ix",
24327 b"PREFIX",
24328 b"1",
24329 b"p:",
24330 b"SCHEMA",
24331 b"t",
24332 b"TEXT",
24333 ]);
24334 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24335 f
24336 }
24337
24338 #[test]
24341 fn a_key_a_keyspace_command_takes_away_loses_its_document() {
24342 for take in [
24343 vec![b"DEL".as_slice(), b"p:1"],
24344 vec![b"UNLINK".as_slice(), b"p:1"],
24345 vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
24346 vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
24347 ] {
24348 let mut f = indexed();
24349 assert_eq!(held(&f, b"ix"), (1, 1));
24350 f.run(&take);
24351 assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
24352 let search = f.server.search.lock();
24353 let index = search.named(b"ix").expect("the index is there");
24354 assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
24355 }
24356
24357 let mut f = indexed();
24359 f.run(&[b"EXPIRE", b"p:1", b"1000"]);
24360 assert_eq!(held(&f, b"ix"), (1, 1));
24361 f.run(&[b"PERSIST", b"p:1"]);
24362 assert_eq!(held(&f, b"ix"), (1, 1));
24363 }
24364
24365 #[test]
24369 fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
24370 let mut f = indexed();
24371 f.run(&[b"RENAME", b"p:1", b"p:2"]);
24372 assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
24373 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24374 assert_eq!(number(&f, b"ix", b"p:1"), None);
24375
24376 f.run(&[b"RENAME", b"p:2", b"q:1"]);
24377 assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
24378
24379 f.run(&[b"RENAME", b"q:1", b"p:3"]);
24380 assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
24381 assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
24382
24383 f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
24386 assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
24387 assert_eq!(held(&f, b"ix"), (2, 3));
24388 f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
24389 assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
24390 }
24391
24392 #[test]
24395 fn a_rename_over_a_document_leaves_one_of_them() {
24396 let mut f = indexed();
24397 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24398 assert_eq!(held(&f, b"ix"), (2, 2));
24399 f.run(&[b"RENAME", b"p:1", b"p:2"]);
24400 assert_eq!(held(&f, b"ix"), (1, 2));
24401 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24402 }
24403
24404 #[test]
24408 fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
24409 let mut f = indexed();
24410 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24411 f.run(&[b"COPY", b"q:1", b"p:2"]);
24412 assert_eq!(held(&f, b"ix"), (2, 2));
24413 assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
24414
24415 f.run(&[b"COPY", b"p:1", b"q:2"]);
24417 assert_eq!(held(&f, b"ix"), (2, 2));
24418
24419 f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
24421 assert_eq!(held(&f, b"ix"), (2, 3));
24422 assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
24423
24424 f.run(&[b"SET", b"s:1", b"plain"]);
24427 f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
24428 assert_eq!(held(&f, b"ix"), (1, 3));
24429 let dump = f.run(&[b"DUMP", b"q:1"]);
24430 assert!(dump.starts_with('$'), "{dump}");
24431 }
24432
24433 #[test]
24438 fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
24439 let mut f = indexed();
24440 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24441 assert_eq!(held(&f, b"ix"), (2, 2));
24442 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24445 assert_eq!(held(&f, b"ix"), (2, 3));
24446 f.run(&[b"DEL", b"p:2"]);
24449 assert_eq!(held(&f, b"ix"), (1, 3));
24450 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24451 assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
24452
24453 let dump = f.run(&[b"DUMP", b"p:1"]);
24455 assert!(dump.starts_with('$'), "{dump}");
24456 f.run(&[b"SELECT", b"1"]);
24457 f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
24458 f.run(&[b"RENAME", b"q:1", b"p:3"]);
24459 assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
24460 }
24461
24462 #[test]
24465 fn a_move_leaves_the_document_where_it_is() {
24466 let mut f = indexed();
24467 f.run(&[b"MOVE", b"p:1", b"1"]);
24468 assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
24469 assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
24470
24471 f.run(&[b"SELECT", b"1"]);
24472 f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
24473 assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
24474 f.run(&[b"DEL", b"p:1"]);
24475 assert_eq!(held(&f, b"ix"), (0, 2));
24476 }
24477
24478 #[test]
24480 fn a_flush_drops_the_indexes() {
24481 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24482 let mut f = indexed();
24483 f.run(&[flush]);
24484 assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
24485 assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
24486 }
24487
24488 let mut f = indexed();
24491 f.run(&[b"SELECT", b"9"]);
24492 f.run(&[b"FLUSHDB"]);
24493 assert!(f.server.search.lock().is_empty());
24494 }
24495
24496 fn tagged() -> Fixture {
24499 let mut f = Fixture::new();
24500 f.run(&[
24501 b"FT.CREATE",
24502 b"tv",
24503 b"PREFIX",
24504 b"1",
24505 b"tv:",
24506 b"SCHEMA",
24507 b"g",
24508 b"AS",
24509 b"gg",
24510 b"TAG",
24511 b"h",
24512 b"TAG",
24513 b"SEPARATOR",
24514 b"|",
24515 b"CASESENSITIVE",
24516 b"n",
24517 b"NUMERIC",
24518 ]);
24519 f.run(&[
24520 b"HSET",
24521 b"tv:1",
24522 b"g",
24523 b"Red, BLUE ",
24524 b"h",
24525 b"Aa|bB",
24526 b"n",
24527 b"1",
24528 ]);
24529 f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
24530 f
24531 }
24532
24533 #[test]
24537 fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
24538 let mut f = tagged();
24539 assert_eq!(
24540 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
24541 "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
24542 );
24543 assert_eq!(
24544 f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
24545 "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
24546 );
24547 }
24548
24549 #[test]
24552 fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
24553 let mut f = tagged();
24554 for (name, want) in [
24555 (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
24556 (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
24557 (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
24558 ] {
24559 assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
24560 }
24561 assert_eq!(
24562 f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
24563 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
24564 );
24565 }
24566
24567 #[test]
24570 fn asking_for_tag_values_counts_a_use_of_the_index() {
24571 let mut f = tagged();
24572 let uses = |f: &mut Fixture| {
24573 let reply = f.run(&[b"FT.INFO", b"tv"]);
24574 let at = reply.find("number_of_uses").expect("the field is reported");
24575 let value = reply[at..].split("\r\n").nth(1).unwrap();
24576 value.trim_start_matches(':').parse::<i64>().unwrap()
24577 };
24578 let before = uses(&mut f);
24579 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
24580 f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
24581 assert_eq!(uses(&mut f), before + 3);
24583 }
24584
24585 #[test]
24588 fn a_tag_field_with_nothing_in_it_answers_empty() {
24589 let mut f = Fixture::new();
24590 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
24591 assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
24592 }
24593
24594 #[test]
24597 fn a_dictionary_is_not_a_key() {
24598 let mut f = Fixture::new();
24599 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
24600 assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
24601 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
24602 assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
24603 }
24604
24605 #[test]
24608 fn a_dictionary_counts_the_terms_it_had_not_seen() {
24609 let mut f = Fixture::new();
24610 assert_eq!(
24611 f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
24612 ":3\r\n"
24613 );
24614 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
24615 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
24616 assert_eq!(
24617 f.run(&[b"FT.DICTDUMP", b"d"]),
24618 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
24619 );
24620 assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
24621 }
24622
24623 #[test]
24627 fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
24628 let mut f = Fixture::new();
24629 assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
24630 assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
24631 }
24632
24633 #[test]
24635 fn a_flush_drops_the_dictionaries() {
24636 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24637 let mut f = Fixture::new();
24638 f.run(&[b"FT.DICTADD", b"d", b"a"]);
24639 f.run(&[flush]);
24640 assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
24641 }
24642 }
24643
24644 fn profiling() -> Fixture {
24649 let mut f = Fixture::new();
24650 f.run(&[
24651 b"FT.CREATE",
24652 b"ix",
24653 b"PREFIX",
24654 b"1",
24655 b"p:",
24656 b"SCHEMA",
24657 b"t",
24658 b"TEXT",
24659 b"n",
24660 b"NUMERIC",
24661 ]);
24662 f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
24663 f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
24664 f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
24665 f
24666 }
24667
24668 fn timeless(reply: &str) -> String {
24671 const KEYS: &[&str] = &[
24672 "+Total profile time",
24673 "+Parsing time",
24674 "+Workers queue time",
24675 "+Pipeline creation time",
24676 "+Time",
24677 ];
24678 let mut out = String::new();
24679 let mut parts = reply.split("\r\n").peekable();
24680 while let Some(part) = parts.next() {
24681 out.push_str(part);
24682 out.push_str("\r\n");
24683 if !KEYS.contains(&part) {
24684 continue;
24685 }
24686 match parts.next() {
24689 Some(head) if head.starts_with('$') => {
24690 parts.next();
24691 }
24692 _ => {}
24693 }
24694 out.push_str("<t>\r\n");
24695 }
24696 out.truncate(out.len() - 2);
24698 out
24699 }
24700
24701 #[test]
24704 fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
24705 let mut f = profiling();
24706 assert_eq!(
24707 timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
24708 "*2\r\n\
24709 *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\
24710 $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\
24711 *4\r\n+Shards\r\n*1\r\n*14\r\n\
24712 +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
24713 +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
24714 +Warning\r\n*1\r\n+None\r\n\
24715 +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24716 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24717 +Estimated number of matches\r\n:2\r\n\
24718 +Result processors profile\r\n*4\r\n\
24719 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24720 *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24721 *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24722 *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24723 +Coordinator\r\n*0\r\n"
24724 );
24725 let mut g = profiling();
24726 g.run(&[b"HELLO", b"3"]);
24727 let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
24728 assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
24729 assert!(
24730 three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
24731 "{three}"
24732 );
24733 assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
24734 assert!(
24735 three.contains(
24736 "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24737 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24738 +Estimated number of matches\r\n:2\r\n"
24739 ),
24740 "{three}"
24741 );
24742 }
24743
24744 #[test]
24747 fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
24748 let mut f = profiling();
24749 let tree = |f: &mut Fixture, query: &[u8]| {
24750 let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
24751 let at = reply.find("+Iterators profile").expect("a tree");
24752 let end = reply.find("+Result processors").expect("a list of steps");
24753 reply[at..end].to_string()
24754 };
24755 assert_eq!(
24756 tree(&mut f, b"alpha beta"),
24757 "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
24758 +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
24759 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
24760 +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
24761 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
24762 +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
24763 );
24764 assert!(tree(&mut f, b"alpha|beta").starts_with(
24765 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
24766 +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
24767 ));
24768 assert!(tree(&mut f, b"-alpha").starts_with(
24771 "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
24772 +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
24773 ));
24774 assert!(tree(&mut f, b"~alpha").starts_with(
24775 "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
24776 +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
24777 ));
24778 assert_eq!(
24780 tree(&mut f, b"*"),
24781 "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
24782 +Number of reading operations\r\n:3\r\n"
24783 );
24784 assert!(tree(&mut f, b"@n:[1 2]").starts_with(
24785 "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
24786 $19\r\n1.000000 - 2.000000\r\n"
24787 ));
24788 }
24789
24790 #[test]
24793 fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
24794 let mut f = profiling();
24795 f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
24796 let tree = |f: &mut Fixture, words: &[&[u8]]| {
24797 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
24798 argv.extend_from_slice(words);
24799 let reply = timeless(&f.run(&argv));
24800 let at = reply.find("+Iterators profile").expect("a tree");
24801 let end = reply.find("+Result processors").expect("a list of steps");
24802 reply[at..end].to_string()
24803 };
24804 assert_eq!(
24805 tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
24806 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
24807 +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
24808 +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
24809 +The number of iterators in the union is 2\r\n"
24810 );
24811 assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
24812 assert!(
24813 tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
24814 .contains("+Child iterators\r\n*2\r\n")
24815 );
24816 assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
24820 assert!(
24821 tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
24822 );
24823 }
24824
24825 #[test]
24828 fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
24829 let mut f = profiling();
24830 let steps = |f: &mut Fixture, words: &[&[u8]]| {
24831 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
24832 argv.extend_from_slice(words);
24833 let reply = timeless(&f.run(&argv));
24834 let at = reply.find("+Result processors").expect("a list of steps");
24835 let end = reply.find("+Coordinator").expect("an end");
24836 let mut out = Vec::new();
24837 let mut parts = reply[at..end].split("\r\n").peekable();
24838 while let Some(part) = parts.next() {
24839 if part == "+Type" {
24840 out.push(parts.next().unwrap_or_default().to_string());
24841 }
24842 }
24843 out
24844 };
24845 assert_eq!(
24846 steps(&mut f, &[]),
24847 ["+Index", "+Scorer", "+Sorter", "+Loader"]
24848 );
24849 assert_eq!(
24850 steps(&mut f, &[b"NOCONTENT"]),
24851 ["+Index", "+Scorer", "+Sorter"]
24852 );
24853 assert_eq!(
24856 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
24857 ["+Index", "+Counter"]
24858 );
24859 assert_eq!(
24862 steps(&mut f, &[b"SORTBY", b"n"]),
24863 ["+Index", "+Sorter", "+Loader"]
24864 );
24865 assert_eq!(
24866 steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
24867 ["+Index", "+Scorer", "+Sorter", "+Loader"]
24868 );
24869 assert_eq!(
24870 steps(&mut f, &[b"HIGHLIGHT"]),
24871 ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
24872 );
24873 assert_eq!(
24874 steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
24875 ["+Index", "+Scorer", "+Sorter"]
24876 );
24877 }
24878
24879 #[test]
24882 fn a_pipeline_names_every_step_after_what_it_runs() {
24883 let mut f = profiling();
24884 let steps = |f: &mut Fixture, words: &[&[u8]]| {
24885 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
24886 argv.extend_from_slice(words);
24887 let reply = timeless(&f.run(&argv));
24888 let at = reply.find("+Result processors").expect("a list of steps");
24889 let end = reply.find("+Coordinator").expect("an end");
24890 let mut out = Vec::new();
24891 let mut parts = reply[at..end].split("\r\n").peekable();
24892 while let Some(part) = parts.next() {
24893 if part == "+Type" {
24894 out.push(parts.next().unwrap_or_default().to_string());
24895 }
24896 }
24897 out
24898 };
24899 assert_eq!(steps(&mut f, &[]), ["+Index"]);
24900 assert_eq!(
24901 steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
24902 ["+Index", "+Projector - Literal 1"]
24903 );
24904 assert_eq!(
24905 steps(
24906 &mut f,
24907 &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
24908 ),
24909 ["+Index", "+Loader", "+Projector - Operator *"]
24910 );
24911 assert_eq!(
24912 steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
24913 ["+Index", "+Loader", "+Filter - Predicate >"]
24914 );
24915 assert_eq!(
24916 steps(
24917 &mut f,
24918 &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
24919 ),
24920 ["+Index", "+Loader", "+Grouper"]
24921 );
24922 assert_eq!(
24923 steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
24924 ["+Index", "+Loader", "+Sorter"]
24925 );
24926 assert_eq!(
24927 steps(&mut f, &[b"LIMIT", b"0", b"2"]),
24928 ["+Index", "+Pager/Limiter"]
24929 );
24930 assert_eq!(
24933 steps(
24934 &mut f,
24935 &[
24936 b"ADDSCORES",
24937 b"LOAD",
24938 b"1",
24939 b"@n",
24940 b"APPLY",
24941 b"@__score",
24942 b"AS",
24943 b"s"
24944 ]
24945 ),
24946 [
24947 "+Index",
24948 "+Scorer",
24949 "+Loader",
24950 "+Projector - Property __score"
24951 ]
24952 );
24953 }
24954
24955 #[test]
24963 fn a_sortable_field_is_read_without_the_key_being_opened() {
24964 let mut f = Fixture::new();
24965 f.run(&[
24966 b"FT.CREATE",
24967 b"sx",
24968 b"PREFIX",
24969 b"1",
24970 b"s:",
24971 b"SCHEMA",
24972 b"n",
24973 b"NUMERIC",
24974 b"SORTABLE",
24975 b"g",
24976 b"TAG",
24977 ]);
24978 f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
24979 f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
24980 let loads = |f: &mut Fixture, words: &[&[u8]]| {
24981 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
24982 argv.extend_from_slice(words);
24983 f.run(&argv).contains("+Loader")
24984 };
24985 assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
24986 assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
24987 assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
24988 assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
24989 assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
24990 assert!(loads(
24991 &mut f,
24992 &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
24993 ));
24994 assert!(loads(&mut f, &[b"LOAD", b"*"]));
24995 }
24996
24997 #[test]
25000 fn a_profile_checks_its_own_words_before_it_runs_anything() {
25001 let mut f = profiling();
25002 assert_eq!(
25003 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
25004 "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
25005 );
25006 assert_eq!(
25007 f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
25008 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
25009 );
25010 assert_eq!(
25013 f.run(&[
25014 b"FT.PROFILE",
25015 b"ix",
25016 b"LIMITED",
25017 b"SEARCH",
25018 b"QUERY",
25019 b"alpha"
25020 ]),
25021 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
25022 );
25023 assert_eq!(
25024 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
25025 "-The QUERY keyword is expected\r\n"
25026 );
25027 assert_eq!(
25028 f.run(&[
25029 b"FT.PROFILE",
25030 b"ix",
25031 b"AGGREGATE",
25032 b"QUERY",
25033 b"alpha",
25034 b"WITHCURSOR"
25035 ]),
25036 "-FT.PROFILE does not support cursor\r\n"
25037 );
25038 assert_eq!(
25041 f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
25042 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
25043 );
25044 assert_eq!(
25045 f.run(&[
25046 b"FT.PROFILE",
25047 b"ix",
25048 b"SEARCH",
25049 b"QUERY",
25050 b"alpha",
25051 b"extra"
25052 ]),
25053 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
25054 );
25055 }
25056
25057 #[test]
25059 fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
25060 let mut f = profiling();
25061 let one = f.run(&[
25062 b"FT.PROFILE",
25063 b"ix",
25064 b"search",
25065 b"limited",
25066 b"query",
25067 b"alpha",
25068 ]);
25069 let two = f.run(&[
25070 b"FT.PROFILE",
25071 b"ix",
25072 b"SEARCH",
25073 b"LIMITED",
25074 b"QUERY",
25075 b"alpha",
25076 ]);
25077 assert_eq!(timeless(&one), timeless(&two));
25078 }
25079
25080 #[test]
25085 fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
25086 let mut f = profiling();
25087 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
25088 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25089
25090 let mut f = profiling();
25091 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25092 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25093
25094 let mut f = profiling();
25095 assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
25096 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25097
25098 let mut f = profiling();
25099 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
25100 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25101 }
25102
25103 #[test]
25106 fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
25107 let mut f = profiling();
25108 let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
25109 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
25110 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
25111 assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
25113 }
25114
25115 #[test]
25118 fn dropping_the_documents_leaves_a_key_the_index_never_read() {
25119 let mut f = profiling();
25120 f.run(&[b"SET", b"p:4", b"alpha"]);
25121 f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
25122 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25123 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25124 assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
25125 }
25126
25127 #[test]
25130 fn another_index_over_the_same_keys_loses_the_documents_too() {
25131 let mut f = profiling();
25132 f.run(&[
25133 b"FT.CREATE",
25134 b"other",
25135 b"PREFIX",
25136 b"1",
25137 b"p:",
25138 b"SCHEMA",
25139 b"t",
25140 b"TEXT",
25141 ]);
25142 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25143 assert_eq!(
25144 f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
25145 "*1\r\n:0\r\n"
25146 );
25147 }
25148
25149 #[test]
25152 fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
25153 let mut f = profiling();
25154 assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
25155 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25156 assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
25157 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25158 }
25159
25160 #[test]
25165 fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
25166 let mut f = Fixture::new();
25167 assert_eq!(
25168 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25169 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25170 );
25171 assert_eq!(
25172 f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25173 "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
25174 );
25175 let mut g = Fixture::new();
25176 g.run(&[b"HELLO", b"3"]);
25177 assert_eq!(
25178 g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25179 "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25180 );
25181 assert_eq!(
25182 g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25183 "%1\r\n+EXTLOAD\r\n_\r\n"
25184 );
25185 }
25186
25187 #[test]
25190 fn a_help_row_carries_the_description_and_the_value_together() {
25191 let mut f = Fixture::new();
25192 assert_eq!(
25193 f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25194 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25195 +Value\r\n$3\r\n500\r\n"
25196 );
25197 let mut g = Fixture::new();
25198 g.run(&[b"HELLO", b"3"]);
25199 assert_eq!(
25200 g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25201 "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
25202 +Value\r\n$3\r\n500\r\n"
25203 );
25204 }
25205
25206 #[test]
25209 fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
25210 let mut f = Fixture::new();
25211 assert_eq!(
25212 f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
25213 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25214 );
25215 for name in [
25216 b"TIMEOUT*".as_slice(),
25217 b"?IMEOUT",
25218 b"*TIMEOUT*",
25219 b"TIME",
25220 b"NOSUCH",
25221 b"",
25222 ] {
25223 assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
25224 }
25225 assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
25226 assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
25227 }
25228
25229 #[test]
25232 fn a_read_ignores_whatever_follows_the_name() {
25233 let mut f = Fixture::new();
25234 assert_eq!(
25235 f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
25236 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25237 );
25238 assert_eq!(
25239 f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
25240 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25241 +Value\r\n$3\r\n500\r\n"
25242 );
25243 }
25244
25245 #[test]
25248 fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
25249 let mut f = Fixture::new();
25250 assert_eq!(
25251 f.run(&[b"FT.CONFIG"]),
25252 "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
25253 );
25254 for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
25255 let want = format!(
25256 "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
25257 String::from_utf8_lossy(sub)
25258 );
25259 assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
25260 }
25261 assert_eq!(
25262 f.run(&[b"ft.config", b"get"]),
25263 "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
25264 );
25265 assert_eq!(
25266 f.run(&[b"FT.CONFIG", b"bogus"]),
25267 "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
25268 );
25269 }
25270
25271 #[test]
25274 fn a_write_checks_the_name_then_the_setting_then_the_value() {
25275 let mut f = Fixture::new();
25276 for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
25277 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
25278 cmd.extend(tail);
25279 assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
25280 }
25281 for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
25282 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
25283 cmd.extend(tail);
25284 assert_eq!(
25285 f.run(&cmd),
25286 "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
25287 );
25288 }
25289 assert_eq!(
25290 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
25291 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
25292 );
25293 }
25294
25295 #[test]
25298 fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
25299 let mut f = Fixture::new();
25300 assert_eq!(
25301 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
25302 "+OK\r\n"
25303 );
25304 assert_eq!(
25305 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
25306 "+EXCESSARGS\r\n"
25307 );
25308 assert_eq!(
25309 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25310 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
25311 );
25312 }
25313
25314 #[test]
25317 fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
25318 let mut f = Fixture::new();
25319 for (given, want) in [
25320 (b"0x10".as_slice(), "16"),
25321 (b"0X1f", "31"),
25322 (b"+0x10", "16"),
25323 (b"+5", "5"),
25324 (b"010", "10"),
25325 (b"08", "8"),
25326 (b"0777", "777"),
25327 (b"1e3", "1000"),
25328 (b"0.0", "0"),
25329 (b"-0.0", "0"),
25330 ] {
25331 assert_eq!(
25332 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25333 "+OK\r\n",
25334 "{given:?}"
25335 );
25336 let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
25337 assert_eq!(
25338 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25339 want,
25340 "{given:?}"
25341 );
25342 }
25343 for given in [
25344 b" 5".as_slice(),
25345 b"5 ",
25346 b"1.5",
25347 b"1e-3",
25348 b"x",
25349 b"",
25350 b"0b11",
25351 b"0xg",
25352 b"nan",
25353 b"inf",
25354 b"1e100",
25355 b"99999999999999999999",
25356 ] {
25357 assert_eq!(
25358 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25359 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25360 "{given:?}"
25361 );
25362 }
25363 }
25364
25365 #[test]
25368 fn a_negative_is_answered_by_whichever_reader_found_it() {
25369 let mut f = Fixture::new();
25370 for given in [b"-1".as_slice(), b"-16"] {
25371 assert_eq!(
25372 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25373 "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
25374 "{given:?}"
25375 );
25376 }
25377 for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
25378 assert_eq!(
25379 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25380 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25381 "{given:?}"
25382 );
25383 }
25384 let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
25385 for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
25386 assert_eq!(
25387 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25388 "+OK\r\n",
25389 "{given:?}"
25390 );
25391 assert_eq!(
25392 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25393 unlimited,
25394 "{given:?}"
25395 );
25396 }
25397 }
25398
25399 #[test]
25402 fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
25403 let mut f = Fixture::new();
25404 for (given, want) in [
25405 (b"2147483647".as_slice(), "2147483647"),
25406 (b"2147483648", "unlimited"),
25407 (b"4294967295", "unlimited"),
25408 (b"9223372036854775806", "unlimited"),
25409 (b"0", "0"),
25410 ] {
25411 assert_eq!(
25412 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25413 "+OK\r\n",
25414 "{given:?}"
25415 );
25416 let want = format!(
25417 "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
25418 want.len()
25419 );
25420 assert_eq!(
25421 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25422 want,
25423 "{given:?}"
25424 );
25425 }
25426 }
25427
25428 #[test]
25431 fn a_number_out_of_range_names_the_limit_it_crossed() {
25432 let mut f = Fixture::new();
25433 let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
25434 for (name, given) in [
25435 (b"MINPREFIX".as_slice(), b"0".as_slice()),
25436 (b"MAX_AGGREGATE_GROUPS", b"0"),
25437 (b"BM25STD_TANH_FACTOR", b"0"),
25438 (b"DEFAULT_DIALECT", b"0"),
25439 (b"MINSTEMLEN", b"4294967296"),
25440 (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
25441 (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
25442 (b"CONNECT_TIMEOUT", b"2147483648"),
25443 ] {
25444 assert_eq!(
25445 f.run(&[b"FT.CONFIG", b"SET", name, given]),
25446 bounds,
25447 "{name:?}"
25448 );
25449 }
25450 for (name, given, want) in [
25451 (
25452 b"MINSTEMLEN".as_slice(),
25453 b"1".as_slice(),
25454 "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
25455 ),
25456 (
25457 b"MAX_AGGREGATE_GROUPS",
25458 b"67108865",
25459 "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
25460 ),
25461 (
25462 b"WORKERS",
25463 b"17",
25464 "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
25465 ),
25466 (
25467 b"_NUMERIC_RANGES_PARENTS",
25468 b"3",
25469 "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
25470 depth for balance\r\n",
25471 ),
25472 (
25473 b"DEFAULT_DIALECT",
25474 b"5",
25475 "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
25476 ),
25477 (
25478 b"_BG_INDEX_MEM_PCT_THR",
25479 b"101",
25480 "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
25481 100%\r\n",
25482 ),
25483 (
25484 b"BM25STD_TANH_FACTOR",
25485 b"10001",
25486 "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
25487 inclusive\r\n",
25488 ),
25489 (
25490 b"BG_INDEX_SLEEP_DURATION_US",
25491 b"1000000",
25492 "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
25493 999999 (usleep POSIX limit)\r\n",
25494 ),
25495 ] {
25496 assert_eq!(
25497 f.run(&[b"FT.CONFIG", b"SET", name, given]),
25498 want,
25499 "{name:?}"
25500 );
25501 }
25502 }
25503
25504 #[test]
25507 fn the_trimming_delays_are_checked_against_one_another() {
25508 let mut f = Fixture::new();
25509 assert_eq!(
25510 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
25511 "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
25512 _MAX_TRIM_DELAY_MS (5000)\r\n"
25513 );
25514 assert_eq!(
25515 f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
25516 "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
25517 _MIN_TRIM_DELAY_MS (2000)\r\n"
25518 );
25519 assert_eq!(
25520 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
25521 "+OK\r\n"
25522 );
25523 }
25524
25525 #[test]
25528 fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
25529 let mut f = Fixture::new();
25530 assert_eq!(
25531 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
25532 "+OK\r\n"
25533 );
25534 assert_eq!(
25535 f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
25536 "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
25537 );
25538 assert_eq!(
25539 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
25540 "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
25541 );
25542 assert_eq!(
25543 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
25544 "+OK\r\n"
25545 );
25546 assert_eq!(
25547 f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
25548 "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
25549 );
25550 assert_eq!(
25551 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
25552 "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
25553 );
25554 let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
25555 for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
25556 assert_eq!(
25557 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
25558 bad,
25559 "{given:?}"
25560 );
25561 }
25562 assert_eq!(
25563 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
25564 "+OK\r\n"
25565 );
25566 }
25567
25568 #[test]
25571 fn a_yes_or_no_setting_takes_those_two_words_only() {
25572 let mut f = Fixture::new();
25573 assert_eq!(
25574 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
25575 "+OK\r\n"
25576 );
25577 assert_eq!(
25578 f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
25579 "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
25580 );
25581 for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
25582 assert_eq!(
25583 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
25584 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25585 "{given:?}"
25586 );
25587 }
25588 }
25589
25590 #[test]
25593 fn two_names_for_one_setting_move_together() {
25594 let mut f = Fixture::new();
25595 f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
25596 assert_eq!(
25597 f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
25598 "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
25599 );
25600 f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
25601 assert_eq!(
25602 f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
25603 "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
25604 );
25605 let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25606 let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25607 f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
25608 assert_eq!(
25609 f.run(&[b"FT.CONFIG", b"GET", short]),
25610 "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
25611 );
25612 assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
25613 assert_eq!(
25614 f.run(&[b"FT.CONFIG", b"GET", long]),
25615 "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
25616 );
25617 }
25618
25619 #[test]
25621 fn a_password_reads_back_as_stars_whatever_was_written() {
25622 let mut f = Fixture::new();
25623 assert_eq!(
25624 f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
25625 "+OK\r\n"
25626 );
25627 assert_eq!(
25628 f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
25629 "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
25630 );
25631 }
25632
25633 #[test]
25636 fn a_flush_leaves_the_settings_alone() {
25637 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
25638 let mut f = Fixture::new();
25639 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
25640 f.run(&[flush]);
25641 assert_eq!(
25642 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25643 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
25644 "{flush:?}"
25645 );
25646 }
25647 }
25648
25649 fn debugging() -> Fixture {
25654 let mut f = Fixture::new();
25655 f.run(&[
25656 b"FT.CREATE",
25657 b"dx",
25658 b"PREFIX",
25659 b"1",
25660 b"d:",
25661 b"SCHEMA",
25662 b"t",
25663 b"TEXT",
25664 b"g",
25665 b"TAG",
25666 b"n",
25667 b"NUMERIC",
25668 b"s",
25669 b"TEXT",
25670 b"SORTABLE",
25671 ]);
25672 f.run(&[
25673 b"HSET",
25674 b"d:1",
25675 b"t",
25676 b"running dogs",
25677 b"g",
25678 b"red,blue",
25679 b"n",
25680 b"1",
25681 b"s",
25682 b"Alpha",
25683 ]);
25684 f.run(&[
25685 b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
25686 ]);
25687 f.run(&[
25688 b"HSET",
25689 b"d:3",
25690 b"t",
25691 b"dogs alpha",
25692 b"g",
25693 b"green",
25694 b"n",
25695 b"3",
25696 ]);
25697 f
25698 }
25699
25700 #[test]
25703 fn a_term_dump_lists_the_stems_beside_the_words() {
25704 let mut f = debugging();
25705 assert_eq!(
25706 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
25707 "*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\
25708 $4\r\ndogs\r\n$7\r\nrunning\r\n"
25709 );
25710 }
25711
25712 #[test]
25715 fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
25716 let mut f = debugging();
25717 assert_eq!(
25718 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25719 "*2\r\n:1\r\n:2\r\n"
25720 );
25721 assert_eq!(
25722 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
25723 "*2\r\n:1\r\n:2\r\n"
25724 );
25725 for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
25726 assert_eq!(
25727 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
25728 "-Can not find the inverted index\r\n",
25729 "{term:?}"
25730 );
25731 }
25732 }
25733
25734 #[test]
25737 fn a_tag_dump_pairs_every_value_with_its_documents() {
25738 let mut f = debugging();
25739 assert_eq!(
25740 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
25741 "*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\
25742 *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
25743 );
25744 }
25745
25746 #[test]
25749 fn a_number_dump_answers_a_single_range() {
25750 let mut f = debugging();
25751 assert_eq!(
25752 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
25753 "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
25754 );
25755 }
25756
25757 #[test]
25760 fn a_geo_field_is_dumped_as_a_numeric_one() {
25761 let mut f = Fixture::new();
25762 f.run(&[
25763 b"FT.CREATE",
25764 b"gx",
25765 b"PREFIX",
25766 b"1",
25767 b"q:",
25768 b"SCHEMA",
25769 b"loc",
25770 b"GEO",
25771 b"gg",
25772 b"AS",
25773 b"tag",
25774 b"TAG",
25775 ]);
25776 f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
25777 f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
25778 assert_eq!(
25779 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
25780 "*1\r\n*2\r\n:1\r\n:2\r\n"
25781 );
25782 assert_eq!(
25783 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
25784 "-Could not find given field in index spec\r\n"
25785 );
25786 }
25787
25788 #[test]
25791 fn a_dump_takes_the_attribute_and_not_the_identifier() {
25792 let mut f = Fixture::new();
25793 f.run(&[
25794 b"FT.CREATE",
25795 b"zx",
25796 b"PREFIX",
25797 b"1",
25798 b"z:",
25799 b"SCHEMA",
25800 b"gg",
25801 b"AS",
25802 b"tag",
25803 b"TAG",
25804 ]);
25805 f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
25806 assert_eq!(
25807 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
25808 "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
25809 );
25810 assert_eq!(
25811 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
25812 "-Could not find given field in index spec\r\n"
25813 );
25814 }
25815
25816 #[test]
25819 fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
25820 let mut f = debugging();
25821 assert_eq!(
25822 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25823 "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25824 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
25825 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25826 +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\
25827 $5\r\nvalue\r\n$5\r\nalpha\r\n"
25828 );
25829 let mut g = debugging();
25830 g.run(&[b"HELLO", b"3"]);
25831 assert_eq!(
25832 g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25833 "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25834 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
25835 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25836 +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\
25837 $5\r\nvalue\r\n$5\r\nalpha\r\n"
25838 );
25839 }
25840
25841 #[test]
25844 fn a_document_with_no_sortable_value_drops_the_key() {
25845 let mut f = debugging();
25846 assert_eq!(
25847 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
25848 "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
25849 +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"
25850 );
25851 }
25852
25853 #[test]
25856 fn the_flag_word_spells_out_the_bits_it_carries() {
25857 let mut f = Fixture::new();
25858 f.run(&[
25859 b"FT.CREATE",
25860 b"nx",
25861 b"NOOFFSETS",
25862 b"PREFIX",
25863 b"1",
25864 b"o:",
25865 b"SCHEMA",
25866 b"t",
25867 b"TEXT",
25868 ]);
25869 f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
25870 assert!(
25871 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
25872 .contains("$6\r\n(0x0):\r\n")
25873 );
25874 }
25875
25876 #[test]
25879 fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
25880 let mut f = debugging();
25881 assert!(
25882 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
25883 .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
25884 );
25885 }
25886
25887 #[test]
25890 fn a_document_row_reads_its_keyword_at_a_fixed_place() {
25891 let mut f = debugging();
25892 let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
25893 assert_eq!(
25894 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
25895 want
25896 );
25897 assert_eq!(
25898 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
25899 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25900 );
25901 assert_eq!(
25902 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
25903 "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
25904 );
25905 }
25906
25907 #[test]
25910 fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
25911 let mut f = debugging();
25912 assert_eq!(
25913 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
25914 "-Document not found in index\r\n"
25915 );
25916 assert_eq!(
25917 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
25918 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25919 );
25920 }
25921
25922 #[test]
25925 fn a_document_number_goes_both_ways() {
25926 let mut f = debugging();
25927 assert_eq!(
25928 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25929 "$3\r\nd:2\r\n"
25930 );
25931 assert_eq!(
25932 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25933 ":2\r\n"
25934 );
25935 assert_eq!(
25936 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
25937 ":0\r\n"
25938 );
25939 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
25940 for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
25941 assert_eq!(
25942 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25943 "-document was removed\r\n",
25944 "{id:?}"
25945 );
25946 }
25947 }
25948
25949 #[test]
25952 fn a_document_number_is_read_the_strict_way() {
25953 let mut f = debugging();
25954 for id in [
25955 b"x".as_slice(),
25956 b"1.5",
25957 b" 1",
25958 b"+1",
25959 b"01",
25960 b"0x1",
25961 b"",
25962 b"9223372036854775808",
25963 b"18446744073709551615",
25964 ] {
25965 assert_eq!(
25966 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25967 "-bad id given\r\n",
25968 "{id:?}"
25969 );
25970 }
25971 }
25972
25973 #[test]
25976 fn a_dump_keeps_a_number_the_table_has_given_up() {
25977 let mut f = debugging();
25978 f.run(&[b"DEL", b"d:2"]);
25979 assert_eq!(
25980 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25981 "*2\r\n:1\r\n:2\r\n"
25982 );
25983 assert_eq!(
25984 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25985 "-document was removed\r\n"
25986 );
25987 assert_eq!(
25988 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25989 ":0\r\n"
25990 );
25991 }
25992
25993 #[test]
25996 fn a_rewrite_takes_a_number_of_its_own() {
25997 let mut f = debugging();
25998 f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
25999 assert_eq!(
26000 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
26001 ":4\r\n"
26002 );
26003 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
26004 assert_eq!(
26005 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
26006 "-document was removed\r\n"
26007 );
26008 assert_eq!(
26009 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
26010 "*2\r\n:1\r\n:2\r\n"
26011 );
26012 }
26013
26014 #[test]
26016 fn a_dump_follows_an_alias() {
26017 let mut f = debugging();
26018 f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
26019 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
26020 assert_eq!(
26021 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
26022 "$3\r\nd:1\r\n"
26023 );
26024 }
26025
26026 #[test]
26029 fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
26030 let mut f = debugging();
26031 assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
26032 assert_eq!(
26033 f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
26034 "-Can not create a search ctx\r\n"
26035 );
26036 assert_eq!(
26037 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
26038 "-Can not create a search ctx\r\n"
26039 );
26040 }
26041
26042 #[test]
26045 fn an_empty_field_dumps_as_nothing_at_all() {
26046 let mut f = Fixture::new();
26047 f.run(&[
26048 b"FT.CREATE",
26049 b"ex",
26050 b"PREFIX",
26051 b"1",
26052 b"e:",
26053 b"SCHEMA",
26054 b"t",
26055 b"TEXT",
26056 b"g",
26057 b"TAG",
26058 b"n",
26059 b"NUMERIC",
26060 ]);
26061 assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
26062 assert_eq!(
26063 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
26064 "*0\r\n"
26065 );
26066 assert_eq!(
26067 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
26068 "*0\r\n"
26069 );
26070 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
26071 }
26072
26073 #[test]
26076 fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
26077 let mut f = debugging();
26078 for (sub, extra) in [
26079 (b"DUMP_TERMS".as_slice(), 1),
26080 (b"GET_MAX_DOC_ID", 1),
26081 (b"DUMP_INVIDX", 2),
26082 (b"DUMP_TAGIDX", 2),
26083 (b"DUMP_NUMIDX", 2),
26084 (b"IDTODOCID", 2),
26085 (b"DOCIDTOID", 2),
26086 ] {
26087 let want = format!(
26088 "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
26089 str::from_utf8(sub).unwrap()
26090 );
26091 for given in [extra - 1, extra + 1] {
26092 let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
26093 cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
26094 assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
26095 }
26096 let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
26097 right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
26098 assert_ne!(f.run(&right), want, "{sub:?}");
26099 }
26100 assert_eq!(
26101 f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
26102 "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
26103 );
26104 }
26105
26106 #[test]
26109 fn the_help_names_the_subcommands_that_answer() {
26110 let mut f = Fixture::new();
26111 let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
26112 $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
26113 $14\r\nGET_MAX_DOC_ID\r\n";
26114 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
26115 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
26116 }
26117
26118 #[test]
26123 fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
26124 let mut f = Fixture::new();
26125 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26126 assert_eq!(
26127 f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
26128 "+OK\r\n"
26129 );
26130 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
26131 assert_eq!(
26132 f.run(&[b"FT.SYNDUMP", b"e"]),
26133 "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
26134 $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
26135 );
26136 }
26137
26138 #[test]
26142 fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
26143 let mut f = Fixture::new();
26144 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26145 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
26146 assert_eq!(
26147 f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
26148 "$69\r\nUNION {\n jogging\n ~gr(expanded)\n +jog(expanded)\n jog(expanded)\n}\n\r\n"
26149 );
26150 }
26151
26152 #[test]
26161 fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
26162 let mut f = Fixture::new();
26163 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26164 f.run(&[b"HSET", b"d1", b"t", b"boy"]);
26165 f.run(&[b"HSET", b"d2", b"t", b"runs"]);
26166 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
26167 assert_eq!(
26168 f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
26169 "*2\r\n:1\r\n$2\r\nd1\r\n"
26170 );
26171 assert_eq!(
26172 f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
26173 "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
26174 );
26175 }
26176
26177 #[test]
26180 fn a_synonym_command_on_a_name_that_is_not_there_fails() {
26181 let mut f = Fixture::new();
26182 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
26183 assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
26184 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
26185 }
26186
26187 #[test]
26191 fn params_counts_the_words_before_it_pairs_them_up() {
26192 let mut f = Fixture::new();
26193 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26194 let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
26195 Expected an argument, but none provided\r\n";
26196 let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
26197 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
26198 assert_eq!(
26199 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
26200 none
26201 );
26202 assert_eq!(
26203 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
26204 odd
26205 );
26206 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
26207 assert_eq!(
26208 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
26209 "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
26210 );
26211 }
26212
26213 fn vectored(f: &mut Fixture) {
26223 f.run(&[
26224 b"FT.CREATE",
26225 b"h",
26226 b"SCHEMA",
26227 b"t",
26228 b"TEXT",
26229 b"v",
26230 b"VECTOR",
26231 b"FLAT",
26232 b"6",
26233 b"TYPE",
26234 b"FLOAT32",
26235 b"DIM",
26236 b"2",
26237 b"DISTANCE_METRIC",
26238 b"L2",
26239 ]);
26240 let at: [&[u8]; 5] = [
26241 b"\x00\x00\x80\x40\x00\x00\x00\x00",
26242 b"\x00\x00\x40\x40\x00\x00\x00\x00",
26243 b"\x00\x00\x00\x40\x00\x00\x00\x00",
26244 b"\x00\x00\x80\x3f\x00\x00\x00\x00",
26245 b"\x00\x00\x00\x00\x00\x00\x00\x00",
26246 ];
26247 for (n, point) in at.iter().enumerate() {
26248 let key = format!("d{}", n + 1);
26249 let word: &[u8] = match n {
26250 0 | 1 | 3 => b"alpha",
26251 _ => b"beta",
26252 };
26253 f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
26254 }
26255 }
26256
26257 const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
26259
26260 #[test]
26264 fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
26265 let mut f = Fixture::new();
26266 vectored(&mut f);
26267 assert_eq!(
26268 f.run(&[
26269 b"FT.SEARCH",
26270 b"h",
26271 b"*=>[KNN 5 @v $vec]",
26272 b"PARAMS",
26273 b"2",
26274 b"vec",
26275 ORIGIN,
26276 b"DIALECT",
26277 b"2",
26278 b"NOCONTENT",
26279 ]),
26280 "*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"
26281 );
26282 assert_eq!(
26283 f.run(&[
26284 b"FT.SEARCH",
26285 b"h",
26286 b"*=>[KNN 3 @v $vec]",
26287 b"PARAMS",
26288 b"2",
26289 b"vec",
26290 ORIGIN,
26291 b"DIALECT",
26292 b"2",
26293 b"NOCONTENT",
26294 ]),
26295 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26296 );
26297 }
26298
26299 #[test]
26302 fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
26303 let mut f = Fixture::new();
26304 vectored(&mut f);
26305 for (radius, want) in [
26306 ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
26307 ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
26308 (
26309 "9",
26310 "*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",
26311 ),
26312 ] {
26313 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26314 assert_eq!(
26315 f.run(&[
26316 b"FT.SEARCH",
26317 b"h",
26318 query.as_bytes(),
26319 b"PARAMS",
26320 b"2",
26321 b"vec",
26322 ORIGIN,
26323 b"DIALECT",
26324 b"2",
26325 b"NOCONTENT",
26326 ]),
26327 want,
26328 "radius {radius}"
26329 );
26330 }
26331 }
26332
26333 #[test]
26337 fn a_knn_measures_what_the_query_in_front_of_it_matched() {
26338 let mut f = Fixture::new();
26339 vectored(&mut f);
26340 assert_eq!(
26341 f.run(&[
26342 b"FT.SEARCH",
26343 b"h",
26344 b"alpha=>[KNN 2 @v $vec]",
26345 b"PARAMS",
26346 b"2",
26347 b"vec",
26348 ORIGIN,
26349 b"DIALECT",
26350 b"2",
26351 b"NOCONTENT",
26352 ]),
26353 "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
26354 );
26355 }
26356
26357 #[test]
26366 fn a_count_and_a_radius_are_refused_in_their_own_words() {
26367 let mut f = Fixture::new();
26368 vectored(&mut f);
26369 let ask = |f: &mut Fixture, query: &str| {
26370 f.run(&[
26371 b"FT.SEARCH",
26372 b"h",
26373 query.as_bytes(),
26374 b"PARAMS",
26375 b"2",
26376 b"vec",
26377 ORIGIN,
26378 b"DIALECT",
26379 b"2",
26380 b"NOCONTENT",
26381 ])
26382 };
26383 for (query, at, near) in [
26384 ("*=>[KNN -1 @v $vec]", 8, "-1"),
26385 ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
26386 ("*=>[KNN +3 @v $vec]", 8, "+3"),
26387 ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
26388 ("*=>[KNN abc @v $vec]", 8, "abc"),
26389 ("*=>[KNN 3 $vec]", 10, "vec"),
26390 ("*=>[KNN 3 @v vec]", 13, "vec"),
26391 ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
26392 ] {
26393 assert_eq!(
26394 ask(&mut f, query),
26395 format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
26396 "{query}"
26397 );
26398 }
26399
26400 let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26403 query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
26404 assert_eq!(
26405 ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
26406 "*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"
26407 );
26408 assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
26409 assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
26410
26411 for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
26412 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26413 assert_eq!(
26414 ask(&mut f, &query),
26415 format!(
26416 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26417 negative radius ({printed}) given in a range query\r\n"
26418 ),
26419 "{query}"
26420 );
26421 }
26422 assert_eq!(
26424 ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
26425 "*2\r\n:1\r\n$2\r\nd5\r\n"
26426 );
26427 }
26428
26429 #[test]
26432 fn a_count_that_came_from_params_is_read_by_its_own_rules() {
26433 let mut f = Fixture::new();
26434 vectored(&mut f);
26435 let ask = |f: &mut Fixture, count: &[u8]| {
26436 f.run(&[
26437 b"FT.SEARCH",
26438 b"h",
26439 b"*=>[KNN $k @v $vec]",
26440 b"PARAMS",
26441 b"4",
26442 b"vec",
26443 ORIGIN,
26444 b"k",
26445 count,
26446 b"DIALECT",
26447 b"2",
26448 b"NOCONTENT",
26449 ])
26450 };
26451 let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
26452 assert_eq!(ask(&mut f, b"3"), three);
26453 assert_eq!(ask(&mut f, b" 3"), three);
26454 assert_eq!(ask(&mut f, b"+3"), three);
26455 for bad in [
26456 &b"3.0"[..],
26457 b"0x3",
26458 b"-1",
26459 b"abc",
26460 b"",
26461 b"99999999999999999999",
26462 ] {
26463 let value = String::from_utf8_lossy(bad).into_owned();
26464 assert_eq!(
26465 ask(&mut f, bad),
26466 format!(
26467 "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
26468 for parameter `k`\r\n"
26469 ),
26470 "{value}"
26471 );
26472 }
26473 assert_eq!(
26474 ask(&mut f, b"288230376151711777"),
26475 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26476 query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
26477 );
26478 }
26479
26480 #[test]
26483 fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
26484 let mut f = Fixture::new();
26485 vectored(&mut f);
26486 assert_eq!(
26487 f.run(&[
26488 b"FT.SEARCH",
26489 b"h",
26490 b"*=>[KNN 5 @v $vec]",
26491 b"PARAMS",
26492 b"2",
26493 b"vec",
26494 b"abc",
26495 b"DIALECT",
26496 b"2",
26497 b"NOCONTENT",
26498 ]),
26499 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26500 query vector blob size (3) does not match index's expected size (8).\r\n"
26501 );
26502 }
26503
26504 #[test]
26509 fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
26510 let mut f = Fixture::new();
26511 vectored(&mut f);
26512 let ask = |f: &mut Fixture, query: &str| {
26513 f.run(&[
26514 b"FT.SEARCH",
26515 b"h",
26516 query.as_bytes(),
26517 b"PARAMS",
26518 b"2",
26519 b"vec",
26520 ORIGIN,
26521 b"DIALECT",
26522 b"2",
26523 b"LIMIT",
26524 b"0",
26525 b"1",
26526 ])
26527 };
26528 assert_eq!(
26529 ask(&mut f, "*=>[KNN 3 @v $vec]"),
26530 "*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"
26531 );
26532 assert_eq!(
26533 ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
26534 "*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"
26535 );
26536 assert_eq!(
26537 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
26538 "*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"
26539 );
26540 assert_eq!(
26541 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
26542 "*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"
26543 );
26544 }
26545
26546 #[test]
26553 fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
26554 let mut f = Fixture::new();
26555 vectored(&mut f);
26556 let ask = |f: &mut Fixture, ret: &[&[u8]]| {
26557 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
26558 args.extend_from_slice(ret);
26559 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26560 f.run(&args)
26561 };
26562 assert_eq!(
26563 ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
26564 "*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"
26565 );
26566 assert_eq!(
26567 ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
26568 "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
26569 );
26570 assert_eq!(
26571 ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
26572 "*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"
26573 );
26574 assert_eq!(
26575 ask(&mut f, &[b"RETURN", b"1", b"t"]),
26576 "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
26577 );
26578 assert_eq!(
26581 ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
26582 "*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"
26583 );
26584 assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
26585 }
26586
26587 #[test]
26591 fn a_sortby_can_name_a_distance_the_query_yielded() {
26592 let mut f = Fixture::new();
26593 vectored(&mut f);
26594 let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
26595 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
26596 if desc {
26597 args.push(b"DESC");
26598 }
26599 args.extend_from_slice(&[
26600 b"PARAMS",
26601 b"2",
26602 b"vec",
26603 ORIGIN,
26604 b"DIALECT",
26605 b"2",
26606 b"NOCONTENT",
26607 ]);
26608 f.run(&args)
26609 };
26610 assert_eq!(
26611 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
26612 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26613 );
26614 assert_eq!(
26615 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
26616 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26617 );
26618 assert_eq!(
26619 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
26620 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26621 );
26622 let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
26625 not loaded nor in schema\r\n";
26626 assert_eq!(
26627 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
26628 missing
26629 );
26630 assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
26631 assert_eq!(
26634 ask(&mut f, "foo(", b"zz", false),
26635 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26636 );
26637 }
26638
26639 #[test]
26641 fn two_vector_clauses_answer_two_distances() {
26642 let mut f = Fixture::new();
26643 vectored(&mut f);
26644 assert_eq!(
26645 f.run(&[
26646 b"FT.SEARCH",
26647 b"h",
26648 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
26649 b"RETURN",
26650 b"2",
26651 b"rr",
26652 b"__v_score",
26653 b"PARAMS",
26654 b"2",
26655 b"vec",
26656 ORIGIN,
26657 b"DIALECT",
26658 b"2",
26659 ]),
26660 "*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"
26661 );
26662 }
26663
26664 #[test]
26668 fn an_aggregation_answers_a_distance_nothing_asked_for() {
26669 let mut f = Fixture::new();
26670 vectored(&mut f);
26671 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26672 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26673 args.extend_from_slice(rest);
26674 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26675 f.run(&args)
26676 };
26677 assert_eq!(
26678 ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
26679 "*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"
26680 );
26681 assert_eq!(
26682 ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
26683 "*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"
26684 );
26685 assert_eq!(
26686 ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
26687 "*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"
26688 );
26689 assert_eq!(
26691 ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
26692 "*3\r\n:1\r\n*0\r\n*0\r\n"
26693 );
26694 assert_eq!(
26695 ask(
26696 &mut f,
26697 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26698 &[]
26699 ),
26700 "*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"
26701 );
26702 }
26703
26704 #[test]
26708 fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
26709 let mut f = Fixture::new();
26710 vectored(&mut f);
26711 const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
26713 let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
26714 f.run(&[
26715 b"FT.AGGREGATE",
26716 b"h",
26717 query.as_bytes(),
26718 b"LOAD",
26719 b"1",
26720 b"@t",
26721 b"PARAMS",
26722 b"2",
26723 b"vec",
26724 vec,
26725 b"DIALECT",
26726 b"2",
26727 ])
26728 };
26729 assert_eq!(
26730 ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
26731 "*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"
26732 );
26733 assert_eq!(
26735 ask(
26736 &mut f,
26737 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26738 MIDDLE
26739 ),
26740 "*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"
26741 );
26742 }
26743
26744 #[test]
26748 fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
26749 let mut f = Fixture::new();
26750 vectored(&mut f);
26751 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26752 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26753 args.extend_from_slice(rest);
26754 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26755 f.run(&args)
26756 };
26757 let knn = "*=>[KNN 2 @v $vec]";
26758 assert_eq!(
26759 ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
26760 "*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"
26761 );
26762 assert_eq!(
26763 ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
26764 "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
26765 );
26766 assert_eq!(
26767 ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
26768 "*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"
26769 );
26770 assert_eq!(
26771 ask(
26772 &mut f,
26773 knn,
26774 &[
26775 b"GROUPBY",
26776 b"1",
26777 b"@t",
26778 b"REDUCE",
26779 b"MAX",
26780 b"1",
26781 b"@__v_score",
26782 b"AS",
26783 b"m"
26784 ]
26785 ),
26786 "*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"
26787 );
26788 assert_eq!(
26789 ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
26790 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
26791 `__v_score`\r\n"
26792 );
26793 assert_eq!(
26794 ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
26795 "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
26796 );
26797 assert_eq!(
26798 ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
26799 "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
26800 schema\r\n"
26801 );
26802 }
26803
26804 #[test]
26812 fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
26813 let mut f = Fixture::new();
26814 vectored(&mut f);
26815 let ask = |f: &mut Fixture, rest: &[&[u8]]| {
26816 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
26817 args.extend_from_slice(rest);
26818 f.run(&args)
26819 };
26820 assert_eq!(
26821 ask(
26822 &mut f,
26823 &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
26824 ),
26825 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
26826 );
26827 assert_eq!(
26828 ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
26829 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26830 );
26831 assert_eq!(
26832 ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
26833 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26834 );
26835 assert_eq!(
26838 ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
26839 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26840 );
26841 assert_eq!(
26842 ask(
26843 &mut f,
26844 &[
26845 b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
26846 ]
26847 ),
26848 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26849 );
26850 assert_eq!(
26851 ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
26852 "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
26853 );
26854 }
26855
26856 #[test]
26859 fn a_vector_step_says_which_way_it_was_answered() {
26860 let mut f = Fixture::new();
26861 vectored(&mut f);
26862 let tree = |f: &mut Fixture, query: &[u8]| {
26863 let reply = timeless(&f.run(&[
26864 b"FT.PROFILE",
26865 b"h",
26866 b"AGGREGATE",
26867 b"QUERY",
26868 query,
26869 b"PARAMS",
26870 b"2",
26871 b"vec",
26872 ORIGIN,
26873 b"DIALECT",
26874 b"2",
26875 ]));
26876 let at = reply.find("+Iterators profile").expect("a tree");
26877 let end = reply.find("+Result processors").expect("a list of steps");
26878 reply[at..end].to_string()
26879 };
26880 assert_eq!(
26881 tree(&mut f, b"*=>[KNN 3 @v $vec]"),
26882 "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26883 +Number of reading operations\r\n:3\r\n\
26884 +Vector search mode\r\n+STANDARD_KNN\r\n"
26885 );
26886 assert_eq!(
26888 tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
26889 tree(&mut f, b"*=>[KNN 3 @v $vec]")
26890 );
26891 assert_eq!(
26894 tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
26895 "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
26896 +Number of reading operations\r\n:4\r\n"
26897 );
26898 assert_eq!(
26899 tree(
26900 &mut f,
26901 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
26902 ),
26903 "+Iterators profile\r\n*8\r\n\
26904 +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
26905 +Number of reading operations\r\n:4\r\n\
26906 +Vector search mode\r\n+RANGE_QUERY\r\n"
26907 );
26908 }
26909
26910 #[test]
26914 fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
26915 let mut f = Fixture::new();
26916 vectored(&mut f);
26917 let ask = |f: &mut Fixture, query: &[u8]| {
26918 timeless(&f.run(&[
26919 b"FT.PROFILE",
26920 b"h",
26921 b"AGGREGATE",
26922 b"QUERY",
26923 query,
26924 b"PARAMS",
26925 b"2",
26926 b"vec",
26927 ORIGIN,
26928 b"DIALECT",
26929 b"2",
26930 ]))
26931 };
26932 let cut = |reply: &str| {
26933 let at = reply.find("+Iterators profile").expect("a tree");
26934 reply[at..].to_string()
26935 };
26936 assert_eq!(
26937 cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
26938 "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26939 +Number of reading operations\r\n:3\r\n\
26940 +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
26941 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
26942 +Number of reading operations\r\n:3\r\n\
26943 +Estimated number of matches\r\n:3\r\n\
26944 +Result processors profile\r\n*2\r\n\
26945 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26946 *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
26947 +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
26948 );
26949 assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
26952 "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
26953 +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
26954 ));
26955 assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
26958 }
26959
26960 #[test]
26964 fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
26965 let mut f = profiling();
26966 let steps = |f: &mut Fixture, words: &[&[u8]]| {
26967 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
26968 argv.extend_from_slice(words);
26969 let reply = timeless(&f.run(&argv));
26970 let at = reply.find("+Result processors").expect("a list of steps");
26971 reply[at..].to_string()
26972 };
26973 assert_eq!(
26974 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
26975 "+Result processors profile\r\n*2\r\n\
26976 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26977 *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
26978 +Coordinator\r\n*0\r\n"
26979 );
26980 assert!(
26981 steps(
26982 &mut f,
26983 &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
26984 )
26985 .contains("+Type\r\n+Counter\r\n")
26986 );
26987 assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
26989 "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
26990 +Results processed\r\n:2\r\n"
26991 ));
26992 }
26993
26994 #[test]
27000 fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
27001 let mut f = Fixture::new();
27002 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27003 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27004 f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
27005 assert_eq!(
27006 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
27007 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27008 *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"
27009 );
27010 }
27011
27012 #[test]
27016 fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
27017 let mut f = Fixture::new();
27018 f.run(&[b"HELLO", b"3"]);
27019 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27020 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27021 assert_eq!(
27022 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
27023 "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
27024 *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
27025 );
27026 }
27027
27028 #[test]
27032 fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
27033 let mut f = Fixture::new();
27034 f.run(&[
27035 b"FT.CREATE",
27036 b"e",
27037 b"SCHEMA",
27038 b"a",
27039 b"TEXT",
27040 b"NOSTEM",
27041 b"b",
27042 b"TEXT",
27043 b"NOSTEM",
27044 ]);
27045 f.run(&[b"HSET", b"d1", b"b", b"world"]);
27046 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
27047 assert_eq!(
27048 f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
27049 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
27050 );
27051 }
27052
27053 #[test]
27057 fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
27058 let mut f = Fixture::new();
27059 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27060 f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
27061 assert_eq!(
27062 f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
27063 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
27064 *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"
27065 );
27066 assert_eq!(
27067 f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
27068 "*0\r\n"
27069 );
27070 assert_eq!(
27071 f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
27072 "-Dict does not exist: nope\r\n"
27073 );
27074 }
27075
27076 #[test]
27080 fn a_spellcheck_reads_its_arguments_leniently() {
27081 let mut f = Fixture::new();
27082 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27083 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27084 let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27085 *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
27086 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
27087 let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
27088 let args: &[&[u8]] = &[
27089 b"FT.SPELLCHECK",
27090 b"e",
27091 b"helqp",
27092 b"DISTANCE",
27093 b"1",
27094 b"DISTANCE",
27095 b"4",
27096 ];
27097 assert_eq!(f.run(args), none);
27098 assert_eq!(
27099 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
27100 "-bad distance given, distance must be a natural number between 1 to 4\r\n"
27101 );
27102 assert_eq!(
27103 f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
27104 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
27105 );
27106 }
27107
27108 #[test]
27113 fn an_add_answers_how_many_suggestions_are_in_there_now() {
27114 let mut f = Fixture::new();
27115 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
27116 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
27117 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
27118 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
27119 assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
27120 }
27121
27122 #[test]
27125 fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
27126 let mut f = Fixture::new();
27127 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27128 assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
27129 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
27130 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
27131 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
27132 assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
27133 assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
27134 assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
27135 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
27136 }
27137
27138 #[test]
27141 fn deleting_the_last_suggestion_deletes_the_key() {
27142 let mut f = Fixture::new();
27143 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27144 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
27145 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
27146 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
27147 assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
27148 }
27149
27150 #[test]
27153 fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
27154 let mut f = Fixture::new();
27155 f.run(&[b"SET", b"s", b"x"]);
27156 for cmd in [
27157 vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
27158 vec![&b"FT.SUGGET"[..], b"s", b"t"],
27159 vec![&b"FT.SUGDEL"[..], b"s", b"t"],
27160 vec![&b"FT.SUGLEN"[..], b"s"],
27161 ] {
27162 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
27163 }
27164 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
27165 }
27166
27167 #[test]
27170 fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
27171 let mut f = Fixture::new();
27172 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27173 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27174 f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
27175 assert_eq!(
27176 f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
27177 "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
27178 $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
27179 $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
27180 );
27181 assert_eq!(
27182 f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
27183 "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
27184 );
27185 assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
27186 }
27187
27188 #[test]
27190 fn fuzzy_allows_one_edit_and_nothing_allows_two() {
27191 let mut f = Fixture::new();
27192 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27193 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
27194 assert_eq!(
27195 f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
27196 "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
27197 );
27198 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
27199 }
27200
27201 #[test]
27203 fn a_lookup_answers_five_unless_it_is_told_otherwise() {
27204 let mut f = Fixture::new();
27205 for (term, score) in [
27206 (&b"a1"[..], &b"1"[..]),
27207 (b"a2", b"2"),
27208 (b"a3", b"3"),
27209 (b"a4", b"4"),
27210 (b"a5", b"5"),
27211 (b"a6", b"6"),
27212 ] {
27213 f.run(&[b"FT.SUGADD", b"s", term, score]);
27214 }
27215 assert_eq!(
27216 f.run(&[b"FT.SUGGET", b"s", b"a"]),
27217 "*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"
27218 );
27219 assert_eq!(
27220 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
27221 "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
27222 );
27223 assert!(
27225 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
27226 .starts_with("*6\r\n")
27227 );
27228 }
27229
27230 #[test]
27233 fn a_payload_comes_back_beside_the_term_or_a_null_does() {
27234 let mut f = Fixture::new();
27235 f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
27236 assert_eq!(
27237 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27238 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27239 );
27240 f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
27241 assert_eq!(
27242 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27243 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27244 );
27245 f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
27248 assert_eq!(
27249 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27250 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27251 );
27252 f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
27254 assert_eq!(
27255 f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
27256 "*2\r\n$5\r\nother\r\n$-1\r\n"
27257 );
27258 }
27259
27260 #[test]
27264 fn incr_adds_to_the_score_that_is_already_there() {
27265 let mut f = Fixture::new();
27266 for _ in 0..3 {
27267 f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
27268 }
27269 assert_eq!(
27270 f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
27271 "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
27272 );
27273 }
27274
27275 #[test]
27277 fn the_suggestion_errors_are_the_lines_the_module_sends() {
27278 let mut f = Fixture::new();
27279 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27280 assert_eq!(
27281 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
27282 "-ERR invalid score\r\n"
27283 );
27284 assert_eq!(
27286 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
27287 "-Unknown argument `NOPE`\r\n"
27288 );
27289 assert_eq!(
27290 f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
27291 "-Invalid payload: Expected an argument, but none provided\r\n"
27292 );
27293 assert!(
27295 f.run(&[
27296 b"FT.SUGADD",
27297 b"s",
27298 b"t",
27299 b"1",
27300 b"PAYLOAD",
27301 b"a",
27302 b"PAYLOAD",
27303 b"b"
27304 ])
27305 .contains("wrong number of arguments")
27306 );
27307 assert_eq!(
27308 f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
27309 "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
27310 );
27311 for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
27315 assert_eq!(
27316 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27317 "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
27318 "{}",
27319 String::from_utf8_lossy(max)
27320 );
27321 }
27322 for max in [
27323 &b"abc"[..],
27324 b"0.0",
27325 b"00",
27326 b"-0",
27327 b"+0",
27328 b"0.5",
27329 b"-1.5",
27330 b"1e400",
27331 ] {
27332 assert_eq!(
27333 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27334 "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
27335 "{}",
27336 String::from_utf8_lossy(max)
27337 );
27338 }
27339 for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
27340 assert_eq!(
27341 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27342 "*1\r\n$3\r\none\r\n",
27343 "{}",
27344 String::from_utf8_lossy(max)
27345 );
27346 }
27347 assert_eq!(
27348 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
27349 "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
27350 );
27351 assert_eq!(
27354 f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
27355 "-ERR invalid score\r\n"
27356 );
27357 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
27358 }
27359
27360 #[test]
27364 fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
27365 let mut f = Fixture::new();
27366 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27367 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
27368 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
27369 assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
27370 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
27371 assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
27372 assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
27373 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
27374 }
27375
27376 #[test]
27379 fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
27380 let mut f = Fixture::new();
27381 f.run(&[
27382 b"FT.CREATE",
27383 b"ix",
27384 b"PREFIX",
27385 b"1",
27386 b"p:",
27387 b"SCHEMA",
27388 b"n",
27389 b"NUMERIC",
27390 ]);
27391 f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
27392 assert_eq!(held(&f, b"ix"), (0, 0));
27393
27394 let reply = f.run(&[b"FT.INFO", b"ix"]);
27395 assert!(
27396 reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
27397 "{reply}"
27398 );
27399 assert!(reply.contains("hash_indexing_failures"), "{reply}");
27400 }
27401
27402 #[test]
27405 fn an_index_can_only_be_made_on_database_zero() {
27406 let mut f = Fixture::new();
27407 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
27408 f.run(&[b"SELECT", b"1"]);
27409 let refused = "-Cannot create index on db != 0\r\n";
27410 assert_eq!(
27411 f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
27412 refused
27413 );
27414 assert_eq!(
27416 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27417 refused
27418 );
27419 assert_eq!(
27421 f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
27422 refused
27423 );
27424 assert_eq!(
27426 f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27427 "+OK\r\n"
27428 );
27429 assert_eq!(f.server.search.lock().len(), 1);
27430 }
27431
27432 #[test]
27438 fn the_scan_is_one_database_and_the_following_is_all_of_them() {
27439 let mut f = Fixture::new();
27440 f.run(&[b"SELECT", b"1"]);
27441 f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
27442 f.run(&[b"SELECT", b"0"]);
27443 f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
27444 f.run(&[
27445 b"FT.CREATE",
27446 b"ix",
27447 b"PREFIX",
27448 b"1",
27449 b"p:",
27450 b"SCHEMA",
27451 b"t",
27452 b"TEXT",
27453 ]);
27454 assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
27455
27456 f.run(&[b"SELECT", b"1"]);
27457 f.run(&[b"HSET", b"p:8", b"t", b"later"]);
27458 assert_eq!(
27459 held(&f, b"ix"),
27460 (2, 2),
27461 "and then it follows every database"
27462 );
27463 }
27464
27465 fn corpus(f: &mut Fixture) {
27468 f.run(&[
27469 b"FT.CREATE",
27470 b"sx",
27471 b"PREFIX",
27472 b"1",
27473 b"d:",
27474 b"SCHEMA",
27475 b"t",
27476 b"TEXT",
27477 b"g",
27478 b"TAG",
27479 b"n",
27480 b"NUMERIC",
27481 ]);
27482 for (key, text, tag, number) in [
27483 (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
27484 (b"d:2", "alpha gamma", "bb", "2"),
27485 (b"d:3", "delta", "cc", "3"),
27486 (b"d:4", "alpha beta gamma", "aa,cc", "4"),
27487 ] {
27488 f.run(&[
27489 b"HSET",
27490 key,
27491 b"t",
27492 text.as_bytes(),
27493 b"g",
27494 tag.as_bytes(),
27495 b"n",
27496 number.as_bytes(),
27497 ]);
27498 }
27499 }
27500
27501 fn sortable(f: &mut Fixture) {
27505 f.run(&[
27506 b"FT.CREATE",
27507 b"sy",
27508 b"PREFIX",
27509 b"1",
27510 b"s:",
27511 b"SCHEMA",
27512 b"t",
27513 b"TEXT",
27514 b"SORTABLE",
27515 b"n",
27516 b"NUMERIC",
27517 b"SORTABLE",
27518 b"body",
27519 b"AS",
27520 b"b",
27521 b"TEXT",
27522 b"SORTABLE",
27523 b"p",
27524 b"TEXT",
27525 ]);
27526 for (key, text, number) in [
27527 (b"s:1".as_slice(), "Banana Split", "2"),
27528 (b"s:2", "apple", "10"),
27529 ] {
27530 f.run(&[
27531 b"HSET",
27532 key,
27533 b"t",
27534 text.as_bytes(),
27535 b"n",
27536 number.as_bytes(),
27537 b"body",
27538 text.as_bytes(),
27539 b"p",
27540 b"alpha",
27541 ]);
27542 }
27543 f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
27546 }
27547
27548 #[test]
27551 fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
27552 let mut f = Fixture::new();
27553 sortable(&mut f);
27554 assert_eq!(
27555 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
27556 "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27557 );
27558 assert_eq!(
27559 f.run(&[
27560 b"FT.SEARCH",
27561 b"sy",
27562 b"alpha",
27563 b"SORTBY",
27564 b"n",
27565 b"DESC",
27566 b"NOCONTENT"
27567 ]),
27568 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27569 );
27570 assert_eq!(
27573 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
27574 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27575 );
27576 }
27577
27578 #[test]
27581 fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
27582 let mut f = Fixture::new();
27583 sortable(&mut f);
27584 f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
27585 assert_eq!(
27586 f.run(&[
27587 b"FT.SEARCH",
27588 b"sy",
27589 b"alpha",
27590 b"SORTBY",
27591 b"p",
27592 b"NOCONTENT",
27593 b"LIMIT",
27594 b"0",
27595 b"2"
27596 ]),
27597 "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27598 );
27599 assert_eq!(
27602 f.run(&[
27603 b"FT.SEARCH",
27604 b"sy",
27605 b"alpha",
27606 b"SORTBY",
27607 b"p",
27608 b"WITHSORTKEYS",
27609 b"NOCONTENT",
27610 b"LIMIT",
27611 b"2",
27612 b"1"
27613 ]),
27614 "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
27615 );
27616 }
27617
27618 #[test]
27621 fn a_search_can_send_the_value_it_sorted_by_back() {
27622 let mut f = Fixture::new();
27623 sortable(&mut f);
27624 assert_eq!(
27625 f.run(&[
27626 b"FT.SEARCH",
27627 b"sy",
27628 b"alpha",
27629 b"SORTBY",
27630 b"n",
27631 b"WITHSORTKEYS",
27632 b"NOCONTENT"
27633 ]),
27634 concat!(
27635 "*7\r\n:3\r\n",
27636 "$3\r\ns:1\r\n$2\r\n#2\r\n",
27637 "$3\r\ns:2\r\n$3\r\n#10\r\n",
27638 "$3\r\ns:3\r\n$-1\r\n"
27639 )
27640 );
27641 assert_eq!(
27642 f.run(&[
27643 b"FT.SEARCH",
27644 b"sy",
27645 b"alpha",
27646 b"SORTBY",
27647 b"t",
27648 b"WITHSORTKEYS",
27649 b"NOCONTENT"
27650 ]),
27651 concat!(
27652 "*7\r\n:3\r\n",
27653 "$3\r\ns:2\r\n$6\r\n$apple\r\n",
27654 "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
27655 "$3\r\ns:3\r\n$-1\r\n"
27656 )
27657 );
27658 assert_eq!(
27661 f.run(&[
27662 b"FT.SEARCH",
27663 b"sy",
27664 b"banana",
27665 b"WITHSORTKEYS",
27666 b"NOCONTENT"
27667 ]),
27668 "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
27669 );
27670 }
27671
27672 #[test]
27675 fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
27676 let mut f = Fixture::new();
27677 sortable(&mut f);
27678 assert_eq!(
27682 f.run(&[
27683 b"FT.SEARCH",
27684 b"sy",
27685 b"alpha",
27686 b"SORTBY",
27687 b"b",
27688 b"LIMIT",
27689 b"0",
27690 b"1"
27691 ]),
27692 concat!(
27693 "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
27694 "$1\r\nb\r\n$5\r\napple\r\n",
27695 "$1\r\nt\r\n$5\r\napple\r\n",
27696 "$1\r\nn\r\n$2\r\n10\r\n",
27697 "$4\r\nbody\r\n$5\r\napple\r\n",
27698 "$1\r\np\r\n$5\r\nalpha\r\n"
27699 )
27700 );
27701 assert_eq!(
27704 f.run(&[
27705 b"FT.SEARCH",
27706 b"sy",
27707 b"alpha",
27708 b"SORTBY",
27709 b"b",
27710 b"RETURN",
27711 b"2",
27712 b"p",
27713 b"b",
27714 b"LIMIT",
27715 b"0",
27716 b"1"
27717 ]),
27718 concat!(
27719 "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
27720 "$1\r\nb\r\n$5\r\napple\r\n",
27721 "$1\r\np\r\n$5\r\nalpha\r\n"
27722 )
27723 );
27724 }
27725
27726 #[test]
27728 fn a_search_refuses_the_sorts_it_cannot_run() {
27729 let mut f = Fixture::new();
27730 sortable(&mut f);
27731 assert_eq!(
27732 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
27733 "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
27734 );
27735 assert_eq!(
27736 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
27737 "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
27738 );
27739 assert_eq!(
27740 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
27741 "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
27742 );
27743 assert_eq!(
27744 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
27745 "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
27746 );
27747 assert_eq!(
27750 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
27751 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
27752 );
27753 }
27754
27755 fn marking(f: &mut Fixture) {
27759 f.run(&[
27760 b"FT.CREATE",
27761 b"mk",
27762 b"ON",
27763 b"HASH",
27764 b"PREFIX",
27765 b"1",
27766 b"m:",
27767 b"SCHEMA",
27768 b"a",
27769 b"TEXT",
27770 b"b",
27771 b"TEXT",
27772 b"n",
27773 b"NUMERIC",
27774 b"g",
27775 b"TAG",
27776 ]);
27777 f.run(&[
27778 b"HSET",
27779 b"m:1",
27780 b"a",
27781 b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
27782 b"b",
27783 b"t1 t2 t3 t4 t5 t6 t7 t8",
27784 b"n",
27785 b"1",
27786 b"g",
27787 b"red",
27788 ]);
27789 }
27790
27791 #[test]
27794 fn a_summarize_cuts_a_field_down_to_what_matched() {
27795 let mut f = Fixture::new();
27796 marking(&mut f);
27797 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
27798 assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
27799 assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
27801 assert!(got.contains("$1\r\nr\r\n"), "{got}");
27803 }
27804
27805 #[test]
27809 fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
27810 let mut f = Fixture::new();
27811 marking(&mut f);
27812 let got = f.run(&[
27813 b"FT.SEARCH",
27814 b"mk",
27815 b"fox",
27816 b"SUMMARIZE",
27817 b"FRAGS",
27818 b"1",
27819 b"LEN",
27820 b"20",
27821 ]);
27822 assert!(
27823 got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
27824 "{got}"
27825 );
27826 let two = f.run(&[
27829 b"FT.SEARCH",
27830 b"mk",
27831 b"fox",
27832 b"SUMMARIZE",
27833 b"FRAGS",
27834 b"2",
27835 b"LEN",
27836 b"20",
27837 ]);
27838 assert!(
27839 two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
27840 "{two}"
27841 );
27842 }
27843
27844 #[test]
27847 fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
27848 let mut f = Fixture::new();
27849 marking(&mut f);
27850 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
27851 assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
27852 let both = f.run(&[
27853 b"FT.SEARCH",
27854 b"mk",
27855 b"fox",
27856 b"SUMMARIZE",
27857 b"LEN",
27858 b"2",
27859 b"HIGHLIGHT",
27860 ]);
27861 assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
27862 assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
27864 assert!(both.contains("$3\r\nred\r\n"), "{both}");
27865 let split = f.run(&[
27867 b"FT.SEARCH",
27868 b"mk",
27869 b"fox",
27870 b"SUMMARIZE",
27871 b"FIELDS",
27872 b"1",
27873 b"b",
27874 b"LEN",
27875 b"2",
27876 b"HIGHLIGHT",
27877 b"FIELDS",
27878 b"1",
27879 b"a",
27880 ]);
27881 assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
27882 }
27883
27884 #[test]
27886 fn a_highlight_does_not_mark_a_tag() {
27887 let mut f = Fixture::new();
27888 marking(&mut f);
27889 f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
27890 let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
27891 assert!(!got.contains("<b>"), "{got}");
27892 assert!(got.contains("red and blue"), "{got}");
27893 }
27894
27895 #[test]
27898 fn a_search_answers_a_total_and_then_the_rows() {
27899 let mut f = Fixture::new();
27900 corpus(&mut f);
27901 assert_eq!(
27902 f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
27903 "*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"
27904 );
27905 f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
27908 assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
27909 assert_eq!(
27912 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
27913 "*2\r\n:1\r\n$3\r\nd:3\r\n"
27914 );
27915 assert_eq!(
27916 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
27917 "*1\r\n:3\r\n"
27918 );
27919 }
27920
27921 #[test]
27924 fn the_window_is_ten_rows_and_a_million_wide_at_most() {
27925 let mut f = Fixture::new();
27926 corpus(&mut f);
27927 assert_eq!(
27928 f.run(&[
27929 b"FT.SEARCH",
27930 b"sx",
27931 b"alpha",
27932 b"NOCONTENT",
27933 b"LIMIT",
27934 b"1",
27935 b"1"
27936 ]),
27937 "*2\r\n:3\r\n$3\r\nd:2\r\n"
27938 );
27939 assert_eq!(
27940 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
27941 "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
27942 );
27943 assert_eq!(
27944 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
27945 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
27946 );
27947 assert_eq!(
27948 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
27949 "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
27950 );
27951 assert_eq!(
27952 f.run(&[
27953 b"FT.SEARCH",
27954 b"sx",
27955 b"alpha",
27956 b"NOCONTENT",
27957 b"LIMIT",
27958 b"999999",
27959 b"1000000"
27960 ]),
27961 "*1\r\n:3\r\n"
27962 );
27963 }
27964
27965 #[test]
27969 fn a_return_of_nothing_is_not_the_same_as_nocontent() {
27970 let mut f = Fixture::new();
27971 corpus(&mut f);
27972 let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
27973 assert_eq!(
27974 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
27975 bare
27976 );
27977 assert_eq!(
27978 f.run(&[
27979 b"FT.SEARCH",
27980 b"sx",
27981 b"delta",
27982 b"NOCONTENT",
27983 b"RETURN",
27984 b"1",
27985 b"t"
27986 ]),
27987 bare
27988 );
27989 assert_eq!(
27990 f.run(&[
27991 b"FT.SEARCH",
27992 b"sx",
27993 b"delta",
27994 b"RETURN",
27995 b"0",
27996 b"RETURN",
27997 b"1",
27998 b"t"
27999 ]),
28000 "*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"
28001 );
28002 }
28003
28004 #[test]
28007 fn the_count_after_return_counts_words() {
28008 let mut f = Fixture::new();
28009 corpus(&mut f);
28010 assert_eq!(
28013 f.run(&[
28014 b"FT.SEARCH",
28015 b"sx",
28016 b"delta",
28017 b"RETURN",
28018 b"3",
28019 b"t",
28020 b"AS",
28021 b"x"
28022 ]),
28023 "*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"
28024 );
28025 assert_eq!(
28028 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
28029 "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
28030 );
28031 assert_eq!(
28032 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
28033 "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
28034 );
28035 assert_eq!(
28039 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
28040 "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
28041 );
28042 }
28043
28044 #[test]
28047 fn a_filter_is_a_range_written_outside_the_query() {
28048 let mut f = Fixture::new();
28049 corpus(&mut f);
28050 assert_eq!(
28051 f.run(&[
28052 b"FT.SEARCH",
28053 b"sx",
28054 b"alpha",
28055 b"NOCONTENT",
28056 b"FILTER",
28057 b"n",
28058 b"2",
28059 b"4"
28060 ]),
28061 "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
28062 );
28063 assert_eq!(
28064 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
28065 "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
28066 );
28067 assert_eq!(
28068 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
28069 "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
28070 );
28071 assert_eq!(
28072 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
28073 "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
28074 );
28075 for field in [b"g".as_slice(), b"nope"] {
28078 assert_eq!(
28079 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
28080 "*1\r\n:0\r\n"
28081 );
28082 }
28083 }
28084
28085 #[test]
28088 fn the_index_is_found_before_the_arguments_are_read() {
28089 let mut f = Fixture::new();
28090 corpus(&mut f);
28091 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
28092 assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
28093 assert_eq!(
28094 f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
28095 missing
28096 );
28097 assert_eq!(
28100 f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
28101 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
28102 );
28103 }
28104
28105 #[test]
28108 fn inkeys_comes_off_the_total() {
28109 let mut f = Fixture::new();
28110 corpus(&mut f);
28111 assert_eq!(
28112 f.run(&[
28113 b"FT.SEARCH",
28114 b"sx",
28115 b"alpha",
28116 b"NOCONTENT",
28117 b"INKEYS",
28118 b"1",
28119 b"d:1"
28120 ]),
28121 "*2\r\n:1\r\n$3\r\nd:1\r\n"
28122 );
28123 assert_eq!(
28124 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
28125 "*1\r\n:0\r\n"
28126 );
28127 }
28128
28129 #[test]
28136 fn the_fields_are_read_from_the_session_database() {
28137 let mut f = Fixture::new();
28138 corpus(&mut f);
28139 f.run(&[b"SELECT", b"1"]);
28140 f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
28141 assert_eq!(
28144 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
28145 "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
28146 );
28147 assert_eq!(
28148 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28149 "*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"
28150 );
28151 }
28152
28153 #[test]
28156 fn the_third_protocol_answers_a_map_of_five() {
28157 let mut f = Fixture::new();
28158 corpus(&mut f);
28159 f.out = Out::new(Proto::Resp3);
28160 assert_eq!(
28161 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28162 concat!(
28163 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28164 "%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",
28165 "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28166 )
28167 );
28168 }
28169
28170 #[test]
28174 fn a_window_of_nothing_has_to_start_at_the_top() {
28175 let mut f = Fixture::new();
28176 corpus(&mut f);
28177 let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
28178 assert_eq!(
28179 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28180 refused
28181 );
28182 assert_eq!(
28183 f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28184 refused
28185 );
28186 assert_eq!(
28187 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28188 refused
28189 );
28190 assert_eq!(
28191 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
28192 "*1\r\n:3\r\n"
28193 );
28194 }
28195
28196 #[test]
28199 fn an_aggregation_answers_a_count_and_then_the_properties() {
28200 let mut f = Fixture::new();
28201 corpus(&mut f);
28202 assert_eq!(
28203 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
28204 "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
28205 );
28206 assert_eq!(
28210 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
28211 concat!(
28212 "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28213 "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
28214 "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
28215 )
28216 );
28217 assert_eq!(
28221 f.run(&[
28222 b"FT.AGGREGATE",
28223 b"sx",
28224 b"alpha",
28225 b"LOAD",
28226 b"1",
28227 b"@n",
28228 b"LIMIT",
28229 b"1",
28230 b"2"
28231 ]),
28232 "*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"
28233 );
28234 assert_eq!(
28236 f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
28237 "*1\r\n:0\r\n"
28238 );
28239 }
28240
28241 #[test]
28245 fn a_load_counts_words_and_can_rename_what_it_reads() {
28246 let mut f = Fixture::new();
28247 corpus(&mut f);
28248 assert_eq!(
28250 f.run(&[
28251 b"FT.AGGREGATE",
28252 b"sx",
28253 b"alpha",
28254 b"LOAD",
28255 b"3",
28256 b"@t",
28257 b"AS",
28258 b"text"
28259 ]),
28260 concat!(
28261 "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
28262 "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
28263 "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
28264 )
28265 );
28266 assert_eq!(
28267 f.run(&[
28268 b"FT.AGGREGATE",
28269 b"sx",
28270 b"alpha",
28271 b"LOAD",
28272 b"*",
28273 b"LIMIT",
28274 b"0",
28275 b"1"
28276 ]),
28277 concat!(
28278 "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28279 "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
28280 )
28281 );
28282 assert_eq!(
28284 f.run(&[
28285 b"FT.AGGREGATE",
28286 b"sx",
28287 b"alpha",
28288 b"LOAD",
28289 b"2",
28290 b"@n",
28291 b"@nope",
28292 b"LIMIT",
28293 b"0",
28294 b"2"
28295 ]),
28296 "*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"
28297 );
28298 }
28299
28300 #[test]
28303 fn a_load_refuses_a_count_it_cannot_use() {
28304 let mut f = Fixture::new();
28305 corpus(&mut f);
28306 let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
28307 assert_eq!(
28308 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
28309 format!("{head}Expected number of fields or `*`\r\n")
28310 );
28311 assert_eq!(
28312 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
28313 format!("{head}Value is outside acceptable bounds\r\n")
28314 );
28315 assert_eq!(
28316 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
28317 format!("{head}Expected an argument, but none provided\r\n")
28318 );
28319 assert_eq!(
28320 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
28321 format!("{head}Expected an argument, but none provided\r\n")
28322 );
28323 assert_eq!(
28327 f.run(&[
28328 b"FT.AGGREGATE",
28329 b"sx",
28330 b"alpha",
28331 b"LOAD",
28332 b"2",
28333 b"@t",
28334 b"AS"
28335 ]),
28336 "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
28337 );
28338 assert_eq!(
28341 f.run(&[
28342 b"FT.AGGREGATE",
28343 b"sx",
28344 b"alpha",
28345 b"LOAD",
28346 b"2",
28347 b"@t",
28348 b"AS",
28349 b"VERBATIM"
28350 ]),
28351 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
28352 );
28353 assert_eq!(
28357 f.run(&[
28358 b"FT.AGGREGATE",
28359 b"sx",
28360 b"alpha",
28361 b"LOAD",
28362 b"0",
28363 b"LIMIT",
28364 b"0",
28365 b"1"
28366 ]),
28367 "*2\r\n:1\r\n*0\r\n"
28368 );
28369 }
28370
28371 #[test]
28374 fn a_pipeline_step_closes_the_door_on_the_search_words() {
28375 let mut f = Fixture::new();
28376 corpus(&mut f);
28377 assert_eq!(
28378 f.run(&[
28379 b"FT.AGGREGATE",
28380 b"sx",
28381 b"alpha",
28382 b"LOAD",
28383 b"1",
28384 b"@t",
28385 b"VERBATIM"
28386 ]),
28387 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
28388 );
28389 assert_eq!(
28390 f.run(&[
28391 b"FT.AGGREGATE",
28392 b"sx",
28393 b"alpha",
28394 b"LIMIT",
28395 b"0",
28396 b"1",
28397 b"VERBATIM"
28398 ]),
28399 "*2\r\n:1\r\n*0\r\n"
28400 );
28401 for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
28404 let name = core::str::from_utf8(word).expect("the three words are text");
28405 assert_eq!(
28406 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
28407 format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
28408 );
28409 }
28410 }
28411
28412 #[test]
28415 fn addscores_writes_a_shorter_score_than_withscores() {
28416 let mut f = Fixture::new();
28417 corpus(&mut f);
28418 assert_eq!(
28419 f.run(&[
28420 b"FT.AGGREGATE",
28421 b"sx",
28422 b"alpha",
28423 b"ADDSCORES",
28424 b"LOAD",
28425 b"1",
28426 b"@n",
28427 b"LIMIT",
28428 b"0",
28429 b"2"
28430 ]),
28431 concat!(
28432 "*3\r\n:1\r\n",
28433 "*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",
28434 "*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"
28435 )
28436 );
28437 assert_eq!(
28441 f.run(&[
28442 b"FT.AGGREGATE",
28443 b"sx",
28444 b"alpha",
28445 b"NOCONTENT",
28446 b"WITHSCORES",
28447 b"LIMIT",
28448 b"0",
28449 b"2"
28450 ]),
28451 "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
28452 );
28453 assert_eq!(
28454 f.run(&[
28455 b"FT.AGGREGATE",
28456 b"sx",
28457 b"alpha",
28458 b"WITHSORTKEYS",
28459 b"LOAD",
28460 b"1",
28461 b"@n",
28462 b"LIMIT",
28463 b"0",
28464 b"1"
28465 ]),
28466 "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
28467 );
28468 }
28469
28470 #[test]
28473 fn a_normalising_scorer_answers_the_rows_backwards() {
28474 let mut f = Fixture::new();
28475 corpus(&mut f);
28476 assert_eq!(
28477 f.run(&[
28478 b"FT.AGGREGATE",
28479 b"sx",
28480 b"alpha",
28481 b"SCORER",
28482 b"BM25STD.NORM",
28483 b"ADDSCORES",
28484 b"LOAD",
28485 b"1",
28486 b"@n",
28487 b"LIMIT",
28488 b"1",
28489 b"2"
28490 ]),
28491 concat!(
28492 "*3\r\n:3\r\n",
28493 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
28494 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
28495 )
28496 );
28497 assert_eq!(
28500 f.run(&[
28501 b"FT.AGGREGATE",
28502 b"sx",
28503 b"alpha",
28504 b"SCORER",
28505 b"BM25STD.NORM",
28506 b"LOAD",
28507 b"1",
28508 b"@n",
28509 b"LIMIT",
28510 b"1",
28511 b"2"
28512 ]),
28513 "*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"
28514 );
28515 }
28516
28517 #[test]
28520 fn an_aggregation_answers_a_map_of_five_as_well() {
28521 let mut f = Fixture::new();
28522 corpus(&mut f);
28523 f.out = Out::new(Proto::Resp3);
28524 assert_eq!(
28525 f.run(&[
28526 b"FT.AGGREGATE",
28527 b"sx",
28528 b"alpha",
28529 b"ADDSCORES",
28530 b"WITHSCORES",
28531 b"WITHSORTKEYS",
28532 b"LOAD",
28533 b"1",
28534 b"@n",
28535 b"LIMIT",
28536 b"0",
28537 b"1"
28538 ]),
28539 concat!(
28540 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28541 "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
28542 "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
28543 "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
28544 "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28545 )
28546 );
28547 assert_eq!(
28550 f.run(&[
28551 b"FT.AGGREGATE",
28552 b"sx",
28553 b"alpha",
28554 b"NOCONTENT",
28555 b"LIMIT",
28556 b"0",
28557 b"1"
28558 ]),
28559 concat!(
28560 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28561 "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28562 )
28563 );
28564 }
28565}