1mod acl;
55mod args;
56mod arrays;
57mod auth;
58mod backup;
59mod bits;
60mod blocking;
61mod bloom;
62mod client;
63mod clients;
64mod cluster;
65mod cms;
66mod cpu;
67mod cuckoo;
68mod debug;
69mod failover;
70mod follow;
71mod geo;
72mod graph;
73mod hashes;
74mod himport;
75mod hll;
76mod indexing;
77mod json;
78mod keyspace;
79pub mod keyspec;
80mod lists;
81mod load;
82mod lua;
83mod memory;
84mod migrate;
85mod misses;
86mod monitor;
87mod multi;
88mod notify;
89mod persist;
90mod pubsub;
91mod repl;
92mod scan;
93mod scripting;
94mod search;
95mod server;
96mod sets;
97mod streams;
98mod strings;
99mod suggest;
100pub mod table;
101mod tdigest;
102mod topk;
103mod ts;
104mod vectors;
105mod vfilter;
106mod zsets;
107
108pub use args::Args;
109pub use blocking::{Parked, Waiters};
110pub use clients::Client;
111pub use load::{Loaded, Refused};
112pub(crate) use pubsub::Envelope;
113pub use server::parse_memory;
114pub use table::{COMMANDS, Spec, arity_ok, lookup};
115
116use crate::reply::Out;
117use std::cell::Cell;
118use std::path::{Path, PathBuf};
119use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
120use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize};
121use std::sync::{Arc, Weak};
122use yo_common::lock::{Held, Lock};
123use yo_common::{Code, Error};
124use yo_kv::cold::Store;
125use yo_kv::lookups;
126use yo_kv::{Clock, Db, Keyspace};
127use yo_search::Registry;
128
129use multi::Watches;
130use search::cursor::Cursors;
131
132pub const DATABASES: usize = 16;
139
140const ALL_DATABASES: u64 = if DATABASES == 64 {
147 u64::MAX
148} else {
149 (1u64 << DATABASES) - 1
150};
151const _: () = assert!(DATABASES <= 64);
152
153const EVICT_BUDGET: usize = 64;
165
166const COMPACT_LOOKS: usize = 8;
190
191const NO_MAXSTORE: u64 = u64::MAX;
199
200const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub enum Flow {
209 Continue,
211 Close,
213 Block,
220 Hold,
227}
228
229#[derive(Debug, Default)]
239pub struct Counter(AtomicU64);
240
241impl Counter {
242 fn bump(&self) {
244 self.0.store(self.get().wrapping_add(1), Relaxed);
245 }
246
247 fn drop_one(&self) {
254 self.0.store(self.get().saturating_sub(1), Relaxed);
255 }
256
257 fn get(&self) -> u64 {
259 self.0.load(Relaxed)
260 }
261
262 fn zero(&self) {
264 self.0.store(0, Relaxed);
265 }
266}
267
268#[derive(Debug, Default)]
274pub struct Stats {
275 clients: Counter,
277 connections: Counter,
279 commands: Counter,
281}
282
283impl Stats {
284 pub fn opened(&self) {
286 self.clients.bump();
287 self.connections.bump();
288 }
289
290 pub fn closed(&self) {
292 self.clients.drop_one();
293 }
294}
295
296#[derive(Debug, Clone, Copy, Default)]
298pub struct Totals {
299 pub clients: u64,
301 pub connections: u64,
303 pub commands: u64,
305}
306
307thread_local! {
308 static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
316}
317
318#[derive(Debug)]
329#[repr(align(64))]
330struct Local {
331 stats: Stats,
333 cmdstats: CommandStats,
335 dirty: AtomicU64,
344 turn: AtomicU64,
357 parked: AtomicUsize,
369 collect_ms: AtomicU64,
381 compact_db: AtomicUsize,
406}
407
408impl Local {
409 fn at(at: usize) -> Local {
411 Local {
412 stats: Stats::default(),
413 cmdstats: CommandStats::default(),
414 dirty: AtomicU64::new(0),
415 turn: AtomicU64::new(ALL_DATABASES),
416 parked: AtomicUsize::new(0),
417 collect_ms: AtomicU64::new(u64::MAX),
418 compact_db: AtomicUsize::new(at),
419 }
420 }
421}
422
423impl Default for Local {
424 fn default() -> Local {
425 Local::at(0)
426 }
427}
428
429impl Local {
430 fn mark(&self, dbs: u64) {
432 self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
433 }
434
435 fn note(&self, dbs: u64) {
437 self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
438 }
439
440 fn done(&self, at: usize) {
442 self.turn
443 .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
444 }
445
446 fn wanted(&self, at: usize) -> bool {
448 self.turn.load(Relaxed) & (1u64 << at) != 0
449 }
450
451 fn collecting(&self, now: u64) -> bool {
456 if self.collect_ms.load(Relaxed) == now {
457 return false;
458 }
459 self.collect_ms.store(now, Relaxed);
460 true
461 }
462
463 fn blocked(&self, n: usize) {
465 self.parked
466 .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
467 }
468
469 fn woke(&self, n: usize) {
471 self.parked
472 .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
473 }
474}
475
476fn one_thread() -> Box<[Local]> {
478 slots(1)
479}
480
481fn slots(threads: usize) -> Box<[Local]> {
483 (0..threads.max(1)).map(Local::at).collect()
486}
487
488fn working_dir() -> PathBuf {
495 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
496}
497
498#[derive(Debug, Clone, Copy, Default)]
507pub struct CommandStat {
508 pub calls: u64,
510 pub rejected: u64,
513 pub failed: u64,
515}
516
517impl CommandStat {
518 const fn seen(&self) -> bool {
524 self.calls != 0 || self.rejected != 0 || self.failed != 0
525 }
526}
527
528#[derive(Debug, Default)]
533struct Row {
534 calls: Counter,
536 rejected: Counter,
538 failed: Counter,
540}
541
542#[derive(Debug)]
549struct CommandStats(Box<[Row]>);
550
551impl Default for CommandStats {
552 fn default() -> CommandStats {
553 CommandStats((0..table::count()).map(|_| Row::default()).collect())
554 }
555}
556
557impl CommandStats {
558 fn at(&self, spec: &'static Spec) -> &Row {
560 &self.0[table::index_of(spec)]
561 }
562}
563
564pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
570
571const _: () = {
576 const fn shareable<T: Send + Sync>() {}
577 shareable::<Server>();
578};
579
580pub struct Server {
588 dbs: Vec<Db>,
589 width: usize,
595 clock: Clock,
596 started_ms: u64,
597 next_db: AtomicUsize,
610 conn_bytes: AtomicUsize,
627 maxmemory: AtomicU64,
635 store: Lock<Option<Box<StoreSource>>>,
652 maxstore: AtomicU64,
674 used: AtomicUsize,
691 startup: AtomicUsize,
698 peak: AtomicUsize,
703 evict_db: AtomicUsize,
713 expire_db: AtomicUsize,
720 expire_ms: AtomicU64,
730 waiters: Lock<Waiters>,
738 parked: AtomicUsize,
751 peers: Lock<migrate::Peers>,
763 locals: Box<[Local]>,
769 claimed: AtomicUsize,
771 next_client: AtomicU64,
779 dir: PathBuf,
787 backup: Lock<backup::State>,
797 sealed: AtomicBool,
807 search: Lock<Registry>,
822 cursors: Lock<Cursors>,
831 scripts: Lock<lua::Scripts>,
843 libraries: Lock<lua::library::Libraries>,
850 stopping: AtomicBool,
859 watches: Lock<Watches>,
866 watched: AtomicUsize,
873 pubsub: Lock<pubsub::Registry>,
880 subs: AtomicUsize,
886 mail: Box<[pubsub::Mailbox]>,
892 notify: AtomicU32,
899 clients: Lock<clients::Clients>,
906 kills: AtomicUsize,
911 pause: AtomicU64,
921 monitors: monitor::Monitors,
927 repl: repl::Replication,
933 follow: follow::Follower,
939 failover: failover::Failover,
945 cluster: cluster::Cluster,
953 myself: Lock<Weak<Server>>,
966 persist: persist::Persistence,
968 acl: acl::Users,
970 acllog: acl::Log,
972 aclfile: PathBuf,
980 plain: acl::Plain,
982 debug: debug::Knobs,
984}
985
986impl Server {
987 #[must_use]
989 pub fn new() -> Server {
990 let clock = Clock::system();
991 let server = Server {
992 dbs: (0..DATABASES)
993 .map(|_| Db::with_clock(clock.clone(), 1))
994 .collect(),
995 width: 1,
996 started_ms: clock.now_ms(),
997 clock,
998 next_db: AtomicUsize::new(0),
999 conn_bytes: AtomicUsize::new(0),
1000 maxmemory: AtomicU64::new(0),
1001 store: Lock::new(None),
1002 maxstore: AtomicU64::new(NO_MAXSTORE),
1003 used: AtomicUsize::new(0),
1004 startup: AtomicUsize::new(0),
1005 peak: AtomicUsize::new(0),
1006 evict_db: AtomicUsize::new(0),
1007 expire_db: AtomicUsize::new(0),
1008 expire_ms: AtomicU64::new(0),
1009 waiters: Lock::default(),
1010 parked: AtomicUsize::new(0),
1011 peers: Lock::default(),
1012 locals: one_thread(),
1013 claimed: AtomicUsize::new(0),
1014 next_client: AtomicU64::new(1),
1015 dir: working_dir(),
1016 backup: Lock::default(),
1017 sealed: AtomicBool::new(false),
1018 search: Lock::new(Registry::new()),
1019 cursors: Lock::default(),
1020 scripts: Lock::default(),
1021 libraries: Lock::default(),
1022 stopping: AtomicBool::new(false),
1023 watches: Lock::default(),
1024 watched: AtomicUsize::new(0),
1025 pubsub: Lock::default(),
1026 subs: AtomicUsize::new(0),
1027 notify: AtomicU32::new(0),
1028 clients: Lock::default(),
1029 kills: AtomicUsize::new(0),
1030 pause: AtomicU64::new(0),
1031 monitors: monitor::Monitors::default(),
1032 repl: repl::Replication::default(),
1033 follow: follow::Follower::default(),
1034 failover: failover::Failover::default(),
1035 cluster: cluster::Cluster::default(),
1036 myself: Lock::new(Weak::new()),
1037 persist: persist::Persistence::default(),
1038 acl: acl::Users::default(),
1039 acllog: acl::Log::default(),
1040 aclfile: PathBuf::new(),
1041 plain: acl::Plain::default(),
1042 debug: debug::Knobs::default(),
1043 mail: pubsub::boxes(1),
1044 };
1045 server.note_startup();
1046 server
1047 }
1048
1049 #[must_use]
1060 pub fn with_width(width: usize) -> Server {
1061 let mut server = Server::new();
1062 let clock = server.clock.clone();
1066 server.dbs = (0..DATABASES)
1067 .map(|_| Db::with_clock(clock.clone(), width))
1068 .collect();
1069 server.width = server.dbs[0].width();
1070 server.note_startup();
1074 server
1075 }
1076
1077 #[must_use]
1079 pub fn with_clock(clock: Clock) -> Server {
1080 let server = Server {
1081 dbs: (0..DATABASES)
1082 .map(|_| Db::with_clock(clock.clone(), 1))
1083 .collect(),
1084 width: 1,
1085 started_ms: clock.now_ms(),
1086 clock,
1087 next_db: AtomicUsize::new(0),
1088 conn_bytes: AtomicUsize::new(0),
1089 maxmemory: AtomicU64::new(0),
1090 store: Lock::new(None),
1091 maxstore: AtomicU64::new(NO_MAXSTORE),
1092 used: AtomicUsize::new(0),
1093 startup: AtomicUsize::new(0),
1094 peak: AtomicUsize::new(0),
1095 evict_db: AtomicUsize::new(0),
1096 expire_db: AtomicUsize::new(0),
1097 expire_ms: AtomicU64::new(0),
1098 waiters: Lock::default(),
1099 parked: AtomicUsize::new(0),
1100 peers: Lock::default(),
1101 locals: one_thread(),
1102 claimed: AtomicUsize::new(0),
1103 next_client: AtomicU64::new(1),
1104 dir: working_dir(),
1105 backup: Lock::default(),
1106 sealed: AtomicBool::new(false),
1107 search: Lock::new(Registry::new()),
1108 cursors: Lock::default(),
1109 scripts: Lock::default(),
1110 libraries: Lock::default(),
1111 stopping: AtomicBool::new(false),
1112 watches: Lock::default(),
1113 watched: AtomicUsize::new(0),
1114 pubsub: Lock::default(),
1115 subs: AtomicUsize::new(0),
1116 notify: AtomicU32::new(0),
1117 clients: Lock::default(),
1118 kills: AtomicUsize::new(0),
1119 pause: AtomicU64::new(0),
1120 monitors: monitor::Monitors::default(),
1121 repl: repl::Replication::default(),
1122 follow: follow::Follower::default(),
1123 failover: failover::Failover::default(),
1124 cluster: cluster::Cluster::default(),
1125 myself: Lock::new(Weak::new()),
1126 persist: persist::Persistence::default(),
1127 acl: acl::Users::default(),
1128 acllog: acl::Log::default(),
1129 aclfile: PathBuf::new(),
1130 plain: acl::Plain::default(),
1131 debug: debug::Knobs::default(),
1132 mail: pubsub::boxes(1),
1133 };
1134 server.note_startup();
1135 server
1136 }
1137
1138 pub fn striped(&self, i: usize) -> &Db {
1162 self.mine().mark(1u64 << i);
1163 &self.dbs[i]
1164 }
1165
1166 fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
1173 self.dbs
1174 .iter()
1175 .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
1176 }
1177
1178 const fn slots(&self) -> usize {
1184 DATABASES * self.width
1185 }
1186
1187 const fn slot_db(&self, i: usize) -> usize {
1189 i / self.width
1190 }
1191
1192 fn slot(&self, i: usize) -> Held<'_, Keyspace> {
1194 let (db, stripe) = (i / self.width, i % self.width);
1195 self.dbs[db].hold_stripe(stripe)
1196 }
1197
1198 #[must_use]
1200 pub fn dir(&self) -> &Path {
1201 &self.dir
1202 }
1203
1204 pub fn set_dir(&mut self, dir: PathBuf) {
1211 self.dir = dir;
1212 }
1213
1214 #[must_use]
1217 pub fn aclfile(&self) -> Option<&Path> {
1218 Some(self.aclfile.as_path()).filter(|p| !p.as_os_str().is_empty())
1219 }
1220
1221 pub fn set_aclfile(&mut self, path: PathBuf) {
1227 self.aclfile = path;
1228 }
1229
1230 pub fn load_acl(&self) -> std::result::Result<(), String> {
1238 match self.aclfile() {
1239 Some(path) => yo_alloc::allow(|| acl::load_file(self, path)),
1240 None => Ok(()),
1241 }
1242 }
1243
1244 pub fn backup_expire(&self) {
1250 backup::expire(self);
1251 }
1252
1253 pub fn stop(&self) {
1259 self.stopping.store(true, Release);
1260 }
1261
1262 #[must_use]
1268 pub fn stopping(&self) -> bool {
1269 self.stopping.load(Acquire)
1270 }
1271
1272 #[must_use]
1279 pub fn striped_ref(&self, i: usize) -> &Db {
1280 &self.dbs[i]
1281 }
1282
1283 fn settings(&self) -> Held<'_, Keyspace> {
1291 self.dbs[0].hold_stripe(0)
1292 }
1293
1294 pub fn refresh_clock(&self) {
1305 self.clock.refresh();
1306 }
1307
1308 pub fn advance_clock_ms(&self, ms: u64) {
1314 let now = self.clock.now_ms() + ms;
1315 self.set_clock_ms(now);
1316 }
1317
1318 pub fn set_clock_ms(&self, ms: u64) {
1326 self.clock.set(ms);
1327 }
1328
1329 #[must_use]
1331 pub fn uptime_secs(&self) -> u64 {
1332 self.clock.now_ms().saturating_sub(self.started_ms) / 1000
1333 }
1334
1335 #[must_use]
1343 pub fn memory_bytes(&self) -> usize {
1344 self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1345 }
1346
1347 #[must_use]
1351 pub fn startup_bytes(&self) -> usize {
1352 self.startup.load(Relaxed)
1353 }
1354
1355 #[must_use]
1365 pub fn peak_bytes(&self) -> usize {
1366 let now = self.memory_bytes();
1367 self.peak.fetch_max(now, Relaxed).max(now)
1368 }
1369
1370 fn note_startup(&self) {
1372 let now = self.memory_bytes();
1373 self.startup.store(now, Relaxed);
1374 self.peak.store(now, Relaxed);
1375 }
1376
1377 #[must_use]
1383 pub fn dataset_bytes(&self) -> usize {
1384 self.keyspaces()
1385 .map(|db| db.map().arena().live_bytes() as usize)
1386 .sum()
1387 }
1388
1389 #[must_use]
1391 pub fn arena_bytes(&self) -> usize {
1392 self.keyspaces()
1393 .map(|db| db.map().arena().reserved_bytes() as usize)
1394 .sum()
1395 }
1396
1397 #[must_use]
1399 pub fn index_bytes(&self) -> usize {
1400 self.keyspaces()
1401 .map(|db| db.map().index().memory_bytes())
1402 .sum()
1403 }
1404
1405 #[must_use]
1412 pub fn compaction(&self) -> yo_kv::Compaction {
1413 self.keyspaces().map(|db| db.map().compaction()).fold(
1414 yo_kv::Compaction::default(),
1415 |a, b| yo_kv::Compaction {
1416 walked: a.walked + b.walked,
1417 moved: a.moved + b.moved,
1418 bytes: a.bytes + b.bytes,
1419 },
1420 )
1421 }
1422
1423 #[must_use]
1431 pub fn listed_runs(&self) -> usize {
1432 self.keyspaces()
1433 .map(|db| db.map().arena().listed_runs())
1434 .sum()
1435 }
1436
1437 #[must_use]
1439 pub fn segment_count(&self) -> usize {
1440 self.keyspaces()
1441 .map(|db| db.map().arena().resident_segments())
1442 .sum()
1443 }
1444
1445 #[must_use]
1447 pub fn conn_bytes(&self) -> usize {
1448 self.conn_bytes.load(Relaxed)
1449 }
1450
1451 pub fn note_conn_bytes(&self, delta: isize) {
1459 self.conn_bytes
1465 .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1466 }
1467
1468 #[must_use]
1470 pub fn expired_keys(&self) -> u64 {
1471 self.keyspaces().map(|db| db.expired_keys()).sum()
1472 }
1473
1474 #[must_use]
1476 pub fn expired_fields(&self) -> u64 {
1477 self.keyspaces().map(|db| db.expired_fields()).sum()
1478 }
1479
1480 #[must_use]
1482 pub fn expired_fields_active(&self) -> u64 {
1483 self.keyspaces().map(|db| db.expired_fields_active()).sum()
1484 }
1485
1486 #[must_use]
1488 pub fn evicted_keys(&self) -> u64 {
1489 self.keyspaces().map(|db| db.evicted_keys()).sum()
1490 }
1491
1492 #[must_use]
1494 pub fn keyspace_hits(&self) -> u64 {
1495 self.keyspaces().map(|db| db.hits()).sum()
1496 }
1497
1498 #[must_use]
1500 pub fn keyspace_misses(&self) -> u64 {
1501 self.keyspaces().map(|db| db.misses()).sum()
1502 }
1503
1504 pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1510 (0..table::count())
1511 .map(|at| (table::name_at(at), self.command_stat(at)))
1512 .filter(|(_, row)| row.seen())
1513 }
1514
1515 fn command_stat(&self, at: usize) -> CommandStat {
1517 let mut sum = CommandStat::default();
1518 for thread in &self.locals {
1519 let row = &thread.cmdstats.0[at];
1520 sum.calls += row.calls.get();
1521 sum.rejected += row.rejected.get();
1522 sum.failed += row.failed.get();
1523 }
1524 sum
1525 }
1526
1527 pub fn counted(&self) -> &Stats {
1536 &self.mine().stats
1537 }
1538
1539 pub fn next_client(&self) -> u64 {
1544 self.next_client.fetch_add(1, Relaxed)
1545 }
1546
1547 pub fn is_behind(self: &Arc<Server>) {
1555 let mut myself = self.myself.lock();
1556 if myself.strong_count() == 0 {
1557 *myself = Arc::downgrade(self);
1558 }
1559 }
1560
1561 pub fn forget_behind(&self) {
1570 let mut myself = self.myself.lock();
1571 *myself = Weak::new();
1572 }
1573
1574 #[must_use]
1580 pub(crate) fn myself(&self) -> Option<Arc<Server>> {
1581 self.myself.lock().upgrade()
1582 }
1583
1584 pub fn my_slot(&self) -> usize {
1591 self.mine_at()
1592 }
1593
1594 fn mine(&self) -> &Local {
1596 &self.locals[self.mine_at()]
1597 }
1598
1599 fn mine_at(&self) -> usize {
1606 let mut slot = SLOT.get();
1607 if slot == usize::MAX {
1608 slot = self.claimed.fetch_add(1, Relaxed);
1609 SLOT.set(slot);
1610 }
1611 slot % self.locals.len()
1612 }
1613
1614 #[must_use]
1616 pub fn totals(&self) -> Totals {
1617 let mut sum = Totals::default();
1618 for thread in &self.locals {
1619 sum.clients += thread.stats.clients.get();
1620 sum.connections += thread.stats.connections.get();
1621 sum.commands += thread.stats.commands.get();
1622 }
1623 sum
1624 }
1625
1626 #[must_use]
1640 pub fn per_thread(&self) -> Vec<Totals> {
1641 self.locals
1642 .iter()
1643 .map(|thread| Totals {
1644 clients: thread.stats.clients.get(),
1645 connections: thread.stats.connections.get(),
1646 commands: thread.stats.commands.get(),
1647 })
1648 .collect()
1649 }
1650
1651 pub fn reset_stats(&self) {
1658 for thread in &self.locals {
1659 thread.stats.connections.zero();
1660 thread.stats.commands.zero();
1661 }
1662 for mut db in self.keyspaces() {
1667 db.zero_stats();
1668 }
1669 }
1670
1671 pub fn set_threads(&mut self, threads: usize) {
1678 self.locals = slots(threads);
1679 self.mail = pubsub::boxes(threads);
1680 self.claimed = AtomicUsize::new(0);
1681 }
1682
1683 #[must_use]
1685 pub fn maxmemory(&self) -> u64 {
1686 self.maxmemory.load(Relaxed)
1687 }
1688
1689 pub fn set_maxmemory(&self, bytes: u64) {
1703 self.maxmemory.store(bytes, Relaxed);
1704 for db in &self.dbs {
1705 db.track_memory(bytes != 0);
1706 }
1707 self.used.store(self.settled_memory(), Relaxed);
1708 }
1709
1710 pub fn set_store_source(
1720 &mut self,
1721 source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1722 ) {
1723 *self.store.lock() = Some(Box::new(source));
1724 }
1725
1726 #[must_use]
1728 pub fn has_store_source(&self) -> bool {
1729 self.store.lock().is_some()
1730 }
1731
1732 fn attach_store(&self, at: usize) {
1739 if self.slot(at).store_bytes().is_some() {
1740 return;
1741 }
1742 let mut source = self.store.lock();
1746 let Some(source) = source.as_mut() else {
1747 return;
1748 };
1749 if let Some(blocks) = source(at) {
1750 self.slot(at).attach(blocks);
1751 }
1752 }
1753
1754 #[must_use]
1756 pub fn maxstore(&self) -> Option<u64> {
1757 match self.maxstore.load(Relaxed) {
1758 NO_MAXSTORE => None,
1759 bytes => Some(bytes),
1760 }
1761 }
1762
1763 pub fn set_maxstore(&self, bytes: Option<u64>) {
1769 self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1770 }
1771
1772 #[must_use]
1778 pub fn store_bytes(&self) -> u64 {
1779 self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1780 }
1781
1782 #[must_use]
1797 pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1798 let mut total = yo_kv::tier::Stats::default();
1799 for db in self.keyspaces() {
1800 let Some(tier) = db.tier() else { continue };
1801 let s = tier.stats();
1802 total.demoted += s.demoted;
1803 total.promoted += s.promoted;
1804 total.faults += s.faults;
1805 total.served += s.served;
1806 total.bytes_out += s.bytes_out;
1807 total.bytes_in += s.bytes_in;
1808 }
1809 total
1810 }
1811
1812 #[must_use]
1819 pub fn regime(&self) -> &'static str {
1820 if (0..self.slots()).any(|at| self.migrates(at)) {
1821 "migrate"
1822 } else {
1823 "evict"
1824 }
1825 }
1826
1827 fn migrates(&self, at: usize) -> bool {
1839 let cap = self.maxstore();
1840 if cap == Some(0) {
1841 return false;
1842 }
1843 let bytes = self.slot(at).store_bytes();
1847 match bytes {
1848 Some(held) => cap.is_none_or(|cap| held < cap),
1849 None => self.store.lock().is_some(),
1853 }
1854 }
1855
1856 pub fn refresh_memory(&self) {
1861 if self.maxmemory() != 0 {
1862 let used = self.settled_memory();
1863 self.used.store(used, Relaxed);
1864 self.peak.fetch_max(used, Relaxed);
1870 }
1871 }
1872
1873 fn settled_memory(&self) -> usize {
1880 self.keyspaces()
1881 .map(|mut db| db.settled_memory_bytes())
1882 .sum::<usize>()
1883 + self.conn_bytes()
1884 }
1885
1886 pub fn make_room(&self) -> bool {
1920 let limit = self.maxmemory();
1921 if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1922 return true;
1923 }
1924 let mut used = self.settled_memory();
1929 self.used.store(used, Relaxed);
1930 let mut budget = EVICT_BUDGET;
1931 while used as u64 > limit {
1932 let over = used - limit as usize;
1933 if !self.relieve_step(over) {
1934 return false;
1935 }
1936 self.compact_hard_step();
1937 used = self.settled_memory();
1938 self.used.store(used, Relaxed);
1939 budget -= 1;
1940 if budget == 0 {
1941 break;
1942 }
1943 }
1944 true
1945 }
1946
1947 fn relieve_step(&self, over: usize) -> bool {
1964 let from = self.evict_db.load(Relaxed);
1965 for turn in 0..self.slots() {
1966 let i = (from + turn) % self.slots();
1967 let used = !self.slot(i).is_empty();
1970 let gave = if used && self.migrates(i) {
1971 self.attach_store(i);
1972 self.slot(i)
1977 .relieve(over)
1978 .is_ok_and(yo_kv::tier::Relief::made_room)
1979 } else {
1980 let armed = notify::arm(self, self.slot_db(i));
1986 let gone = self.slot(i).evict_one();
1987 notify::drain(self, armed);
1988 gone
1989 };
1990 if gave {
1991 self.evict_db.store((i + 1) % self.slots(), Relaxed);
1992 self.mine().mark(1u64 << self.slot_db(i));
1993 return true;
1994 }
1995 }
1996 false
1997 }
1998
1999 pub fn expire_slice(&self, budget: usize) -> usize {
2014 if !self.expiring() {
2019 return 0;
2020 }
2021 let now = self.clock.now_ms();
2022 if now == self.expire_ms.load(Relaxed) {
2023 return 0;
2024 }
2025 self.expire_ms.store(now, Relaxed);
2026 self.expire_step(budget)
2027 }
2028
2029 pub fn expire_step(&self, budget: usize) -> usize {
2045 let slots = self.slots();
2046 let mut spent = 0;
2047 let from = self.expire_db.load(Relaxed);
2048 for turn in 0..slots {
2049 if spent >= budget {
2050 break;
2051 }
2052 let i = (from + turn) % slots;
2053 let armed = notify::arm(self, self.slot_db(i));
2058 let mut slot = self.slot(i);
2063 let c = slot.expire_cycle(budget - spent);
2064 let left = (budget - spent).saturating_sub(c.examined);
2070 let fields = slot.field_expire_cycle(left);
2071 drop(slot);
2072 notify::drain(self, armed);
2073 repl::swept(self, self.slot_db(i));
2077 spent += c.examined + fields;
2078 if c.expired > 0 {
2079 self.expire_db.store((i + 1) % slots, Relaxed);
2080 self.mine().note(1u64 << self.slot_db(i));
2081 }
2082 }
2083 spent
2084 }
2085
2086 fn compact_hard_step(&self) -> Option<usize> {
2093 let from = self.next_db.load(Relaxed);
2094 for turn in 0..self.slots() {
2095 let i = (from + turn) % self.slots();
2096 if let Some(moved) = self.slot(i).compact_hard() {
2097 self.next_db.store((i + 1) % self.slots(), Relaxed);
2098 return Some(moved);
2099 }
2100 }
2101 None
2102 }
2103
2104 fn collect_marks(&self) {
2113 let mut marked = 0;
2114 for thread in &self.locals {
2115 marked |= thread.dirty.swap(0, Relaxed);
2116 }
2117 self.mine().note(marked);
2118 }
2119
2120 pub fn compact_step(&self) -> Option<usize> {
2133 if !self.resizing() {
2138 return None;
2139 }
2140 let slots = self.slots();
2141 let looks = COMPACT_LOOKS.min(slots);
2142 if self.mine().collecting(self.clock.now_ms()) {
2148 self.collect_marks();
2149 }
2150 let mine = self.mine();
2151 let from = mine.compact_db.load(Relaxed);
2156 for turn in 0..looks {
2157 let i = (from + turn) % slots;
2158 let at = self.slot_db(i);
2162 if !mine.wanted(at) {
2163 continue;
2164 }
2165 if let Some(moved) = self.slot(i).compact_step() {
2166 mine.compact_db.store((i + 1) % slots, Relaxed);
2167 return Some(moved);
2168 }
2169 if i % self.width == self.width - 1 {
2173 mine.done(at);
2174 }
2175 }
2176 mine.compact_db.store((from + looks) % slots, Relaxed);
2177 None
2178 }
2179}
2180
2181impl Server {
2182 fn watching(&self) -> bool {
2191 self.watched.load(Relaxed) != 0
2192 }
2193
2194 pub(crate) fn notify_flags(&self) -> u32 {
2201 self.notify.load(Relaxed)
2202 }
2203
2204 pub(crate) fn set_notify_flags(&self, flags: u32) {
2206 self.notify.store(flags, Relaxed);
2207 }
2208
2209 fn recount(&self, watches: &Watches) {
2214 self.watched.store(watches.len(), Relaxed);
2215 }
2216}
2217
2218impl Default for Server {
2219 fn default() -> Server {
2220 Server::new()
2221 }
2222}
2223
2224pub struct Session {
2226 db: usize,
2227 id: u64,
2228 conn: u32,
2236 name: Vec<u8>,
2237 sets: himport::Fieldsets,
2243 scripted: bool,
2253 multi: Option<multi::Queue>,
2259 watching: Vec<multi::Watched>,
2266 running: bool,
2273 replay: crate::request::Argv,
2282 subs: Option<Box<pubsub::Subs>>,
2290 lib_name: Vec<u8>,
2298 lib_ver: Vec<u8>,
2299 no_evict: bool,
2306 no_touch: bool,
2309 reply: Reply,
2311 sock: Arc<Client>,
2320 authenticated: bool,
2328 acl: Box<acl::Identity>,
2335 master: bool,
2344 asking: bool,
2357 asking_next: bool,
2358 internal: bool,
2369 closing: bool,
2376}
2377
2378#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
2385pub enum Reply {
2386 #[default]
2388 On,
2389 Off,
2391 SkipNext,
2393 SkipNow,
2395}
2396
2397impl Session {
2398 #[must_use]
2400 pub fn new(id: u64) -> Session {
2401 Session {
2402 db: 0,
2403 id,
2404 conn: u32::MAX,
2405 name: Vec::new(),
2406 sets: himport::Fieldsets::default(),
2407 scripted: false,
2408 multi: None,
2409 watching: Vec::new(),
2410 running: false,
2411 replay: crate::request::Argv::new(),
2412 subs: None,
2413 lib_name: Vec::new(),
2414 lib_ver: Vec::new(),
2415 no_evict: false,
2416 no_touch: false,
2417 reply: Reply::On,
2418 sock: Arc::new(Client::new(id)),
2419 authenticated: false,
2420 master: false,
2421 asking: false,
2422 asking_next: false,
2423 internal: false,
2424 closing: false,
2425 acl: Box::default(),
2426 }
2427 }
2428
2429 pub fn admit(&mut self, yes: bool) {
2436 self.authenticated = yes;
2437 }
2438
2439 #[must_use]
2441 pub(crate) fn authenticated(&self) -> bool {
2442 self.authenticated
2443 }
2444
2445 pub(crate) fn serve_master(&mut self, yes: bool) {
2451 self.master = yes;
2452 }
2453
2454 #[must_use]
2456 pub(crate) fn serving_master(&self) -> bool {
2457 self.master
2458 }
2459
2460 pub(crate) fn serve_internal(&mut self, yes: bool) {
2466 self.internal = yes;
2467 }
2468
2469 #[must_use]
2475 pub(crate) fn internal(&self) -> bool {
2476 self.internal || self.master
2477 }
2478
2479 pub(crate) fn hang_up(&mut self) {
2481 self.closing = true;
2482 }
2483
2484 #[must_use]
2486 pub(crate) fn hanging_up(&self) -> bool {
2487 self.closing
2488 }
2489
2490 pub(crate) fn ask_next(&mut self) {
2493 self.asking_next = true;
2494 }
2495
2496 #[must_use]
2502 pub fn row(&self) -> &Arc<Client> {
2503 &self.sock
2504 }
2505
2506 pub fn opened(&mut self, now_ms: u64) {
2512 self.sock.since_ms.store(now_ms, Relaxed);
2513 self.sock.last_ms.store(now_ms, Relaxed);
2514 }
2515
2516 pub fn set_socket(&mut self, peer: &str, local: &str, fd: i32, unix: bool) {
2523 yo_alloc::allow(|| {
2524 let mut text = self.sock.text.lock();
2525 text.peer.clear();
2526 text.peer.extend_from_slice(peer.as_bytes());
2527 text.local.clear();
2528 text.local.extend_from_slice(local.as_bytes());
2529 });
2530 self.sock.fd.store(fd, Relaxed);
2531 self.sock.set_flag(clients::UNIX, unix);
2532 }
2533
2534 pub fn read_bytes(&mut self, n: usize) {
2536 let row = &self.sock;
2537 row.net_in
2538 .store(row.net_in.load(Relaxed) + n as u64, Relaxed);
2539 row.reads.store(row.reads.load(Relaxed) + 1, Relaxed);
2540 }
2541
2542 pub fn wrote_bytes(&mut self, n: usize) {
2544 let row = &self.sock;
2545 row.net_out
2546 .store(row.net_out.load(Relaxed) + n as u64, Relaxed);
2547 }
2548
2549 pub fn note_buffers(&mut self, waiting: usize, room: usize, held: usize, reply: usize) {
2557 let row = &self.sock;
2558 row.qbuf.store(waiting as u64, Relaxed);
2559 row.qbuf_free.store(room as u64, Relaxed);
2560 row.obl.store(held as u64, Relaxed);
2561 row.rbs.store(reply as u64, Relaxed);
2562 row.rbp
2563 .store(row.rbp.load(Relaxed).max(reply as u64), Relaxed);
2564 }
2565
2566 pub fn note_proto(&mut self, version: i64) {
2572 self.sock.resp.store(version as u32, Relaxed);
2573 }
2574
2575 pub(crate) fn ran(&mut self, at: usize, sub: Option<&[u8]>, argv: u64, now_ms: u64) {
2582 self.sock.last_ms.store(now_ms, Relaxed);
2583 self.sock.argv_mem.store(argv, Relaxed);
2584 self.sock.note_command(at, sub);
2585 }
2586
2587 pub fn finished(&mut self) {
2602 self.asking = core::mem::take(&mut self.asking_next);
2606 let (sub, psub, ssub) = self.sub_counts();
2607 let (multi, multi_mem) = self.queued();
2608 let subscribed = self.subscribed();
2609 let in_multi = self.in_multi();
2610 let watching = self.watching.len();
2611 let db = self.db;
2612 let row = &self.sock;
2613 row.cmds.store(row.cmds.load(Relaxed) + 1, Relaxed);
2614 row.db.store(db as u32, Relaxed);
2615 row.sub.store(sub as u32, Relaxed);
2616 row.psub.store(psub as u32, Relaxed);
2617 row.ssub.store(ssub as u32, Relaxed);
2618 row.watch.store(watching as u32, Relaxed);
2619 row.multi.store(multi, Relaxed);
2620 row.multi_mem.store(multi_mem, Relaxed);
2621 row.set_flag(clients::SUBSCRIBED, subscribed);
2622 row.set_flag(clients::IN_MULTI, in_multi);
2623 }
2624
2625 #[must_use]
2627 pub const fn reply_mode(&self) -> Reply {
2628 self.reply
2629 }
2630
2631 pub const fn step_reply(&mut self) {
2636 self.reply = match self.reply {
2637 Reply::SkipNext => Reply::SkipNow,
2638 Reply::SkipNow => Reply::On,
2639 other => other,
2640 };
2641 }
2642
2643 pub(crate) const fn scripted(&self) -> bool {
2645 self.scripted
2646 }
2647
2648 pub(crate) const fn running(&self) -> bool {
2650 self.running
2651 }
2652
2653 pub(crate) fn monitoring(&self) -> bool {
2660 self.sock.flag(clients::MONITOR)
2661 }
2662
2663 pub(crate) fn replicating(&self) -> bool {
2670 self.sock.flag(clients::REPLICA)
2671 }
2672
2673 pub(crate) fn set_conn(&mut self, conn: u32) {
2679 self.conn = conn;
2680 self.sock.conn.store(conn, Relaxed);
2681 }
2682
2683 #[must_use]
2685 pub const fn id(&self) -> u64 {
2686 self.id
2687 }
2688
2689 #[must_use]
2691 pub const fn db(&self) -> usize {
2692 self.db
2693 }
2694
2695 #[must_use]
2697 pub fn name(&self) -> &[u8] {
2698 &self.name
2699 }
2700
2701 pub fn reset(&mut self) {
2706 self.db = 0;
2707 self.name.clear();
2708 self.sock.set_text(|text| &mut text.name, b"");
2709 self.sets.clear();
2713 self.reply = Reply::On;
2718 self.set_no_evict(false);
2719 self.set_no_touch(false);
2720 self.forget_user();
2724 }
2725
2726 fn set_name(&mut self, name: &[u8]) {
2728 yo_alloc::allow(|| {
2729 self.name.clear();
2730 self.name.extend_from_slice(name);
2731 });
2732 self.sock.set_text(|text| &mut text.name, name);
2733 }
2734
2735 fn set_lib_name(&mut self, value: &[u8]) {
2737 yo_alloc::allow(|| {
2738 self.lib_name.clear();
2739 self.lib_name.extend_from_slice(value);
2740 });
2741 self.sock.set_text(|text| &mut text.lib_name, value);
2742 }
2743
2744 fn set_lib_ver(&mut self, value: &[u8]) {
2746 yo_alloc::allow(|| {
2747 self.lib_ver.clear();
2748 self.lib_ver.extend_from_slice(value);
2749 });
2750 self.sock.set_text(|text| &mut text.lib_ver, value);
2751 }
2752
2753 fn set_no_evict(&mut self, on: bool) {
2755 self.no_evict = on;
2756 self.sock.set_flag(clients::NO_EVICT, on);
2757 }
2758
2759 fn set_no_touch(&mut self, on: bool) {
2761 self.no_touch = on;
2762 self.sock.set_flag(clients::NO_TOUCH, on);
2763 }
2764}
2765
2766pub fn forget_session(server: &Server, session: &mut Session) {
2776 multi::release(server, session);
2777 pubsub::release(server, session);
2778 if session.monitoring() {
2779 server.watch_no_more(session.row());
2780 }
2781 if session.replicating() {
2782 server.drop_replica(session.row().id);
2783 }
2784}
2785
2786pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
2791 if args.is_empty() {
2794 return Flow::Continue;
2795 }
2796 let flow = resolved(server, session, lookup(args.name()), args, out);
2797 if flow != Flow::Hold {
2807 session.finished();
2808 }
2809 flow
2810}
2811
2812const CONTAINERS: [&str; 13] = [
2818 "acl", "backup", "client", "cluster", "command", "config", "function", "memory", "object",
2819 "pubsub", "script", "xgroup", "xinfo",
2820];
2821
2822fn container_sub<'a>(spec: &Spec, args: &Args<'a>) -> Option<&'a [u8]> {
2828 (args.len() > 1 && CONTAINERS.contains(&spec.name)).then(|| args.get(1))
2829}
2830
2831const MAY_REPLICATE: [&str; 6] = ["eval", "evalsha", "fcall", "pfcount", "publish", "spublish"];
2837
2838fn may_replicate(spec: &Spec, session: &Session) -> bool {
2845 spec.flags.contains(&"write")
2846 || MAY_REPLICATE.contains(&spec.name)
2847 || (spec.name == "exec" && session.queued_writes())
2848}
2849
2850fn touches_keyspace(spec: &Spec) -> bool {
2858 spec.flags.contains(&"write")
2859 || spec.flags.contains(&"readonly")
2860 || MAY_REPLICATE.contains(&spec.name)
2861}
2862
2863pub fn resolved(
2875 server: &Server,
2876 session: &mut Session,
2877 spec: Option<&'static Spec>,
2878 args: Args<'_>,
2879 out: &mut Out,
2880) -> Flow {
2881 if args.is_empty() {
2882 return Flow::Continue;
2883 }
2884 server.mine().stats.commands.bump();
2885
2886 let Some(spec) = spec else {
2893 multi::refuse(server, session, None, &args::unknown_command(args), out);
2894 return Flow::Continue;
2895 };
2896 if !arity_ok(spec, args.len()) {
2897 server.mine().cmdstats.at(spec).rejected.bump();
2898 multi::refuse(
2899 server,
2900 session,
2901 Some(spec),
2902 &args::wrong_arity(spec.name),
2903 out,
2904 );
2905 return Flow::Continue;
2906 }
2907 let argv = (0..args.len()).map(|i| args.get(i).len() as u64).sum();
2913 session.ran(
2914 table::index_of(spec),
2915 container_sub(spec, &args),
2916 argv,
2917 server.now_ms(),
2918 );
2919
2920 if server.guarded()
2932 && !session.authenticated()
2933 && !session.serving_master()
2934 && !spec.flags.contains(&"no_auth")
2935 {
2936 server.mine().cmdstats.at(spec).rejected.bump();
2937 if spec.name == "exec" {
2938 multi::abort(server, session, auth::NOAUTH, out);
2939 } else {
2940 session.dirty_multi();
2941 out.error(auth::NOAUTH.as_bytes());
2942 }
2943 return Flow::Continue;
2944 }
2945
2946 if session.in_multi()
2947 && let Some(e) = multi::refused_in_multi(spec)
2948 {
2949 server.mine().cmdstats.at(spec).rejected.bump();
2950 multi::refuse(server, session, Some(spec), &e, out);
2951 return Flow::Continue;
2952 }
2953
2954 if server.restricted()
2963 && !session.serving_master()
2964 && let Some(said) = acl::gate(server, session, spec, args, out)
2965 {
2966 server.mine().cmdstats.at(spec).rejected.bump();
2967 if spec.name == "exec" {
2968 multi::abort(server, session, &said, out);
2969 } else {
2970 session.dirty_multi();
2971 out.error(said.as_bytes());
2972 }
2973 return Flow::Continue;
2974 }
2975
2976 if server.cluster_enabled()
2989 && !session.serving_master()
2990 && let Some(said) = cluster::gate(
2991 server,
2992 session.db,
2993 session.asking || cluster::asks(spec),
2994 spec,
2995 args,
2996 )
2997 {
2998 server.mine().cmdstats.at(spec).rejected.bump();
2999 if spec.name == "exec" {
3000 multi::abort(server, session, said.message(), out);
3001 } else {
3002 session.dirty_multi();
3003 out.error(said.message().as_bytes());
3004 }
3005 return Flow::Continue;
3006 }
3007
3008 if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
3018 server.mine().cmdstats.at(spec).rejected.bump();
3019 session.dirty_multi();
3020 out.error_line(b"OOM ", OOM);
3021 return Flow::Continue;
3022 }
3023
3024 if server.read_only_replica() && !session.serving_master() && spec.flags.contains(&"write") {
3035 server.mine().cmdstats.at(spec).rejected.bump();
3036 if spec.name == "exec" {
3037 multi::abort(server, session, follow::READONLY, out);
3038 } else {
3039 session.dirty_multi();
3040 out.error(follow::READONLY.as_bytes());
3041 }
3042 return Flow::Continue;
3043 }
3044
3045 if let Some(e) = pubsub::refused(session, spec, out) {
3053 server.mine().cmdstats.at(spec).rejected.bump();
3054 multi::refuse(server, session, Some(spec), &e, out);
3055 return Flow::Continue;
3056 }
3057
3058 if session.monitoring() && touches_keyspace(spec) {
3069 server.mine().cmdstats.at(spec).rejected.bump();
3070 multi::refuse(server, session, Some(spec), &monitor::replica(), out);
3071 return Flow::Continue;
3072 }
3073
3074 if !session.running
3092 && !session.monitoring()
3093 && !session.serving_master()
3094 && let Some(all) = server.paused(server.now_ms())
3095 && (all || may_replicate(spec, session))
3096 {
3097 return Flow::Hold;
3098 }
3099
3100 if !session.running && server.frozen() && may_replicate(spec, session) {
3108 return Flow::Hold;
3109 }
3110
3111 if session.queues(spec.name) {
3116 return multi::queue(session, spec, args, out);
3117 }
3118
3119 server.mine().mark(match spec.group {
3126 "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
3127 | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
3128 1u64 << session.db
3129 }
3130 _ => ALL_DATABASES,
3131 });
3132
3133 let watched = server.monitored() && !monitor::hidden(spec, args);
3142 if watched && monitor::SCRIPTS.contains(&spec.name) {
3143 monitor::feed(server, session, args);
3144 }
3145
3146 let mark = out.len();
3147 let armed = notify::arm(server, session.db);
3158 let copying = server.replicated();
3166 let verbatim = spec.flags.contains(&"write");
3167 misses::report(&server.dbs[session.db], session.db, spec, args);
3172 let reading = lookups::reading(misses::reading(spec, args));
3177 let done = if spec.flags.contains(&"blocking") {
3178 blocking::execute(server, session, spec, args, out)
3179 } else {
3180 match spec.group {
3181 "string" => {
3182 let db = session.db;
3183 strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3184 }
3185 "bitmap" => {
3189 let db = session.db;
3190 bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3191 }
3192 "hyperloglog" => {
3195 let db = session.db;
3196 hll::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3197 }
3198 "set" => {
3199 let db = session.db;
3200 sets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3201 }
3202 "hash" if spec.name == "himport" => {
3207 let db = session.db;
3208 himport::execute(&server.dbs[db], &mut session.sets, args, out)
3209 .map(|()| Flow::Continue)
3210 }
3211 "hash" => {
3216 let db = session.db;
3217 let changed = hashes::execute(&server.dbs[db], db, spec, args, out);
3218 changed.map(|changed| {
3219 indexing::changed(server, db, args.get(1), changed);
3220 Flow::Continue
3221 })
3222 }
3223 "list" => {
3224 let db = session.db;
3225 lists::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3226 }
3227 "zset" => {
3228 let db = session.db;
3229 zsets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3230 }
3231 "geo" => {
3235 let db = session.db;
3236 geo::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3237 }
3238 "array" => {
3239 let db = session.db;
3240 arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3241 }
3242 "graph" => {
3243 let db = session.db;
3244 graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3245 }
3246 "json" => {
3250 let db = session.db;
3251 json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3252 }
3253 "vector" => {
3254 let db = session.db;
3255 vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3256 }
3257 "bloom" => {
3258 let db = session.db;
3259 bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3260 }
3261 "cuckoo" => {
3262 let db = session.db;
3263 cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3264 }
3265 "cms" => {
3266 let db = session.db;
3267 cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3268 }
3269 "topk" => {
3270 let db = session.db;
3271 topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3272 }
3273 "tdigest" => {
3274 let db = session.db;
3275 tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3276 }
3277 "ts" => {
3278 let db = session.db;
3279 ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3280 }
3281 "stream" => {
3286 let db = session.db;
3287 let now = server.now_ms();
3288 streams::execute(&server.dbs[db], db, spec, args, now, out).map(|()| Flow::Continue)
3289 }
3290 "keyspace" if spec.name == "migrate" => {
3294 migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
3295 }
3296 "keyspace" | "server" if spec.group == "keyspace" || spec.name == "restore-asking" => {
3306 let mut touched = indexing::Touched::new(server);
3307 let done =
3308 keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
3309 done.map(|()| {
3310 indexing::touched(server, &touched);
3311 Flow::Continue
3312 })
3313 }
3314 "search" if spec.name == "FT.SEARCH" => {
3321 search::find(server, session.db, args, out).map(|()| Flow::Continue)
3326 }
3327 "search" if spec.name == "FT.AGGREGATE" => {
3328 search::roll(server, session.db, args, out).map(|()| Flow::Continue)
3329 }
3330 "search" if spec.name == "FT.HYBRID" => {
3331 search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
3332 }
3333 "search" if spec.name == "FT.PROFILE" => {
3334 search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
3337 }
3338 "search" if spec.name.starts_with("FT.SUG") => {
3342 let db = session.db;
3343 suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3344 }
3345 "search"
3349 if matches!(
3350 spec.name,
3351 "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
3352 ) =>
3353 {
3354 let db = session.db;
3355 search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
3356 }
3357 "search" if spec.name == "FT.CURSOR" => {
3358 search::cursor::execute(server, args, out).map(|()| Flow::Continue)
3362 }
3363 "search" => {
3364 let db = session.db;
3365 let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
3366 made.map(|made| {
3367 match made {
3368 Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
3369 Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
3370 None => {}
3371 }
3372 Flow::Continue
3373 })
3374 }
3375 "scripting" => {
3376 scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
3377 }
3378 "transactions" => multi::execute(server, session, spec, args, out),
3379 "pubsub" => pubsub::execute(server, session, spec, args, out),
3383 _ => server::execute(server, session, spec, args, out),
3384 }
3385 };
3386 drop(reading);
3387 if watched && !monitor::SCRIPTS.contains(&spec.name) {
3391 monitor::feed(server, session, args);
3392 }
3393 if let Err(e) = &done {
3401 misses::undo(spec, e);
3402 }
3403 notify::drain(server, armed);
3404
3405 repl::swept(server, session.db);
3421 if copying {
3422 if done.is_ok() {
3423 repl::feed(server, session.db, args, verbatim);
3424 } else {
3425 repl::forget();
3426 }
3427 }
3428
3429 let flow = match done {
3430 Ok(flow) => flow,
3431 Err(e) => {
3432 out.truncate(mark);
3433 write_error(out, &e);
3434 Flow::Continue
3435 }
3436 };
3437
3438 if server.watching() && spec.flags.contains(&"write") {
3443 multi::touched(server, session, spec, args);
3444 }
3445
3446 let row = server.mine().cmdstats.at(spec);
3457 row.calls.bump();
3458 if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
3459 row.failed.bump();
3460 }
3461 if session.hanging_up() {
3466 return Flow::Close;
3467 }
3468 flow
3469}
3470
3471fn write_error(out: &mut Out, e: &Error) {
3481 let prefix: &[u8] = match e.code() {
3482 Code::WrongType => b"WRONGTYPE ",
3483 Code::Corrupt => b"INVALIDOBJ ",
3487 _ => b"ERR ",
3488 };
3489 out.error_line(prefix, e.message().as_bytes());
3490}
3491
3492#[cfg(test)]
3493mod tests {
3494 use super::*;
3495 use crate::proto::{Limits, Proto};
3496 use crate::request::Argv;
3497
3498 pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
3503 let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
3504 for p in parts {
3505 wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
3506 wire.extend_from_slice(p);
3507 wire.extend_from_slice(b"\r\n");
3508 }
3509 wire
3510 }
3511
3512 struct Fixture {
3514 server: Server,
3515 session: Session,
3516 argv: Argv,
3517 out: Out,
3518 mark: u64,
3520 }
3521
3522 fn int_of(reply: &str) -> i64 {
3525 reply
3526 .strip_prefix(':')
3527 .and_then(|s| s.strip_suffix("\r\n"))
3528 .unwrap_or_else(|| panic!("not an integer reply: {reply:?}"))
3529 .parse()
3530 .expect("an integer reply holds an integer")
3531 }
3532
3533 impl Fixture {
3534 fn new() -> Fixture {
3535 Fixture::on(Server::new())
3536 }
3537
3538 fn striped(width: usize) -> Fixture {
3540 Fixture::on(Server::with_width(width))
3541 }
3542
3543 fn on(server: Server) -> Fixture {
3544 Fixture {
3545 server,
3546 session: Session::new(7),
3547 argv: Argv::new(),
3548 out: Out::new(Proto::Resp2),
3549 mark: 0,
3550 }
3551 }
3552
3553 fn replicated() -> Fixture {
3560 let f = Fixture::new();
3561 f.server.pretend_replica();
3562 f
3563 }
3564
3565 fn crossed(&mut self, parts: &[&[u8]]) -> String {
3571 self.run(parts);
3572 let (text, upto) = self.server.stream_since(self.mark);
3573 self.mark = upto;
3574 text
3575 }
3576
3577 fn run(&mut self, parts: &[&[u8]]) -> String {
3579 self.flow(parts).1
3580 }
3581
3582 fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
3588 let wire = encode(parts);
3589 self.argv.decode(&wire, &Limits::default()).unwrap();
3590 self.out.clear();
3591 execute(
3592 &self.server,
3593 &mut self.session,
3594 Args::new(&self.argv, &wire),
3595 &mut self.out,
3596 );
3597 self.out.as_slice().to_vec()
3598 }
3599
3600 fn advance(&mut self, ms: u64) {
3602 self.server.advance_clock_ms(ms);
3603 }
3604
3605 fn other(&mut self, parts: &[&[u8]]) -> String {
3610 self.other_in(self.session.db(), parts)
3611 }
3612
3613 fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
3615 let mut session = Session::new(8);
3616 session.db = db;
3617 let reply = self.by(&mut session, parts);
3618 forget_session(&self.server, &mut session);
3619 reply
3620 }
3621
3622 fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
3624 let wire = encode(parts);
3625 let mut argv = Argv::new();
3626 argv.decode(&wire, &Limits::default()).unwrap();
3627 let mut out = Out::new(Proto::Resp2);
3628 execute(&self.server, session, Args::new(&argv, &wire), &mut out);
3629 String::from_utf8_lossy(out.as_slice()).into_owned()
3630 }
3631
3632 fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
3634 let wire = encode(parts);
3635 self.argv.decode(&wire, &Limits::default()).unwrap();
3636 self.out.clear();
3637 let flow = execute(
3638 &self.server,
3639 &mut self.session,
3640 Args::new(&self.argv, &wire),
3641 &mut self.out,
3642 );
3643 (
3644 flow,
3645 String::from_utf8_lossy(self.out.as_slice()).into_owned(),
3646 )
3647 }
3648 }
3649
3650 #[test]
3651 fn multi_holds_commands_and_exec_runs_them() {
3652 let mut f = Fixture::new();
3653 assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
3654 assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
3655 assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
3656 assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
3658 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
3659 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
3660 }
3661
3662 #[test]
3671 fn the_second_exec_of_a_shape_does_not_grow_the_buffer() {
3672 let mut f = Fixture::new();
3673 for _ in 0..2 {
3674 f.run(&[b"MULTI"]);
3675 f.run(&[b"SET", b"k", b"1"]);
3676 f.run(&[b"INCR", b"k"]);
3677 f.run(&[b"EXEC"]);
3678 }
3679 let room = f.session.replay.room();
3680 assert!(room > 0, "the first transaction should have set the room");
3681 f.run(&[b"MULTI"]);
3682 f.run(&[b"SET", b"k", b"1"]);
3683 f.run(&[b"INCR", b"k"]);
3684 f.run(&[b"EXEC"]);
3685 assert_eq!(f.session.replay.room(), room);
3686 }
3687
3688 #[test]
3689 fn an_empty_transaction_answers_an_empty_array() {
3690 let mut f = Fixture::new();
3691 f.run(&[b"MULTI"]);
3692 assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
3693 }
3694
3695 #[test]
3696 fn exec_and_discard_want_a_transaction_to_be_open() {
3697 let mut f = Fixture::new();
3698 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
3699 assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
3700 assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
3703 }
3704
3705 #[test]
3706 fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
3707 let mut f = Fixture::new();
3708 f.run(&[b"MULTI"]);
3709 assert_eq!(
3710 f.run(&[b"MULTI"]),
3711 "-ERR MULTI calls can not be nested\r\n",
3712 "nested MULTI is raised by the command and not by the funnel"
3713 );
3714 assert_eq!(
3715 f.run(&[b"WATCH", b"k"]),
3716 "-ERR WATCH inside MULTI is not allowed\r\n"
3717 );
3718 f.run(&[b"SET", b"k", b"1"]);
3719 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
3720 }
3721
3722 #[test]
3723 fn an_error_the_funnel_raises_kills_the_transaction() {
3724 for bad in [
3725 &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
3726 &[b"GET".as_slice()],
3727 ] {
3728 let mut f = Fixture::new();
3729 f.run(&[b"MULTI"]);
3730 assert!(f.run(bad).starts_with("-ERR "));
3731 assert_eq!(
3732 f.run(&[b"SET", b"k", b"1"]),
3733 "+QUEUED\r\n",
3734 "a dead transaction still answers QUEUED, which is Redis"
3735 );
3736 assert_eq!(
3737 f.run(&[b"EXEC"]),
3738 "-EXECABORT Transaction discarded because of previous errors.\r\n"
3739 );
3740 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3741 }
3742 }
3743
3744 #[test]
3745 fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
3746 let mut f = Fixture::new();
3747 f.run(&[b"MULTI"]);
3748 f.run(&[b"SET", b"k", b"1"]);
3749 assert_eq!(
3750 f.run(&[b"EXEC", b"x"]),
3751 "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
3752 );
3753 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
3754 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3755 }
3756
3757 #[test]
3758 fn a_command_a_transaction_may_not_hold_kills_it() {
3759 let mut f = Fixture::new();
3760 f.run(&[b"MULTI"]);
3761 assert_eq!(
3762 f.run(&[b"SHUTDOWN", b"NOSAVE"]),
3763 "-ERR Command not allowed inside a transaction\r\n"
3764 );
3765 assert_eq!(
3766 f.run(&[b"EXEC"]),
3767 "-EXECABORT Transaction discarded because of previous errors.\r\n"
3768 );
3769 }
3770
3771 #[test]
3772 fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
3773 let mut f = Fixture::new();
3774 f.run(&[b"RPUSH", b"l", b"v"]);
3775 f.run(&[b"MULTI"]);
3776 f.run(&[b"INCR", b"l"]);
3777 f.run(&[b"SET", b"y", b"2"]);
3778 assert_eq!(
3779 f.run(&[b"EXEC"]),
3780 "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
3781 );
3782 assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
3783 }
3784
3785 #[test]
3786 fn discard_and_reset_both_throw_the_queue_away() {
3787 let mut f = Fixture::new();
3788 f.run(&[b"MULTI"]);
3789 f.run(&[b"SET", b"k", b"1"]);
3790 assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
3791 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
3792 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3793
3794 f.run(&[b"MULTI"]);
3795 f.run(&[b"SET", b"k", b"1"]);
3796 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
3797 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
3798 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3799 }
3800
3801 #[test]
3802 fn select_is_queued_and_applied_when_exec_runs_it() {
3803 let mut f = Fixture::new();
3804 f.run(&[b"MULTI"]);
3805 assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
3806 f.run(&[b"SET", b"k", b"1"]);
3807 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
3808 assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
3809 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
3810 }
3811
3812 #[test]
3813 fn a_write_by_another_connection_fails_the_transaction() {
3814 let mut f = Fixture::new();
3815 f.run(&[b"SET", b"k", b"1"]);
3816 assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
3817 f.other(&[b"SET", b"k", b"2"]);
3818 f.run(&[b"MULTI"]);
3819 f.run(&[b"GET", b"k"]);
3820 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3821 }
3822
3823 #[test]
3824 fn a_write_that_puts_the_same_value_back_still_fails_it() {
3825 let mut f = Fixture::new();
3826 f.run(&[b"SET", b"k", b"1"]);
3827 f.run(&[b"WATCH", b"k"]);
3828 f.other(&[b"SET", b"k", b"1"]);
3829 f.run(&[b"MULTI"]);
3830 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3831 }
3832
3833 #[test]
3834 fn a_read_by_another_connection_does_not() {
3835 let mut f = Fixture::new();
3836 f.run(&[b"SET", b"k", b"1"]);
3837 f.run(&[b"WATCH", b"k"]);
3838 f.other(&[b"GET", b"k"]);
3839 f.other(&[b"STRLEN", b"k"]);
3840 f.run(&[b"MULTI"]);
3841 f.run(&[b"GET", b"k"]);
3842 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
3843 }
3844
3845 #[test]
3846 fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
3847 let mut f = Fixture::new();
3848 f.run(&[b"WATCH", b"k"]);
3849 f.other(&[b"DEL", b"k"]);
3850 f.run(&[b"MULTI"]);
3851 f.run(&[b"PING"]);
3852 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
3853 f.run(&[b"WATCH", b"k"]);
3855 f.other(&[b"SET", b"k", b"1"]);
3856 f.run(&[b"MULTI"]);
3857 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3858 }
3859
3860 #[test]
3861 fn a_watched_key_that_expires_fails_the_transaction() {
3862 let mut f = Fixture::new();
3863 f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
3864 f.run(&[b"WATCH", b"k"]);
3865 f.run(&[b"MULTI"]);
3866 f.advance(100);
3867 assert_eq!(
3868 f.run(&[b"EXEC"]),
3869 "*-1\r\n",
3870 "nothing wrote to the key, so only the liveness check can catch this"
3871 );
3872 }
3873
3874 #[test]
3875 fn every_way_a_transaction_ends_lets_go_of_the_watches() {
3876 for end in [
3877 &[b"EXEC".as_slice()] as &[&[u8]],
3878 &[b"DISCARD".as_slice()],
3879 &[b"UNWATCH".as_slice()],
3880 &[b"RESET".as_slice()],
3881 ] {
3882 let mut f = Fixture::new();
3883 f.run(&[b"SET", b"k", b"1"]);
3884 f.run(&[b"WATCH", b"k"]);
3885 if end[0] != b"UNWATCH" && end[0] != b"RESET" {
3886 f.run(&[b"MULTI"]);
3887 }
3888 f.run(end);
3889 assert!(!f.server.watching(), "{end:?} left a row behind");
3890 f.other(&[b"SET", b"k", b"2"]);
3892 f.run(&[b"MULTI"]);
3893 f.run(&[b"GET", b"k"]);
3894 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
3895 }
3896 }
3897
3898 #[test]
3899 fn a_connection_going_away_lets_go_of_its_watches() {
3900 let mut f = Fixture::new();
3901 f.run(&[b"SET", b"k", b"1"]);
3902 f.run(&[b"WATCH", b"k"]);
3903 assert!(f.server.watching());
3904 forget_session(&f.server, &mut f.session);
3905 assert!(!f.server.watching());
3906 }
3907
3908 #[test]
3909 fn watching_the_same_key_twice_is_one_watch() {
3910 let mut f = Fixture::new();
3911 f.run(&[b"SET", b"k", b"1"]);
3912 f.run(&[b"WATCH", b"k", b"k"]);
3913 f.run(&[b"UNWATCH"]);
3914 assert!(
3915 !f.server.watching(),
3916 "the row counts watchers, so a doubled watch would leave one behind"
3917 );
3918 }
3919
3920 #[test]
3921 fn two_connections_can_watch_the_same_key() {
3922 let mut f = Fixture::new();
3923 f.run(&[b"SET", b"k", b"1"]);
3924 f.run(&[b"WATCH", b"k"]);
3925 let mut second = Session::new(9);
3926 second.db = f.session.db();
3927 assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
3928 forget_session(&f.server, &mut second);
3930 assert!(f.server.watching());
3931 f.other(&[b"SET", b"k", b"2"]);
3932 f.run(&[b"MULTI"]);
3933 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3934 }
3935
3936 #[test]
3937 fn flushdb_fails_a_watch_on_a_key_that_was_there() {
3938 let mut f = Fixture::new();
3939 f.run(&[b"SET", b"k", b"1"]);
3940 f.run(&[b"WATCH", b"k"]);
3941 f.other(&[b"FLUSHDB"]);
3942 f.run(&[b"MULTI"]);
3943 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3944 }
3945
3946 #[test]
3947 fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
3948 let mut f = Fixture::new();
3949 f.run(&[b"WATCH", b"k"]);
3950 f.other(&[b"FLUSHDB"]);
3951 f.run(&[b"MULTI"]);
3952 f.run(&[b"PING"]);
3953 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
3954 }
3955
3956 #[test]
3957 fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
3958 let mut f = Fixture::new();
3959 f.run(&[b"SET", b"k", b"1"]);
3960 f.run(&[b"WATCH", b"k"]);
3961 let elsewhere = f.session.db() + 1;
3963 f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
3964 f.run(&[b"MULTI"]);
3965 f.run(&[b"GET", b"k"]);
3966 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
3967 }
3968
3969 #[test]
3970 fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
3971 let mut f = Fixture::new();
3972 f.run(&[b"RPUSH", b"src", b"1"]);
3973 f.run(&[b"WATCH", b"dst"]);
3974 f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
3975 f.run(&[b"MULTI"]);
3976 assert_eq!(
3977 f.run(&[b"EXEC"]),
3978 "*-1\r\n",
3979 "SORT is movablekeys, so every watched key in the database is asked"
3980 );
3981 }
3982
3983 #[test]
3984 fn a_server_nobody_is_watching_says_so() {
3985 let mut f = Fixture::new();
3986 assert!(!f.server.watching());
3987 f.run(&[b"SET", b"k", b"1"]);
3988 assert!(!f.server.watching());
3989 }
3990
3991 #[test]
3997 fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
3998 let mut f = Fixture::new();
3999 assert_eq!(
4000 f.run(&[b"SUBSCRIBE", b"a", b"b"]),
4001 "*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"
4002 );
4003 assert_eq!(
4004 f.run(&[b"PSUBSCRIBE", b"c*"]),
4005 "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
4006 );
4007 assert_eq!(
4008 f.run(&[b"SSUBSCRIBE", b"s"]),
4009 "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
4010 );
4011 assert_eq!(
4014 f.run(&[b"SUBSCRIBE", b"a"]),
4015 "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
4016 );
4017 }
4018
4019 #[test]
4022 fn unsubscribe_answers_for_names_it_was_not_holding_too() {
4023 let mut f = Fixture::new();
4024 f.run(&[b"SUBSCRIBE", b"a"]);
4025
4026 assert_eq!(
4029 f.run(&[b"UNSUBSCRIBE", b"zz"]),
4030 "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
4031 );
4032 f.run(&[b"SUBSCRIBE", b"b"]);
4034 f.run(&[b"PSUBSCRIBE", b"p*"]);
4035 assert_eq!(
4036 f.run(&[b"UNSUBSCRIBE"]),
4037 "*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"
4038 );
4039 assert_eq!(
4042 f.run(&[b"UNSUBSCRIBE"]),
4043 "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
4044 "the pattern is still held, so the count is one"
4045 );
4046 assert_eq!(
4047 f.run(&[b"SUNSUBSCRIBE"]),
4048 "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
4049 "shard channels are counted on their own"
4050 );
4051 }
4052
4053 #[test]
4058 fn the_subscribe_gate_does_not_reach_inside_exec() {
4059 let mut f = Fixture::new();
4060 f.run(&[b"SET", b"k", b"1"]);
4061 f.run(&[b"MULTI"]);
4062 assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
4063 assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
4064 assert_eq!(
4065 f.run(&[b"EXEC"]),
4066 "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
4067 );
4068 assert_eq!(
4071 f.run(&[b"GET", b"k"]),
4072 "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
4073 );
4074 }
4075
4076 #[test]
4079 fn exec_sent_by_a_subscriber_aborts_the_transaction() {
4080 let mut f = Fixture::new();
4081 f.run(&[b"MULTI"]);
4082 f.run(&[b"SET", b"k", b"1"]);
4083 f.run(&[b"SUBSCRIBE", b"z"]);
4084 f.run(&[b"EXEC"]);
4085 f.run(&[b"MULTI"]);
4086 assert_eq!(
4087 f.run(&[b"EXEC"]),
4088 "-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"
4089 );
4090 }
4091
4092 #[test]
4095 fn reset_lets_go_of_every_subscription() {
4096 let mut f = Fixture::new();
4097 f.run(&[b"SUBSCRIBE", b"a"]);
4098 f.run(&[b"PSUBSCRIBE", b"p*"]);
4099 f.run(&[b"SSUBSCRIBE", b"s"]);
4100 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
4101 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
4102 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
4103 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
4104 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4106 }
4107
4108 #[test]
4111 fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
4112 let mut f = Fixture::new();
4113 let mut sub = Session::new(9);
4114 f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
4115 f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
4116 f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
4117
4118 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
4119 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
4120 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
4121 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
4122 assert_eq!(
4123 f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
4124 "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
4125 );
4126 assert_eq!(
4127 f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
4128 "*2\r\n$1\r\na\r\n:1\r\n",
4129 "the shard channel and the channel share a name and not a count"
4130 );
4131 assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
4132
4133 forget_session(&f.server, &mut sub);
4134 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
4135 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
4136 }
4137
4138 #[test]
4141 fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
4142 let mut f = Fixture::new();
4143 assert_eq!(
4144 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4145 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
4146 );
4147 assert_eq!(
4148 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
4149 "+OK\r\n"
4150 );
4151 assert_eq!(
4154 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4155 "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
4156 );
4157 assert_eq!(
4158 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
4159 "+OK\r\n"
4160 );
4161 assert_eq!(
4162 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4163 "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
4164 );
4165 }
4166
4167 #[test]
4168 fn a_letter_the_notification_setting_does_not_know_is_refused() {
4169 let mut f = Fixture::new();
4170 assert_eq!(
4171 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
4172 "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
4173 - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
4174 );
4175 assert_eq!(
4178 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4179 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
4180 );
4181 }
4182
4183 #[test]
4189 fn pubsub_says_no_two_different_ways() {
4190 let mut f = Fixture::new();
4191 assert_eq!(
4192 f.run(&[b"PUBSUB"]),
4193 "-ERR wrong number of arguments for 'pubsub' command\r\n"
4194 );
4195 assert_eq!(
4196 f.run(&[b"PUBSUB", b"NOPE"]),
4197 "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
4198 );
4199 assert_eq!(
4200 f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
4201 "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
4202 );
4203 assert_eq!(
4204 f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
4205 "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
4206 );
4207 assert_eq!(
4208 f.run(&[b"PUBSUB", b"HELP", b"x"]),
4209 "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
4210 );
4211 }
4212
4213 #[test]
4216 fn publishing_to_nobody_answers_zero() {
4217 let mut f = Fixture::new();
4218 assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
4219 assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
4220 assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
4222 }
4223
4224 #[test]
4228 fn a_publish_counts_the_deliveries_and_not_the_clients() {
4229 let mut f = Fixture::new();
4230 let mut sub = Session::new(9);
4231 f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
4232 f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
4233 f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
4234 assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
4235 forget_session(&f.server, &mut sub);
4236 }
4237
4238 #[cfg_attr(miri, ignore = "megabytes through the arena")]
4251 #[test]
4252 fn rewriting_the_same_keys_does_not_grow_the_server() {
4253 let mut f = Fixture::new();
4254 let val = vec![b'v'; 1024];
4255 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
4256
4257 for k in &keys {
4258 f.run(&[b"SET", k, &val]);
4259 }
4260 f.server.compact_step();
4261 let after_first = f.server.memory_bytes();
4262
4263 for _ in 0..500 {
4268 for k in &keys {
4269 f.run(&[b"SET", k, &val]);
4270 }
4271 f.server.compact_step();
4272 }
4273
4274 assert!(
4275 f.server.memory_bytes() <= after_first * 2,
4276 "held {} after five hundred passes against {after_first} after one",
4277 f.server.memory_bytes()
4278 );
4279 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
4280 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
4281 }
4282
4283 #[cfg_attr(miri, ignore = "megabytes through the arena")]
4300 #[test]
4301 fn a_database_nobody_started_on_is_still_collected() {
4302 let mut f = Fixture::new();
4303 assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
4304 let val = vec![b'v'; 1024];
4305 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
4306
4307 for k in &keys {
4308 f.run(&[b"SET", k, &val]);
4309 }
4310 let drain = |f: &Fixture| {
4314 for _ in 0..4 * f.server.slots() {
4315 if f.server.compact_step().is_none() && !f.server.mine().wanted(9) {
4316 return;
4317 }
4318 }
4319 panic!("compaction never got to the end of database nine");
4320 };
4321 drain(&f);
4322 assert!(
4323 !f.server.mine().wanted(9),
4324 "database nine was drained and should not be asked again until it is written to"
4325 );
4326 let after_first = f.server.memory_bytes();
4327
4328 for _ in 0..500 {
4329 for k in &keys {
4330 f.run(&[b"SET", k, &val]);
4331 }
4332 f.server.compact_step();
4333 }
4334
4335 assert!(
4336 f.server.memory_bytes() <= after_first * 2,
4337 "held {} after five hundred passes against {after_first} after one",
4338 f.server.memory_bytes()
4339 );
4340 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
4341 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
4342 f.run(&[b"SELECT", b"0"]);
4344 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4345 }
4346
4347 #[test]
4360 fn the_maintenance_turn_does_not_write_a_shared_cursor() {
4361 let f = Fixture::new();
4362 let before = f.server.next_db.load(Relaxed);
4363 let mine = f.server.mine().compact_db.load(Relaxed);
4364 assert!(f.server.compact_step().is_none());
4368 assert_eq!(
4369 f.server.next_db.load(Relaxed),
4370 before,
4371 "the turn wrote the cursor the over limit path reads"
4372 );
4373 assert_ne!(
4374 f.server.mine().compact_db.load(Relaxed),
4375 mine,
4376 "the turn did not move on, so it will look at the same stripes forever"
4377 );
4378 }
4379
4380 #[test]
4388 fn each_thread_starts_its_compaction_somewhere_else() {
4389 let mut server = Server::new();
4390 server.set_threads(8);
4391 let starts: Vec<usize> = server
4392 .locals
4393 .iter()
4394 .map(|t| t.compact_db.load(Relaxed))
4395 .collect();
4396 assert_eq!(starts, (0..8).collect::<Vec<usize>>());
4397 }
4398
4399 #[test]
4400 fn a_command_goes_from_bytes_to_bytes() {
4401 let mut f = Fixture::new();
4402 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
4403 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
4404 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
4405 assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
4406 assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
4408 assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
4409 }
4410
4411 #[test]
4412 fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
4413 let mut f = Fixture::new();
4414 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
4415 assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
4418 assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
4419 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
4420 assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
4422 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4423 }
4424
4425 #[test]
4426 fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
4427 let mut f = Fixture::new();
4428 f.run(&[b"SET", b"k", b"v"]);
4429 assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
4432 assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
4433 }
4434
4435 #[test]
4436 fn touch_counts_the_way_exists_counts() {
4437 let mut f = Fixture::new();
4438 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4439 assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
4440 assert_eq!(
4441 f.run(&[b"TOUCH", b"a", b"a"]),
4442 ":2\r\n",
4443 "twice counts twice"
4444 );
4445 assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
4446 assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
4447 }
4448
4449 #[test]
4450 fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
4451 let mut f = Fixture::new();
4452 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
4453 f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
4454
4455 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
4456 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
4457 assert_eq!(
4458 f.run(&[b"TTL", b"b"]),
4459 ":100\r\n",
4460 "the source's and not b's"
4461 );
4462 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
4463 }
4464
4465 #[test]
4466 fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
4467 let mut f = Fixture::new();
4468 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
4469 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
4472 }
4473
4474 #[test]
4475 fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
4476 let mut f = Fixture::new();
4477 f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
4478
4479 assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
4480 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
4481 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
4484 assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
4485 assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
4486 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
4487 }
4488
4489 #[test]
4490 fn renaming_a_set_does_not_touch_a_member() {
4491 let mut f = Fixture::new();
4492 for i in 0..300 {
4493 f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
4494 }
4495 let before = f.server.memory_bytes();
4496
4497 assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
4498 assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
4499 assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
4500 assert!(
4501 f.server.memory_bytes().abs_diff(before) < 256,
4502 "the members were copied: {} against {before}",
4503 f.server.memory_bytes()
4504 );
4505 }
4506
4507 #[test]
4508 fn a_copy_is_a_second_value_and_not_a_second_name() {
4509 let mut f = Fixture::new();
4510 f.run(&[b"SADD", b"s", b"m1", b"m2"]);
4511
4512 assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
4513 f.run(&[b"SADD", b"t", b"m3"]);
4514 assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
4515 assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
4516 }
4517
4518 #[test]
4528 fn every_type_can_be_copied() {
4529 let mut f = Fixture::new();
4530 f.run(&[b"SET", b"str", b"v1"]);
4531 f.run(&[b"SADD", b"set", b"m1"]);
4532 f.run(&[b"HSET", b"hash", b"f", b"v"]);
4533 f.run(&[b"RPUSH", b"list", b"a", b"b"]);
4534 f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
4535
4536 for name in [
4537 &b"str"[..],
4538 &b"set"[..],
4539 &b"hash"[..],
4540 &b"list"[..],
4541 &b"zset"[..],
4542 ] {
4543 let dst = [name, b":copy"].concat();
4544 assert_eq!(
4545 f.run(&[b"COPY", name, &dst]),
4546 ":1\r\n",
4547 "copying {}",
4548 String::from_utf8_lossy(name)
4549 );
4550 assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
4551 }
4552
4553 assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
4554 let mut want = String::from("*2\r\n");
4555 want.push_str("$1\r\na\r\n$1\r\nb\r\n");
4556 want
4557 });
4558 assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
4559
4560 f.run(&[b"RPUSH", b"list:copy", b"c"]);
4562 assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
4563 assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
4564 }
4565
4566 #[test]
4567 fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
4568 let mut f = Fixture::new();
4569 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
4570 f.run(&[b"SET", b"b", b"v2"]);
4571
4572 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
4573 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
4574 assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
4575 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
4576 assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
4577 assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
4578 }
4579
4580 #[test]
4581 fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
4582 let mut f = Fixture::new();
4583 f.run(&[b"SET", b"a", b"v1"]);
4584
4585 assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
4588 f.run(&[b"SELECT", b"1"]);
4589 assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
4590 assert_eq!(
4591 f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
4592 ":0\r\n",
4593 "taken"
4594 );
4595 assert_eq!(
4596 f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
4597 ":1\r\n"
4598 );
4599 }
4600
4601 #[test]
4602 fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
4603 let mut f = Fixture::new();
4604 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
4605 assert_eq!(
4606 f.run(&[b"SORT", b"l"]),
4607 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
4608 );
4609 assert_eq!(
4611 f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
4612 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
4613 );
4614 assert_eq!(
4615 f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
4616 "*1\r\n$1\r\n2\r\n"
4617 );
4618 }
4619
4620 #[test]
4621 fn sort_reads_a_key_per_element_for_by_and_for_get() {
4622 let mut f = Fixture::new();
4623 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4624 f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
4625 assert_eq!(
4628 f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
4629 "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
4630 );
4631 }
4632
4633 #[test]
4634 fn sort_store_writes_a_list_and_answers_its_length() {
4635 let mut f = Fixture::new();
4636 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
4637 assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
4638 assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
4639 assert_eq!(
4640 f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
4641 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
4642 );
4643 assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
4646 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
4647 }
4648
4649 #[test]
4650 fn sort_ro_does_not_know_the_word_store() {
4651 let mut f = Fixture::new();
4652 f.run(&[b"RPUSH", b"l", b"2", b"1"]);
4653 assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
4654 assert_eq!(
4655 f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
4656 "-ERR syntax error\r\n"
4657 );
4658 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
4659 }
4660
4661 #[test]
4662 fn sort_refuses_what_it_cannot_sort() {
4663 let mut f = Fixture::new();
4664 assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
4665 f.run(&[b"SET", b"s", b"x"]);
4666 assert_eq!(
4667 f.run(&[b"SORT", b"s"]),
4668 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
4669 );
4670 f.run(&[b"RPUSH", b"words", b"one", b"two"]);
4671 assert_eq!(
4672 f.run(&[b"SORT", b"words"]),
4673 "-ERR One or more scores can't be converted into double\r\n"
4674 );
4675 assert_eq!(
4676 f.run(&[b"SORT", b"words", b"ALPHA"]),
4677 "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
4678 );
4679 assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
4680 }
4681
4682 #[test]
4683 fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
4684 let mut f = Fixture::new();
4685 assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
4686 assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
4687 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
4688 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4689 assert_eq!(
4690 f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
4691 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4692 );
4693 assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
4696 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
4697 }
4698
4699 #[test]
4700 fn move_answers_zero_when_either_end_says_no() {
4701 let mut f = Fixture::new();
4702 assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
4703 assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
4704 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4705 assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
4706 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
4707 assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
4710 assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
4711 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4712 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
4713 }
4714
4715 #[test]
4716 fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
4717 let mut f = Fixture::new();
4718 assert_eq!(
4719 f.run(&[b"MOVE", b"a", b"0"]),
4720 "-ERR source and destination objects are the same\r\n"
4721 );
4722 assert_eq!(
4723 f.run(&[b"MOVE", b"a", b"99"]),
4724 "-ERR DB index is out of range\r\n"
4725 );
4726 assert_eq!(
4727 f.run(&[b"MOVE", b"a", b"-1"]),
4728 "-ERR DB index is out of range\r\n"
4729 );
4730 assert_eq!(
4731 f.run(&[b"MOVE", b"a", b"x"]),
4732 "-ERR value is not an integer or out of range\r\n"
4733 );
4734 }
4735
4736 #[test]
4737 fn swapdb_swaps_what_two_connections_would_see() {
4738 let mut f = Fixture::new();
4739 assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
4740 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4741 assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
4742 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
4743
4744 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
4745 assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
4747 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4748 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
4749 assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
4751 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
4752 }
4753
4754 #[test]
4759 fn a_wide_server_puts_its_databases_on_its_own_clock() {
4760 let mut f = Fixture::striped(8);
4761 f.server.set_clock_ms(1_700_000_000_000);
4762 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
4763 assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
4764 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4765 f.server.set_clock_ms(1_700_000_050_000);
4766 assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
4767 }
4768
4769 #[test]
4774 fn swapdb_swaps_every_stripe_of_a_wide_database() {
4775 let mut f = Fixture::striped(4);
4776 for i in 0..16u32 {
4777 let key = format!("k{i}");
4778 assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
4779 }
4780 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4781 assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
4782 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
4783
4784 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
4785 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4786 assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
4787 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4788 assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
4789 for i in 0..16u32 {
4790 let key = format!("k{i}");
4791 assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
4792 }
4793 }
4794
4795 #[test]
4796 fn swapdb_says_which_index_it_could_not_read() {
4797 let mut f = Fixture::new();
4798 assert_eq!(
4799 f.run(&[b"SWAPDB", b"x", b"1"]),
4800 "-ERR invalid first DB index\r\n"
4801 );
4802 assert_eq!(
4803 f.run(&[b"SWAPDB", b"0", b"y"]),
4804 "-ERR invalid second DB index\r\n"
4805 );
4806 assert_eq!(
4810 f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
4811 "-ERR invalid first DB index\r\n"
4812 );
4813 assert_eq!(
4814 f.run(&[b"SWAPDB", b"0", b"99"]),
4815 "-ERR DB index is out of range\r\n"
4816 );
4817 assert_eq!(
4818 f.run(&[b"SWAPDB", b"-1", b"0"]),
4819 "-ERR DB index is out of range\r\n"
4820 );
4821 }
4822
4823 #[test]
4824 fn wait_answers_zero_replicas_without_waiting() {
4825 let mut f = Fixture::new();
4826 assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
4827 assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
4828 assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
4831 assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
4834 assert_eq!(
4835 f.run(&[b"WAIT", b"x", b"0"]),
4836 "-ERR value is not an integer or out of range\r\n"
4837 );
4838 assert_eq!(
4839 f.run(&[b"WAIT", b"0", b"-1"]),
4840 "-ERR timeout is negative\r\n"
4841 );
4842 assert_eq!(
4843 f.run(&[b"WAIT", b"0", b"1.5"]),
4844 "-ERR timeout is not an integer or out of range\r\n"
4845 );
4846 }
4847
4848 #[test]
4849 fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
4850 let mut f = Fixture::new();
4851 assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
4852 assert_eq!(
4853 f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
4854 "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
4855 );
4856 assert_eq!(
4857 f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
4858 "-ERR value is out of range, value must between 0 and 1\r\n"
4859 );
4860 assert_eq!(
4861 f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
4862 "-ERR value is out of range, must be positive\r\n"
4863 );
4864 assert_eq!(
4867 f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
4868 "-ERR timeout is negative\r\n"
4869 );
4870 }
4871
4872 fn payload(reply: &[u8]) -> Vec<u8> {
4876 let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
4877 reply[head + 2..reply.len() - 2].to_vec()
4878 }
4879
4880 #[test]
4881 fn a_value_survives_a_dump_and_a_restore() {
4882 let mut f = Fixture::new();
4883 f.run(&[b"SET", b"s", b"hello"]);
4884 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
4885 f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
4886 f.run(&[b"SADD", b"u", b"x", b"y"]);
4887 f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
4888 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
4889
4890 for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
4891 let mut copy = key.to_vec();
4892 copy.push(b'2');
4893 let bytes = payload(&f.raw(&[b"DUMP", key]));
4894 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
4895 assert_eq!(f.run(&[b"TYPE", ©]), f.run(&[b"TYPE", key]));
4896 }
4897
4898 assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
4899 assert_eq!(
4900 f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
4901 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4902 );
4903 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
4904 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
4905 assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
4906 assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
4907 assert_eq!(
4910 f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
4911 f.run(&[b"OBJECT", b"ENCODING", b"t"])
4912 );
4913 }
4914
4915 #[test]
4916 fn a_dumped_hash_keeps_its_field_deadlines() {
4917 let mut f = Fixture::new();
4918 f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
4919 assert_eq!(
4920 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
4921 "*1\r\n:1\r\n"
4922 );
4923 let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
4924 assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
4925 assert_eq!(
4926 f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
4927 "*2\r\n:-1\r\n:100\r\n"
4928 );
4929 }
4930
4931 #[test]
4932 fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
4933 let mut f = Fixture::new();
4934 f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
4935 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
4936 assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
4937 assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
4938 assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
4939 assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
4940 assert_eq!(
4943 f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
4944 "+OK\r\n"
4945 );
4946 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
4947 }
4948
4949 #[test]
4950 fn dump_answers_nothing_for_a_key_that_is_not_there() {
4951 let mut f = Fixture::new();
4952 assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
4953 f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
4954 f.advance(50);
4955 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
4956 }
4957
4958 #[test]
4959 fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
4960 let mut f = Fixture::new();
4961 f.run(&[b"SET", b"a", b"first"]);
4962 f.run(&[b"SET", b"b", b"second"]);
4963 let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
4964 assert_eq!(
4965 f.run(&[b"RESTORE", b"a", b"0", &bytes]),
4966 "-BUSYKEY Target key name already exists.\r\n"
4967 );
4968 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
4969 assert_eq!(
4970 f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
4971 "+OK\r\n"
4972 );
4973 assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
4974 }
4975
4976 #[test]
4980 fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
4981 let mut f = Fixture::new();
4982 f.run(&[b"SET", b"a", b"v"]);
4983 assert_eq!(
4984 f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
4985 "-BUSYKEY Target key name already exists.\r\n"
4986 );
4987 assert_eq!(
4990 f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
4991 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
4992 );
4993 }
4994
4995 #[test]
4996 fn restore_can_tell_a_bad_footer_from_bad_bytes() {
4997 let mut f = Fixture::new();
4998 f.run(&[b"SET", b"a", b"hello"]);
4999 let good = payload(&f.raw(&[b"DUMP", b"a"]));
5000
5001 let mut flipped = good.clone();
5002 flipped[2] ^= 0x40;
5003 assert_eq!(
5004 f.run(&[b"RESTORE", b"b", b"0", &flipped]),
5005 "-ERR DUMP payload version or checksum are wrong\r\n"
5006 );
5007 assert_eq!(
5008 f.run(&[b"RESTORE", b"b", b"0", b"short"]),
5009 "-ERR DUMP payload version or checksum are wrong\r\n"
5010 );
5011 let mut truncated = good[..1].to_vec();
5015 truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
5016 let crc = yo_common::crc::crc64(0, &truncated);
5017 truncated.extend_from_slice(&crc.to_le_bytes());
5018 assert_eq!(
5019 f.run(&[b"RESTORE", b"b", b"0", &truncated]),
5020 "-ERR Bad data format\r\n"
5021 );
5022 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
5023 }
5024
5025 #[test]
5026 fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
5027 let mut f = Fixture::new();
5028 f.run(&[b"SET", b"a", b"v"]);
5029 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
5030 assert_eq!(
5031 f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
5032 "-ERR Invalid TTL value, must be >= 0\r\n"
5033 );
5034 assert_eq!(
5035 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
5036 "-ERR Invalid IDLETIME value, must be >= 0\r\n"
5037 );
5038 assert_eq!(
5039 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
5040 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
5041 );
5042 assert_eq!(
5044 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
5045 "+OK\r\n"
5046 );
5047 assert_eq!(
5048 f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
5049 "+OK\r\n"
5050 );
5051 }
5052
5053 #[test]
5057 fn restore_takes_idletime_or_freq_and_not_both() {
5058 let mut f = Fixture::new();
5059 f.run(&[b"SET", b"a", b"v"]);
5060 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
5061 assert_eq!(
5062 f.run(&[
5063 b"RESTORE",
5064 b"b",
5065 b"0",
5066 &bytes,
5067 b"IDLETIME",
5068 b"1",
5069 b"FREQ",
5070 b"2"
5071 ]),
5072 "-ERR syntax error\r\n"
5073 );
5074 assert_eq!(
5075 f.run(&[
5076 b"RESTORE",
5077 b"b",
5078 b"0",
5079 &bytes,
5080 b"FREQ",
5081 b"2",
5082 b"IDLETIME",
5083 b"1"
5084 ]),
5085 "-ERR syntax error\r\n"
5086 );
5087 assert_eq!(
5088 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
5089 "-ERR syntax error\r\n"
5090 );
5091 assert_eq!(
5092 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
5093 "-ERR syntax error\r\n"
5094 );
5095 }
5096
5097 #[test]
5098 fn copy_checks_its_options_before_it_looks_for_anything() {
5099 let mut f = Fixture::new();
5100 assert_eq!(
5103 f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
5104 "-ERR DB index is out of range\r\n"
5105 );
5106 assert_eq!(
5107 f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
5108 "-ERR DB index is out of range\r\n"
5109 );
5110 assert_eq!(
5111 f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
5112 "-ERR value is not an integer or out of range\r\n"
5113 );
5114 assert_eq!(
5115 f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
5116 "-ERR syntax error\r\n"
5117 );
5118 assert_eq!(
5119 f.run(&[b"COPY", b"a", b"a"]),
5120 "-ERR source and destination objects are the same\r\n"
5121 );
5122 assert_eq!(
5124 f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
5125 ":0\r\n"
5126 );
5127 }
5128
5129 #[test]
5130 fn time_is_two_bulk_strings_and_moves() {
5131 let mut f = Fixture::new();
5132 let first = f.run(&[b"TIME"]);
5133 assert!(first.starts_with("*2\r\n$"), "got {first}");
5134 let parts: Vec<&str> = first.split("\r\n").collect();
5135 let secs: i64 = parts[2].parse().expect("seconds as decimal text");
5136 let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
5137 assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
5138 assert!((0..1_000_000).contains(µs), "got {micros}");
5139 assert_ne!(first, f.run(&[b"TIME"]));
5143 }
5144
5145 #[test]
5146 fn a_keyspace_scan_walks_every_key_once() {
5147 let n = if cfg!(miri) { 96 } else { 500 };
5151 let mut f = Fixture::new();
5152 for i in 0..n {
5153 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
5154 }
5155
5156 let mut seen: Vec<String> = Vec::new();
5157 let mut cursor = "0".to_owned();
5158 let mut calls = 0;
5159 loop {
5160 let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
5161 seen.extend(keys);
5162 cursor = next;
5163 calls += 1;
5164 assert!(calls < 10_000, "the cursor is not advancing");
5165 if cursor == "0" {
5166 break;
5167 }
5168 }
5169
5170 seen.sort();
5171 seen.dedup();
5172 assert_eq!(seen.len(), n, "every key once and only once");
5173 assert!(calls > 1, "{n} keys came back in one batch");
5176 }
5177
5178 #[test]
5179 fn a_scan_narrows_by_pattern_and_by_type() {
5180 let mut f = Fixture::new();
5181 f.run(&[b"SET", b"str", b"v"]);
5182 f.run(&[b"SADD", b"members", b"a"]);
5183 f.run(&[b"HSET", b"fields", b"f", b"v"]);
5184
5185 let all = |f: &mut Fixture, args: &[&[u8]]| {
5186 let mut out: Vec<String> = Vec::new();
5187 let mut cursor = "0".to_owned();
5188 loop {
5189 let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
5190 line.extend_from_slice(args);
5191 let (next, keys) = scan_reply(&f.run(&line));
5192 out.extend(keys);
5193 cursor = next;
5194 if cursor == "0" {
5195 break;
5196 }
5197 }
5198 out.sort();
5199 out
5200 };
5201
5202 assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
5203 assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
5204 assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
5205 assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
5207 assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
5209 assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
5210 assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
5212 }
5213
5214 #[test]
5215 fn a_scan_says_what_is_wrong_with_it() {
5216 let mut f = Fixture::new();
5217 assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
5218 assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
5219 assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
5220 assert_eq!(
5221 f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
5222 "-ERR syntax error\r\n"
5223 );
5224 assert_eq!(
5225 f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
5226 "-ERR value is not an integer or out of range\r\n"
5227 );
5228 assert_eq!(
5229 f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
5230 "-ERR syntax error\r\n"
5231 );
5232 assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
5237 }
5238
5239 #[test]
5240 fn keys_and_randomkey_look_at_the_whole_database() {
5241 let mut f = Fixture::new();
5242 assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
5243 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
5244
5245 for name in ["one", "two", "three"] {
5246 f.run(&[b"SET", name.as_bytes(), b"v"]);
5247 }
5248 assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
5249 assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
5250 assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
5251
5252 for _ in 0..50 {
5253 let got = f.run(&[b"RANDOMKEY"]);
5254 assert!(
5255 ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
5256 "got {got}"
5257 );
5258 }
5259 }
5260
5261 #[test]
5262 fn a_walk_does_not_answer_keys_that_have_expired() {
5263 let mut f = Fixture::new();
5264 f.run(&[b"SET", b"alive", b"v"]);
5265 f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
5266 f.server.advance_clock_ms(2);
5267 assert_eq!(
5268 f.run(&[b"DBSIZE"]),
5269 ":2\r\n",
5270 "nothing has collected it yet"
5271 );
5272
5273 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
5274 let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
5275 assert_eq!(keys, ["alive"]);
5276 for _ in 0..20 {
5277 assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
5278 }
5279 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
5282 }
5283
5284 #[test]
5285 fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
5286 let mut f = Fixture::new();
5287 f.run(&[b"SET", b"k", b"v"]);
5288 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
5289 assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
5290
5291 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
5292 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
5293 let ms = int(&f.run(&[b"PTTL", b"k"]));
5294 assert!((99_000..=100_000).contains(&ms), "got {ms}");
5295
5296 let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
5298 let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
5299 assert_eq!(at, (at_ms + 500) / 1000);
5300 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
5301
5302 assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
5303 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
5304 assert_eq!(
5305 f.run(&[b"PERSIST", b"k"]),
5306 ":0\r\n",
5307 "nothing to take off the second time"
5308 );
5309 assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
5310 assert_eq!(
5311 f.run(&[b"GET", b"k"]),
5312 "$1\r\nv\r\n",
5313 "and the value went through all of that untouched"
5314 );
5315 }
5316
5317 #[test]
5318 fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
5319 let mut f = Fixture::new();
5320 f.run(&[b"SET", b"str", b"v"]);
5321 f.run(&[b"SADD", b"set", b"a", b"b"]);
5322 f.run(&[b"HSET", b"hash", b"f", b"v"]);
5323
5324 for key in [b"str".as_slice(), b"set", b"hash"] {
5325 assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
5326 assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
5327 }
5328 assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
5331 assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
5332 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
5333 }
5334
5335 #[test]
5336 fn a_deadline_that_has_already_gone_deletes_the_key_now() {
5337 let mut f = Fixture::new();
5338 for key in [b"a".as_slice(), b"b", b"c", b"d"] {
5339 f.run(&[b"SET", key, b"v"]);
5340 }
5341 assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
5345 assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
5346 assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
5347 assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
5348 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5349 assert_eq!(
5350 f.run(&[b"EXPIRE", b"a", b"100"]),
5351 ":0\r\n",
5352 "and the key really went, so there is nothing to put a deadline on"
5353 );
5354 }
5355
5356 #[test]
5357 fn the_four_conditions_decide_whether_the_deadline_moves() {
5358 let mut f = Fixture::new();
5359 f.run(&[b"SET", b"k", b"v"]);
5360
5361 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
5362 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
5363 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
5364 assert_eq!(
5365 f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
5366 ":1\r\n",
5367 "no deadline reads as infinitely far away, so LT passes where GT fails"
5368 );
5369
5370 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
5371 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
5372 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
5373 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
5374 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
5375 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
5376
5377 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
5380 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
5381 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
5382 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
5383 }
5384
5385 #[test]
5386 fn the_conditions_are_a_set_and_not_a_keyword() {
5387 let mut f = Fixture::new();
5388 f.run(&[b"SET", b"k", b"v"]);
5389
5390 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
5391 assert_eq!(
5392 f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
5393 ":0\r\n",
5394 "the same keyword twice means it once, and NX now has a deadline to fail on"
5395 );
5396
5397 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
5400 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
5401 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
5402 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
5403 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
5404 f.run(&[b"PERSIST", b"k"]);
5405 assert_eq!(
5406 f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
5407 ":0\r\n",
5408 "where LT on its own would have taken it"
5409 );
5410 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
5411 }
5412
5413 #[test]
5414 fn a_key_is_gone_once_its_moment_passes() {
5415 let mut f = Fixture::new();
5416 f.run(&[b"SET", b"k", b"v"]);
5417 f.run(&[b"EXPIRE", b"k", b"100"]);
5418
5419 let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
5420 f.server.set_clock_ms(at as u64 + 1);
5421 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
5422 assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
5423 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
5424 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5425 }
5426
5427 #[test]
5428 fn the_expiry_commands_refuse_what_a_real_server_refuses() {
5429 let mut f = Fixture::new();
5430 f.run(&[b"SET", b"k", b"v"]);
5431 for (bad, want) in [
5432 (
5433 &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
5434 "-ERR value is not an integer or out of range\r\n",
5435 ),
5436 (
5437 &[b"EXPIRE", b"k", b"100", b"MAYBE"],
5438 "-ERR Unsupported option MAYBE\r\n",
5439 ),
5440 (
5441 &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
5442 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
5443 ),
5444 (
5445 &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
5446 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
5447 ),
5448 (
5449 &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
5450 "-ERR GT and LT options at the same time are not compatible\r\n",
5451 ),
5452 (
5455 &[b"EXPIRE", b"k", b"9223372036854775807"],
5456 "-ERR invalid expire time in 'expire' command\r\n",
5457 ),
5458 (
5459 &[b"EXPIREAT", b"k", b"9223372036854775807"],
5460 "-ERR invalid expire time in 'expireat' command\r\n",
5461 ),
5462 (
5463 &[b"PEXPIRE", b"k", b"9223372036854775807"],
5464 "-ERR invalid expire time in 'pexpire' command\r\n",
5465 ),
5466 ] {
5467 assert_eq!(f.run(bad), want, "for {bad:?}");
5468 }
5469 assert_eq!(
5470 f.run(&[b"TTL", b"k"]),
5471 ":-1\r\n",
5472 "and none of those put a deadline on anything"
5473 );
5474
5475 assert_eq!(
5479 f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
5480 ":1\r\n"
5481 );
5482 assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
5483 }
5484
5485 #[test]
5486 fn flushing_empties_this_database_or_every_one_of_them() {
5487 let mut f = Fixture::new();
5488 f.run(&[b"SELECT", b"0"]);
5489 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
5490 f.run(&[b"SELECT", b"1"]);
5491 f.run(&[b"SET", b"c", b"3"]);
5492 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
5493 assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
5496 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5497 f.run(&[b"SELECT", b"0"]);
5499 assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
5500 assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
5501 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5502 f.run(&[b"SELECT", b"1"]);
5503 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5504 assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
5507 assert_eq!(
5508 f.run(&[b"FLUSHDB", b"sync", b"sync"]),
5509 "-ERR syntax error\r\n"
5510 );
5511 }
5512
5513 #[test]
5514 fn the_script_cache_and_the_library_set_answer_for_being_empty() {
5515 let mut f = Fixture::new();
5516 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
5517 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
5518 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
5519 assert_eq!(
5522 f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
5523 "*2\r\n:0\r\n:0\r\n"
5524 );
5525 assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
5526 assert_eq!(
5527 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
5528 "*0\r\n"
5529 );
5530 assert_eq!(
5531 f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
5532 "-ERR Library not found\r\n"
5533 );
5534
5535 assert_eq!(
5538 f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
5539 "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
5540 );
5541 assert_eq!(
5542 f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
5543 "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
5544 );
5545 assert_eq!(
5550 f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
5551 "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
5552 );
5553 assert_eq!(
5554 f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
5555 "-ERR Unknown argument bogus\r\n"
5556 );
5557 assert_eq!(
5558 f.run(&[b"SCRIPT", b"EXISTS"]),
5559 "-ERR wrong number of arguments for 'script|exists' command\r\n"
5560 );
5561
5562 assert_eq!(
5563 f.run(&[b"FUNCTION", b"NOPE"]),
5564 "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
5565 );
5566 }
5567
5568 #[test]
5569 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5570 fn the_script_cache_holds_what_was_loaded_into_it() {
5571 let mut f = Fixture::new();
5572 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
5575 assert_eq!(
5576 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
5577 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
5578 );
5579 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
5580 assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
5581 assert_eq!(
5584 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
5585 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
5586 );
5587 assert!(
5588 f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
5589 .starts_with("-ERR Error compiling script"),
5590 );
5591
5592 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
5593 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
5594 assert_eq!(
5595 f.run(&[b"EVALSHA", sha, b"0"]),
5596 "-NOSCRIPT No matching script. Please use EVAL.\r\n"
5597 );
5598
5599 assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
5602 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
5603
5604 assert_eq!(
5607 f.run(&[b"SCRIPT", b"KILL"]),
5608 "-NOTBUSY No scripts in execution right now.\r\n"
5609 );
5610 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
5611 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
5612 assert_eq!(
5613 f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
5614 "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
5615 );
5616 }
5617
5618 #[test]
5619 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5620 fn eval_counts_its_keys_before_it_compiles_anything() {
5621 let mut f = Fixture::new();
5622 assert_eq!(
5623 f.run(&[b"EVAL", b"return 1"]),
5624 "-ERR wrong number of arguments for 'eval' command\r\n"
5625 );
5626 assert_eq!(
5627 f.run(&[b"EVAL", b"return 1", b"abc"]),
5628 "-ERR value is not an integer or out of range\r\n"
5629 );
5630 assert_eq!(
5631 f.run(&[b"EVAL", b"return 1", b"-1"]),
5632 "-ERR Number of keys can't be negative\r\n"
5633 );
5634 assert_eq!(
5635 f.run(&[b"EVAL", b"return 1", b"1"]),
5636 "-ERR Number of keys can't be greater than number of args\r\n"
5637 );
5638 assert_eq!(
5640 f.run(&[
5641 b"EVAL",
5642 b"return {KEYS[1],KEYS[2],ARGV[1]}",
5643 b"2",
5644 b"a",
5645 b"b",
5646 b"c"
5647 ]),
5648 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
5649 );
5650 assert_eq!(
5651 f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
5652 ":0\r\n"
5653 );
5654 assert_eq!(
5655 f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
5656 ":2\r\n"
5657 );
5658 }
5659
5660 #[test]
5661 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5662 fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
5663 let mut f = Fixture::new();
5664 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
5665
5666 assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
5669 assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
5670 assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
5671 assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
5672 assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
5673 assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
5674 assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
5675
5676 assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
5677 assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
5678 assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
5680 assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
5681 assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
5682 assert_eq!(eval(&mut f, b""), "$-1\r\n");
5683
5684 assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
5687 assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
5688 assert_eq!(
5689 eval(&mut f, b"return {1,'a',{2}}"),
5690 "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
5691 );
5692
5693 assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
5695 assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
5696 assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
5697 assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
5698 assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a b\r\n");
5701 assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
5704 assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
5705 }
5706
5707 #[test]
5708 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5709 fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
5710 let mut f = Fixture::new();
5711 assert_eq!(
5714 f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
5715 "$3\r\n3.5\r\n"
5716 );
5717 assert_eq!(
5718 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
5719 "$3\r\n123\r\n"
5720 );
5721 assert_eq!(
5722 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
5723 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5724 );
5725 assert_eq!(
5726 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
5727 "*1\r\n$1\r\na\r\n"
5728 );
5729 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
5730
5731 f.out = Out::new(Proto::Resp3);
5732 assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
5733 assert_eq!(
5734 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
5735 "(123\r\n"
5736 );
5737 assert_eq!(
5738 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
5739 "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
5740 );
5741 assert_eq!(
5742 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
5743 "~1\r\n$1\r\na\r\n"
5744 );
5745 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
5746 }
5747
5748 #[test]
5749 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5750 fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
5751 let mut f = Fixture::new();
5752 f.run(&[b"SET", b"s", b"hello"]);
5753 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
5754 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
5755
5756 assert_eq!(
5757 eval(&mut f, b"return type(redis.call('get','s'))"),
5758 "$6\r\nstring\r\n"
5759 );
5760 assert_eq!(
5761 eval(&mut f, b"return type(redis.call('llen','l'))"),
5762 "$6\r\nnumber\r\n"
5763 );
5764 assert_eq!(
5765 eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
5766 "$5\r\ntable\r\n"
5767 );
5768 assert_eq!(
5771 eval(&mut f, b"return redis.call('set','s','v')['ok']"),
5772 "$2\r\nOK\r\n"
5773 );
5774 assert_eq!(
5777 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
5778 "$5\r\nfalse\r\n"
5779 );
5780 assert_eq!(
5781 eval(
5782 &mut f,
5783 b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
5784 ),
5785 "$3\r\nnil\r\n"
5786 );
5787 assert_eq!(
5789 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
5790 "$5\r\nfalse\r\n"
5791 );
5792 }
5793
5794 #[test]
5795 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5796 fn an_error_from_a_script_names_the_line_it_came_from() {
5797 let mut f = Fixture::new();
5798 assert_eq!(
5801 f.run(&[b"EVAL", b"error('boom')", b"0"]),
5802 "-ERR user_script:1: boom script: \
5803 82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
5804 );
5805 assert_eq!(
5807 f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
5808 "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
5809 );
5810 assert!(
5812 f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
5813 .starts_with("-structured script: "),
5814 );
5815 assert_eq!(
5818 f.run(&[b"EVAL", b"return this is not lua", b"0"]),
5819 "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
5820 );
5821
5822 assert_eq!(
5825 f.run(&[
5826 b"EVAL",
5827 b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
5828 b"0"
5829 ]),
5830 "$8\r\nstring:z\r\n"
5831 );
5832 assert_eq!(
5833 f.run(&[
5834 b"EVAL",
5835 b"local a, b = pcall(function() error({a=1}) end) return type(b)",
5836 b"0"
5837 ]),
5838 "$5\r\ntable\r\n"
5839 );
5840 }
5841
5842 #[test]
5843 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5844 fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
5845 let mut f = Fixture::new();
5846 let sentence = |f: &mut Fixture, body: &[u8]| {
5847 let reply = f.run(&[b"EVAL", body, b"0"]);
5848 reply.split(" script: ").next().unwrap().to_owned()
5849 };
5850
5851 assert_eq!(
5852 sentence(&mut f, b"return redis.call()"),
5853 "-ERR Please specify at least one argument for this redis lib call"
5854 );
5855 assert_eq!(
5856 sentence(&mut f, b"return redis.call('get', {})"),
5857 "-ERR Lua redis lib command arguments must be strings or integers"
5858 );
5859 assert_eq!(
5860 sentence(&mut f, b"return redis.call('nosuchcmd')"),
5861 "-ERR Unknown Redis command called from script"
5862 );
5863 assert_eq!(
5864 sentence(&mut f, b"return redis.call('get')"),
5865 "-ERR Wrong number of args calling Redis command from script"
5866 );
5867 for name in [
5871 &b"return redis.call('multi')"[..],
5872 b"return redis.call('exec')",
5873 b"return redis.call('watch','k')",
5874 b"return redis.call('subscribe','c')",
5875 b"return redis.call('debug','jmap')",
5876 b"return redis.call('eval','return 1',0)",
5877 b"return redis.call('config','get','maxmemory')",
5878 ] {
5879 assert_eq!(
5880 sentence(&mut f, name),
5881 "-ERR This Redis command is not allowed from script",
5882 "for {}",
5883 String::from_utf8_lossy(name)
5884 );
5885 }
5886 assert!(
5889 f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
5890 .starts_with('*'),
5891 );
5892
5893 assert_eq!(
5896 f.run(&[
5897 b"EVAL",
5898 b"local x = redis.pcall('nosuchcmd') return x.err",
5899 b"0"
5900 ]),
5901 "$44\r\nERR Unknown Redis command called from script\r\n"
5902 );
5903 assert_eq!(
5905 f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
5906 "-ERR Unknown Redis command called from script\r\n"
5907 );
5908 }
5909
5910 #[test]
5911 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5912 fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
5913 let mut f = Fixture::new();
5914 f.run(&[b"SET", b"k", b"v"]);
5915 assert_eq!(
5916 f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
5917 "$1\r\nv\r\n"
5918 );
5919 assert!(
5920 f.run(&[
5921 b"EVAL_RO",
5922 b"return redis.call('set', KEYS[1], 'x')",
5923 b"1",
5924 b"k"
5925 ])
5926 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
5927 );
5928 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
5930 assert_eq!(
5931 f.run(&[
5932 b"EVAL",
5933 b"return redis.call('set', KEYS[1], 'x')",
5934 b"1",
5935 b"k"
5936 ]),
5937 "+OK\r\n"
5938 );
5939 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
5940
5941 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
5943 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
5944 assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
5945 }
5946
5947 #[test]
5948 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5949 fn a_script_cannot_leave_anything_behind_for_the_next_one() {
5950 let mut f = Fixture::new();
5951 for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
5954 let reply = f.run(&[b"EVAL", body, b"0"]);
5955 assert!(
5956 reply
5957 .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
5958 "{body:?} gave {reply}",
5959 );
5960 }
5961 for body in [
5964 &b"rawset(redis, 'call', 1)"[..],
5965 b"rawset(_G, 'zz', 1)",
5966 b"setmetatable(_G, {})",
5967 b"setmetatable(redis, {})",
5968 ] {
5969 let reply = f.run(&[b"EVAL", body, b"0"]);
5970 assert!(
5971 reply.starts_with("-ERR Attempt to modify a readonly table script: "),
5972 "{body:?} gave {reply}",
5973 );
5974 }
5975 assert!(
5978 f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
5979 .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
5980 );
5981 assert_eq!(
5984 f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
5985 "$3\r\nnil\r\n"
5986 );
5987
5988 assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
5991 assert_eq!(
5992 f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
5993 "$8\r\nfunction\r\n"
5994 );
5995 assert_eq!(
5996 f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
5997 "$8\r\nfunction\r\n"
5998 );
5999 }
6000
6001 #[test]
6002 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6003 fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
6004 let mut f = Fixture::new();
6005 assert_eq!(
6009 f.run(&[
6010 b"EVAL",
6011 b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
6012 b"0",
6013 ]),
6014 ":23\r\n"
6015 );
6016 assert_eq!(
6017 f.run(&[
6018 b"EVAL",
6019 b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
6020 table.sort(t) return table.concat(t, ' ')",
6021 b"0",
6022 ]),
6023 "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
6024 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
6025 acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
6026 set_repl setresp sha1hex status_reply\r\n"
6027 );
6028 assert_eq!(
6031 f.run(&[
6032 b"EVAL",
6033 b"local n = 0 for k, v in pairs(redis) do \
6034 if type(v) == 'function' then n = n + 1 end end return n",
6035 b"0",
6036 ]),
6037 ":12\r\n"
6038 );
6039 assert_eq!(
6041 f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
6042 "$6\r\nstring\r\n"
6043 );
6044 assert_eq!(
6045 f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
6046 "$8\r\nfunction\r\n"
6047 );
6048 assert_eq!(
6049 f.run(&[
6050 b"EVAL",
6051 b"return tostring(rawget(redis, 'nosuchfield'))",
6052 b"0",
6053 ]),
6054 "$3\r\nnil\r\n"
6055 );
6056 for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
6059 assert!(
6060 f.run(&[b"EVAL", body, b"0"])
6061 .contains("Attempt to modify a readonly table script: "),
6062 "{body:?}",
6063 );
6064 }
6065 assert_eq!(
6068 f.run(&[
6069 b"EVAL",
6070 b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
6071 b"0",
6072 ]),
6073 ":2\r\n"
6074 );
6075 assert_eq!(
6076 f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
6077 "$3\r\nnil\r\n"
6078 );
6079 assert_eq!(
6080 f.run(&[
6081 b"EVAL",
6082 b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
6083 return type(f)",
6084 b"0",
6085 ]),
6086 "$8\r\nfunction\r\n"
6087 );
6088 }
6089
6090 #[test]
6091 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6092 fn a_script_gets_the_bit_library_a_real_server_carries() {
6093 let mut f = Fixture::new();
6094 for (body, want) in [
6097 ("bit.tobit(1)", ":1\r\n"),
6098 ("bit.tobit(2^32 + 1)", ":1\r\n"),
6099 ("bit.tobit(2^31)", ":-2147483648\r\n"),
6100 ("bit.tobit(0xffffffff)", ":-1\r\n"),
6101 ("bit.tobit(1.5)", ":2\r\n"),
6103 ("bit.tobit(2.5)", ":2\r\n"),
6104 ("bit.bnot(0)", ":-1\r\n"),
6105 ("bit.band(0xff, 0x0f)", ":15\r\n"),
6106 ("bit.band(1, 2, 3)", ":0\r\n"),
6107 ("bit.bor(1, 2, 4)", ":7\r\n"),
6108 ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
6109 ("bit.lshift(1, 31)", ":-2147483648\r\n"),
6111 ("bit.lshift(1, 32)", ":1\r\n"),
6112 ("bit.lshift(1, 33)", ":2\r\n"),
6113 ("bit.rshift(-1, 1)", ":2147483647\r\n"),
6114 ("bit.arshift(-1, 1)", ":-1\r\n"),
6115 ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
6116 ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
6117 ("bit.bswap(0x12345678)", ":2018915346\r\n"),
6118 ("bit.tobit('0x10')", ":16\r\n"),
6121 ] {
6122 let script = format!("return {body}");
6123 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6124 }
6125 for (body, want) in [
6128 ("bit.tohex(1)", "00000001"),
6129 ("bit.tohex(-1)", "ffffffff"),
6130 ("bit.tohex(255, 2)", "ff"),
6131 ("bit.tohex(255, -8)", "000000FF"),
6132 ("bit.tohex(0x87654321, 4)", "4321"),
6133 ("bit.tohex(1, 0)", ""),
6134 ("bit.tohex(1, 9)", "00000001"),
6135 ] {
6136 let script = format!("return {body}");
6137 assert_eq!(
6138 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6139 format!("${}\r\n{want}\r\n", want.len()),
6140 "{body}",
6141 );
6142 }
6143 for (body, want) in [
6146 (
6147 "return bit.band()",
6148 "bad argument #1 to 'band' (number expected, got no value)",
6149 ),
6150 (
6151 "return bit.band('x')",
6152 "bad argument #1 to 'band' (number expected, got string)",
6153 ),
6154 (
6155 "return bit.tobit(true)",
6156 "bad argument #1 to 'tobit' (number expected, got boolean)",
6157 ),
6158 (
6159 "return bit.lshift(1)",
6160 "bad argument #2 to 'lshift' (number expected, got no value)",
6161 ),
6162 ] {
6163 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6164 assert!(
6165 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6166 "{body} gave {reply}",
6167 );
6168 }
6169 assert_eq!(
6172 f.run(&[
6173 b"EVAL",
6174 b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
6175 b"0",
6176 ]),
6177 "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
6178 );
6179 assert_eq!(
6181 f.run(&[
6182 b"EVAL",
6183 b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
6184 table.sort(t) return table.concat(t, ' ')",
6185 b"0",
6186 ]),
6187 "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
6188 );
6189 for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
6190 assert!(
6191 f.run(&[b"EVAL", body, b"0"])
6192 .contains("Attempt to modify a readonly table script: "),
6193 "{body:?}",
6194 );
6195 }
6196 }
6197
6198 #[test]
6199 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6200 fn a_script_gets_the_cjson_library_a_real_server_carries() {
6201 let mut f = Fixture::new();
6202 for (body, want) in [
6206 ("cjson.encode(nil)", "null"),
6207 ("cjson.encode(true)", "true"),
6208 ("cjson.encode(cjson.null)", "null"),
6209 ("cjson.encode(100)", "100"),
6210 ("cjson.encode(1/3)", "0.33333333333333"),
6211 ("cjson.encode(1e300)", "1e+300"),
6212 ("cjson.encode(2^53)", "9.007199254741e+15"),
6213 ("cjson.encode({})", "{}"),
6214 ("cjson.encode({1,2,3})", "[1,2,3]"),
6215 ("cjson.encode({a=1})", "{\"a\":1}"),
6216 ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
6217 ("cjson.encode({[0]=1})", "{\"0\":1}"),
6218 ("cjson.encode('a\\nb')", "\"a\\nb\""),
6219 ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
6221 ("cjson.encode('\\11')", "\"\\u000b\""),
6222 (
6225 "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
6226 "[1,[2,{\"a\":null}]]",
6227 ),
6228 ("cjson.encode(cjson.decode('[]'))", "{}"),
6231 ] {
6232 let script = format!("return {body}");
6233 assert_eq!(
6234 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6235 format!("${}\r\n{want}\r\n", want.len()),
6236 "{body}",
6237 );
6238 }
6239 for (body, want) in [
6242 ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
6243 ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
6244 ("cjson.decode('0x10')", ":16\r\n"),
6245 ("cjson.decode('+1')", ":1\r\n"),
6246 ("cjson.decode('01')", ":1\r\n"),
6247 ("cjson.decode(1) + 1", ":2\r\n"),
6248 ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
6250 ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
6251 ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
6252 ] {
6253 let script = format!("return {body}");
6254 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6255 }
6256 for (body, want) in [
6258 (
6259 "cjson.encode_number_precision(3) return cjson.encode(1/3)",
6260 "0.333",
6261 ),
6262 (
6263 "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
6264 "null",
6265 ),
6266 (
6267 "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
6268 "inf",
6269 ),
6270 (
6271 "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
6272 "{\"1\":1,\"100\":1}",
6273 ),
6274 (
6275 "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
6276 "[]",
6277 ),
6278 ("return tostring(cjson.encode_max_depth())", "1000"),
6279 ("return tostring(cjson.encode_keep_buffer(false))", "false"),
6280 ("return tostring(cjson.encode_sparse_array())", "false"),
6281 ("return tostring(cjson.encode_number_precision())", "14"),
6284 ] {
6285 assert_eq!(
6286 f.run(&[b"EVAL", body.as_bytes(), b"0"]),
6287 format!("${}\r\n{want}\r\n", want.len()),
6288 "{body}",
6289 );
6290 }
6291 for (body, want) in [
6293 (
6294 "return cjson.encode(1/0)",
6295 "Cannot serialise number: must not be NaN or Inf",
6296 ),
6297 (
6298 "return cjson.encode({[1]=1,[100]=1})",
6299 "Cannot serialise table: excessively sparse array",
6300 ),
6301 (
6302 "return cjson.encode({[true]=1})",
6303 "Cannot serialise boolean: table key must be a number or string",
6304 ),
6305 (
6306 "return cjson.encode(tostring)",
6307 "Cannot serialise function: type not supported",
6308 ),
6309 (
6310 "return cjson.encode()",
6311 "bad argument #1 to 'encode' (expected 1 argument)",
6312 ),
6313 (
6314 "return cjson.decode('[1,2')",
6315 "Expected comma or array end but found T_END at character 5",
6316 ),
6317 (
6318 "return cjson.decode('{\"a\" 1}')",
6319 "Expected colon but found T_NUMBER at character 6",
6320 ),
6321 (
6322 "return cjson.decode('tru')",
6323 "Expected value but found invalid token at character 1",
6324 ),
6325 (
6326 "return cjson.decode('[1] 2')",
6327 "Expected the end but found T_NUMBER at character 5",
6328 ),
6329 (
6330 "return cjson.encode_max_depth(0)",
6331 "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
6332 ),
6333 (
6334 "return cjson.encode_invalid_numbers('yes')",
6335 "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
6336 ),
6337 (
6338 "return cjson.encode_max_depth(1, 2)",
6339 "bad argument #2 to 'encode_max_depth' (found too many arguments)",
6340 ),
6341 ] {
6342 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6343 assert!(
6344 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6345 "{body} gave {reply}",
6346 );
6347 }
6348 assert_eq!(
6351 f.run(&[
6352 b"EVAL",
6353 b"local n = cjson.new() n.encode_number_precision(3) \
6354 return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
6355 b"0",
6356 ]),
6357 "$22\r\n0.33333333333333 0.333\r\n"
6358 );
6359 let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
6361 decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
6362 encode_max_depth encode_number_precision encode_sparse_array new null";
6363 assert_eq!(
6364 f.run(&[
6365 b"EVAL",
6366 b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
6367 table.sort(t) return table.concat(t, ' ')",
6368 b"0",
6369 ]),
6370 format!("${}\r\n{names}\r\n", names.len())
6371 );
6372 for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
6373 assert!(
6374 f.run(&[b"EVAL", body, b"0"])
6375 .contains("Attempt to modify a readonly table script: "),
6376 "{body:?}",
6377 );
6378 }
6379 }
6380
6381 #[test]
6382 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6383 fn a_script_gets_the_struct_library_a_real_server_carries() {
6384 let mut f = Fixture::new();
6385 for (body, want) in [
6388 ("#struct.pack('i4', 1)", ":4\r\n"),
6389 ("#struct.pack('l', 1)", ":8\r\n"),
6390 ("#struct.pack('d', 1)", ":8\r\n"),
6391 ("#struct.pack('f', 1)", ":4\r\n"),
6392 ("#struct.pack('s', 'abc')", ":4\r\n"),
6393 ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
6394 ("#struct.pack('x')", ":1\r\n"),
6395 ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
6396 ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
6397 ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
6398 ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
6401 ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
6402 ("#struct.pack('c0', 'abcd')", ":4\r\n"),
6405 ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
6406 ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
6407 ] {
6408 let script = format!("return {body}");
6409 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6410 }
6411 for (body, want) in [
6414 ("struct.size('i')", ":4\r\n"),
6415 ("struct.size('l')", ":8\r\n"),
6416 ("struct.size('T')", ":8\r\n"),
6417 ("struct.size('h')", ":2\r\n"),
6418 ("struct.size('c10')", ":10\r\n"),
6419 ("struct.size('ic')", ":5\r\n"),
6420 ("struct.size('!8ic')", ":5\r\n"),
6421 ("struct.size('!4i')", ":4\r\n"),
6422 ("struct.size('bd')", ":9\r\n"),
6425 ("struct.size('!bd')", ":16\r\n"),
6426 ("struct.size('A')", ":0\r\n"),
6427 ("struct.size('7')", ":0\r\n"),
6428 ] {
6429 let script = format!("return {body}");
6430 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6431 }
6432 for (body, want) in [
6435 ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
6436 ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
6437 ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
6438 ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
6439 ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
6440 (
6441 "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
6442 ":-70000\r\n",
6443 ),
6444 ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
6445 ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
6449 ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
6450 ] {
6451 let script = format!("return {body}");
6452 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6453 }
6454 for (body, want) in [
6455 ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
6456 ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
6457 ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
6458 ] {
6459 let script = format!("return {body}");
6460 assert_eq!(
6461 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6462 format!("${}\r\n{want}\r\n", want.len()),
6463 "{body}",
6464 );
6465 }
6466 for (body, want) in [
6469 (
6470 "return struct.pack()",
6471 "bad argument #1 to 'pack' (string expected, got no value)",
6472 ),
6473 (
6476 "return struct.pack('i4')",
6477 "bad argument #2 to 'pack' (number expected, got nil)",
6478 ),
6479 (
6482 "return struct.pack('c6', 'abc')",
6483 "bad argument #3 to 'pack' (string too short)",
6484 ),
6485 (
6486 "return struct.pack('A', 'x')",
6487 "bad argument #1 to 'pack' (invalid format option 'A')",
6488 ),
6489 (
6490 "return struct.pack('i33', 1)",
6491 "integral size 33 is larger than limit of 32",
6492 ),
6493 (
6494 "return struct.pack('!3i', 1)",
6495 "alignment 3 is not a power of 2",
6496 ),
6497 (
6498 "return struct.unpack()",
6499 "bad argument #1 to 'unpack' (string expected, got no value)",
6500 ),
6501 (
6502 "return struct.unpack('i4')",
6503 "bad argument #2 to 'unpack' (string expected, got no value)",
6504 ),
6505 (
6506 "return struct.unpack('i4', 'ab')",
6507 "bad argument #2 to 'unpack' (data string too short)",
6508 ),
6509 (
6510 "return struct.unpack('i1', 'abc', 0)",
6511 "bad argument #3 to 'unpack' (offset must be 1 or greater)",
6512 ),
6513 (
6514 "return struct.unpack('c0', 'abc')",
6515 "format 'c0' needs a previous size",
6516 ),
6517 (
6518 "return struct.unpack('s', 'abc')",
6519 "unfinished string in data",
6520 ),
6521 (
6522 "return struct.size()",
6523 "bad argument #1 to 'size' (string expected, got no value)",
6524 ),
6525 (
6526 "return struct.size('s')",
6527 "bad argument #1 to 'size' (option 's' has no fixed size)",
6528 ),
6529 (
6530 "return struct.size('c0')",
6531 "bad argument #1 to 'size' (option 'c0' has no fixed size)",
6532 ),
6533 ] {
6534 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6535 assert!(
6536 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6537 "{body} gave {reply}",
6538 );
6539 }
6540 let names = "pack size unpack";
6542 assert_eq!(
6543 f.run(&[
6544 b"EVAL",
6545 b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
6546 table.sort(t) return table.concat(t, ' ')",
6547 b"0",
6548 ]),
6549 format!("${}\r\n{names}\r\n", names.len())
6550 );
6551 for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
6552 assert!(
6553 f.run(&[b"EVAL", body, b"0"])
6554 .contains("Attempt to modify a readonly table script: "),
6555 "{body:?}",
6556 );
6557 }
6558 }
6559
6560 #[test]
6561 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6562 fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
6563 let mut f = Fixture::new();
6564 let hex = "local function hx(s) return (string.gsub(s, '.', \
6567 function(c) return string.format('%02x', string.byte(c)) end)) end ";
6568 for (body, want) in [
6569 ("cmsgpack.pack(nil)", "c0"),
6570 ("cmsgpack.pack(true)", "c3"),
6571 ("cmsgpack.pack(false)", "c2"),
6572 ("cmsgpack.pack(0)", "00"),
6573 ("cmsgpack.pack(127)", "7f"),
6574 ("cmsgpack.pack(128)", "cc80"),
6575 ("cmsgpack.pack(-1)", "ff"),
6576 ("cmsgpack.pack(-33)", "d0df"),
6577 ("cmsgpack.pack(65535)", "cdffff"),
6578 ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
6579 ("cmsgpack.pack(2^53)", "cf0020000000000000"),
6580 ("cmsgpack.pack(-2^63)", "d38000000000000000"),
6581 ("cmsgpack.pack(2^64)", "ca5f800000"),
6584 ("cmsgpack.pack(1.5)", "ca3fc00000"),
6585 ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
6586 ("cmsgpack.pack('abc')", "a3616263"),
6587 ("cmsgpack.pack('')", "a0"),
6588 ("cmsgpack.pack({})", "90"),
6589 ("cmsgpack.pack({1, 2})", "920102"),
6590 ("cmsgpack.pack({a = 1})", "81a16101"),
6591 ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
6592 (
6595 "(function() local t = {} local c = t \
6596 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
6597 "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
6598 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
6599 ),
6600 ] {
6601 let script = format!("{hex} return hx({body})");
6602 assert_eq!(
6603 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6604 format!("${}\r\n{want}\r\n", want.len()),
6605 "{body}",
6606 );
6607 }
6608 for (body, want) in [
6612 ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
6613 ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
6614 ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
6615 ("select('#', cmsgpack.unpack(''))", 0),
6616 ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
6617 ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
6618 ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
6619 ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
6620 ("select(1, cmsgpack.unpack_one('\\1'))", -1),
6621 ("select(1, cmsgpack.unpack_one('', 0))", -1),
6622 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
6623 ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
6624 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
6627 ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
6628 ] {
6629 let script = format!("return {body}");
6630 assert_eq!(
6631 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6632 format!(":{want}\r\n"),
6633 "{body}",
6634 );
6635 }
6636 for (body, want) in [
6637 ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
6638 ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
6639 ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
6640 (
6641 "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
6642 "inf",
6643 ),
6644 ("cmsgpack._NAME", "cmsgpack"),
6645 ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
6646 (
6647 "cmsgpack._COPYRIGHT",
6648 "Copyright (C) 2012, Salvatore Sanfilippo",
6649 ),
6650 (
6651 "cmsgpack._DESCRIPTION",
6652 "MessagePack C implementation for Lua",
6653 ),
6654 ] {
6655 let script = format!("return {body}");
6656 assert_eq!(
6657 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6658 format!("${}\r\n{want}\r\n", want.len()),
6659 "{body}",
6660 );
6661 }
6662 for (body, want) in [
6663 (
6666 "return cmsgpack.pack()",
6667 "bad argument #0 to 'pack' (MessagePack pack needs input.)",
6668 ),
6669 (
6670 "return cmsgpack.unpack()",
6671 "bad argument #1 to 'unpack' (string expected, got no value)",
6672 ),
6673 (
6674 "return cmsgpack.unpack(string.char(193))",
6675 "Bad data format in input.",
6676 ),
6677 (
6678 "return cmsgpack.unpack(string.char(204))",
6679 "Missing bytes in input.",
6680 ),
6681 (
6682 "return cmsgpack.unpack(string.char(146, 1))",
6683 "Missing bytes in input.",
6684 ),
6685 (
6686 "return cmsgpack.unpack_one('\\1', 5)",
6687 "Start offset 5 greater than input length 1.",
6688 ),
6689 (
6690 "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
6691 "Start offset 5 greater than input length 2.",
6692 ),
6693 (
6696 "return cmsgpack.unpack_one('\\1', -1)",
6697 "Invalid request to unpack with offset of -1 and limit of 1.",
6698 ),
6699 (
6700 "return cmsgpack.unpack_limit('\\1', -1, 0)",
6701 "Invalid request to unpack with offset of 0 and limit of 1.",
6702 ),
6703 ] {
6704 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6705 assert!(
6706 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6707 "{body} gave {reply}",
6708 );
6709 }
6710 let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
6712 assert_eq!(
6713 f.run(&[
6714 b"EVAL",
6715 b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
6716 table.sort(t) return table.concat(t, ' ')",
6717 b"0",
6718 ]),
6719 format!("${}\r\n{names}\r\n", names.len())
6720 );
6721 for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
6722 assert!(
6723 f.run(&[b"EVAL", body, b"0"])
6724 .contains("Attempt to modify a readonly table script: "),
6725 "{body:?}",
6726 );
6727 }
6728 assert_eq!(
6731 f.run(&[
6732 b"EVAL",
6733 b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
6734 b"0",
6735 ]),
6736 "$8\r\ncmsgpack\r\n"
6737 );
6738 }
6739
6740 const LIB: &[u8] = b"#!lua name=mylib\n\
6746 local counter = 0\n\
6747 redis.register_function{function_name = 'ping', description = 'says pong',\n\
6748 callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
6749 redis.register_function('count', function() counter = counter + 1 return counter end)\n\
6750 redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
6751 redis.register_function('setit', function(keys, args) \
6752 return redis.call('SET', keys[1], args[1]) end)\n\
6753 redis.register_function('raise', function() error('boom') end)\n";
6754
6755 const OTHER: &[u8] = b"#!lua name=other\n\
6757 redis.register_function('twice', function(keys, args) return 2 end)\n";
6758
6759 #[test]
6760 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6761 fn a_library_is_loaded_once_and_called_by_name_forever_after() {
6762 let mut f = Fixture::new();
6763 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6764 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6765 assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
6768 assert_eq!(
6772 f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
6773 "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6774 );
6775 assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
6776 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
6777 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
6780 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
6781 assert_eq!(
6784 f.run(&[b"FCALL", b"raise", b"0"]),
6785 "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
6786 );
6787 assert_eq!(
6790 f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
6791 "-ERR Library not found\r\n"
6792 );
6793 assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
6794 assert_eq!(
6795 f.run(&[b"FCALL", b"ping", b"0"]),
6796 "-ERR Function not found\r\n"
6797 );
6798 }
6799
6800 #[test]
6801 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6802 fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
6803 let mut f = Fixture::new();
6804 for (code, want) in [
6805 (&b"return 1"[..], "ERR Missing library metadata"),
6806 (b"#!lua name=x", "ERR Invalid library metadata"),
6807 (b"#!\n", "ERR Library name was not given"),
6808 (b"#!lua\nx", "ERR Library name was not given"),
6809 (
6810 b"#!lua name=a name=b\nx",
6811 "ERR Invalid metadata value, name argument was given multiple times",
6812 ),
6813 (
6814 b"#!lua nome=a\nx",
6815 "ERR Invalid metadata value given: nome=a",
6816 ),
6817 (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
6818 (
6819 b"#!lua name=a-b\nx",
6820 "ERR Library names can only contain letters, numbers, or underscores(_) \
6821 and must be at least one character long",
6822 ),
6823 (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
6824 (
6825 b"#!lua name=c\nthis is not lua",
6826 "ERR Error compiling function: user_function:2: '=' expected near 'is'",
6827 ),
6828 (
6832 b"#!lua name=r\nerror('boom')",
6833 "ERR Error registering functions: ERR user_function:2: \
6834 Script attempted to access nonexistent global variable 'error'",
6835 ),
6836 (
6839 b"#!lua name=r\nredis.call('PING')",
6840 "ERR Error registering functions: ERR user_function:2: \
6841 Script attempted to access nonexistent global variable 'call'",
6842 ),
6843 (
6844 b"#!lua name=r\nx = 1",
6845 "ERR Error registering functions: ERR user_function:2: \
6846 Attempt to modify a readonly table",
6847 ),
6848 (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
6849 ] {
6850 assert_eq!(
6851 f.run(&[b"FUNCTION", b"LOAD", code]),
6852 format!("-{want}\r\n"),
6853 "{}",
6854 String::from_utf8_lossy(code),
6855 );
6856 }
6857 }
6858
6859 #[test]
6860 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6861 fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
6862 let mut f = Fixture::new();
6863 for (call, want) in [
6864 (
6865 &b"redis.register_function()"[..],
6866 "wrong number of arguments to redis.register_function",
6867 ),
6868 (
6869 b"redis.register_function('a', function() end, 1)",
6870 "wrong number of arguments to redis.register_function",
6871 ),
6872 (
6873 b"redis.register_function('a')",
6874 "calling redis.register_function with a single argument is only \
6875 applicable to Lua table (representing named arguments).",
6876 ),
6877 (
6878 b"redis.register_function({foo = 'a'})",
6879 "unknown argument given to redis.register_function",
6880 ),
6881 (
6882 b"redis.register_function({callback = function() end})",
6883 "redis.register_function must get a function name argument",
6884 ),
6885 (
6886 b"redis.register_function({function_name = 'a'})",
6887 "redis.register_function must get a callback argument",
6888 ),
6889 (
6890 b"redis.register_function({function_name = {}, callback = function() end})",
6891 "function_name argument given to redis.register_function must be a string",
6892 ),
6893 (
6894 b"redis.register_function({function_name = 'a', description = {}, \
6895 callback = function() end})",
6896 "description argument given to redis.register_function must be a string",
6897 ),
6898 (
6899 b"redis.register_function({function_name = 'a', callback = 1})",
6900 "callback argument given to redis.register_function must be a function",
6901 ),
6902 (
6903 b"redis.register_function({function_name = 'a', callback = function() end, \
6904 flags = 1})",
6905 "flags argument to redis.register_function must be a table \
6906 representing function flags",
6907 ),
6908 (
6909 b"redis.register_function({function_name = 'a', callback = function() end, \
6910 flags = {'zz'}})",
6911 "unknown flag given",
6912 ),
6913 (
6914 b"redis.register_function({}, function() end)",
6915 "first argument to redis.register_function must be a string",
6916 ),
6917 (
6918 b"redis.register_function('a', 1)",
6919 "second argument to redis.register_function must be a function",
6920 ),
6921 (
6922 b"redis.register_function('a-b', function() end)",
6923 "Library names can only contain letters, numbers, or underscores(_) \
6924 and must be at least one character long",
6925 ),
6926 (
6927 b"redis.register_function('d', function() end) \
6928 redis.register_function('d', function() end)",
6929 "Function already exists in the library",
6930 ),
6931 ] {
6932 let mut code = b"#!lua name=e\n".to_vec();
6933 code.extend_from_slice(call);
6934 assert_eq!(
6938 f.run(&[b"FUNCTION", b"LOAD", &code]),
6939 format!("-ERR Error registering functions: ERR {want}\r\n"),
6940 "{}",
6941 String::from_utf8_lossy(call),
6942 );
6943 }
6944 assert_eq!(
6947 f.run(&[
6948 b"FUNCTION",
6949 b"LOAD",
6950 b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
6951 ]),
6952 "$1\r\nn\r\n"
6953 );
6954 assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
6955 assert_eq!(
6958 f.run(&[
6959 b"FUNCTION",
6960 b"LOAD",
6961 b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
6962 redis.register_function('D', function() return 2 end)",
6963 ]),
6964 "$1\r\nc\r\n"
6965 );
6966 }
6967
6968 #[test]
6969 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6970 fn a_library_cannot_take_a_name_another_library_already_has() {
6971 let mut f = Fixture::new();
6972 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6973 assert_eq!(
6974 f.run(&[b"FUNCTION", b"LOAD", LIB]),
6975 "-ERR Library 'mylib' already exists\r\n"
6976 );
6977 assert_eq!(
6981 f.run(&[
6982 b"FUNCTION",
6983 b"LOAD",
6984 b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
6985 ]),
6986 "-ERR Function PING already exists\r\n"
6987 );
6988 assert_eq!(
6991 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
6992 "$5\r\nmylib\r\n"
6993 );
6994 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
6997 assert_eq!(
6998 f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
6999 "-ERR Unknown option given: NOPE\r\n"
7000 );
7001 assert_eq!(
7004 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
7005 "-ERR Missing library metadata\r\n"
7006 );
7007 assert_eq!(
7008 f.run(&[b"FUNCTION", b"LOAD"]),
7009 "-ERR wrong number of arguments for 'function|load' command\r\n"
7010 );
7011 }
7012
7013 #[test]
7014 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7015 fn fcall_checks_the_name_before_it_looks_at_anything_else() {
7016 let mut f = Fixture::new();
7017 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7018 for (args, want) in [
7019 (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
7020 (&[b"ping", b"x"], "ERR Bad number of keys provided"),
7021 (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
7022 (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
7023 (
7024 &[b"ping", b"99999999999999999999"],
7025 "ERR Bad number of keys provided",
7026 ),
7027 (
7028 &[b"ping", b"3", b"a"],
7029 "ERR Number of keys can't be greater than number of args",
7030 ),
7031 (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
7032 ] {
7033 let mut wire: Vec<&[u8]> = vec![b"FCALL"];
7034 wire.extend_from_slice(args);
7035 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
7036 }
7037 assert_eq!(
7040 f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
7041 "-ERR Can not execute a script with write flag using *_ro command.\r\n"
7042 );
7043 assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
7044 assert_eq!(
7045 f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
7046 "-ERR Function not found\r\n"
7047 );
7048 assert_eq!(
7051 f.run(&[
7052 b"FUNCTION",
7053 b"LOAD",
7054 b"#!lua name=w\nredis.register_function{function_name = 'w', \
7055 flags = {'no-writes'}, callback = function(keys) \
7056 return redis.call('SET', keys[1], 'x') end}",
7057 ]),
7058 "$1\r\nw\r\n"
7059 );
7060 assert!(
7061 f.run(&[b"FCALL", b"w", b"1", b"k"])
7062 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
7063 );
7064 }
7065
7066 #[test]
7067 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7068 fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
7069 let mut f = Fixture::new();
7070 let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
7073 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
7074 acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
7075 status_reply";
7076 let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
7077 gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
7078 rawequal rawget rawset redis select setmetatable string struct table \
7079 tonumber tostring type unpack xpcall";
7080 assert_eq!(
7081 f.run(&[
7082 b"FUNCTION",
7083 b"LOAD",
7084 b"#!lua name=g\n\
7085 local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
7086 table.sort(o) return table.concat(o, ' ') end\n\
7087 redis.register_function('names', function() return sorted(redis) end)\n\
7088 redis.register_function('globals', function() return sorted(_G) end)\n\
7089 redis.register_function('keysg', function() return KEYS[1] end)\n\
7090 redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
7091 redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
7092 redis.register_function('gwr', function() _G.pcall = 1 end)\n",
7093 ]),
7094 "$1\r\ng\r\n"
7095 );
7096 assert_eq!(
7097 f.run(&[b"FCALL", b"names", b"0"]),
7098 format!("${}\r\n{names}\r\n", names.len())
7099 );
7100 assert_eq!(
7101 f.run(&[b"FCALL", b"globals", b"0"]),
7102 format!("${}\r\n{globals}\r\n", globals.len())
7103 );
7104 assert!(
7107 f.run(&[b"FCALL", b"keysg", b"1", b"k"])
7108 .contains("nonexistent global variable 'KEYS'"),
7109 );
7110 assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
7113 for name in [&b"wr"[..], b"gwr"] {
7116 assert!(
7117 f.run(&[b"FCALL", name, b"0"])
7118 .contains("Attempt to modify a readonly table"),
7119 "{}",
7120 String::from_utf8_lossy(name),
7121 );
7122 }
7123 }
7124
7125 #[test]
7126 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7127 fn function_list_says_what_every_library_registered() {
7128 let mut f = Fixture::new();
7129 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7130 f.out = Out::new(Proto::Resp3);
7133 let listed = f.run(&[b"FUNCTION", b"LIST"]);
7134 assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
7135 assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
7136 assert!(listed.contains(
7137 "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
7138 $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
7139 ));
7140 assert!(listed.contains(
7143 "$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"
7144 ));
7145 assert!(!listed.contains("library_code"));
7146 assert!(
7147 f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
7148 .contains("library_code")
7149 );
7150 assert!(
7153 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
7154 .starts_with("*1\r\n")
7155 );
7156 assert_eq!(
7157 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
7158 "*0\r\n"
7159 );
7160 f.out = Out::new(Proto::Resp2);
7163 assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
7164 for (args, want) in [
7165 (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
7166 (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
7167 (
7168 &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
7169 "ERR Unknown argument LIBRARYNAME",
7170 ),
7171 (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
7172 ] {
7173 let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
7174 wire.extend_from_slice(args);
7175 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
7176 }
7177 }
7178
7179 #[test]
7180 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7181 fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
7182 let mut f = Fixture::new();
7183 f.out = Out::new(Proto::Resp3);
7184 assert_eq!(
7185 f.run(&[b"FUNCTION", b"STATS"]),
7186 "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
7187 %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
7188 );
7189 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7190 assert!(
7191 f.run(&[b"FUNCTION", b"STATS"])
7192 .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
7193 );
7194 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
7195 assert!(
7196 f.run(&[b"FUNCTION", b"STATS"])
7197 .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
7198 );
7199 }
7200
7201 #[test]
7202 fn every_function_subcommand_complains_about_its_own_arity() {
7203 let mut f = Fixture::new();
7204 for (args, want) in [
7205 (
7206 &[&b"STATS"[..], b"X"][..],
7207 "ERR wrong number of arguments for 'function|stats' command",
7208 ),
7209 (
7210 &[b"KILL", b"X"],
7211 "ERR wrong number of arguments for 'function|kill' command",
7212 ),
7213 (
7214 &[b"HELP", b"X"],
7215 "ERR wrong number of arguments for 'function|help' command",
7216 ),
7217 (
7218 &[b"DELETE"],
7219 "ERR wrong number of arguments for 'function|delete' command",
7220 ),
7221 (
7222 &[b"DELETE", b"a", b"b"],
7223 "ERR wrong number of arguments for 'function|delete' command",
7224 ),
7225 (
7226 &[b"DUMP", b"X"],
7227 "ERR wrong number of arguments for 'function|dump' command",
7228 ),
7229 (
7230 &[b"RESTORE"],
7231 "ERR wrong number of arguments for 'function|restore' command",
7232 ),
7233 (
7236 &[b"RESTORE", b"a", b"FLUSH", b"X"],
7237 "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
7238 Try FUNCTION HELP.",
7239 ),
7240 (
7241 &[b"RESTORE", b"a", b"ZZ"],
7242 "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
7243 or REPLACE.",
7244 ),
7245 (
7248 &[b"FLUSH", b"SYNC", b"X"],
7249 "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
7250 Try FUNCTION HELP.",
7251 ),
7252 (
7253 &[b"FLUSH", b"ZZ"],
7254 "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
7255 ),
7256 (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
7257 ] {
7258 let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
7259 wire.extend_from_slice(args);
7260 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
7261 }
7262 assert_eq!(
7263 f.run(&[b"FUNCTION"]),
7264 "-ERR wrong number of arguments for 'function' command\r\n"
7265 );
7266 assert_eq!(
7267 f.run(&[b"FUNCTION", b"KILL"]),
7268 "-NOTBUSY No scripts in execution right now.\r\n"
7269 );
7270 }
7271
7272 #[test]
7278 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7279 fn a_library_survives_a_dump_and_a_restore() {
7280 let mut f = Fixture::new();
7281 let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7282 assert_eq!(empty.len(), 10);
7283 assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
7284
7285 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7286 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7287 assert!(full.len() > empty.len());
7288
7289 assert_eq!(
7292 f.run(&[b"FUNCTION", b"RESTORE", &full]),
7293 "-ERR Library mylib already exists\r\n"
7294 );
7295 assert_eq!(
7296 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
7297 "+OK\r\n"
7298 );
7299 assert_eq!(
7300 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
7301 "+OK\r\n"
7302 );
7303 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
7305
7306 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
7309 assert_eq!(
7310 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
7311 "+OK\r\n"
7312 );
7313 assert_eq!(
7314 f.run(&[b"FUNCTION", b"DELETE", b"other"]),
7315 "-ERR Library not found\r\n"
7316 );
7317 }
7318
7319 #[test]
7326 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7327 fn a_restore_that_fails_changes_nothing() {
7328 let mut f = Fixture::new();
7329 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7330 let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7331
7332 let reseal = |body: &[u8], version: u16| {
7336 let mut out = body.to_vec();
7337 out.extend_from_slice(&version.to_le_bytes());
7338 let crc = yo_common::crc::crc64(0, &out);
7339 out.extend_from_slice(&crc.to_le_bytes());
7340 out
7341 };
7342 let body = &good[..good.len() - 10];
7343
7344 let mut torn = good.clone();
7345 let n = torn.len();
7346 torn[n - 1] ^= 0xff;
7347 let future = reseal(body, 999);
7348 let mut pre_ga = body.to_vec();
7351 pre_ga[0] = 246;
7352 let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
7353 let mut other = body.to_vec();
7354 other[0] = 0;
7355 let other = reseal(&other, yo_kv::rdb::VERSION);
7356 let mut cut = body.to_vec();
7358 cut.truncate(body.len() - 1);
7359 let cut = reseal(&cut, yo_kv::rdb::VERSION);
7360
7361 for (bytes, want) in [
7362 (vec![], "ERR DUMP payload version or checksum are wrong"),
7363 (
7364 b"0123456789".to_vec(),
7365 "ERR DUMP payload version or checksum are wrong",
7366 ),
7367 (torn, "ERR DUMP payload version or checksum are wrong"),
7368 (future, "ERR DUMP payload version or checksum are wrong"),
7369 (pre_ga, "ERR Pre-GA function format not supported"),
7370 (other, "ERR given type is not a function"),
7371 (cut, "ERR Failed loading library payload"),
7372 ] {
7373 assert_eq!(
7374 f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
7375 format!("-{want}\r\n")
7376 );
7377 }
7378
7379 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
7381 let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7382 assert_eq!(again, good);
7383 }
7384
7385 #[test]
7388 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7389 fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
7390 let mut f = Fixture::new();
7391 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7392 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7393 let clash =
7395 b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
7396 .as_slice();
7397 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
7398 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
7399 assert_eq!(
7400 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
7401 "-ERR Function ping already exists\r\n"
7402 );
7403 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
7405 }
7406
7407 #[test]
7408 fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
7409 let mut f = Fixture::new();
7410 assert_eq!(
7411 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
7412 "*1\r\n$1\r\nk\r\n"
7413 );
7414 assert_eq!(
7415 f.run(&[
7416 b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
7417 ]),
7418 "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
7419 );
7420 assert_eq!(
7423 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
7424 "*0\r\n"
7425 );
7426 assert_eq!(
7429 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
7430 "*0\r\n"
7431 );
7432 assert_eq!(
7433 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
7434 "*0\r\n"
7435 );
7436 assert_eq!(
7437 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
7438 "*0\r\n"
7439 );
7440 assert_eq!(
7442 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
7443 "-ERR Invalid number of arguments specified for command\r\n"
7444 );
7445 }
7446
7447 #[test]
7448 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7449 fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
7450 let mut f = Fixture::new();
7451 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
7452
7453 assert_eq!(
7454 eval(&mut f, b"return redis.sha1hex('')"),
7455 "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
7456 );
7457 assert_eq!(
7458 eval(&mut f, b"return redis.sha1hex('return 1')"),
7459 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
7460 );
7461 assert_eq!(
7464 eval(&mut f, b"return redis.error_reply('boom')"),
7465 "-ERR boom\r\n"
7466 );
7467 assert_eq!(
7468 eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
7469 "-WRONGTYPE nope\r\n"
7470 );
7471 assert_eq!(
7472 eval(&mut f, b"return redis.status_reply('fine')"),
7473 "+fine\r\n"
7474 );
7475 assert_eq!(
7478 eval(&mut f, b"return redis.error_reply(1)"),
7479 "-ERR wrong number or type of arguments\r\n"
7480 );
7481 assert_eq!(
7482 eval(&mut f, b"local x = redis.status_reply() return x.err"),
7483 "$37\r\nERR wrong number or type of arguments\r\n"
7484 );
7485
7486 assert_eq!(
7488 eval(
7489 &mut f,
7490 b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
7491 ),
7492 "$4\r\n0123\r\n"
7493 );
7494 assert_eq!(
7495 eval(
7496 &mut f,
7497 b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
7498 ),
7499 "$5\r\n01223\r\n"
7500 );
7501 assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
7503 assert_eq!(
7504 eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
7505 ":1\r\n"
7506 );
7507 assert_eq!(
7508 eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
7509 ":1\r\n"
7510 );
7511 assert_eq!(
7512 eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
7513 ":1\r\n"
7514 );
7515 assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
7517 assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
7518 assert!(
7519 eval(&mut f, b"redis.log('x', 'y')")
7520 .contains("First argument must be a number (log level)."),
7521 );
7522 assert!(
7523 eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
7524 .contains("Invalid command passed to redis.acl_check_cmd()"),
7525 );
7526 assert!(
7527 eval(&mut f, b"return redis.acl_check_cmd('get')")
7528 .contains("Wrong number of args for redis.acl_check_cmd()"),
7529 );
7530 }
7531
7532 #[test]
7533 fn a_counter_is_an_integer_and_not_a_string_of_digits() {
7534 let mut f = Fixture::new();
7535 assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
7536 assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
7537 assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
7538 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
7541 assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
7542 f.run(&[b"SET", b"k", b"hello"]);
7545 assert_eq!(
7546 f.run(&[b"INCR", b"k"]),
7547 "-ERR value is not an integer or out of range\r\n"
7548 );
7549 assert_eq!(
7550 f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
7551 "-ERR increment would produce NaN or Infinity\r\n"
7552 );
7553 }
7554
7555 #[test]
7560 fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
7561 let mut f = Fixture::new();
7562 assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
7563 assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
7566 assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
7567 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
7568 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
7569 assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
7570 assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
7571 assert_eq!(
7572 f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
7573 "*2\r\n:1\r\n:0\r\n",
7574 "a refused increment reports the value it left alone and applied nothing"
7575 );
7576 assert_eq!(
7577 f.run(&[
7578 b"INCREX",
7579 b"n",
7580 b"BYINT",
7581 b"5",
7582 b"UBOUND",
7583 b"3",
7584 b"SATURATE"
7585 ]),
7586 "*2\r\n:3\r\n:2\r\n"
7587 );
7588 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
7589 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
7590 }
7591
7592 #[test]
7593 fn the_same_answers_come_out_in_resp3_spelling() {
7594 let mut f = Fixture::new();
7595 assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
7596 assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
7597 assert_eq!(
7600 f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
7601 "*2\r\n,1.5\r\n,1.5\r\n"
7602 );
7603 assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
7604 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
7607 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
7608 }
7609
7610 #[test]
7611 fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
7612 let mut f = Fixture::new();
7613 let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
7614 assert_eq!(flow, Flow::Continue);
7615 assert_eq!(
7616 reply,
7617 "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
7618 );
7619 let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
7622 assert_eq!(reply.matches("\r\n").count(), 1);
7623 }
7624
7625 #[test]
7626 fn arity_is_checked_before_the_command_is() {
7627 let mut f = Fixture::new();
7628 assert_eq!(
7629 f.run(&[b"GET"]),
7630 "-ERR wrong number of arguments for 'get' command\r\n"
7631 );
7632 assert_eq!(
7633 f.run(&[b"MSET", b"k"]),
7634 "-ERR wrong number of arguments for 'mset' command\r\n"
7635 );
7636 assert_eq!(
7640 f.run(&[b"PING", b"a", b"b"]),
7641 "-ERR wrong number of arguments for 'ping' command\r\n"
7642 );
7643 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
7644 assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
7645 assert_eq!(
7647 f.run(&[b"DELEX", b"k", b"IFEQ"]),
7648 "-ERR wrong number of arguments for 'delex' command\r\n"
7649 );
7650 }
7651
7652 #[test]
7656 fn the_option_combinations_are_the_ones_a_real_server_accepts() {
7657 let mut f = Fixture::new();
7658 let syntax = "-ERR syntax error\r\n";
7659 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
7660 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
7661 assert_eq!(
7662 f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
7663 syntax
7664 );
7665 assert_eq!(
7666 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
7667 syntax
7668 );
7669 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
7670 assert_eq!(
7672 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
7673 "+OK\r\n"
7674 );
7675 assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
7676 assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
7677 assert_eq!(
7679 f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
7680 syntax
7681 );
7682 assert_eq!(
7683 f.run(&[b"INCREX", b"n", b"ENX"]),
7684 "-ERR ENX flag requires an expiration\r\n"
7685 );
7686 assert_eq!(
7687 f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
7688 "-ERR UBOUND is not an integer or out of range\r\n"
7689 );
7690 assert_eq!(
7691 f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
7692 "-ERR LBOUND can't be greater than UBOUND\r\n"
7693 );
7694 assert_eq!(
7695 f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
7696 "-ERR If you want both the length and indexes, please just use IDX.\r\n"
7697 );
7698 }
7699
7700 #[test]
7704 fn the_expiry_rules_are_redis_own() {
7705 let mut f = Fixture::new();
7706 let bad = "-ERR invalid expire time in 'set' command\r\n";
7707 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
7708 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
7709 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
7710 assert_eq!(
7711 f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
7712 bad
7713 );
7714 assert_eq!(
7715 f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
7716 "-ERR value is not an integer or out of range\r\n"
7717 );
7718 assert_eq!(
7719 f.run(&[b"SETEX", b"k", b"0", b"v"]),
7720 "-ERR invalid expire time in 'setex' command\r\n"
7721 );
7722 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
7723 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
7724 assert_eq!(
7725 f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
7726 "-ERR syntax error\r\n",
7727 "the option list is still checked before the key is looked up"
7728 );
7729 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
7731 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
7732 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
7733 }
7734
7735 #[test]
7736 fn mset_takes_its_pairs_from_the_read_buffer() {
7737 let mut f = Fixture::new();
7738 assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
7739 assert_eq!(
7740 f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
7741 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
7742 );
7743 assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
7744 assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
7745 assert_eq!(
7746 f.run(&[b"MSETEX", b"2", b"e", b"5"]),
7747 "-ERR wrong number of key-value pairs\r\n"
7748 );
7749 assert_eq!(
7750 f.run(&[b"MSETEX", b"0", b"e", b"5"]),
7751 "-ERR invalid numkeys value\r\n"
7752 );
7753 assert_eq!(
7754 f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
7755 "-ERR invalid numkeys value\r\n"
7756 );
7757 }
7758
7759 #[test]
7760 fn lcs_answers_the_length_the_string_and_the_runs() {
7761 let mut f = Fixture::new();
7762 f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
7763 assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
7764 assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
7765 assert_eq!(
7766 f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
7767 "*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"
7768 );
7769 assert_eq!(
7772 f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
7773 "$6\r\nmytext\r\n"
7774 );
7775 }
7776
7777 #[test]
7778 fn select_moves_the_connection_and_the_databases_stay_apart() {
7779 let mut f = Fixture::new();
7780 f.run(&[b"SET", b"k", b"zero"]);
7781 assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
7782 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
7783 f.run(&[b"SET", b"k", b"four"]);
7784 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
7785 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
7786 assert_eq!(
7787 f.run(&[b"SELECT", b"99"]),
7788 "-ERR DB index is out of range\r\n"
7789 );
7790 assert_eq!(
7791 f.run(&[b"SELECT", b"-1"]),
7792 "-ERR DB index is out of range\r\n"
7793 );
7794 assert_eq!(
7795 f.run(&[b"SELECT", b"abc"]),
7796 "-ERR value is not an integer or out of range\r\n"
7797 );
7798 f.run(&[b"SELECT", b"4"]);
7800 f.run(&[b"RESET"]);
7801 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
7802 }
7803
7804 #[test]
7805 fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
7806 let mut f = Fixture::new();
7807 let reply = f.run(&[b"HELLO"]);
7808 assert!(reply.starts_with("*14\r\n"), "{reply}");
7809 assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
7810 assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
7811 assert!(
7812 reply.contains(":7\r\n"),
7813 "the connection id is in there: {reply}"
7814 );
7815 assert_eq!(
7816 f.run(&[b"HELLO", b"4"]),
7817 "-NOPROTO unsupported protocol version\r\n"
7818 );
7819 assert_eq!(
7820 f.run(&[b"HELLO", b"abc"]),
7821 "-ERR Protocol version is not an integer or out of range\r\n"
7822 );
7823 assert_eq!(
7824 f.run(&[b"HELLO", b"3", b"SETNAME"]),
7825 "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
7826 );
7827 assert!(
7828 f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
7829 .starts_with("%7\r\n")
7830 );
7831 assert_eq!(f.session.name(), b"bob");
7832 f.run(&[b"RESET"]);
7833 assert_eq!(f.session.name(), b"");
7834 }
7835
7836 #[test]
7837 fn command_describes_this_server_in_the_shape_a_driver_reads() {
7838 let mut f = Fixture::new();
7839 let count = format!(":{}\r\n", COMMANDS.len());
7840 assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
7841 let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
7842 assert_eq!(
7843 info,
7844 "*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\
7845 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*1\r\n*6\r\n\
7846 $5\r\nflags\r\n*2\r\n+RO\r\n+access\r\n\
7847 $12\r\nbegin_search\r\n*4\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
7848 *2\r\n$5\r\nindex\r\n:1\r\n\
7849 $9\r\nfind_keys\r\n*4\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
7850 *6\r\n$7\r\nlastkey\r\n:0\r\n$7\r\nkeystep\r\n:1\r\n$5\r\nlimit\r\n:0\r\n\
7851 *0\r\n"
7852 );
7853 assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
7855 assert_eq!(
7856 f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
7857 "*1\r\n$8\r\ngetrange\r\n"
7858 );
7859 assert_eq!(
7860 f.run(&[b"COMMAND", b"NOPE"]),
7861 "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
7862 );
7863 }
7864
7865 #[test]
7869 fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
7870 let mut f = Fixture::new();
7871 assert_eq!(
7872 f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
7873 "*1\r\n$1\r\nk\r\n"
7874 );
7875 assert_eq!(
7876 f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
7877 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
7878 );
7879 assert_eq!(
7880 f.run(&[
7881 b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
7882 ]),
7883 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
7884 );
7885 assert_eq!(
7886 f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
7887 "-ERR The command has no key arguments\r\n"
7888 );
7889 assert_eq!(
7890 f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
7891 "-ERR Invalid number of arguments specified for command\r\n"
7892 );
7893 }
7894
7895 #[test]
7896 fn config_answers_what_it_can_and_refuses_what_it_cannot() {
7897 let mut f = Fixture::new();
7898 assert_eq!(
7899 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
7900 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
7901 );
7902 let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
7905 assert!(both.starts_with("*6\r\n"), "{both}");
7906 assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
7907 assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
7908 assert_eq!(
7909 f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
7910 "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
7911 );
7912 assert_eq!(
7913 f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
7914 "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
7915 );
7916 assert_eq!(
7917 f.run(&[b"CONFIG", b"GET"]),
7918 "-ERR wrong number of arguments for 'config|get' command\r\n"
7919 );
7920 assert_eq!(
7924 f.run(&[b"CONFIG", b"SET", b"appendonly"]),
7925 "-ERR wrong number of arguments for 'config|set' command\r\n"
7926 );
7927 assert_eq!(
7928 f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
7929 "-ERR syntax error\r\n"
7930 );
7931 assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
7932 assert_eq!(
7933 f.run(&[b"CONFIG", b"REWRITE"]),
7934 "-ERR The server is running without a config file\r\n"
7935 );
7936 }
7937
7938 #[test]
7939 fn the_eviction_policy_reads_back_what_was_written_to_it() {
7940 let mut f = Fixture::new();
7941 assert_eq!(
7942 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
7943 "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
7944 );
7945 assert_eq!(
7946 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
7947 "+OK\r\n",
7948 "the name is matched without regard to case, like every other one"
7949 );
7950 assert_eq!(
7951 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
7952 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
7953 );
7954 assert!(
7956 f.run(&[b"INFO", b"memory"])
7957 .contains("maxmemory_policy:allkeys-lfu"),
7958 "INFO and CONFIG disagree about the policy"
7959 );
7960 assert_eq!(
7964 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
7965 "-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"
7966 );
7967 assert_eq!(
7970 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
7971 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
7972 );
7973 f.run(&[
7974 b"CONFIG",
7975 b"SET",
7976 b"hash-max-listpack-entries",
7977 b"7",
7978 b"maxmemory-policy",
7979 b"nonsense",
7980 ]);
7981 assert_eq!(
7982 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7983 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
7984 );
7985 }
7986
7987 #[test]
7988 fn the_three_eviction_numbers_read_back_too() {
7989 let mut f = Fixture::new();
7990 for (name, default, set) in [
7991 ("maxmemory-samples", "5", "12"),
7992 ("lfu-log-factor", "10", "3"),
7993 ("lfu-decay-time", "1", "60"),
7994 ] {
7995 let get = || {
7996 format!(
7997 "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
7998 name.len(),
7999 default.len()
8000 )
8001 };
8002 assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
8003 assert_eq!(
8004 f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
8005 "+OK\r\n"
8006 );
8007 assert_eq!(
8008 f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
8009 format!(
8010 "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
8011 name.len(),
8012 set.len()
8013 )
8014 );
8015 assert_eq!(
8018 f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
8019 format!(
8020 "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
8021 )
8022 );
8023 }
8024 }
8025
8026 #[test]
8027 fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
8028 let mut f = Fixture::new();
8029 assert_eq!(
8030 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
8031 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
8032 "no limit is the default"
8033 );
8034 for (typed, bytes) in [
8037 (&b"1024"[..], "1024"),
8038 (b"1k", "1000"),
8039 (b"1kb", "1024"),
8040 (b"1M", "1000000"),
8041 (b"1Mb", "1048576"),
8042 (b"1gb", "1073741824"),
8043 (b"100mb", "104857600"),
8044 ] {
8045 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
8046 assert_eq!(
8047 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
8048 format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
8049 "set {}",
8050 String::from_utf8_lossy(typed)
8051 );
8052 }
8053 assert!(
8054 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
8055 "the report agrees with the setting"
8056 );
8057
8058 for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
8061 assert_eq!(
8062 f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
8063 "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
8064 "refused {}",
8065 String::from_utf8_lossy(bad)
8066 );
8067 }
8068 assert!(
8069 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
8070 "and the refusal left the old one alone"
8071 );
8072 }
8073
8074 #[test]
8075 fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
8076 let mut f = Fixture::new();
8077 f.run(&[b"SET", b"here", b"already"]);
8078 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
8082 assert_eq!(
8083 f.run(&[b"SET", b"k", b"v"]),
8084 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
8085 );
8086 assert_eq!(
8087 f.run(&[b"LPUSH", b"l", b"v"]),
8088 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
8089 );
8090 assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
8092 assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
8093 assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
8094
8095 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
8097 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
8098 }
8099
8100 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
8104 #[test]
8105 fn an_allkeys_policy_makes_room_instead_of_refusing() {
8106 let mut f = Fixture::new();
8107 let val = vec![b'v'; 256];
8108 for i in 0..24000u32 {
8109 let k = format!("key:{i:08}");
8110 f.run(&[b"SET", k.as_bytes(), &val]);
8111 }
8112 let full = f.server.memory_bytes();
8113 assert!(
8114 full > 3 * 1024 * 1024,
8115 "the arena is several segments: {full}"
8116 );
8117
8118 let limit = full - 2 * 1024 * 1024;
8122 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
8123 f.run(&[
8124 b"CONFIG",
8125 b"SET",
8126 b"maxmemory",
8127 limit.to_string().as_bytes(),
8128 ]);
8129
8130 for i in 0..2000u32 {
8134 let k = format!("new:{i:08}");
8135 assert_eq!(
8136 f.run(&[b"SET", k.as_bytes(), &val]),
8137 "+OK\r\n",
8138 "write {i} was refused"
8139 );
8140 f.server.refresh_memory();
8141 if f.server.memory_bytes() <= limit {
8142 break;
8143 }
8144 }
8145 assert!(
8146 f.server.memory_bytes() <= limit,
8147 "it never got under: {} against {limit}",
8148 f.server.memory_bytes()
8149 );
8150 let info = f.run(&[b"INFO", b"stats"]);
8151 assert!(!info.contains("evicted_keys:0"), "{info}");
8152 assert!(
8153 f.run(&[b"DBSIZE"]) != ":0\r\n",
8154 "and it did not empty the database to get there"
8155 );
8156 }
8157
8158 #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
8169 #[test]
8170 fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
8171 let mut f = Fixture::new();
8178 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8179 let big = vec![b'v'; 200];
8180
8181 for i in 0..400u32 {
8182 let n = i.to_string();
8183 let n = n.as_bytes();
8184 f.run(&[b"SADD", b"s", n]);
8185 f.run(&[b"SADD", b"s2", &big]);
8186 f.run(&[b"HSET", b"h", n, &big]);
8187 f.run(&[b"RPUSH", b"l", &big]);
8188 f.run(&[b"ZADD", b"z", n, n]);
8189 f.run(&[b"ARSET", b"a", n, &big]);
8190 if i % 7 == 0 {
8191 f.run(&[b"SREM", b"s", n]);
8192 f.run(&[b"HDEL", b"h", n]);
8193 f.run(&[b"LPOP", b"l"]);
8194 f.run(&[b"ZREM", b"z", n]);
8195 f.run(&[b"ARDEL", b"a", n]);
8196 }
8197 if i % 53 == 0 {
8198 f.run(&[b"DEL", b"s2"]);
8201 }
8202 assert_eq!(
8203 f.server.settled_memory(),
8204 f.server.memory_bytes(),
8205 "after round {i}"
8206 );
8207 }
8208
8209 assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
8212 assert!(
8213 f.server.memory_bytes() > 512 * 1024,
8214 "{}",
8215 f.server.memory_bytes()
8216 );
8217
8218 f.run(&[b"FLUSHALL"]);
8220 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
8221 }
8222
8223 #[test]
8224 fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
8225 let mut f = Fixture::new();
8230 for i in 0..200u32 {
8231 let n = i.to_string();
8232 f.run(&[b"SADD", b"s", n.as_bytes()]);
8233 f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
8234 }
8235 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8236 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
8237
8238 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
8239 for i in 200..400u32 {
8240 let n = i.to_string();
8241 f.run(&[b"SADD", b"s", n.as_bytes()]);
8242 }
8243 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8244 assert_eq!(
8245 f.server.settled_memory(),
8246 f.server.memory_bytes(),
8247 "the writes it was not watching are in the number it started from"
8248 );
8249 }
8250
8251 #[test]
8252 fn evicted_keys_and_expired_keys_are_different_numbers() {
8253 let mut f = Fixture::new();
8254 f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
8257 f.server.advance_clock_ms(20);
8258 f.run(&[b"GET", b"gone"]);
8259 let info = f.run(&[b"INFO", b"stats"]);
8260 assert!(info.contains("expired_keys:1"), "{info}");
8261 assert!(info.contains("evicted_keys:0"), "{info}");
8262 }
8263
8264 #[test]
8265 fn the_two_counters_count_the_reads_and_nothing_else() {
8266 let mut f = Fixture::new();
8267 f.run(&[b"SET", b"k", b"v"]);
8268 f.run(&[b"GET", b"k"]);
8269 f.run(&[b"GET", b"nope"]);
8270 f.run(&[b"EXISTS", b"k", b"nope"]);
8271 let info = f.run(&[b"INFO", b"stats"]);
8274 assert!(info.contains("keyspace_hits:2"), "{info}");
8275 assert!(info.contains("keyspace_misses:2"), "{info}");
8276
8277 f.run(&[b"CONFIG", b"RESETSTAT"]);
8278 let info = f.run(&[b"INFO", b"stats"]);
8279 assert!(info.contains("keyspace_hits:0"), "{info}");
8280 assert!(info.contains("keyspace_misses:0"), "{info}");
8281 }
8282
8283 #[test]
8286 fn a_read_that_visits_its_key_twice_is_counted_once() {
8287 let mut f = Fixture::new();
8288 f.run(&[b"ZADD", b"z", b"1", b"m"]);
8289 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]);
8290 f.run(&[b"ZMSCORE", b"z", b"m", b"gone", b"also gone"]);
8291 f.run(&[b"OBJECT", b"ENCODING", b"z"]);
8292 f.run(&[b"DUMP", b"z"]);
8293 let info = f.run(&[b"INFO", b"stats"]);
8294 assert!(info.contains("keyspace_hits:4"), "{info}");
8295 assert!(info.contains("keyspace_misses:0"), "{info}");
8298 }
8299
8300 #[test]
8303 fn the_key_a_read_writes_afterwards_is_not_counted() {
8304 let mut f = Fixture::new();
8305 f.run(&[b"SET", b"s", b"v"]);
8306 f.run(&[b"COPY", b"s", b"dst"]);
8307 f.run(&[b"GETEX", b"s", b"EX", b"100"]);
8308 f.run(&[b"BITOP", b"AND", b"into", b"s", b"nope"]);
8309 let info = f.run(&[b"INFO", b"stats"]);
8310 assert!(info.contains("keyspace_hits:3"), "{info}");
8314 assert!(info.contains("keyspace_misses:1"), "{info}");
8315 }
8316
8317 #[test]
8318 fn the_object_subcommands_follow_the_policy() {
8319 let mut f = Fixture::new();
8320 f.run(&[b"SET", b"s", b"v"]);
8321 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
8325 assert!(
8326 f.run(&[b"OBJECT", b"FREQ", b"s"])
8327 .starts_with("-ERR An LFU maxmemory policy is not selected"),
8328 );
8329
8330 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
8331 assert!(
8332 f.run(&[b"OBJECT", b"IDLETIME", b"s"])
8333 .starts_with("-ERR An LFU maxmemory policy is selected"),
8334 );
8335 assert!(
8340 f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
8341 "FREQ should answer under an LFU policy"
8342 );
8343 }
8344
8345 #[test]
8346 fn object_says_which_rung_of_the_ladder_a_key_is_on() {
8347 let mut f = Fixture::new();
8348 f.run(&[b"SET", b"s", b"hello"]);
8349 f.run(&[b"SET", b"n", b"123"]);
8350 f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
8351 f.run(&[b"SADD", b"ss", b"a", b"b"]);
8352 f.run(&[b"HSET", b"h", b"f", b"v"]);
8353 for (key, want) in [
8354 (b"s".as_slice(), "embstr"),
8355 (b"n", "int"),
8356 (b"si", "intset"),
8357 (b"ss", "listpack"),
8358 (b"h", "listpack"),
8359 ] {
8360 let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
8361 assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
8362 }
8363
8364 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
8367 assert_eq!(
8368 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
8369 "$10\r\nlistpackex\r\n"
8370 );
8371
8372 assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
8373 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
8374 assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
8375 }
8376
8377 #[test]
8378 fn object_answers_nil_for_a_key_that_is_not_there() {
8379 let mut f = Fixture::new();
8380 for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
8381 assert_eq!(
8382 f.run(&[b"OBJECT", sub, b"nokey"]),
8383 "$-1\r\n",
8384 "a nil and not an error, which is what 8.10.1 does"
8385 );
8386 }
8387 f.run(&[b"SET", b"s", b"v"]);
8390 assert!(
8391 f.run(&[b"OBJECT", b"FREQ", b"s"])
8392 .starts_with("-ERR An LFU maxmemory policy is not"),
8393 );
8394 assert_eq!(
8395 f.run(&[b"OBJECT", b"NOPE", b"s"]),
8396 "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
8397 );
8398 assert_eq!(
8399 f.run(&[b"OBJECT", b"ENCODING"]),
8400 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
8401 );
8402 assert_eq!(
8403 f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
8404 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
8405 );
8406 assert_eq!(
8407 f.run(&[b"OBJECT"]),
8408 "-ERR wrong number of arguments for 'object' command\r\n"
8409 );
8410 }
8411
8412 #[test]
8413 fn memory_usage_counts_the_record_the_body_and_a_share_of_the_index() {
8414 let mut f = Fixture::new();
8415 assert_eq!(
8416 f.run(&[b"MEMORY", b"USAGE", b"nokey"]),
8417 "$-1\r\n",
8418 "a null and not an error, the same as OBJECT"
8419 );
8420 f.run(&[b"SET", b"s", b"hello"]);
8421 let small = int_of(&f.run(&[b"MEMORY", b"USAGE", b"s"]));
8422 assert!(
8423 small > 5,
8424 "the value is in there and so are the name and the header"
8425 );
8426 f.run(&[b"SET", b"s", &[b'x'; 1000]]);
8429 let big = int_of(&f.run(&[b"MEMORY", b"USAGE", b"s"]));
8430 assert!(
8431 big - small >= 995 && big - small <= 1005,
8432 "{small} then {big}"
8433 );
8434 f.run(&[b"SADD", b"one", b"a"]);
8437 f.run(&[b"SADD", b"many", b"a"]);
8438 for i in 0..100u32 {
8439 f.run(&[b"SADD", b"many", format!("member:{i}").as_bytes()]);
8440 }
8441 assert!(
8442 int_of(&f.run(&[b"MEMORY", b"USAGE", b"many"]))
8443 > int_of(&f.run(&[b"MEMORY", b"USAGE", b"one"]))
8444 );
8445 assert_eq!(
8448 f.run(&[b"MEMORY", b"USAGE", b"many"]),
8449 f.run(&[b"MEMORY", b"USAGE", b"many"])
8450 );
8451 }
8452
8453 #[test]
8454 fn memory_usage_reads_samples_and_does_not_use_it() {
8455 let mut f = Fixture::new();
8456 f.run(&[b"SET", b"s", b"v"]);
8457 let plain = f.run(&[b"MEMORY", b"USAGE", b"s"]);
8458 for count in [b"0".as_slice(), b"1", b"5", b"1000"] {
8459 assert_eq!(
8460 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", count]),
8461 plain
8462 );
8463 }
8464 assert_eq!(
8467 f.run(&[
8468 b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"1", b"SAMPLES", b"2"
8469 ]),
8470 plain
8471 );
8472 assert_eq!(
8473 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES"]),
8474 "-ERR syntax error\r\n"
8475 );
8476 assert_eq!(
8477 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"-1"]),
8478 "-ERR syntax error\r\n"
8479 );
8480 assert_eq!(
8481 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"nine"]),
8482 "-ERR value is not an integer or out of range\r\n"
8483 );
8484 assert_eq!(
8485 f.run(&[b"MEMORY", b"USAGE", b"s", b"BAD", b"1"]),
8486 "-ERR syntax error\r\n"
8487 );
8488 assert_eq!(
8489 f.run(&[b"MEMORY", b"USAGE"]),
8490 "-ERR wrong number of arguments for 'memory|usage' command\r\n"
8491 );
8492 }
8493
8494 #[test]
8495 fn memory_stats_grows_a_field_for_every_database_holding_a_key() {
8496 let mut f = Fixture::new();
8497 assert!(
8498 f.run(&[b"MEMORY", b"STATS"]).starts_with("*72\r\n"),
8499 "thirty six pairs on a server nobody has written to"
8500 );
8501 f.run(&[b"SET", b"a", b"1"]);
8502 assert!(f.run(&[b"MEMORY", b"STATS"]).starts_with("*74\r\n"));
8503 f.run(&[b"SELECT", b"7"]);
8504 f.run(&[b"SET", b"b", b"2"]);
8505 let reply = f.run(&[b"MEMORY", b"STATS"]);
8506 assert!(reply.starts_with("*76\r\n"));
8507 assert!(reply.contains("\r\n$4\r\ndb.0\r\n"));
8508 assert!(reply.contains("\r\n$4\r\ndb.7\r\n"));
8509 assert!(reply.contains("overhead.hashtable.main"));
8511 assert!(reply.contains("overhead.hashtable.expires"));
8512 assert!(reply.contains("fragmentation.bytes"));
8513 }
8514
8515 #[test]
8516 fn memory_answers_the_four_that_only_look() {
8517 let mut f = Fixture::new();
8518 assert!(f.run(&[b"MEMORY", b"HELP"]).starts_with("*14\r\n+MEMORY "));
8519 assert_eq!(f.run(&[b"MEMORY", b"PURGE"]), "+OK\r\n");
8520 assert_eq!(
8521 f.run(&[b"MEMORY", b"MALLOC-STATS"]),
8522 "$45\r\nStats not supported for the current allocator\r\n"
8523 );
8524 assert!(
8527 f.run(&[b"MEMORY", b"DOCTOR"])
8528 .contains("my issues detector can't be used in these conditions")
8529 );
8530 assert_eq!(
8531 f.run(&[b"MEMORY", b"NOPE"]),
8532 "-ERR unknown subcommand 'NOPE'. Try MEMORY HELP.\r\n"
8533 );
8534 for sub in [
8535 b"STATS".as_slice(),
8536 b"DOCTOR",
8537 b"PURGE",
8538 b"MALLOC-STATS",
8539 b"HELP",
8540 ] {
8541 let name = String::from_utf8_lossy(sub).to_lowercase();
8542 assert_eq!(
8543 f.run(&[b"MEMORY", sub, b"extra"]),
8544 format!("-ERR wrong number of arguments for 'memory|{name}' command\r\n"),
8545 "the subcommand is named and not the container"
8546 );
8547 }
8548 assert_eq!(
8549 f.run(&[b"MEMORY"]),
8550 "-ERR wrong number of arguments for 'memory' command\r\n"
8551 );
8552 }
8553
8554 #[test]
8555 fn command_getkeys_finds_the_key_memory_usage_names() {
8556 let mut f = Fixture::new();
8557 assert_eq!(
8558 f.run(&[b"COMMAND", b"GETKEYS", b"MEMORY", b"USAGE", b"k"]),
8559 "*1\r\n$1\r\nk\r\n"
8560 );
8561 assert!(
8564 f.run(&[b"COMMAND", b"GETKEYS", b"MEMORY", b"DOCTOR"])
8565 .starts_with("-ERR ")
8566 );
8567 }
8568
8569 #[test]
8570 fn config_moves_the_ladder_and_object_encoding_agrees() {
8571 let mut f = Fixture::new();
8572 assert_eq!(
8573 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
8574 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
8575 "512 and not the 128 everyone remembers, which is what 8.10.1 says"
8576 );
8577 assert_eq!(
8580 f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
8581 "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
8582 );
8583 assert!(
8584 f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
8585 .starts_with("*8\r\n")
8586 );
8587 assert!(
8588 f.run(&[b"CONFIG", b"GET", b"set-max-*"])
8589 .starts_with("*6\r\n")
8590 );
8591
8592 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
8593 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
8594
8595 assert_eq!(
8596 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
8597 "+OK\r\n",
8598 "written under the old name and read back under the new one"
8599 );
8600 assert_eq!(
8601 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
8602 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
8603 );
8604 assert_eq!(
8605 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
8606 "$8\r\nlistpack\r\n",
8607 "the hash that already exists is left exactly where it was"
8608 );
8609 f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
8610 assert_eq!(
8611 f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
8612 "$9\r\nhashtable\r\n",
8613 "and the next one built goes straight to a table"
8614 );
8615
8616 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
8618 f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
8619 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
8620 f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
8621 f.run(&[b"SADD", b"s2", b"abcdefgh"]);
8622 assert_eq!(
8623 f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
8624 "$9\r\nhashtable\r\n"
8625 );
8626 }
8627
8628 #[test]
8629 fn config_set_takes_all_of_the_ladder_or_none_of_it() {
8630 let mut f = Fixture::new();
8631 assert_eq!(
8632 f.run(&[
8633 b"CONFIG",
8634 b"SET",
8635 b"hash-max-listpack-entries",
8636 b"7",
8637 b"set-max-listpack-entries",
8638 b"abc"
8639 ]),
8640 "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
8641 );
8642 assert_eq!(
8643 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
8644 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
8645 "the pair in front of the bad one did not go in"
8646 );
8647 assert_eq!(
8650 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
8651 "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
8652 );
8653 assert_eq!(
8654 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
8655 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
8656 );
8657 assert_eq!(
8660 f.run(&[
8661 b"CONFIG",
8662 b"SET",
8663 b"set-max-intset-entries",
8664 b"99999999999999999999"
8665 ]),
8666 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
8667 );
8668 assert_eq!(
8669 f.run(&[
8670 b"CONFIG",
8671 b"SET",
8672 b"set-max-intset-entries",
8673 b"9223372036854775807"
8674 ]),
8675 "+OK\r\n"
8676 );
8677 }
8678
8679 #[test]
8680 fn a_setting_moved_on_one_database_moved_on_all_of_them() {
8681 let mut f = Fixture::new();
8682 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
8683 f.run(&[b"SELECT", b"3"]);
8684 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8685 assert_eq!(
8686 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
8687 "$9\r\nhashtable\r\n",
8688 "these are one server wide number in Redis, whatever a Keyspace carries"
8689 );
8690 }
8691
8692 #[test]
8693 fn info_reports_the_numbers_it_can_stand_behind() {
8694 let mut f = Fixture::new();
8695 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
8696 let all = f.run(&[b"INFO"]);
8697 assert!(all.contains("redis_version:8.8.0"), "{all}");
8698 assert!(
8699 all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
8700 "{all}"
8701 );
8702 assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
8703 assert!(all.contains("role:master"), "{all}");
8704 let clients = f.run(&[b"INFO", b"clients"]);
8706 assert!(clients.contains("connected_clients:0"), "{clients}");
8707 assert!(!clients.contains("redis_version"), "{clients}");
8708 assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
8709 }
8710
8711 #[test]
8718 fn commandstats_is_asked_for_and_replication_is_not() {
8719 let mut f = Fixture::new();
8720 for arg in ["", "all", "default", "everything"] {
8721 let info = if arg.is_empty() {
8722 f.run(&[b"INFO"])
8723 } else {
8724 f.run(&[b"INFO", arg.as_bytes()])
8725 };
8726 assert!(info.contains("redis_version"), "{arg}: {info}");
8727 assert!(info.contains("used_cpu_user"), "{arg}: {info}");
8728 assert!(info.contains("used_memory"), "{arg}: {info}");
8729 assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
8730 let asked = arg == "all" || arg == "everything";
8731 assert_eq!(
8732 info.contains("rejected_calls"),
8733 asked,
8734 "{arg} should{} carry the command counters: {info}",
8735 if asked { "" } else { " not" }
8736 );
8737 }
8738
8739 let cpu = f.run(&[b"INFO", b"cpu"]);
8740 assert!(cpu.contains("used_cpu_user"), "{cpu}");
8741 assert!(!cpu.contains("used_memory"), "{cpu}");
8742
8743 let stats = f.run(&[b"INFO", b"commandSTATS"]);
8746 assert!(!stats.contains("used_memory"), "{stats}");
8747 assert!(stats.contains("rejected_calls"), "{stats}");
8748
8749 let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
8751 assert!(pair.contains("used_cpu_user"), "{pair}");
8752 assert!(!pair.contains("master_repl_offset"), "{pair}");
8753
8754 let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
8755 assert!(with_all.contains("used_memory"), "{with_all}");
8756 assert!(with_all.contains("master_repl_offset"), "{with_all}");
8757 assert!(with_all.contains("rejected_calls"), "{with_all}");
8758 assert_eq!(
8760 with_all.matches("used_cpu_user_children").count(),
8761 1,
8762 "{with_all}"
8763 );
8764
8765 let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
8766 assert!(with_default.contains("used_memory"), "{with_default}");
8767 assert!(
8768 with_default.contains("master_repl_offset"),
8769 "{with_default}"
8770 );
8771 assert!(!with_default.contains("rejected_calls"), "{with_default}");
8772 assert_eq!(
8773 with_default.matches("used_cpu_user_children").count(),
8774 1,
8775 "{with_default}"
8776 );
8777 }
8778
8779 #[test]
8789 fn the_threads_section_says_where_the_work_landed() {
8790 let mut server = Server::new();
8791 server.set_threads(4);
8792 let mut f = Fixture::on(server);
8793 for _ in 0..3 {
8794 f.run(&[b"PING"]);
8795 }
8796
8797 assert!(!f.run(&[b"INFO"]).contains("# Threads"));
8798 assert!(f.run(&[b"INFO", b"all"]).contains("# Threads"));
8799
8800 let info = f.run(&[b"INFO", b"threads"]);
8801 assert!(info.contains("io_threads:4"), "{info}");
8802 for at in 0..4 {
8803 assert!(info.contains(&format!("thread_{at}:clients=")), "{info}");
8804 }
8805 assert!(!info.contains("thread_4:"), "{info}");
8806
8807 let per = f.server.per_thread();
8808 assert_eq!(per.len(), 4);
8809 assert_eq!(
8810 per.iter().map(|t| t.commands).sum::<u64>(),
8811 f.server.totals().commands
8812 );
8813 assert_eq!(per.iter().filter(|t| t.commands > 0).count(), 1, "{per:?}");
8814 }
8815
8816 #[test]
8825 fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
8826 let mut f = Fixture::new();
8827 let info = f.run(&[b"INFO", b"memory"]);
8828 for field in [
8829 "total_system_memory:",
8830 "mem_cgroup_limit:",
8831 "mem_limit:",
8832 "mem_budget:",
8833 ] {
8834 assert!(info.contains(field), "no {field} in {info}");
8835 }
8836
8837 let field = |name: &str| -> u64 {
8838 info.lines()
8839 .find_map(|l| l.strip_prefix(name))
8840 .unwrap_or_else(|| panic!("no {name} in {info}"))
8841 .trim()
8842 .parse()
8843 .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
8844 };
8845 let limit = field("mem_limit:");
8846 assert_eq!(field("mem_budget:"), limit / 4, "{info}");
8847 if limit != 0 {
8850 let host = field("total_system_memory:");
8851 let cgroup = field("mem_cgroup_limit:");
8852 assert!(
8853 limit == host || limit == cgroup,
8854 "the limit came from neither number: {info}"
8855 );
8856 }
8857 }
8858
8859 #[test]
8868 fn a_command_counts_what_it_did_separately_from_what_it_refused() {
8869 let mut f = Fixture::new();
8870 f.run(&[b"SET", b"k", b"v"]);
8871 f.run(&[b"SET", b"k", b"w"]);
8872 f.run(&[b"LPUSH", b"k", b"x"]);
8874 f.run(&[b"LPUSH", b"k"]);
8876
8877 let stats = f.run(&[b"INFO", b"commandstats"]);
8878 assert!(
8879 stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
8880 "{stats}"
8881 );
8882 assert!(
8883 stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
8884 "{stats}"
8885 );
8886 assert!(
8887 !stats.contains("cmdstat_zadd"),
8888 "a command nobody has sent has no row: {stats}"
8889 );
8890 }
8891
8892 #[test]
8896 fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
8897 let (dead, live) = if cfg!(miri) {
8904 (375, 125)
8905 } else {
8906 (3_000, 1_000)
8907 };
8908 let mut f = Fixture::new();
8909 for i in 0..dead {
8910 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
8911 }
8912 for i in 0..live {
8913 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
8914 }
8915 let all = format!(":{}\r\n", dead + live);
8916 assert_eq!(f.run(&[b"DBSIZE"]), all);
8917 f.advance(100);
8918 assert_eq!(
8919 f.run(&[b"DBSIZE"]),
8920 all,
8921 "DBSIZE counts records and nothing has read past the dead ones yet"
8922 );
8923
8924 let rest = format!(":{live}\r\n");
8926 let mut spent = 0;
8927 for _ in 0..2_000 {
8928 spent += f.server.expire_step(4096);
8929 if f.run(&[b"DBSIZE"]) == rest {
8930 break;
8931 }
8932 }
8933 assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
8934 assert!(
8935 f.run(&[b"INFO", b"stats"])
8936 .contains(&format!("expired_keys:{dead}"))
8937 );
8938 for i in 0..live {
8939 assert_eq!(
8940 f.run(&[b"GET", format!("k{i}").as_bytes()]),
8941 "$1\r\nv\r\n",
8942 "it took a key that had no deadline"
8943 );
8944 }
8945 }
8946
8947 #[test]
8948 fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
8949 let n = if cfg!(miri) { 200 } else { 2_000 };
8953 let mut f = Fixture::new();
8954 for i in 0..n {
8955 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
8956 }
8957 assert_eq!(f.server.expire_step(4096), 0);
8958 f.run(&[b"SELECT", b"3"]);
8960 f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
8961 f.advance(100);
8962 for _ in 0..64 {
8963 f.server.expire_step(4096);
8964 }
8965 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8966 f.run(&[b"SELECT", b"0"]);
8967 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
8968 assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
8969 }
8970
8971 #[test]
8974 fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
8975 let mut f = Fixture::new();
8976 for i in 0..500u32 {
8977 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
8978 }
8979 f.advance(100);
8980 let at = f.server.striped(0).now_ms();
8981 f.server.set_clock_ms(at);
8982 assert!(f.server.expire_slice(8) > 0, "the first one works");
8986 for _ in 0..1_000 {
8987 assert_eq!(
8988 f.server.expire_slice(8),
8989 0,
8990 "the millisecond has not moved and neither should this"
8991 );
8992 }
8993 assert!(
8994 f.server.striped(0).expires() > 400,
8995 "there is plenty left to take"
8996 );
8997 f.server.set_clock_ms(at + 1);
8998 assert!(f.server.expire_slice(8) > 0, "and then it goes again");
8999 }
9000
9001 #[test]
9004 fn info_keyspace_counts_the_keys_that_have_a_deadline() {
9005 let mut f = Fixture::new();
9006 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
9007 assert!(
9008 f.run(&[b"INFO", b"keyspace"])
9009 .contains("db0:keys=3,expires=0"),
9010 "none of them has one yet"
9011 );
9012 f.run(&[b"EXPIRE", b"a", b"1000"]);
9013 f.run(&[b"EXPIRE", b"b", b"1000"]);
9014 let two = f.run(&[b"INFO", b"keyspace"]);
9015 assert!(two.contains("db0:keys=3,expires=2"), "{two}");
9016 f.run(&[b"PERSIST", b"a"]);
9017 f.run(&[b"DEL", b"b"]);
9018 let none = f.run(&[b"INFO", b"keyspace"]);
9019 assert!(none.contains("db0:keys=2,expires=0"), "{none}");
9020
9021 f.run(&[b"SELECT", b"1"]);
9023 f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
9024 let both = f.run(&[b"INFO", b"keyspace"]);
9025 assert!(both.contains("db0:keys=2,expires=0"), "{both}");
9026 assert!(both.contains("db1:keys=1,expires=1"), "{both}");
9027 }
9028
9029 #[cfg(unix)]
9036 #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
9037 #[test]
9038 fn info_cpu_reports_processor_time_that_was_really_measured() {
9039 let mut f = Fixture::new();
9040 let cpu = f.run(&[b"INFO", b"cpu"]);
9041 assert!(cpu.contains("# CPU"), "{cpu}");
9042 assert!(cpu.contains("used_cpu_user:"), "{cpu}");
9044 assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
9045 assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
9046 assert!(!cpu.contains("redis_version"), "{cpu}");
9047
9048 let before = used_cpu_user(&cpu);
9052 let mut n = 0u64;
9053 let mut rounds = 0;
9054 while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
9055 for i in 0..1_000_000u64 {
9056 n = n.wrapping_add(i.wrapping_mul(i));
9057 }
9058 rounds += 1;
9059 assert!(rounds < 1_000, "cpu time never moved, n is {n}");
9063 }
9064 }
9065
9066 #[cfg(unix)]
9068 fn used_cpu_user(info: &str) -> f64 {
9069 info.lines()
9070 .find_map(|l| l.strip_prefix("used_cpu_user:"))
9071 .expect("no used_cpu_user in the reply")
9072 .trim()
9073 .parse()
9074 .expect("used_cpu_user is not a number")
9075 }
9076
9077 #[test]
9083 fn a_command_that_fails_leaves_nothing_half_written() {
9084 let mut f = Fixture::new();
9085 let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
9086 assert_eq!(reply, "-ERR offset is out of range\r\n");
9087 assert!(!reply.contains(':'), "no integer went out in front of it");
9088 }
9089
9090 #[test]
9091 fn quit_answers_first_and_closes_after() {
9092 let mut f = Fixture::new();
9093 let (flow, reply) = f.flow(&[b"QUIT"]);
9094 assert_eq!(reply, "+OK\r\n");
9095 assert_eq!(flow, Flow::Close);
9096 }
9097
9098 #[test]
9105 fn shutdown_writes_nothing_and_sets_the_flag() {
9106 let mut f = Fixture::new();
9107 assert!(!f.server.stopping(), "nobody has asked yet");
9108
9109 let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
9110 assert_eq!(reply, "");
9111 assert_eq!(flow, Flow::Close);
9112 assert!(f.server.stopping());
9113 }
9114
9115 #[test]
9127 fn shutdown_takes_the_flags_redis_takes() {
9128 let s = Saves::new("shutdown-flags");
9129 for flags in [
9130 &[b"NOSAVE".as_slice()][..],
9131 &[b"SAVE"],
9132 &[b"NOW"],
9133 &[b"FORCE"],
9134 &[b"nosave"],
9135 &[b"NOW", b"NOW"],
9136 &[b"SAVE", b"SAVE"],
9137 &[b"NOSAVE", b"NOW", b"FORCE"],
9138 ] {
9139 let mut f = Fixture::new();
9140 f.server.set_dir(s.dir.clone());
9141 let mut parts = vec![b"SHUTDOWN".as_slice()];
9142 parts.extend_from_slice(flags);
9143 let (flow, reply) = f.flow(&parts);
9144 assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
9145 assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
9146 assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
9147 }
9148
9149 for flags in [
9150 &[b"BOGUS".as_slice()][..],
9151 &[b"SAVE", b"NOSAVE"],
9152 &[b"NOSAVE", b"SAVE"],
9153 &[b"ABORT", b"NOW"],
9154 &[b"NOSAVE", b"ABORT"],
9155 &[b"NOW", b"FORCE", b"ABORT"],
9156 ] {
9157 let mut f = Fixture::new();
9158 let mut parts = vec![b"SHUTDOWN".as_slice()];
9159 parts.extend_from_slice(flags);
9160 assert_eq!(
9161 f.run(&parts),
9162 "-ERR syntax error\r\n",
9163 "SHUTDOWN {flags:?} was accepted"
9164 );
9165 assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
9166 }
9167 }
9168
9169 #[test]
9177 fn shutdown_abort_never_has_anything_to_abort() {
9178 let mut f = Fixture::new();
9179 for parts in [
9180 &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
9181 &[b"SHUTDOWN", b"ABORT", b"ABORT"],
9182 ] {
9183 assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
9184 assert!(!f.server.stopping(), "an abort stopped the server");
9185 }
9186 }
9187
9188 struct Backups {
9196 f: Fixture,
9197 dir: PathBuf,
9198 }
9199
9200 impl Backups {
9201 fn new(name: &str) -> Backups {
9202 let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
9203 let _ = std::fs::remove_dir_all(&dir);
9204 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
9205 let mut f = Fixture::new();
9206 f.server.set_dir(dir.clone());
9207 Backups { f, dir }
9208 }
9209
9210 fn run(&mut self, parts: &[&[u8]]) -> String {
9211 self.f.run(parts)
9212 }
9213
9214 fn files(&self) -> Vec<String> {
9216 let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
9217 Ok(entries) => entries
9218 .filter_map(|e| e.ok())
9219 .map(|e| e.file_name().to_string_lossy().into_owned())
9220 .collect(),
9221 Err(_) => Vec::new(),
9222 };
9223 names.sort();
9224 names
9225 }
9226
9227 fn read(&self, name: &str) -> Vec<u8> {
9228 std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
9229 }
9230 }
9231
9232 impl Drop for Backups {
9233 fn drop(&mut self) {
9234 let _ = std::fs::remove_dir_all(&self.dir);
9235 }
9236 }
9237
9238 #[test]
9241 fn backup_walks_the_states_the_reference_walks() {
9242 let mut b = Backups::new("states");
9243 let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
9244
9245 assert!(status(&mut b).contains("idle"));
9246 assert!(b.files().is_empty(), "an idle server has written a backup");
9247
9248 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
9249 assert!(status(&mut b).contains("incrementing"));
9250 assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
9251
9252 assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
9253 assert!(status(&mut b).contains("sealed"));
9254 assert_eq!(
9255 b.files(),
9256 [
9257 "appendonly.aof.1.base.rdb",
9258 "appendonly.aof.1.incr.aof",
9259 "appendonly.aof.manifest",
9260 ]
9261 );
9262
9263 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
9264 assert!(status(&mut b).contains("idle"));
9265 assert!(b.files().is_empty(), "cleanup left something behind");
9266 }
9267
9268 #[test]
9270 fn backup_refuses_the_moves_the_reference_refuses() {
9271 let mut b = Backups::new("refusals");
9272
9273 assert_eq!(
9274 b.run(&[b"BACKUP", b"SEAL"]),
9275 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
9276 );
9277 assert_eq!(
9278 b.run(&[b"BACKUP", b"ABORT"]),
9279 "-ERR No backup in progress\r\n"
9280 );
9281 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
9284
9285 b.run(&[b"BACKUP", b"START"]);
9286 assert_eq!(
9287 b.run(&[b"BACKUP", b"START"]),
9288 "-ERR A backup is already in progress, ABORT it first\r\n"
9289 );
9290 assert_eq!(
9291 b.run(&[b"BACKUP", b"CLEANUP"]),
9292 "-ERR Backup is in progress\r\n"
9293 );
9294
9295 b.run(&[b"BACKUP", b"SEAL"]);
9296 assert_eq!(
9297 b.run(&[b"BACKUP", b"START"]),
9298 "-ERR A sealed backup exists, CLEANUP it first\r\n"
9299 );
9300 assert_eq!(
9301 b.run(&[b"BACKUP", b"SEAL"]),
9302 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
9303 );
9304 assert_eq!(
9305 b.run(&[b"BACKUP", b"ABORT"]),
9306 "-ERR No backup in progress\r\n"
9307 );
9308 }
9309
9310 #[test]
9317 fn backup_abort_removes_the_file_and_says_who_did_it() {
9318 let mut b = Backups::new("abort");
9319 b.run(&[b"BACKUP", b"START"]);
9320 assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
9321
9322 let status = b.run(&[b"BACKUP", b"STATUS"]);
9323 assert!(status.contains("failed"), "{status}");
9324 assert!(status.contains("aborted by user"), "{status}");
9325 assert!(b.files().is_empty(), "abort left the base file behind");
9326 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
9327
9328 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
9330 assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
9331 let status = b.run(&[b"BACKUP", b"STATUS"]);
9332 assert!(status.contains("incrementing"), "{status}");
9333 assert!(!status.contains("aborted"), "the old error was kept");
9334 }
9335
9336 #[test]
9338 fn backup_list_names_the_files_that_are_pinned_so_far() {
9339 let mut b = Backups::new("list");
9340 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
9341
9342 b.run(&[b"BACKUP", b"START"]);
9343 let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
9344 let base = base.to_string_lossy().into_owned();
9345 assert_eq!(
9346 b.run(&[b"BACKUP", b"LIST"]),
9347 format!("*1\r\n${}\r\n{base}\r\n", base.len())
9348 );
9349
9350 b.run(&[b"BACKUP", b"SEAL"]);
9351 let listed = b.run(&[b"BACKUP", b"LIST"]);
9352 assert!(listed.starts_with("*3\r\n"), "{listed}");
9353 let names: Vec<&str> = listed
9356 .lines()
9357 .filter(|l| l.starts_with('/') || l.contains(":\\"))
9358 .collect();
9359 assert_eq!(names.len(), 3, "{listed}");
9360 assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
9361 assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
9362 assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
9363 }
9364
9365 #[test]
9373 fn a_backup_holds_the_dataset_as_it_was_at_start() {
9374 let mut b = Backups::new("contents");
9375 b.run(&[b"SET", b"bk", b"v1"]);
9376 b.run(&[b"BACKUP", b"START"]);
9377 b.run(&[b"SET", b"bk", b"v2"]);
9378 b.run(&[b"BACKUP", b"SEAL"]);
9379
9380 let base = b.read("appendonly.aof.1.base.rdb");
9381 assert!(base.starts_with(b"REDIS"), "not an RDB file");
9382 assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
9383 assert!(
9384 !base.windows(2).any(|w| w == b"v2"),
9385 "the base file moved on after START"
9386 );
9387 let at = base
9392 .windows(8)
9393 .position(|w| w == b"aof-base")
9394 .expect("no aof-base aux field");
9395 assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
9396
9397 assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
9398 assert_eq!(
9399 String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
9400 "file appendonly.aof.1.base.rdb seq 1 type b\n\
9401 file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
9402 );
9403 }
9404
9405 #[test]
9408 fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
9409 let mut b = Backups::new("status");
9410 b.f.server.set_clock_ms(1_700_000_000_000);
9411
9412 assert_eq!(
9413 b.run(&[b"BACKUP", b"STATUS"]),
9414 "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
9415 $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
9416 );
9417
9418 b.f.out = Out::new(Proto::Resp3);
9419 b.run(&[b"BACKUP", b"START"]);
9420 assert_eq!(
9421 b.run(&[b"BACKUP", b"STATUS"]),
9422 "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
9423 $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
9424 );
9425
9426 b.run(&[b"BACKUP", b"SEAL"]);
9427 let sealed = b.run(&[b"BACKUP", b"STATUS"]);
9428 assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
9429 }
9430
9431 #[test]
9434 fn a_sealed_backup_is_swept_away_after_the_timeout() {
9435 let mut b = Backups::new("ttl");
9436 b.f.server.set_clock_ms(1_000_000);
9437 assert_eq!(
9438 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
9439 "+OK\r\n"
9440 );
9441 b.run(&[b"BACKUP", b"START"]);
9442 b.run(&[b"BACKUP", b"SEAL"]);
9443
9444 b.f.server.set_clock_ms(1_000_000 + 59_000);
9446 b.f.server.backup_expire();
9447 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
9448 assert_eq!(b.files().len(), 3);
9449
9450 b.f.server.set_clock_ms(1_000_000 + 60_000);
9451 b.f.server.backup_expire();
9452 let status = b.run(&[b"BACKUP", b"STATUS"]);
9453 assert!(status.contains("idle"), "{status}");
9454 assert!(b.files().is_empty(), "the timeout left the files behind");
9455
9456 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
9458 b.run(&[b"BACKUP", b"START"]);
9459 b.run(&[b"BACKUP", b"SEAL"]);
9460 b.f.server.set_clock_ms(9_000_000_000);
9461 b.f.server.backup_expire();
9462 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
9463 }
9464
9465 #[test]
9468 fn the_backup_settings_behave_the_way_the_reference_does() {
9469 let mut b = Backups::new("config");
9470 let dir = b.dir.to_string_lossy().into_owned();
9471
9472 assert_eq!(
9473 b.run(&[b"CONFIG", b"GET", b"dir"]),
9474 format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
9475 );
9476 assert_eq!(
9477 b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
9478 "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
9479 );
9480 assert_eq!(
9481 b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
9482 "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
9483 );
9484
9485 assert_eq!(
9488 b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
9489 "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
9490 );
9491 assert_eq!(
9492 b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
9493 "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
9494 );
9495 assert!(
9496 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
9497 .contains("argument couldn't be parsed into an integer")
9498 );
9499 assert!(
9500 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
9501 .contains("argument must be between 0 and 9223372036854775807 inclusive")
9502 );
9503 }
9504
9505 #[test]
9507 fn backup_help_is_the_text_the_reference_sends() {
9508 let mut f = Fixture::new();
9509 let help = f.run(&[b"BACKUP", b"HELP"]);
9510 assert!(help.starts_with("*17\r\n"), "{help}");
9511 assert!(
9512 help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
9513 );
9514 assert!(help.contains("+ Start a new backup into the configured 'backupdirname'.\r\n"));
9515 assert!(help.contains("+ Freeze the current backup (BASE + INCR + manifest).\r\n"));
9516 assert!(help.contains("+ Return this help.\r\n+HELP\r\n+ Print this help.\r\n"));
9517 }
9518
9519 #[test]
9527 fn backup_refuses_what_it_cannot_read() {
9528 let mut f = Fixture::new();
9529 assert_eq!(
9530 f.run(&[b"BACKUP"]),
9531 "-ERR wrong number of arguments for 'backup' command\r\n"
9532 );
9533 assert_eq!(
9534 f.run(&[b"BACKUP", b"START", b"x"]),
9535 "-ERR wrong number of arguments for 'backup' command\r\n"
9536 );
9537 assert_eq!(
9538 f.run(&[b"BACKUP", b"NOPE"]),
9539 "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
9540 );
9541 }
9542
9543 struct Saves {
9549 f: Fixture,
9550 dir: PathBuf,
9551 }
9552
9553 impl Saves {
9554 fn new(name: &str) -> Saves {
9555 let dir = std::env::temp_dir().join(format!("yo-save-{name}-{}", std::process::id()));
9556 let _ = std::fs::remove_dir_all(&dir);
9557 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
9558 let mut f = Fixture::new();
9559 f.server.set_dir(dir.clone());
9560 Saves { f, dir }
9561 }
9562
9563 fn run(&mut self, parts: &[&[u8]]) -> String {
9564 self.f.run(parts)
9565 }
9566
9567 fn files(&self) -> Vec<String> {
9569 let mut names: Vec<String> = match std::fs::read_dir(&self.dir) {
9570 Ok(entries) => entries
9571 .filter_map(|e| e.ok())
9572 .map(|e| e.file_name().to_string_lossy().into_owned())
9573 .collect(),
9574 Err(_) => Vec::new(),
9575 };
9576 names.sort();
9577 names
9578 }
9579
9580 fn image(&self) -> Vec<u8> {
9581 std::fs::read(self.dir.join("dump.rdb")).expect("could not read the file")
9582 }
9583
9584 fn field(&mut self, name: &str) -> String {
9586 let text = self.run(&[b"INFO", b"persistence"]);
9587 let head = format!("\r\n{name}:");
9588 let at = text.find(&head).expect("the field is not in the section");
9589 let rest = &text[at + head.len()..];
9590 rest[..rest.find("\r\n").expect("the field has no end")].to_owned()
9591 }
9592 }
9593
9594 impl Drop for Saves {
9595 fn drop(&mut self) {
9596 let _ = std::fs::remove_dir_all(&self.dir);
9597 }
9598 }
9599
9600 #[test]
9601 fn save_writes_a_file_that_carries_the_dataset() {
9602 let mut s = Saves::new("writes");
9603 s.run(&[b"SET", b"k", b"v"]);
9604 s.run(&[b"RPUSH", b"l", b"a", b"b"]);
9605 assert!(
9606 s.files().is_empty(),
9607 "a server has saved without being asked"
9608 );
9609
9610 assert_eq!(s.run(&[b"SAVE"]), "+OK\r\n");
9611 assert_eq!(s.files(), ["dump.rdb"]);
9612
9613 let image = s.image();
9618 assert!(
9619 image.starts_with(b"REDIS00"),
9620 "the header is not an RDB one"
9621 );
9622 assert!(
9623 image.windows(1).any(|w| w == [0xFF]),
9624 "there is no end marker"
9625 );
9626 assert!(image.len() > 40, "the file is too small to hold anything");
9627 }
9628
9629 #[test]
9630 fn a_save_leaves_no_temporary_file_behind() {
9631 let mut s = Saves::new("temp");
9632 s.run(&[b"SET", b"k", b"v"]);
9633 s.run(&[b"SAVE"]);
9634 s.run(&[b"BGSAVE"]);
9635 assert_eq!(s.files(), ["dump.rdb"]);
9636 }
9637
9638 #[test]
9639 fn a_save_that_cannot_write_says_so_in_one_word() {
9640 let mut s = Saves::new("nowhere");
9641 s.f.server.set_dir(s.dir.join("gone"));
9644 assert_eq!(s.run(&[b"SAVE"]), "-ERR\r\n");
9645 assert_eq!(s.field("rdb_last_bgsave_status"), "err");
9646 assert_eq!(s.field("rdb_saves"), "1");
9648 }
9649
9650 #[test]
9651 fn lastsave_starts_at_the_time_the_server_did_and_moves_on_a_save() {
9652 let mut s = Saves::new("lastsave");
9653 let started = s.run(&[b"LASTSAVE"]);
9654 assert_eq!(started, format!(":{}\r\n", s.f.server.started_ms / 1_000));
9655
9656 s.f.server.set_clock_ms(s.f.server.started_ms + 5_000);
9657 s.run(&[b"SAVE"]);
9658 let after = s.run(&[b"LASTSAVE"]);
9659 assert_eq!(after, format!(":{}\r\n", s.f.server.started_ms / 1_000 + 5));
9660
9661 s.run(&[b"SET", b"k", b"v"]);
9663 assert_eq!(s.run(&[b"LASTSAVE"]), after);
9664 }
9665
9666 #[test]
9667 fn bgsave_takes_the_one_word_it_takes_and_nothing_else() {
9668 let mut s = Saves::new("bgsave");
9669 for parts in [
9670 &[b"BGSAVE".as_slice()][..],
9671 &[b"BGSAVE", b"SCHEDULE"],
9672 &[b"BGSAVE", b"schedule"],
9673 ] {
9674 assert_eq!(s.run(parts), "+Background saving started\r\n");
9675 }
9676 for parts in [
9677 &[b"BGSAVE".as_slice(), b"x"][..],
9678 &[b"BGSAVE", b"SCHEDULE", b"x"],
9679 &[b"BGSAVE", b"SCHEDULE", b"SCHEDULE"],
9680 ] {
9681 assert_eq!(s.run(parts), "-ERR syntax error\r\n");
9682 }
9683 }
9684
9685 #[test]
9686 fn a_save_inside_a_transaction_says_it_was_scheduled() {
9687 let mut s = Saves::new("queued");
9688 assert_eq!(s.run(&[b"MULTI"]), "+OK\r\n");
9690 assert_eq!(
9691 s.run(&[b"SAVE"]),
9692 "-ERR Command not allowed inside a transaction\r\n"
9693 );
9694 assert_eq!(
9695 s.run(&[b"EXEC"]),
9696 "-EXECABORT Transaction discarded because of previous errors.\r\n"
9697 );
9698
9699 assert_eq!(s.run(&[b"MULTI"]), "+OK\r\n");
9700 assert_eq!(s.run(&[b"BGSAVE"]), "+QUEUED\r\n");
9701 assert_eq!(s.run(&[b"BGREWRITEAOF"]), "+QUEUED\r\n");
9702 assert_eq!(
9703 s.run(&[b"EXEC"]),
9704 "*2\r\n+Background saving scheduled\r\n\
9705 +Background append only file rewriting scheduled\r\n"
9706 );
9707 assert_eq!(s.files(), ["dump.rdb"]);
9709 }
9710
9711 #[test]
9712 fn a_rewrite_counts_itself_and_writes_nothing() {
9713 let mut s = Saves::new("rewrite");
9714 assert_eq!(
9715 s.run(&[b"BGREWRITEAOF"]),
9716 "+Background append only file rewriting started\r\n"
9717 );
9718 assert_eq!(s.field("aof_rewrites"), "1");
9719 assert_eq!(s.field("aof_enabled"), "0");
9720 assert!(s.files().is_empty(), "a rewrite has written a file");
9721 }
9722
9723 #[test]
9724 fn role_says_master_with_nothing_following_it() {
9725 let mut f = Fixture::new();
9726 assert_eq!(f.run(&[b"ROLE"]), "*3\r\n$6\r\nmaster\r\n:0\r\n*0\r\n");
9727 assert_eq!(
9728 f.run(&[b"ROLE", b"x"]),
9729 "-ERR wrong number of arguments for 'role' command\r\n"
9730 );
9731 }
9732
9733 #[test]
9734 fn the_persistence_section_counts_the_saves_that_were_asked_for() {
9735 let mut s = Saves::new("counts");
9736 assert_eq!(s.field("rdb_saves"), "0");
9737 assert_eq!(s.field("rdb_last_bgsave_status"), "ok");
9738 s.run(&[b"SAVE"]);
9739 s.run(&[b"BGSAVE"]);
9740 s.run(&[b"BGSAVE", b"SCHEDULE"]);
9741 assert_eq!(s.field("rdb_saves"), "3");
9742 assert_eq!(s.field("rdb_bgsave_in_progress"), "0");
9743 assert_eq!(s.field("loading"), "0");
9744 }
9745
9746 #[test]
9747 fn the_persistence_section_is_in_a_bare_info_and_not_in_another_one() {
9748 let mut f = Fixture::new();
9749 assert!(f.run(&[b"INFO"]).contains("# Persistence"));
9750 assert!(f.run(&[b"INFO", b"persistence"]).contains("# Persistence"));
9751 assert!(f.run(&[b"INFO", b"all"]).contains("# Persistence"));
9752 assert!(!f.run(&[b"INFO", b"clients"]).contains("# Persistence"));
9753 }
9754
9755 #[test]
9756 fn the_file_name_reads_back_and_cannot_be_written() {
9757 let mut f = Fixture::new();
9758 assert_eq!(
9759 f.run(&[b"CONFIG", b"GET", b"dbfilename"]),
9760 "*2\r\n$10\r\ndbfilename\r\n$8\r\ndump.rdb\r\n"
9761 );
9762 assert_eq!(
9763 f.run(&[b"CONFIG", b"SET", b"dbfilename", b"other.rdb"]),
9764 "-ERR CONFIG SET failed (possibly related to argument 'dbfilename') - can't set protected config\r\n"
9765 );
9766 assert_eq!(
9769 f.run(&[b"CONFIG", b"SET", b"dbfilename", b"dump.rdb"]),
9770 "-ERR CONFIG SET failed (possibly related to argument 'dbfilename') - can't set protected config\r\n"
9771 );
9772 }
9773
9774 #[test]
9775 fn shutdown_save_writes_the_file_and_shutdown_on_its_own_does_not() {
9776 let mut s = Saves::new("shutdown");
9777 s.run(&[b"SET", b"k", b"v"]);
9778 s.run(&[b"SHUTDOWN", b"NOSAVE"]);
9779 assert!(s.files().is_empty(), "a nosave shutdown wrote a file");
9780
9781 let mut s = Saves::new("shutdown-save");
9782 s.run(&[b"SET", b"k", b"v"]);
9783 s.run(&[b"SHUTDOWN", b"SAVE"]);
9784 assert_eq!(s.files(), ["dump.rdb"]);
9785 }
9786
9787 #[test]
9793 fn debug_reload_brings_every_type_back_the_way_it_went_in() {
9794 let mut s = Saves::new("reload-types");
9795 s.run(&[b"SET", b"str", b"hello"]);
9796 s.run(&[b"SET", b"num", b"1234"]);
9797 s.run(&[b"RPUSH", b"list", b"a", b"b", b"c"]);
9798 s.run(&[b"SADD", b"set", b"x", b"y"]);
9799 s.run(&[b"SADD", b"ints", b"1", b"2", b"3"]);
9800 s.run(&[b"HSET", b"hash", b"f", b"v", b"g", b"w"]);
9801 s.run(&[b"ZADD", b"zset", b"1.5", b"m", b"2", b"n"]);
9802 s.run(&[b"XADD", b"stream", b"1-1", b"f", b"v"]);
9803 s.run(&[b"PEXPIREAT", b"str", b"4102444800000"]);
9804 let before = s.run(&[b"DBSIZE"]);
9805
9806 assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "+OK\r\n");
9807
9808 assert_eq!(s.run(&[b"DBSIZE"]), before);
9809 assert_eq!(s.run(&[b"GET", b"str"]), "$5\r\nhello\r\n");
9810 assert_eq!(s.run(&[b"GET", b"num"]), "$4\r\n1234\r\n");
9811 assert_eq!(
9812 s.run(&[b"LRANGE", b"list", b"0", b"-1"]),
9813 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
9814 );
9815 assert_eq!(s.run(&[b"SCARD", b"set"]), ":2\r\n");
9816 assert_eq!(s.run(&[b"SISMEMBER", b"set", b"y"]), ":1\r\n");
9817 assert_eq!(s.run(&[b"SCARD", b"ints"]), ":3\r\n");
9818 assert_eq!(s.run(&[b"HGET", b"hash", b"g"]), "$1\r\nw\r\n");
9819 assert_eq!(s.run(&[b"ZSCORE", b"zset", b"m"]), "$3\r\n1.5\r\n");
9820 assert_eq!(s.run(&[b"XLEN", b"stream"]), ":1\r\n");
9821 assert_eq!(s.run(&[b"PEXPIRETIME", b"str"]), ":4102444800000\r\n");
9824 assert_eq!(s.run(&[b"PEXPIRETIME", b"num"]), ":-1\r\n");
9825 }
9826
9827 #[test]
9829 fn debug_reload_puts_every_key_back_in_its_own_database() {
9830 let mut s = Saves::new("reload-dbs");
9831 s.run(&[b"SET", b"home", b"zero"]);
9832 s.run(&[b"SELECT", b"9"]);
9833 s.run(&[b"SET", b"away", b"nine"]);
9834 s.run(&[b"SELECT", b"0"]);
9835
9836 assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "+OK\r\n");
9837
9838 assert_eq!(s.run(&[b"GET", b"home"]), "$4\r\nzero\r\n");
9839 assert_eq!(s.run(&[b"EXISTS", b"away"]), ":0\r\n");
9840 s.run(&[b"SELECT", b"9"]);
9841 assert_eq!(s.run(&[b"GET", b"away"]), "$4\r\nnine\r\n");
9842 assert_eq!(s.run(&[b"EXISTS", b"home"]), ":0\r\n");
9843 }
9844
9845 #[test]
9847 fn debug_reload_nosave_reads_the_file_that_is_already_there() {
9848 let mut s = Saves::new("reload-nosave");
9849 s.run(&[b"SET", b"k", b"first"]);
9850 s.run(&[b"SAVE"]);
9851 s.run(&[b"SET", b"k", b"second"]);
9852 s.run(&[b"SET", b"later", b"x"]);
9853
9854 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), "+OK\r\n");
9855
9856 assert_eq!(s.run(&[b"GET", b"k"]), "$5\r\nfirst\r\n");
9858 assert_eq!(s.run(&[b"EXISTS", b"later"]), ":0\r\n");
9859 }
9860
9861 #[test]
9863 fn debug_reload_noflush_keeps_what_the_file_does_not_mention() {
9864 let mut s = Saves::new("reload-noflush");
9865 s.run(&[b"SET", b"k", b"first"]);
9866 s.run(&[b"SAVE"]);
9867 s.run(&[b"SET", b"k", b"second"]);
9868 s.run(&[b"SET", b"later", b"x"]);
9869
9870 assert_eq!(
9871 s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE", b"NOFLUSH"]),
9872 "+OK\r\n"
9873 );
9874
9875 assert_eq!(s.run(&[b"GET", b"k"]), "$5\r\nfirst\r\n");
9879 assert_eq!(s.run(&[b"GET", b"later"]), "$1\r\nx\r\n");
9880 }
9881
9882 #[test]
9884 fn debug_reload_takes_its_three_words_and_no_others() {
9885 let mut s = Saves::new("reload-words");
9886 s.run(&[b"SET", b"k", b"v"]);
9887 for parts in [
9888 &[b"DEBUG".as_slice(), b"RELOAD"][..],
9889 &[b"DEBUG", b"RELOAD", b"NOSAVE"],
9890 &[b"DEBUG", b"RELOAD", b"nosave"],
9891 &[b"DEBUG", b"RELOAD", b"MERGE"],
9892 &[b"DEBUG", b"RELOAD", b"NOFLUSH"],
9893 &[b"DEBUG", b"RELOAD", b"MERGE", b"NOFLUSH", b"NOSAVE"],
9894 &[b"DEBUG", b"RELOAD", b"NOSAVE", b"NOSAVE"],
9896 ] {
9897 assert_eq!(s.run(parts), "+OK\r\n", "{parts:?}");
9898 }
9899 for parts in [
9900 &[b"DEBUG".as_slice(), b"RELOAD", b"BOGUS"][..],
9901 &[b"DEBUG", b"RELOAD", b"NOSAVE", b"BOGUS"],
9902 &[b"DEBUG", b"RELOAD", b""],
9903 ] {
9904 assert_eq!(
9905 s.run(parts),
9906 "-ERR DEBUG RELOAD only supports the MERGE, NOFLUSH and NOSAVE options.\r\n",
9907 "{parts:?}"
9908 );
9909 }
9910 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
9912 }
9913
9914 #[test]
9916 fn debug_reload_that_cannot_write_the_file_says_so_in_one_word() {
9917 let mut s = Saves::new("reload-nowhere");
9918 s.run(&[b"SET", b"k", b"v"]);
9919 s.f.server.set_dir(s.dir.join("gone"));
9920 assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "-ERR\r\n");
9921 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
9923 }
9924
9925 #[test]
9931 fn debug_reload_that_cannot_read_the_file_says_to_check_the_log() {
9932 let mut s = Saves::new("reload-unreadable");
9933 s.run(&[b"SET", b"k", b"v"]);
9934 let failed = "-ERR Error trying to load the RDB dump, check server logs.\r\n";
9935 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), failed);
9936 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
9938
9939 s.run(&[b"SAVE"]);
9940 std::fs::write(s.dir.join("dump.rdb"), b"not an RDB file at all")
9941 .expect("could not write over the file");
9942 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), failed);
9943 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
9944 }
9945
9946 #[test]
9953 fn debug_reload_refuses_to_drop_a_key_with_no_rdb_form() {
9954 let mut s = Saves::new("reload-foreign");
9955 s.run(&[b"SET", b"k", b"v"]);
9956 s.run(&[b"TS.CREATE", b"ts"]);
9957 s.run(&[b"TS.ADD", b"ts", b"1000", b"1.5"]);
9958
9959 assert_eq!(
9960 s.run(&[b"DEBUG", b"RELOAD"]),
9961 "-ERR DEBUG RELOAD would drop 1 key with no RDB form, use NOFLUSH to keep it\r\n"
9962 );
9963 assert_eq!(s.run(&[b"EXISTS", b"ts"]), ":1\r\n");
9964
9965 s.run(&[b"TS.CREATE", b"ts2"]);
9966 assert_eq!(
9967 s.run(&[b"DEBUG", b"RELOAD"]),
9968 "-ERR DEBUG RELOAD would drop 2 keys with no RDB form, use NOFLUSH to keep them\r\n"
9969 );
9970
9971 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOFLUSH"]), "+OK\r\n");
9973 assert_eq!(s.run(&[b"EXISTS", b"ts"]), ":1\r\n");
9974 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
9975 assert_eq!(s.run(&[b"TS.GET", b"ts"]), "*2\r\n:1000\r\n+1.5\r\n");
9976 }
9977
9978 #[test]
9980 fn debug_reload_drops_a_key_whose_deadline_went_by() {
9981 let mut s = Saves::new("reload-expired");
9982 s.run(&[b"SET", b"gone", b"v"]);
9983 s.run(&[b"SET", b"stays", b"v"]);
9984 s.run(&[b"PEXPIREAT", b"gone", b"4102444800000"]);
9985 s.run(&[b"SAVE"]);
9986 s.f.server.set_clock_ms(4_102_444_800_001);
9987
9988 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), "+OK\r\n");
9989
9990 assert_eq!(s.run(&[b"EXISTS", b"gone"]), ":0\r\n");
9991 assert_eq!(s.run(&[b"GET", b"stays"]), "$1\r\nv\r\n");
9992 }
9993
9994 #[test]
10003 fn a_file_one_server_wrote_loads_into_a_server_that_has_never_seen_it() {
10004 let mut wrote = Saves::new("restore-across");
10005 wrote.run(&[b"SET", b"s", b"hello"]);
10006 wrote.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10007 wrote.run(&[b"HSET", b"h", b"f", b"v"]);
10008 wrote.run(&[b"ZADD", b"z", b"1.5", b"m"]);
10009 wrote.run(&[b"SELECT", b"7"]);
10010 wrote.run(&[b"SADD", b"far", b"x"]);
10011 assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10012
10013 let mut fresh = Fixture::new();
10014 let done = fresh
10015 .server
10016 .load_image(&wrote.image(), true)
10017 .expect("the file one server wrote is a file another can read");
10018 assert_eq!(done.keys[0], 4);
10019 assert_eq!(done.keys[7], 1);
10020 assert_eq!(done.total(), 5);
10021 assert_eq!(done.expired, 0);
10022
10023 assert_eq!(fresh.run(&[b"GET", b"s"]), "$5\r\nhello\r\n");
10024 assert_eq!(
10025 fresh.run(&[b"LRANGE", b"l", b"0", b"-1"]),
10026 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10027 );
10028 assert_eq!(fresh.run(&[b"HGET", b"h", b"f"]), "$1\r\nv\r\n");
10029 assert_eq!(fresh.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n1.5\r\n");
10030 fresh.run(&[b"SELECT", b"7"]);
10031 assert_eq!(fresh.run(&[b"SMEMBERS", b"far"]), "*1\r\n$1\r\nx\r\n");
10032 }
10033
10034 #[test]
10037 fn a_load_reports_what_it_kept_and_what_had_already_died() {
10038 let mut wrote = Saves::new("restore-counts");
10039 wrote.run(&[b"SET", b"gone", b"v"]);
10040 wrote.run(&[b"SET", b"stays", b"v"]);
10041 wrote.run(&[b"PEXPIREAT", b"gone", b"4102444800000"]);
10042 assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10043
10044 let mut fresh = Saves::new("restore-counts-into");
10045 fresh.f.server.set_clock_ms(4_102_444_800_001);
10046 let done = fresh
10047 .f
10048 .server
10049 .load_image(&wrote.image(), true)
10050 .expect("a file with a dead key in it is still a good file");
10051 assert_eq!(done.total(), 1);
10052 assert_eq!(done.expired, 1);
10053
10054 assert_eq!(fresh.field("rdb_last_load_keys_loaded"), "1");
10055 assert_eq!(fresh.field("rdb_last_load_keys_expired"), "1");
10056 assert_eq!(fresh.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10057 assert_eq!(fresh.run(&[b"GET", b"stays"]), "$1\r\nv\r\n");
10058 }
10059
10060 #[test]
10063 fn a_server_that_has_loaded_nothing_says_so() {
10064 let mut s = Saves::new("restore-never");
10065 assert_eq!(s.field("rdb_last_load_keys_loaded"), "0");
10066 assert_eq!(s.field("rdb_last_load_keys_expired"), "0");
10067 }
10068
10069 #[test]
10074 fn a_file_that_is_not_an_rdb_is_refused_with_the_dataset_still_there() {
10075 let mut f = Fixture::new();
10076 f.run(&[b"SET", b"k", b"v"]);
10077 let refused = f
10078 .server
10079 .load_image(b"this is not a Redis dump at all, not even close", true)
10080 .expect_err("that is not an RDB");
10081 assert_eq!(refused.to_string(), "the file does not start with REDIS");
10082 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10083 }
10084
10085 #[test]
10089 fn a_damaged_file_is_refused_with_the_dataset_still_there() {
10090 let mut wrote = Saves::new("restore-damaged");
10091 wrote.run(&[b"SET", b"a", b"b"]);
10092 assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10093 let mut image = wrote.image();
10094 let middle = image.len() / 2;
10097 image[middle] ^= 0xff;
10098
10099 let mut f = Fixture::new();
10100 f.run(&[b"SET", b"k", b"v"]);
10101 let refused = f
10102 .server
10103 .load_image(&image, true)
10104 .expect_err("the checksum does not match");
10105 assert_eq!(
10106 refused.to_string(),
10107 "the checksum does not match, so the file is damaged"
10108 );
10109 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10110 }
10111
10112 #[test]
10117 fn debug_object_describes_how_a_value_is_written_down() {
10118 let mut f = Fixture::new();
10119 f.run(&[b"SET", b"s", b"hello"]);
10120
10121 let line = f.run(&[b"DEBUG", b"OBJECT", b"s"]);
10122 let line = line
10123 .strip_prefix('+')
10124 .and_then(|l| l.strip_suffix("\r\n"))
10125 .expect("a simple string");
10126 let mut fields = line.split(' ');
10127 assert_eq!(fields.next(), Some("Value"));
10128 assert!(
10129 fields.next().expect("an address").starts_with("at:0x"),
10130 "{line}"
10131 );
10132 assert_eq!(fields.next(), Some("refcount:1"));
10133 assert_eq!(fields.next(), Some("encoding:embstr"));
10134 assert_eq!(fields.next(), Some("serializedlength:6"));
10138 assert!(
10139 fields.next().expect("a clock").starts_with("lru:"),
10140 "{line}"
10141 );
10142 assert_eq!(fields.next(), Some("lru_seconds_idle:0"));
10143 assert_eq!(fields.next(), None);
10144 }
10145
10146 #[test]
10148 fn debug_object_counts_the_nodes_a_list_broke_into() {
10149 let mut f = Fixture::new();
10150 let member = vec![b'x'; 200];
10153 for _ in 0..100 {
10154 f.run(&[b"RPUSH", b"l", &member]);
10155 }
10156 assert_eq!(
10157 f.run(&[b"OBJECT", b"ENCODING", b"l"]),
10158 "$9\r\nquicklist\r\n"
10159 );
10160
10161 let line = f.run(&[b"DEBUG", b"OBJECT", b"l"]);
10162 let nodes: usize = field(&line, "ql_nodes:").parse().expect("a count");
10163 assert!(nodes > 1, "{line}");
10164 let avg: f64 = field(&line, "ql_avg_node:").parse().expect("an average");
10165 assert!((avg - 100.0 / nodes as f64).abs() < 0.01, "{line}");
10166 assert_eq!(field(&line, "ql_listpack_max:"), "-2");
10167 assert_eq!(field(&line, "ql_compressed:"), "0");
10168 let bytes: usize = field(&line, "ql_uncompressed_size:")
10169 .parse()
10170 .expect("a size");
10171 assert!(bytes > 100 * 200, "{line}");
10172
10173 f.run(&[b"RPUSH", b"small", b"a"]);
10175 let line = f.run(&[b"DEBUG", b"OBJECT", b"small"]);
10176 assert!(!line.contains("ql_nodes"), "{line}");
10177 }
10178
10179 #[test]
10183 fn debug_object_does_not_count_as_using_the_key() {
10184 let mut f = Fixture::new();
10185 f.run(&[b"SET", b"s", b"hello"]);
10186 let was = field(&f.run(&[b"DEBUG", b"OBJECT", b"s"]), "lru:").to_owned();
10187
10188 f.server.set_clock_ms(f.server.clock.now_ms() + 60_000);
10189 let line = f.run(&[b"DEBUG", b"OBJECT", b"s"]);
10190
10191 assert_eq!(field(&line, "lru_seconds_idle:"), "60");
10192 assert_eq!(field(&line, "lru:"), was);
10195 }
10196
10197 #[test]
10200 fn debug_sdslen_measures_the_name_and_the_string_under_it() {
10201 let mut f = Fixture::new();
10202 f.run(&[b"SET", b"name", b"hello"]);
10203
10204 assert_eq!(
10205 f.run(&[b"DEBUG", b"SDSLEN", b"name"]),
10206 "+key_sds_len:4, key_sds_avail:0, key_zmalloc: 4, \
10207 val_sds_len:5, val_sds_avail:0, val_zmalloc: 5\r\n"
10208 );
10209 }
10210
10211 #[test]
10213 fn the_inspecting_subcommands_refuse_what_they_cannot_describe() {
10214 let mut f = Fixture::new();
10215 f.run(&[b"SET", b"s", b"hello"]);
10216 f.run(&[b"SET", b"n", b"12345"]);
10217 f.run(&[b"RPUSH", b"l", b"a"]);
10218
10219 for sub in [
10222 b"OBJECT".as_slice(),
10223 b"SDSLEN".as_slice(),
10224 b"LISTPACK".as_slice(),
10225 b"QUICKLIST".as_slice(),
10226 ] {
10227 assert_eq!(
10228 f.run(&[b"DEBUG", sub, b"nosuch"]),
10229 "-ERR no such key\r\n",
10230 "{}",
10231 String::from_utf8_lossy(sub)
10232 );
10233 }
10234
10235 assert_eq!(
10237 f.run(&[b"DEBUG", b"SDSLEN", b"n"]),
10238 "-ERR Not an sds encoded string.\r\n"
10239 );
10240 assert_eq!(
10241 f.run(&[b"DEBUG", b"SDSLEN", b"l"]),
10242 "-ERR Not an sds encoded string.\r\n"
10243 );
10244
10245 assert_eq!(
10248 f.run(&[b"DEBUG", b"LISTPACK", b"l"]),
10249 "+Listpack structure printed on stdout\r\n"
10250 );
10251 assert_eq!(
10252 f.run(&[b"DEBUG", b"QUICKLIST", b"l"]),
10253 "-ERR Not a quicklist encoded object.\r\n"
10254 );
10255 assert_eq!(
10256 f.run(&[b"DEBUG", b"LISTPACK", b"s"]),
10257 "-ERR Not a listpack encoded object.\r\n"
10258 );
10259 }
10260
10261 #[test]
10264 fn debug_listpack_answers_for_anything_written_as_one() {
10265 let mut f = Fixture::new();
10266 f.run(&[b"RPUSH", b"l", b"a"]);
10267 f.run(&[b"HSET", b"h", b"f", b"v"]);
10268 f.run(&[b"SADD", b"st", b"a"]);
10269 f.run(&[b"ZADD", b"z", b"1", b"m"]);
10270 f.run(&[b"SADD", b"ints", b"1", b"2"]);
10271
10272 for key in [b"l".as_slice(), b"h", b"st", b"z"] {
10273 assert_eq!(
10274 f.run(&[b"DEBUG", b"LISTPACK", key]),
10275 "+Listpack structure printed on stdout\r\n",
10276 "{}",
10277 String::from_utf8_lossy(key)
10278 );
10279 }
10280 assert_eq!(
10281 f.run(&[b"OBJECT", b"ENCODING", b"ints"]),
10282 "$6\r\nintset\r\n"
10283 );
10284 assert_eq!(
10285 f.run(&[b"DEBUG", b"LISTPACK", b"ints"]),
10286 "-ERR Not a listpack encoded object.\r\n"
10287 );
10288 }
10289
10290 #[test]
10293 fn debug_quicklist_takes_a_level_it_does_nothing_with() {
10294 let mut f = Fixture::new();
10295 let member = vec![b'x'; 200];
10296 for _ in 0..100 {
10297 f.run(&[b"RPUSH", b"l", &member]);
10298 }
10299
10300 let said = "+Quicklist structure printed on stdout\r\n";
10301 assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l"]), said);
10302 assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"1"]), said);
10303 assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"abc"]), said);
10306
10307 assert_eq!(
10308 f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"1", b"2"]),
10309 "-ERR unknown subcommand or wrong number of arguments for \
10310 'QUICKLIST'. Try DEBUG HELP.\r\n"
10311 );
10312 assert_eq!(
10313 f.run(&[b"DEBUG", b"LISTPACK", b"l", b"0"]),
10314 "-ERR unknown subcommand or wrong number of arguments for \
10315 'LISTPACK'. Try DEBUG HELP.\r\n"
10316 );
10317 }
10318
10319 #[test]
10324 fn a_value_digest_is_the_number_the_reference_computes() {
10325 let mut f = Fixture::new();
10326 f.run(&[b"SET", b"s", b"hello"]);
10327 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10328 f.run(&[b"SADD", b"t", b"a", b"b", b"c"]);
10329 f.run(&[b"HSET", b"h", b"f", b"v"]);
10330 f.run(&[b"ZADD", b"z", b"1", b"a", b"2.5", b"b"]);
10331 f.run(&[b"XADD", b"x", b"1-1", b"f", b"v"]);
10332
10333 for (key, want) in [
10334 (&b"s"[..], "36b23a1456b2dce2c3ed252c456761301dba8060"),
10335 (b"l", "8bf72d812571eea9b927f3c11beb0c4165a6ff89"),
10336 (b"t", "593c2414786d75446e97f4ea5d4b731f3313da72"),
10337 (b"h", "90c76e9e9f4c62d642a34fc97c7dad503b51f906"),
10338 (b"z", "c45c5b051acd64070e5ed1a949939d5145f806c5"),
10339 (b"x", "2ed9a7a81688084b1f7eae33456ef7727d357031"),
10340 ] {
10341 assert_eq!(
10342 f.run(&[b"DEBUG", b"DIGEST-VALUE", key]),
10343 format!("*1\r\n+{want}\r\n"),
10344 "{}",
10345 String::from_utf8_lossy(key)
10346 );
10347 }
10348 }
10349
10350 #[test]
10353 fn a_deadline_shows_up_without_the_time_left_showing_up() {
10354 let mut f = Fixture::new();
10355 f.run(&[b"SET", b"e", b"value"]);
10356 let bare = "*1\r\n+d59ec93db87f4cd915db3cdf44bb63755bc4a635\r\n";
10357 let dated = "*1\r\n+331b37c26446a68dd4cdd72701d1acee416ae7b6\r\n";
10358 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), bare);
10359
10360 f.run(&[b"EXPIRE", b"e", b"1000"]);
10361 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), dated);
10362 f.run(&[b"EXPIRE", b"e", b"999999"]);
10364 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), dated);
10365 f.run(&[b"PERSIST", b"e"]);
10366 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), bare);
10367
10368 f.run(&[b"HSET", b"he", b"f1", b"v1", b"f2", b"v2"]);
10371 f.run(&[b"HEXPIRE", b"he", b"1000", b"FIELDS", b"1", b"f2"]);
10372 assert_eq!(
10373 f.run(&[b"DEBUG", b"DIGEST-VALUE", b"he"]),
10374 "*1\r\n+8911d6d4d198f5e022f80dfefd5e15d6c0eaabe3\r\n"
10375 );
10376 }
10377
10378 #[test]
10381 fn a_value_digest_does_not_know_what_the_key_is_called() {
10382 let mut f = Fixture::new();
10383 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10384 f.run(&[b"COPY", b"l", b"l2"]);
10385 let want = "*1\r\n+8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n";
10386 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l"]), want);
10387 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l2"]), want);
10388
10389 assert_eq!(
10392 f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l", b"gone", b"l2"]),
10393 format!(
10394 "*3\r\n+8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n+{0}\r\n\
10395 +8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n",
10396 "0".repeat(40)
10397 )
10398 );
10399 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE"]), "*0\r\n");
10400 }
10401
10402 #[test]
10405 fn the_whole_digest_folds_in_the_names_and_the_database_numbers() {
10406 let mut f = Fixture::new();
10407 let empty = format!("+{}\r\n", "0".repeat(40));
10408 assert_eq!(f.run(&[b"DEBUG", b"DIGEST"]), empty);
10409
10410 f.run(&[b"SET", b"k", b"hello"]);
10411 assert_eq!(
10412 f.run(&[b"DEBUG", b"DIGEST"]),
10413 "+d101db227d1e3b31616b18b0b8700f84c3ffa5e9\r\n"
10414 );
10415
10416 f.run(&[b"SELECT", b"3"]);
10417 f.run(&[b"SET", b"k", b"hello"]);
10418 assert_eq!(
10419 f.run(&[b"DEBUG", b"DIGEST"]),
10420 "+a541f66c15932c1014da1569ff27c15bcde7d1dc\r\n"
10421 );
10422
10423 f.run(&[b"SELECT", b"0"]);
10426 f.run(&[b"FLUSHDB"]);
10427 assert_eq!(
10428 f.run(&[b"DEBUG", b"DIGEST"]),
10429 "+f9b35ab00ad2f456386a2f73d316bf8266013606\r\n"
10430 );
10431
10432 f.run(&[b"FLUSHALL"]);
10433 assert_eq!(f.run(&[b"DEBUG", b"DIGEST"]), empty);
10434 }
10435
10436 #[test]
10439 fn digesting_does_not_count_as_using_anything() {
10440 let mut f = Fixture::new();
10441 f.run(&[b"SET", b"k", b"value"]);
10442 f.run(&[b"CONFIG", b"RESETSTAT"]);
10443 f.run(&[b"DEBUG", b"DIGEST"]);
10444 f.run(&[b"DEBUG", b"DIGEST-VALUE", b"k", b"gone"]);
10445 let stats = f.run(&[b"INFO", b"stats"]);
10446 assert!(stats.contains("keyspace_hits:0\r\n"), "{stats}");
10447 assert!(stats.contains("keyspace_misses:0\r\n"), "{stats}");
10448
10449 f.run(&[b"GET", b"k"]);
10452 f.run(&[b"GET", b"gone"]);
10453 let stats = f.run(&[b"INFO", b"stats"]);
10454 assert!(stats.contains("keyspace_hits:1\r\n"), "{stats}");
10455 assert!(stats.contains("keyspace_misses:1\r\n"), "{stats}");
10456 }
10457
10458 fn field<'a>(line: &'a str, label: &str) -> &'a str {
10460 let at = line
10461 .find(label)
10462 .unwrap_or_else(|| panic!("no {label} in {line}"));
10463 let rest = &line[at + label.len()..];
10464 rest.split([' ', '\r']).next().expect("a value")
10465 }
10466
10467 fn guarded(password: &[u8]) -> Fixture {
10477 let mut f = Fixture::new();
10478 f.server.set_password(password);
10479 f.session.admit(false);
10480 f
10481 }
10482
10483 #[test]
10486 fn a_server_with_a_password_answers_everything_else_with_noauth() {
10487 let mut f = guarded(b"hunter2");
10488 for parts in [
10489 &[b"PING".as_slice()][..],
10490 &[b"GET", b"k"],
10491 &[b"SET", b"k", b"v"],
10492 &[b"COMMAND", b"COUNT"],
10493 &[b"SUBSCRIBE", b"ch"],
10494 &[b"MULTI"],
10495 &[b"INFO"],
10496 ] {
10497 assert_eq!(
10498 f.run(parts),
10499 "-NOAUTH Authentication required.\r\n",
10500 "{parts:?} got through"
10501 );
10502 }
10503 }
10504
10505 #[test]
10512 fn the_four_commands_that_do_not_need_the_password_get_through() {
10513 for name in ["auth", "hello", "reset", "quit"] {
10514 let spec = table::lookup(name.as_bytes()).expect(name);
10515 assert!(spec.flags.contains(&"no_auth"), "{name} is not no_auth");
10516 }
10517 let mut f = guarded(b"hunter2");
10518 assert_eq!(f.run(&[b"QUIT"]), "+OK\r\n");
10519 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
10520 assert_eq!(
10521 f.run(&[b"AUTH", b"wrong"]),
10522 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
10523 );
10524 assert_eq!(f.run(&[b"AUTH", b"hunter2"]), "+OK\r\n");
10525 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10526 }
10527
10528 #[test]
10530 fn auth_takes_the_password_on_its_own_or_behind_the_user_name() {
10531 let mut f = guarded(b"hunter2");
10532 let wrong = "-WRONGPASS invalid username-password pair or user is disabled.\r\n";
10533 assert_eq!(f.run(&[b"AUTH", b"default", b"hunter2"]), "+OK\r\n");
10534 assert_eq!(f.run(&[b"AUTH", b"default", b"wrong"]), wrong);
10535 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10538 assert_eq!(f.run(&[b"AUTH", b"someone", b"hunter2"]), wrong);
10539 assert_eq!(
10540 f.run(&[b"AUTH"]),
10541 "-ERR wrong number of arguments for 'auth' command\r\n"
10542 );
10543 assert_eq!(f.run(&[b"AUTH", b"a", b"b", b"c"]), "-ERR syntax error\r\n");
10544 }
10545
10546 #[test]
10554 fn auth_on_a_server_with_no_password_says_so_at_length() {
10555 let mut f = Fixture::new();
10556 assert_eq!(
10557 f.run(&[b"AUTH", b"anything"]),
10558 "-ERR AUTH <password> called without any password configured for the \
10559 default user. Are you sure your configuration is correct?\r\n"
10560 );
10561 assert_eq!(f.run(&[b"AUTH", b"default", b"anything"]), "+OK\r\n");
10562 assert_eq!(
10563 f.run(&[b"AUTH", b"nobody", b"anything"]),
10564 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
10565 );
10566 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10567 }
10568
10569 #[test]
10578 fn hello_says_which_option_would_have_worked() {
10579 let long = "-NOAUTH HELLO must be called with the client already authenticated, \
10580 otherwise the HELLO <proto> AUTH <user> <pass> option can be used to \
10581 authenticate the client and select the RESP protocol version at the \
10582 same time\r\n";
10583 let mut f = guarded(b"hunter2");
10584 assert_eq!(f.run(&[b"HELLO"]), long);
10585 assert_eq!(f.run(&[b"HELLO", b"3"]), long);
10586 assert_eq!(
10587 f.run(&[b"HELLO", b"9"]),
10588 "-NOPROTO unsupported protocol version\r\n"
10589 );
10590 assert_eq!(
10593 f.run(&[b"HELLO", b"9", b"AUTH", b"default", b"hunter2"]),
10594 "-NOPROTO unsupported protocol version\r\n"
10595 );
10596 assert_eq!(f.run(&[b"PING"]), "-NOAUTH Authentication required.\r\n");
10597 assert_eq!(
10598 f.run(&[b"HELLO", b"2", b"AUTH", b"default", b"wrong"]),
10599 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
10600 );
10601 assert!(
10602 f.run(&[b"HELLO", b"3", b"AUTH", b"default", b"hunter2"])
10603 .starts_with("%7\r\n"),
10604 "the option did not let it in"
10605 );
10606 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10607 }
10608
10609 #[test]
10617 fn exec_without_the_password_is_an_abort_and_says_why() {
10618 let mut f = guarded(b"hunter2");
10619 assert_eq!(f.run(&[b"MULTI"]), "-NOAUTH Authentication required.\r\n");
10620 assert_eq!(
10621 f.run(&[b"EXEC"]),
10622 "-EXECABORT Transaction discarded because of: NOAUTH Authentication \
10623 required.\r\n"
10624 );
10625 }
10626
10627 #[test]
10630 fn reset_gives_the_password_back_to_the_server_to_ask_for_again() {
10631 let mut f = guarded(b"hunter2");
10632 assert_eq!(f.run(&[b"AUTH", b"hunter2"]), "+OK\r\n");
10633 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10634 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
10635 assert_eq!(f.run(&[b"PING"]), "-NOAUTH Authentication required.\r\n");
10636
10637 let mut f = Fixture::new();
10639 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
10640 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10641 }
10642
10643 #[test]
10650 fn a_password_set_under_an_open_connection_leaves_it_alone() {
10651 let mut f = Fixture::new();
10652 f.session.admit(true);
10653 assert_eq!(
10654 f.run(&[b"CONFIG", b"SET", b"requirepass", b"hunter2"]),
10655 "+OK\r\n"
10656 );
10657 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10658 let mut later = Fixture::on(Server::new());
10660 later.server.set_password(b"hunter2");
10661 later.session.admit(false);
10662 assert_eq!(
10663 later.run(&[b"PING"]),
10664 "-NOAUTH Authentication required.\r\n"
10665 );
10666 later.server.set_password(b"");
10667 assert_eq!(later.run(&[b"PING"]), "+PONG\r\n");
10668 }
10669
10670 #[test]
10673 fn requirepass_reads_back_what_was_written_and_an_empty_one_clears_it() {
10674 let mut f = Fixture::new();
10675 f.session.admit(true);
10678 assert_eq!(
10679 f.run(&[b"CONFIG", b"GET", b"requirepass"]),
10680 "*2\r\n$11\r\nrequirepass\r\n$0\r\n\r\n"
10681 );
10682 f.run(&[b"CONFIG", b"SET", b"requirepass", b"hunter2"]);
10683 assert_eq!(
10684 f.run(&[b"CONFIG", b"GET", b"requirepass"]),
10685 "*2\r\n$11\r\nrequirepass\r\n$7\r\nhunter2\r\n"
10686 );
10687 assert!(f.server.guarded());
10688 f.run(&[b"CONFIG", b"SET", b"requirepass", b""]);
10689 assert!(!f.server.guarded(), "an empty password did not clear it");
10690 assert_eq!(
10691 f.run(&[b"CONFIG", b"GET", b"requirepass"]),
10692 "*2\r\n$11\r\nrequirepass\r\n$0\r\n\r\n"
10693 );
10694 }
10695
10696 #[test]
10698 fn debug_protocol_writes_what_the_reference_writes_on_resp2() {
10699 let mut f = Fixture::new();
10700 for (kind, want) in [
10701 ("string", "$11\r\nHello World\r\n"),
10702 ("integer", ":12345\r\n"),
10703 ("double", "$5\r\n3.141\r\n"),
10704 ("bignum", "$37\r\n1234567999999999999999999999999999999\r\n"),
10705 ("null", "$-1\r\n"),
10706 ("array", "*3\r\n:0\r\n:1\r\n:2\r\n"),
10707 ("set", "*3\r\n:0\r\n:1\r\n:2\r\n"),
10708 ("map", "*6\r\n:0\r\n:0\r\n:1\r\n:1\r\n:2\r\n:0\r\n"),
10709 (
10710 "attrib",
10711 "$39\r\nSome real reply following the attribute\r\n",
10712 ),
10713 ("push", "-ERR RESP2 is not supported by this command\r\n"),
10714 ("verbatim", "$25\r\nThis is a verbatim\nstring\r\n"),
10715 ("true", ":1\r\n"),
10716 ("false", ":0\r\n"),
10717 ] {
10718 assert_eq!(
10719 f.run(&[b"DEBUG", b"PROTOCOL", kind.as_bytes()]),
10720 want,
10721 "{kind}"
10722 );
10723 }
10724 }
10725
10726 #[test]
10728 fn debug_protocol_writes_what_the_reference_writes_on_resp3() {
10729 let mut f = Fixture::new();
10730 f.out = Out::new(Proto::Resp3);
10731 for (kind, want) in [
10732 ("string", "$11\r\nHello World\r\n"),
10733 ("integer", ":12345\r\n"),
10734 ("double", ",3.141\r\n"),
10735 ("bignum", "(1234567999999999999999999999999999999\r\n"),
10736 ("null", "_\r\n"),
10737 ("array", "*3\r\n:0\r\n:1\r\n:2\r\n"),
10738 ("set", "~3\r\n:0\r\n:1\r\n:2\r\n"),
10739 ("map", "%3\r\n:0\r\n#f\r\n:1\r\n#t\r\n:2\r\n#f\r\n"),
10740 (
10741 "attrib",
10742 "|1\r\n$14\r\nkey-popularity\r\n*2\r\n$7\r\nkey:123\r\n:90\r\n\
10743 $39\r\nSome real reply following the attribute\r\n",
10744 ),
10745 (
10746 "push",
10747 "$40\r\nSome real reply following the push reply\r\n\
10748 >2\r\n$16\r\nserver-cpu-usage\r\n:42\r\n",
10749 ),
10750 ("verbatim", "=29\r\ntxt:This is a verbatim\nstring\r\n"),
10751 ("true", "#t\r\n"),
10752 ("false", "#f\r\n"),
10753 ] {
10754 assert_eq!(
10755 f.run(&[b"DEBUG", b"PROTOCOL", kind.as_bytes()]),
10756 want,
10757 "{kind}"
10758 );
10759 }
10760 }
10761
10762 #[test]
10764 fn debug_protocol_names_every_type_when_it_is_given_none_of_them() {
10765 let mut f = Fixture::new();
10766 for kind in [&b"bogus"[..], b""] {
10767 assert_eq!(
10768 f.run(&[b"DEBUG", b"PROTOCOL", kind]),
10769 "-ERR Wrong protocol type name. Please use one of the following: \
10770 string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false\r\n"
10771 );
10772 }
10773 }
10774
10775 #[test]
10782 fn debug_says_the_same_thing_about_a_bad_name_and_a_bad_count() {
10783 let mut f = Fixture::new();
10784 for parts in [
10785 &[b"DEBUG".as_slice(), b"NOSUCH"][..],
10786 &[b"DEBUG", b"PROTOCOL"],
10787 &[b"DEBUG", b"PROTOCOL", b"string", b"extra"],
10788 &[b"DEBUG", b"SLEEP"],
10789 &[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"1", b"2"],
10790 &[b"DEBUG", b"HELP", b"me"],
10791 ] {
10792 let got = f.run(parts);
10793 let name = String::from_utf8_lossy(parts[1]).to_string();
10794 assert_eq!(
10795 got,
10796 format!(
10797 "-ERR unknown subcommand or wrong number of arguments for '{name}'. \
10798 Try DEBUG HELP.\r\n"
10799 ),
10800 "{name}"
10801 );
10802 }
10803 assert_eq!(
10804 f.run(&[b"DEBUG"]),
10805 "-ERR wrong number of arguments for 'debug' command\r\n"
10806 );
10807 }
10808
10809 #[test]
10811 fn debug_error_hands_back_whatever_it_was_given() {
10812 let mut f = Fixture::new();
10813 assert_eq!(f.run(&[b"DEBUG", b"ERROR", b"my error"]), "-my error\r\n");
10814 assert_eq!(f.run(&[b"DEBUG", b"ERROR", b""]), "-\r\n");
10815 assert_eq!(
10818 f.run(&[b"DEBUG", b"ERROR", b"-WEIRD thing"]),
10819 "--WEIRD thing\r\n"
10820 );
10821 assert_eq!(
10823 f.run(&[b"DEBUG", b"ERROR", b"two\nlines"]),
10824 "-two lines\r\n"
10825 );
10826 }
10827
10828 #[test]
10830 fn debug_populate_fills_and_skips_what_is_there() {
10831 let mut f = Fixture::new();
10832 assert_eq!(f.run(&[b"SET", b"key:0", b"mine"]), "+OK\r\n");
10833 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"3"]), "+OK\r\n");
10834 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n");
10835 assert_eq!(f.run(&[b"GET", b"key:0"]), "$4\r\nmine\r\n");
10836 assert_eq!(f.run(&[b"GET", b"key:2"]), "$7\r\nvalue:2\r\n");
10837 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"p:"]), "+OK\r\n");
10840 assert_eq!(f.run(&[b"GET", b"p::0"]), "$7\r\nvalue:0\r\n");
10841 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"q", b"9"]), "+OK\r\n");
10843 assert_eq!(f.raw(&[b"GET", b"q:0"]), b"$9\r\nvalue:0\0\0\r\n");
10844 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"r", b"4"]), "+OK\r\n");
10845 assert_eq!(f.run(&[b"GET", b"r:0"]), "$4\r\nvalu\r\n");
10846 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"s", b"0"]), "+OK\r\n");
10848 assert_eq!(f.run(&[b"GET", b"s:0"]), "$7\r\nvalue:0\r\n");
10849 }
10850
10851 #[test]
10853 fn debug_populate_wants_two_numbers_that_are_not_negative() {
10854 let mut f = Fixture::new();
10855 for parts in [
10856 &[b"DEBUG".as_slice(), b"POPULATE", b"abc"][..],
10857 &[b"DEBUG", b"POPULATE", b"-1"],
10858 &[b"DEBUG", b"POPULATE", b"1.5"],
10859 &[b"DEBUG", b"POPULATE", b"1", b"p", b"-1"],
10860 &[b"DEBUG", b"POPULATE", b"1", b"p", b"x"],
10861 ] {
10862 assert_eq!(
10863 f.run(parts),
10864 "-ERR value is out of range, must be positive\r\n"
10865 );
10866 }
10867 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"0"]), "+OK\r\n");
10868 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
10869 }
10870
10871 #[test]
10877 fn debug_quicklist_packed_threshold_takes_what_the_reference_takes() {
10878 let mut f = Fixture::new();
10879 for good in [
10880 &b"1"[..],
10881 b"2",
10882 b"1b",
10883 b"1K",
10884 b"1kb",
10885 b"1G",
10886 b"3gb",
10887 b"0",
10888 b"0b",
10889 ] {
10890 assert_eq!(
10891 f.run(&[b"DEBUG", b"QUICKLIST-PACKED-THRESHOLD", good]),
10892 "+OK\r\n",
10893 "{}",
10894 String::from_utf8_lossy(good)
10895 );
10896 }
10897 for bad in [
10898 &b"4gb"[..],
10899 b"4294967295",
10900 b"4294967296",
10901 b"abc",
10902 b"",
10903 b"+5",
10904 b"1.5",
10905 ] {
10906 assert_eq!(
10907 f.run(&[b"DEBUG", b"QUICKLIST-PACKED-THRESHOLD", bad]),
10908 "-ERR argument must be a memory value bigger than 1 and smaller than 4gb\r\n",
10909 "{}",
10910 String::from_utf8_lossy(bad)
10911 );
10912 }
10913 }
10914
10915 #[test]
10917 fn the_debug_gates_turn_the_things_they_name_off_and_on_again() {
10918 let mut f = Fixture::new();
10919 for (sub, read) in [
10920 (&b"SET-ACTIVE-EXPIRE"[..], 0),
10921 (b"DICT-RESIZING", 1),
10922 (b"PAUSE-CRON", 2),
10923 ] {
10924 let reads: [fn(&Server) -> bool; 3] =
10925 [Server::expiring, Server::resizing, Server::cron_running];
10926 let on = reads[read];
10927 let stop: &[u8] = if read == 2 { b"1" } else { b"0" };
10930 let go: &[u8] = if read == 2 { b"0" } else { b"1" };
10931 assert!(on(&f.server), "{}", String::from_utf8_lossy(sub));
10932 assert_eq!(f.run(&[b"DEBUG", sub, stop]), "+OK\r\n");
10933 assert!(!on(&f.server), "{}", String::from_utf8_lossy(sub));
10934 assert_eq!(f.run(&[b"DEBUG", sub, b"nonsense"]), "+OK\r\n");
10937 assert_eq!(on(&f.server), read == 2);
10938 assert_eq!(f.run(&[b"DEBUG", sub, go]), "+OK\r\n");
10939 assert!(on(&f.server), "{}", String::from_utf8_lossy(sub));
10940 }
10941 }
10942
10943 #[test]
10949 fn the_sweep_stops_when_debug_turns_it_off() {
10950 let mut f = Fixture::new();
10951 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PX", b"10"]), "+OK\r\n");
10952 assert_eq!(f.run(&[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"0"]), "+OK\r\n");
10953 f.advance(50);
10954 assert_eq!(f.server.expire_slice(64), 0);
10955 assert_eq!(f.run(&[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"1"]), "+OK\r\n");
10956 assert_eq!(f.server.expire_slice(64), 1);
10957 }
10958
10959 #[test]
10965 fn command_info_sends_sets_where_the_reference_sends_sets() {
10966 let mut f = Fixture::new();
10967 f.out = Out::new(Proto::Resp3);
10968 assert_eq!(
10969 f.run(&[b"COMMAND", b"INFO", b"get"]),
10970 "*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\
10971 ~3\r\n+@read\r\n+@string\r\n+@fast\r\n~0\r\n~1\r\n%3\r\n\
10972 $5\r\nflags\r\n~2\r\n+RO\r\n+access\r\n\
10973 $12\r\nbegin_search\r\n%2\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
10974 %1\r\n$5\r\nindex\r\n:1\r\n\
10975 $9\r\nfind_keys\r\n%2\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
10976 %3\r\n$7\r\nlastkey\r\n:0\r\n$7\r\nkeystep\r\n:1\r\n$5\r\nlimit\r\n:0\r\n\
10977 ~0\r\n"
10978 );
10979 let mut f = Fixture::new();
10981 assert_eq!(
10982 f.run(&[b"COMMAND", b"INFO", b"get"]),
10983 "*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\
10984 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*1\r\n*6\r\n\
10985 $5\r\nflags\r\n*2\r\n+RO\r\n+access\r\n\
10986 $12\r\nbegin_search\r\n*4\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
10987 *2\r\n$5\r\nindex\r\n:1\r\n\
10988 $9\r\nfind_keys\r\n*4\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
10989 *6\r\n$7\r\nlastkey\r\n:0\r\n$7\r\nkeystep\r\n:1\r\n$5\r\nlimit\r\n:0\r\n\
10990 *0\r\n"
10991 );
10992 }
10993
10994 #[test]
10996 fn debug_is_admin_and_stays_off_a_monitor_feed() {
10997 let spec = table::lookup(b"debug").expect("debug is in the table");
10998 assert!(spec.flags.contains(&"admin"));
10999 assert_eq!(spec.arity, -2);
11000 assert_eq!(spec.acl, ["@admin", "@slow", "@dangerous"]);
11001 }
11002
11003 #[test]
11004 fn the_command_counter_counts_every_command_including_the_bad_ones() {
11005 let mut f = Fixture::new();
11006 f.run(&[b"PING"]);
11007 f.run(&[b"NOPE"]);
11008 f.run(&[b"GET"]);
11009 assert_eq!(f.server.totals().commands, 3);
11010 }
11011
11012 #[test]
11013 fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
11014 let mut server = Server::new();
11015 server.set_threads(2);
11016 server.mine().turn.store(0, Relaxed);
11019 server.locals[1].mark(1 << 9);
11020 server.collect_marks();
11021 assert!(server.mine().wanted(9));
11022 assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
11024 }
11025
11026 #[test]
11027 fn what_two_threads_counted_is_added_up_when_info_asks() {
11028 let mut server = Server::new();
11029 server.set_threads(2);
11030 let ping = lookup(b"PING").expect("PING is a command");
11034 for (at, calls) in [(0, 2), (1, 3)] {
11035 let counters = &server.locals[at];
11036 for _ in 0..calls {
11037 counters.stats.commands.bump();
11038 counters.cmdstats.at(ping).calls.bump();
11039 }
11040 counters.stats.opened();
11041 }
11042 assert_eq!(server.totals().commands, 5);
11043 assert_eq!(server.totals().clients, 2);
11044 assert_eq!(server.totals().connections, 2);
11045 let rows: Vec<_> = server.command_stats().collect();
11046 assert_eq!(rows.len(), 1);
11047 assert_eq!(rows[0].0, "ping");
11048 assert_eq!(rows[0].1.calls, 5);
11049 server.reset_stats();
11052 assert_eq!(server.totals().commands, 0);
11053 assert_eq!(server.totals().connections, 0);
11054 assert_eq!(server.totals().clients, 2);
11055 }
11056
11057 #[test]
11058 fn the_parked_count_says_what_the_waiter_list_says() {
11059 let mut f = Fixture::new();
11060 assert_eq!(f.server.parked(), 0);
11061 for client in 1..=3u64 {
11062 f.session = Session::new(client);
11063 assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
11064 }
11065 assert_eq!(f.server.parked(), 3);
11066 assert_eq!(f.server.waiters().len(), 3);
11067
11068 f.server.forget_waiters(2);
11072 assert_eq!(f.server.parked(), f.server.waiters().len());
11073 f.server.forget_waiters(1);
11074 assert_eq!(f.server.parked(), f.server.waiters().len());
11075 f.run(&[b"RPUSH", b"q", b"v"]);
11076 let mut out = Out::new(Proto::Resp2);
11077 assert!(f.server.serve_waiter(3, 0, &mut out));
11078 f.server.forget_waiters(3);
11079 assert_eq!(f.server.parked(), 0);
11080 assert!(f.server.waiters().is_empty());
11081 }
11082
11083 #[test]
11084 fn a_set_goes_from_bytes_to_bytes() {
11085 let mut f = Fixture::new();
11086 assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
11087 assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
11088 assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
11089 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
11090 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
11091 assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
11092 assert_eq!(
11093 f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
11094 "*3\r\n:1\r\n:0\r\n:1\r\n"
11095 );
11096 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
11097 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
11098 }
11099
11100 #[test]
11101 fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
11102 let mut f = Fixture::new();
11103 assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
11104 assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
11105 assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
11106 assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
11107 assert_eq!(
11108 f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
11109 "*2\r\n:0\r\n:0\r\n"
11110 );
11111 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
11112 }
11113
11114 #[test]
11115 fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
11116 let mut f = Fixture::new();
11120 f.run(&[b"SADD", b"s", b"one"]);
11121 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
11122
11123 f.run(&[b"HELLO", b"3"]);
11124 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
11125 }
11126
11127 #[test]
11128 fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
11129 let mut f = Fixture::new();
11132 f.run(&[b"SADD", b"s", b"42"]);
11133 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
11134 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
11135 assert_eq!(
11136 f.run(&[b"SISMEMBER", b"s", b"042"]),
11137 ":0\r\n",
11138 "the member is the bytes and not the number they parse to"
11139 );
11140 }
11141
11142 #[test]
11143 fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
11144 let mut f = Fixture::new();
11145 f.run(&[b"SET", b"str", b"v"]);
11146 f.run(&[b"SADD", b"set", b"a"]);
11147
11148 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11149 assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
11150 assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
11151 assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
11152 assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
11153 assert_eq!(f.run(&[b"GET", b"set"]), wrong);
11154 assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
11155 assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
11156 assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
11157
11158 assert_eq!(
11161 f.run(&[b"MGET", b"str", b"set", b"nope"]),
11162 "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
11163 );
11164 assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
11166 assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
11167 }
11168
11169 #[test]
11170 fn a_wrongtype_leaves_nothing_half_written() {
11171 let mut f = Fixture::new();
11175 f.run(&[b"SET", b"k", b"v"]);
11176 let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
11177 assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
11178 assert!(!reply.contains('*'), "an array header went out in front");
11179 }
11180
11181 #[test]
11182 fn emptying_a_set_takes_the_key_with_it() {
11183 let mut f = Fixture::new();
11184 f.run(&[b"SADD", b"s", b"a", b"b"]);
11185 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
11186 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
11187 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
11188 assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
11189 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11190 }
11191
11192 fn split_scan(reply: &str) -> (String, Vec<String>) {
11198 let mut lines = reply.split("\r\n");
11199 assert_eq!(lines.next(), Some("*2"), "got {reply}");
11200 lines.next().expect("the cursor header");
11201 let cursor = lines.next().expect("the cursor").to_owned();
11202 let header = lines.next().expect("the member header");
11203 let n: usize = header[1..].parse().expect("a member count");
11204 let mut members = Vec::with_capacity(n);
11205 for _ in 0..n {
11206 lines.next().expect("a member header");
11207 members.push(lines.next().expect("a member").to_owned());
11208 }
11209 (cursor, members)
11210 }
11211
11212 #[test]
11213 fn popping_takes_a_member_off_the_set_and_hands_it_back() {
11214 let mut f = Fixture::new();
11215 f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
11216
11217 let one = f.run(&[b"SPOP", b"s"]);
11218 assert!(
11219 ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
11220 "got {one}"
11221 );
11222 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
11223
11224 let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
11226 assert!(rest.starts_with("*3\r\n"), "got {rest}");
11227 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
11228 assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
11230 assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
11231 }
11232
11233 #[test]
11234 fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
11235 let mut f = Fixture::new();
11240 f.run(&[b"HELLO", b"3"]);
11241 f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
11242
11243 assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
11244 assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
11246
11247 f.run(&[b"SADD", b"one", b"z"]);
11251 assert_eq!(
11252 f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
11253 "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
11254 );
11255 }
11256
11257 #[test]
11258 fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
11259 let mut f = Fixture::new();
11260 f.run(&[b"SADD", b"s", b"only"]);
11261 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
11262 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
11263 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
11264
11265 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
11266 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
11269 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
11270 assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
11272 }
11273
11274 #[test]
11275 fn a_pop_count_that_is_not_a_positive_number_says_so() {
11276 let mut f = Fixture::new();
11277 f.run(&[b"SADD", b"s", b"a"]);
11278 let bad = "-ERR value is out of range, must be positive\r\n";
11279 assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
11280 assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
11281 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
11282 assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
11284 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
11285 }
11286
11287 #[test]
11288 fn a_scan_walks_a_set_of_any_size_exactly_once() {
11289 let mut f = Fixture::new();
11290 let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
11291 let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
11292 .into_iter()
11293 .chain(members.iter().map(Vec::as_slice))
11294 .collect();
11295 f.run(&args);
11296
11297 let mut seen = Vec::new();
11298 let mut cursor = "0".to_owned();
11299 loop {
11300 let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
11301 let (next, got) = split_scan(&reply);
11302 seen.extend(got);
11303 cursor = next;
11304 if cursor == "0" {
11305 break;
11306 }
11307 }
11308 seen.sort();
11309 seen.dedup();
11310 assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
11311
11312 f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
11315 let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
11316 assert_eq!(cursor, "0");
11317 assert_eq!(got.len(), 3);
11318 assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
11320 }
11321
11322 #[test]
11323 fn a_scan_takes_match_and_count_and_refuses_anything_else() {
11324 let mut f = Fixture::new();
11325 f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
11326
11327 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
11328 let mut got = got;
11329 got.sort();
11330 assert_eq!(got, ["aa", "ab"]);
11331
11332 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
11335 let mut got = got;
11336 got.sort();
11337 assert_eq!(got, ["12", "13"]);
11338
11339 assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
11340 assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
11341 assert_eq!(
11342 f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
11343 "-ERR syntax error\r\n"
11344 );
11345 assert_eq!(
11348 f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
11349 "-ERR syntax error\r\n"
11350 );
11351 }
11352
11353 #[test]
11354 fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
11355 let mut f = Fixture::new();
11356 f.run(&[b"SADD", b"src", b"a", b"b"]);
11357 f.run(&[b"SADD", b"dst", b"c"]);
11358
11359 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
11360 assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
11361 assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
11362 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
11364 assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
11365
11366 assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
11369 assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
11370 assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
11371 }
11372
11373 #[test]
11374 fn moving_checks_the_types_in_the_order_redis_checks_them() {
11375 let mut f = Fixture::new();
11379 f.run(&[b"SET", b"str", b"v"]);
11380 f.run(&[b"SADD", b"set", b"a"]);
11381
11382 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11383 assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
11384 assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
11385 assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
11386 assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
11387 assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
11388 assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
11389 assert_eq!(
11390 f.run(&[b"SISMEMBER", b"set", b"a"]),
11391 ":1\r\n",
11392 "and none of that moved anything"
11393 );
11394 }
11395
11396 #[test]
11397 fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
11398 let mut f = Fixture::new();
11401 f.run(&[b"SADD", b"s", b"a"]);
11402 for bad in [
11403 &[b"SSCAN".as_slice(), b"s", b"abc"][..],
11404 &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
11405 &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
11406 ] {
11407 let reply = f.run(bad);
11408 assert!(reply.starts_with("-ERR"), "got {reply}");
11409 assert!(!reply.contains('*'), "an array header went out in front");
11410 }
11411 }
11412
11413 #[test]
11414 fn a_hash_writes_reads_and_deletes_its_fields() {
11415 let mut f = Fixture::new();
11416 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
11417 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
11418 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
11419 assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
11420 assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
11421 assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
11422 assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
11423 assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
11424 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
11425 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
11426
11427 assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
11430
11431 assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
11432 assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
11433 assert_eq!(
11434 f.run(&[b"EXISTS", b"h"]),
11435 ":0\r\n",
11436 "and losing the last field lost the key"
11437 );
11438 }
11439
11440 #[test]
11441 fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
11442 let mut f = Fixture::new();
11443 f.run(&[b"HSET", b"h", b"a", b"1"]);
11444 assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
11445 assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
11446 assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
11447 assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
11448 assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
11449
11450 f.run(&[b"HELLO", b"3"]);
11451 assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
11452 assert_eq!(
11453 f.run(&[b"HGETALL", b"nokey"]),
11454 "%0\r\n",
11455 "a missing key is the empty hash and never a nil"
11456 );
11457 assert_eq!(
11458 f.run(&[b"HKEYS", b"h"]),
11459 "*1\r\n$1\r\na\r\n",
11460 "and the two that answer one side stay arrays"
11461 );
11462 }
11463
11464 #[test]
11465 fn hmget_answers_once_per_field_and_hmset_answers_ok() {
11466 let mut f = Fixture::new();
11467 assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
11468 assert_eq!(
11469 f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
11470 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
11471 "the reply is positional, so b is a nil and not a gap"
11472 );
11473 assert_eq!(
11474 f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
11475 "*2\r\n$-1\r\n$-1\r\n",
11476 "and a missing key is all nils rather than an empty array"
11477 );
11478
11479 assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
11480 assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
11481 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
11482 }
11483
11484 #[test]
11485 fn a_hash_counts_up_and_says_so_when_it_cannot() {
11486 let mut f = Fixture::new();
11487 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
11488 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
11489 assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
11490 assert_eq!(
11491 f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
11492 "$4\r\n10.5\r\n",
11493 "a bulk string and not a double, on both protocols"
11494 );
11495
11496 f.run(&[b"HSET", b"h", b"s", b"words"]);
11497 let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
11498 assert!(
11499 bad.starts_with("-ERR hash value is not an integer"),
11500 "{bad}"
11501 );
11502 let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
11503 assert!(
11504 bad.starts_with("-ERR value is not an integer"),
11505 "a bad argument is not yet a hash value, {bad}"
11506 );
11507 assert_eq!(
11508 f.run(&[b"HGET", b"h", b"s"]),
11509 "$5\r\nwords\r\n",
11510 "and neither of them wrote anything"
11511 );
11512 }
11513
11514 #[test]
11515 fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
11516 let fields = if cfg!(miri) { 96 } else { 500 };
11521 let mut f = Fixture::new();
11522 for i in 0..fields {
11523 let field = format!("field-{i}");
11524 let value = format!("value-{i}");
11525 f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
11526 }
11527
11528 let mut seen: Vec<String> = Vec::new();
11529 let mut cursor = "0".to_owned();
11530 loop {
11531 let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
11532 let (next, items) = scan_reply(&reply);
11533 assert_eq!(items.len() % 2, 0, "a pair went out half written");
11534 for pair in items.chunks(2) {
11535 assert_eq!(
11536 pair[0].strip_prefix("field-"),
11537 pair[1].strip_prefix("value-"),
11538 "a field came back with someone else's value"
11539 );
11540 seen.push(pair[0].clone());
11541 }
11542 cursor = next;
11543 if cursor == "0" {
11544 break;
11545 }
11546 }
11547 seen.sort();
11548 seen.dedup();
11549 assert_eq!(seen.len(), fields, "every field once and only once");
11550
11551 let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
11552 assert!(
11553 items.iter().all(|s| s.starts_with("field-")),
11554 "NOVALUES still sent the values"
11555 );
11556
11557 let last = fields - 1;
11558 let (_, one) = scan_reply(&f.run(&[
11559 b"HSCAN",
11560 b"h",
11561 b"0",
11562 b"MATCH",
11563 format!("field-{last}").as_bytes(),
11564 b"COUNT",
11565 b"1000",
11566 ]));
11567 assert_eq!(
11568 one,
11569 [format!("field-{last}"), format!("value-{last}")],
11570 "MATCH is on the field"
11571 );
11572 }
11573
11574 #[test]
11575 fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
11576 let mut f = Fixture::new();
11577 f.run(&[b"HSET", b"h", b"a", b"1"]);
11578 assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
11579 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
11580 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
11581 assert_eq!(
11582 f.run(&[b"HRANDFIELD", b"h", b"3"]),
11583 "*1\r\n$1\r\na\r\n",
11584 "a positive count is capped at the size of the hash"
11585 );
11586 assert_eq!(
11587 f.run(&[b"HRANDFIELD", b"h", b"-3"]),
11588 "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
11589 "and a negative one repeats itself"
11590 );
11591 assert_eq!(
11592 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
11593 "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
11594 "flat on RESP2"
11595 );
11596
11597 f.run(&[b"HELLO", b"3"]);
11598 assert_eq!(
11599 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
11600 "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
11601 "and nested on RESP3, but still an array and never a map"
11602 );
11603 }
11604
11605 #[test]
11606 fn every_hash_command_says_wrongtype_and_writes_nothing() {
11607 let mut f = Fixture::new();
11608 f.run(&[b"SET", b"str", b"v"]);
11609 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11610
11611 for cmd in [
11612 &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
11613 &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
11614 &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
11615 &[b"HGET".as_slice(), b"str", b"f"][..],
11616 &[b"HMGET".as_slice(), b"str", b"f"][..],
11617 &[b"HDEL".as_slice(), b"str", b"f"][..],
11618 &[b"HLEN".as_slice(), b"str"][..],
11619 &[b"HEXISTS".as_slice(), b"str", b"f"][..],
11620 &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
11621 &[b"HGETALL".as_slice(), b"str"][..],
11622 &[b"HKEYS".as_slice(), b"str"][..],
11623 &[b"HVALS".as_slice(), b"str"][..],
11624 &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
11625 &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
11626 &[b"HRANDFIELD".as_slice(), b"str"][..],
11627 &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
11628 &[b"HSCAN".as_slice(), b"str", b"0"][..],
11629 ] {
11630 let reply = f.run(cmd);
11631 assert_eq!(reply, wrong, "{:?}", cmd[0]);
11632 }
11633 assert_eq!(
11634 f.run(&[b"GET", b"str"]),
11635 "$1\r\nv\r\n",
11636 "and none of them touched the value"
11637 );
11638 }
11639
11640 #[test]
11641 fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
11642 let mut f = Fixture::new();
11643 f.run(&[b"HSET", b"h", b"f", b"v"]);
11644 for bad in [
11645 &[b"HSCAN".as_slice(), b"h", b"abc"][..],
11646 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
11647 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
11648 &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
11649 ] {
11650 let reply = f.run(bad);
11651 assert!(reply.starts_with("-ERR"), "got {reply}");
11652 assert!(!reply.contains('*'), "an array header went out in front");
11653 }
11654 }
11655
11656 #[test]
11657 fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
11658 let mut f = Fixture::new();
11659 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
11660 assert_eq!(
11661 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
11662 "*1\r\n:1\r\n"
11663 );
11664 assert_eq!(
11665 f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
11666 "*3\r\n:100\r\n:-1\r\n:-2\r\n",
11667 "one answer per field, and the two sentinels are TTL's own"
11668 );
11669
11670 let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
11673 assert!((99_000..=100_000).contains(&ms), "got {ms}");
11674 let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
11675 let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
11676 assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
11677 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
11678
11679 assert_eq!(
11680 f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
11681 "*3\r\n:1\r\n:-1\r\n:-2\r\n",
11682 "one for the deadline taken off, and it does not say what it was"
11683 );
11684 assert_eq!(
11685 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
11686 "*1\r\n:-1\r\n"
11687 );
11688 assert_eq!(
11689 f.run(&[b"HGET", b"h", b"a"]),
11690 "$1\r\n1\r\n",
11691 "and the field is still there with the value it had"
11692 );
11693 }
11694
11695 #[test]
11696 fn a_deadline_that_has_already_gone_deletes_the_field_now() {
11697 let mut f = Fixture::new();
11698 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
11699 assert_eq!(
11700 f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
11701 "*1\r\n:2\r\n",
11702 "two, and not one, because nothing was stored"
11703 );
11704 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
11705 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
11706
11707 assert_eq!(
11708 f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
11709 "*1\r\n:2\r\n"
11710 );
11711 assert_eq!(
11712 f.run(&[b"EXISTS", b"h"]),
11713 ":0\r\n",
11714 "and the last field going took the key with it"
11715 );
11716
11717 f.run(&[b"HSET", b"h", b"a", b"1"]);
11720 assert_eq!(
11721 f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
11722 "*1\r\n:2\r\n"
11723 );
11724 }
11725
11726 #[test]
11727 fn a_field_is_gone_once_its_moment_passes() {
11728 let mut f = Fixture::new();
11729 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
11730 assert_eq!(
11731 f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
11732 "*1\r\n:1\r\n"
11733 );
11734 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
11735
11736 f.server.advance_clock_ms(60);
11740 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
11741 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
11742 assert_eq!(
11743 f.run(&[b"HGETALL", b"h"]),
11744 "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
11745 "and the walks do not hand back a field that has expired"
11746 );
11747 }
11748
11749 #[test]
11750 fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
11751 let mut f = Fixture::new();
11752 for cmd in [
11753 &[
11754 b"HEXPIRE".as_slice(),
11755 b"nokey",
11756 b"100",
11757 b"FIELDS",
11758 b"2",
11759 b"a",
11760 b"b",
11761 ][..],
11762 &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
11763 &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
11764 &[
11765 b"HEXPIRETIME".as_slice(),
11766 b"nokey",
11767 b"FIELDS",
11768 b"2",
11769 b"a",
11770 b"b",
11771 ][..],
11772 &[
11773 b"HPERSIST".as_slice(),
11774 b"nokey",
11775 b"FIELDS",
11776 b"2",
11777 b"a",
11778 b"b",
11779 ][..],
11780 ] {
11781 assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
11782 }
11783 }
11784
11785 #[test]
11786 fn writing_a_field_clears_the_deadline_that_was_on_it() {
11787 let mut f = Fixture::new();
11788 f.run(&[b"HSET", b"h", b"a", b"1"]);
11789 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
11790 f.run(&[b"HSET", b"h", b"a", b"2"]);
11791 assert_eq!(
11792 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
11793 "*1\r\n:-1\r\n",
11794 "Redis has done this since 7.4, and it is why HGETEX exists"
11795 );
11796 }
11797
11798 #[test]
11799 fn the_four_conditions_reach_the_store_the_way_they_were_written() {
11800 let mut f = Fixture::new();
11801 f.run(&[b"HSET", b"h", b"a", b"1"]);
11802 assert_eq!(
11803 f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
11804 "*1\r\n:0\r\n",
11805 "XX on a field with no deadline changes nothing"
11806 );
11807 assert_eq!(
11808 f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
11809 "*1\r\n:1\r\n"
11810 );
11811 assert_eq!(
11812 f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
11813 "*1\r\n:0\r\n",
11814 "and NX will not move one that is already there"
11815 );
11816 assert_eq!(
11817 f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
11818 "*1\r\n:0\r\n"
11819 );
11820 assert_eq!(
11821 f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
11822 "*1\r\n:1\r\n"
11823 );
11824 assert_eq!(
11825 f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
11826 "*1\r\n:1\r\n"
11827 );
11828 assert_eq!(
11829 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
11830 "*1\r\n:50\r\n"
11831 );
11832 }
11833
11834 #[test]
11835 fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
11836 let mut f = Fixture::new();
11837 f.run(&[b"HSET", b"h", b"a", b"1"]);
11838 for (bad, want) in [
11839 (
11840 &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
11841 "-ERR invalid expire time, must be >= 0",
11842 ),
11843 (
11844 &[
11845 b"HEXPIRE".as_slice(),
11846 b"h",
11847 b"9999999999999999",
11848 b"FIELDS",
11849 b"1",
11850 b"a",
11851 ][..],
11852 "-ERR invalid expire time in 'hexpire' command",
11853 ),
11854 (
11855 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
11856 "-ERR wrong number of arguments for 'hexpire' command",
11857 ),
11858 (
11859 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
11860 "-ERR Parameter `numFields` should be greater than 0",
11861 ),
11862 (
11863 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
11864 "-ERR wrong number of arguments",
11865 ),
11866 (
11867 &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
11868 "-ERR wrong number of arguments",
11869 ),
11870 ] {
11871 let reply = f.run(bad);
11872 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
11873 assert!(!reply.contains('*'), "an array header went out in front");
11874 }
11875 assert_eq!(
11876 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
11877 "*1\r\n:-1\r\n",
11878 "and not one of them put a deadline on anything"
11879 );
11880 }
11881
11882 #[test]
11883 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
11884 let mut f = Fixture::new();
11885 f.run(&[b"SET", b"str", b"v"]);
11886 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11887
11888 for cmd in [
11889 &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
11890 &[
11891 b"HPEXPIRE".as_slice(),
11892 b"str",
11893 b"100",
11894 b"FIELDS",
11895 b"1",
11896 b"f",
11897 ][..],
11898 &[
11899 b"HEXPIREAT".as_slice(),
11900 b"str",
11901 b"9999999999",
11902 b"FIELDS",
11903 b"1",
11904 b"f",
11905 ][..],
11906 &[
11907 b"HPEXPIREAT".as_slice(),
11908 b"str",
11909 b"9999999999999",
11910 b"FIELDS",
11911 b"1",
11912 b"f",
11913 ][..],
11914 &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
11915 &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
11916 &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
11917 &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
11918 &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
11919 ] {
11920 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
11921 }
11922 assert_eq!(
11923 f.run(&[b"GET", b"str"]),
11924 "$1\r\nv\r\n",
11925 "and none of them touched the value"
11926 );
11927 }
11928
11929 #[test]
11930 fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
11931 let mut f = Fixture::new();
11932 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
11933 assert_eq!(
11934 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
11935 "*2\r\n$1\r\n1\r\n$-1\r\n",
11936 "positional, so the field that was not there is a nil in its place"
11937 );
11938 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
11939 assert_eq!(
11940 f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
11941 "*1\r\n$-1\r\n"
11942 );
11943 assert_eq!(
11944 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
11945 "*1\r\n$1\r\n2\r\n"
11946 );
11947 assert_eq!(
11948 f.run(&[b"EXISTS", b"h"]),
11949 ":0\r\n",
11950 "and the last field took the key"
11951 );
11952 }
11953
11954 #[test]
11955 fn hgetex_reads_and_moves_the_deadline_in_one_command() {
11956 let mut f = Fixture::new();
11957 f.run(&[b"HSET", b"h", b"a", b"1"]);
11958 assert_eq!(
11959 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
11960 "*1\r\n$1\r\n1\r\n"
11961 );
11962 assert_eq!(
11963 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
11964 "*1\r\n:-1\r\n",
11965 "no option means leave it alone, which is the one place this is not GETEX"
11966 );
11967
11968 f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
11969 assert_eq!(
11970 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
11971 "*1\r\n:100\r\n"
11972 );
11973 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
11974 assert_eq!(
11975 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
11976 "*1\r\n:100\r\n",
11977 "and a plain read really does leave it alone"
11978 );
11979 assert_eq!(
11980 f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
11981 "*1\r\n$1\r\n1\r\n"
11982 );
11983 assert_eq!(
11984 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
11985 "*1\r\n:-1\r\n"
11986 );
11987
11988 assert_eq!(
11989 f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
11990 "*1\r\n$1\r\n1\r\n",
11991 "the value goes out before the deadline that has already gone is applied"
11992 );
11993 assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
11994 assert_eq!(
11995 f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
11996 "*1\r\n$-1\r\n"
11997 );
11998 }
11999
12000 #[test]
12001 fn hsetex_writes_all_of_it_or_none_of_it() {
12002 let mut f = Fixture::new();
12003 assert_eq!(
12004 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
12005 ":1\r\n"
12006 );
12007 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12008 assert_eq!(
12009 f.run(&[
12010 b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
12011 ]),
12012 ":0\r\n",
12013 "FNX wants every field named to be missing"
12014 );
12015 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12016 assert_eq!(
12017 f.run(&[b"HEXISTS", b"h", b"new"]),
12018 ":0\r\n",
12019 "and none of the list was written"
12020 );
12021 assert_eq!(
12022 f.run(&[
12023 b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
12024 ]),
12025 ":0\r\n",
12026 "and FXX wants every one of them to be there"
12027 );
12028 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12029 assert_eq!(
12030 f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
12031 ":1\r\n"
12032 );
12033 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
12034
12035 assert_eq!(
12036 f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
12037 ":0\r\n"
12038 );
12039 assert_eq!(
12040 f.run(&[b"EXISTS", b"gone"]),
12041 ":0\r\n",
12042 "a key with no fields cannot meet FXX and is not created trying"
12043 );
12044 }
12045
12046 #[test]
12047 fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
12048 let mut f = Fixture::new();
12049 f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
12050 assert_eq!(
12051 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12052 "*1\r\n:100\r\n"
12053 );
12054
12055 f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
12056 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
12057 assert_eq!(
12058 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12059 "*1\r\n:100\r\n",
12060 "KEEPTTL put back what the write cleared"
12061 );
12062
12063 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
12064 assert_eq!(
12065 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12066 "*1\r\n:-1\r\n",
12067 "and without it a write clears the deadline the way HSET does"
12068 );
12069
12070 assert_eq!(
12073 f.run(&[
12074 b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
12075 ]),
12076 ":1\r\n"
12077 );
12078 assert_eq!(
12079 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12080 "*1\r\n:100\r\n"
12081 );
12082
12083 assert_eq!(
12084 f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
12085 ":1\r\n",
12086 "written, and not the separate code the HEXPIRE family has for this"
12087 );
12088 assert_eq!(
12089 f.run(&[b"EXISTS", b"h"]),
12090 ":0\r\n",
12091 "and storing it and then removing it emptied the hash"
12092 );
12093 }
12094
12095 #[test]
12096 fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
12097 let mut f = Fixture::new();
12098 f.run(&[b"HSET", b"h", b"a", b"1"]);
12099 for (bad, want) in [
12100 (
12102 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
12103 "-ERR Number of fields must be a positive integer",
12104 ),
12105 (
12106 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
12107 "-ERR The `numfields` parameter must match the number of arguments",
12108 ),
12109 (
12110 &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
12111 "-ERR Mandatory argument FIELDS is missing or not at the right position",
12112 ),
12113 (
12115 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
12116 "-ERR invalid number of fields",
12117 ),
12118 (
12119 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
12120 "-ERR wrong number of arguments",
12121 ),
12122 (
12123 &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
12124 "-ERR unknown argument: FIELD",
12125 ),
12126 (
12127 &[
12128 b"HGETEX".as_slice(),
12129 b"h",
12130 b"KEEPTTL",
12131 b"FIELDS",
12132 b"1",
12133 b"a",
12134 ][..],
12135 "-ERR unknown argument: KEEPTTL",
12136 ),
12137 (
12138 &[
12139 b"HGETEX".as_slice(),
12140 b"h",
12141 b"EX",
12142 b"100",
12143 b"PERSIST",
12144 b"FIELDS",
12145 b"1",
12146 b"a",
12147 ][..],
12148 "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
12149 ),
12150 (
12151 &[
12152 b"HSETEX".as_slice(),
12153 b"h",
12154 b"EX",
12155 b"1",
12156 b"KEEPTTL",
12157 b"FIELDS",
12158 b"1",
12159 b"a",
12160 b"1",
12161 ][..],
12162 "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
12163 ),
12164 (
12165 &[
12166 b"HSETEX".as_slice(),
12167 b"h",
12168 b"FNX",
12169 b"FXX",
12170 b"FIELDS",
12171 b"1",
12172 b"a",
12173 b"1",
12174 ][..],
12175 "-ERR Only one of FXX or FNX arguments can be specified",
12176 ),
12177 (
12178 &[
12179 b"HSETEX".as_slice(),
12180 b"h",
12181 b"FIELDS",
12182 b"2",
12183 b"a",
12184 b"1",
12185 b"b",
12186 ][..],
12187 "-ERR wrong number of arguments",
12188 ),
12189 (
12190 &[
12191 b"HGETEX".as_slice(),
12192 b"h",
12193 b"EX",
12194 b"-1",
12195 b"FIELDS",
12196 b"1",
12197 b"a",
12198 ][..],
12199 "-ERR invalid expire time, must be >= 0",
12200 ),
12201 (
12202 &[
12203 b"HGETEX".as_slice(),
12204 b"h",
12205 b"PXAT",
12206 b"99999999999999",
12207 b"FIELDS",
12208 b"1",
12209 b"a",
12210 ][..],
12211 "-ERR invalid expire time in 'hgetex' command",
12212 ),
12213 (
12214 &[
12215 b"HSETEX".as_slice(),
12216 b"h",
12217 b"EX",
12218 b"abc",
12219 b"FIELDS",
12220 b"1",
12221 b"a",
12222 b"1",
12223 ][..],
12224 "-ERR value is not an integer or out of range",
12225 ),
12226 ] {
12227 let reply = f.run(bad);
12228 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
12229 assert!(!reply.contains('*'), "an array header went out in front");
12230 }
12231 assert_eq!(
12232 f.run(&[b"HGET", b"h", b"a"]),
12233 "$1\r\n1\r\n",
12234 "and not one of them wrote anything"
12235 );
12236 assert_eq!(
12237 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12238 "*1\r\n:-1\r\n"
12239 );
12240 }
12241
12242 #[test]
12243 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
12244 let mut f = Fixture::new();
12245 f.run(&[b"SET", b"str", b"v"]);
12246 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12247 for cmd in [
12248 &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12249 &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12250 &[
12251 b"HGETEX".as_slice(),
12252 b"str",
12253 b"EX",
12254 b"100",
12255 b"FIELDS",
12256 b"1",
12257 b"f",
12258 ][..],
12259 &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
12260 ] {
12261 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12262 }
12263 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
12264 }
12265
12266 #[test]
12274 fn himport_writes_declared_values_into_sorted_fields() {
12275 let mut f = Fixture::new();
12276 assert_eq!(
12277 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
12278 "+OK\r\n"
12279 );
12280 assert_eq!(
12281 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
12282 "+OK\r\n"
12283 );
12284 assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
12285 assert_eq!(
12286 f.run(&[b"HGETALL", b"k"]),
12287 bulks(&["a", "3", "b", "1", "aa", "2"])
12288 );
12289 }
12290
12291 #[test]
12294 fn himport_set_replaces_the_whole_key() {
12295 let mut f = Fixture::new();
12296 f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
12297 f.run(&[b"EXPIRE", b"k", b"100"]);
12298 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12299 assert_eq!(
12300 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
12301 "+OK\r\n"
12302 );
12303 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
12304 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
12305 }
12306
12307 #[test]
12310 fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
12311 let mut f = Fixture::new();
12312 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
12313 f.run(&[b"SELECT", b"1"]);
12314 assert_eq!(
12315 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
12316 "+OK\r\n"
12317 );
12318 f.run(&[b"SELECT", b"0"]);
12319 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
12320 assert_eq!(
12321 f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
12322 "-ERR no such fieldset\r\n"
12323 );
12324 }
12325
12326 #[test]
12333 fn himport_complains_in_the_order_a_real_server_does() {
12334 let mut f = Fixture::new();
12335 f.run(&[b"SET", b"str", b"v"]);
12336 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12337 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12338 assert_eq!(
12339 f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
12340 wrong,
12341 "the type beats a missing fieldset"
12342 );
12343 assert_eq!(
12344 f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
12345 wrong,
12346 "and it beats a value count that does not fit"
12347 );
12348 assert_eq!(
12349 f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
12350 "-ERR no such fieldset\r\n"
12351 );
12352 for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
12354 let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
12355 line.extend_from_slice(values);
12356 assert_eq!(
12357 f.run(&line),
12358 "-ERR value count does not match fieldset field count\r\n",
12359 "{} values into two fields",
12360 values.len()
12361 );
12362 }
12363 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
12364 }
12365
12366 #[test]
12368 fn himport_checks_each_subcommand_count_under_its_own_name() {
12369 let mut f = Fixture::new();
12370 assert_eq!(
12371 f.run(&[b"HIMPORT"]),
12372 "-ERR wrong number of arguments for 'himport' command\r\n"
12373 );
12374 for (rest, name) in [
12375 (&["PREPARE"][..], "prepare"),
12376 (&["PREPARE", "fs"][..], "prepare"),
12377 (&["SET"][..], "set"),
12378 (&["SET", "k"][..], "set"),
12379 (&["SET", "k", "fs"][..], "set"),
12380 (&["DISCARD"][..], "discard"),
12381 (&["DISCARD", "a", "b"][..], "discard"),
12382 (&["DISCARDALL", "x"][..], "discardall"),
12383 ] {
12384 let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
12385 line.extend(rest.iter().map(|a| a.as_bytes()));
12386 assert_eq!(
12387 f.run(&line),
12388 format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
12389 "HIMPORT {}",
12390 rest.join(" ")
12391 );
12392 }
12393 assert_eq!(
12394 f.run(&[b"HIMPORT", b"NOPE", b"x"]),
12395 "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
12396 );
12397 }
12398
12399 #[test]
12402 fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
12403 let mut f = Fixture::new();
12404 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12405 assert_eq!(
12406 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
12407 "-ERR duplicate field name in fieldset\r\n"
12408 );
12409 assert_eq!(
12410 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
12411 "+OK\r\n"
12412 );
12413 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
12414 }
12415
12416 #[test]
12419 fn himport_prepare_replaces_and_the_discards_count() {
12420 let mut f = Fixture::new();
12421 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12422 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
12423 assert_eq!(
12424 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
12425 "+OK\r\n"
12426 );
12427 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
12428
12429 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
12430 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
12431 f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
12432 f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
12433 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
12434 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
12435 }
12436
12437 fn int(reply: &str) -> i64 {
12443 let body = reply
12444 .strip_prefix(':')
12445 .and_then(|s| s.strip_suffix("\r\n"))
12446 .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
12447 body.parse().expect("an integer")
12448 }
12449
12450 fn int_reply(reply: &str) -> i64 {
12451 let body = reply
12452 .strip_prefix("*1\r\n:")
12453 .and_then(|s| s.strip_suffix("\r\n"))
12454 .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
12455 body.parse().expect("an integer")
12456 }
12457
12458 fn scan_reply(reply: &str) -> (String, Vec<String>) {
12460 let mut lines = reply.split("\r\n");
12461 assert_eq!(lines.next(), Some("*2"), "got {reply}");
12462 lines.next().expect("the cursor header");
12463 let cursor = lines.next().expect("a cursor").to_owned();
12464 let header = lines.next().expect("an item count");
12465 let n: usize = header[1..].parse().expect("a count");
12466 let mut items = Vec::with_capacity(n);
12467 for _ in 0..n {
12468 lines.next().expect("an item header");
12469 items.push(lines.next().expect("an item").to_owned());
12470 }
12471 (cursor, items)
12472 }
12473
12474 fn sorted(reply: &str) -> Vec<String> {
12477 let mut lines = reply.split("\r\n");
12478 let header = lines.next().expect("a header");
12479 assert!(
12480 header.starts_with('*') || header.starts_with('~'),
12481 "got {reply}"
12482 );
12483 let n: usize = header[1..].parse().expect("a member count");
12484 let mut got = Vec::with_capacity(n);
12485 for _ in 0..n {
12486 lines.next().expect("a member header");
12487 got.push(lines.next().expect("a member").to_owned());
12488 }
12489 got.sort();
12490 got
12491 }
12492
12493 #[test]
12494 fn the_algebra_answers_what_the_sets_share_and_do_not() {
12495 let mut f = Fixture::new();
12496 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
12497 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
12498 f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
12499
12500 assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
12501 assert_eq!(
12502 sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
12503 ["1", "2", "3", "4", "5"]
12504 );
12505 assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
12506 assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
12507
12508 assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
12511 assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
12512 assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
12513 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
12514 }
12515
12516 #[test]
12517 fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
12518 let mut f = Fixture::new();
12519 f.run(&[b"SADD", b"a", b"x"]);
12520 assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
12521 assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
12522 assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
12523
12524 f.run(&[b"HELLO", b"3"]);
12525 assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
12526 assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
12527 assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
12528 assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
12529 }
12530
12531 #[test]
12532 fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
12533 let mut f = Fixture::new();
12534 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
12535 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
12536
12537 assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
12538 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
12539 assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
12540 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
12541 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
12542 assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
12543
12544 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
12547 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
12548 assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
12549 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
12550
12551 f.run(&[b"SET", b"str", b"v"]);
12554 assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
12555 assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
12556 }
12557
12558 #[test]
12559 fn sintercard_counts_without_building_and_stops_at_a_limit() {
12560 let mut f = Fixture::new();
12561 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
12562 f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
12563
12564 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
12565 assert_eq!(
12566 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
12567 ":2\r\n"
12568 );
12569 assert_eq!(
12570 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
12571 ":3\r\n",
12572 "a limit of zero is no limit"
12573 );
12574 assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
12575 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
12576
12577 assert_eq!(
12579 f.run(&[b"SINTERCARD", b"0", b"a"]),
12580 "-ERR numkeys should be greater than 0\r\n"
12581 );
12582 assert_eq!(
12583 f.run(&[b"SINTERCARD", b"abc", b"a"]),
12584 "-ERR numkeys should be greater than 0\r\n"
12585 );
12586 assert_eq!(
12587 f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
12588 "-ERR Number of keys can't be greater than number of args\r\n"
12589 );
12590 assert_eq!(
12591 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
12592 "-ERR LIMIT can't be negative\r\n"
12593 );
12594 assert_eq!(
12595 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
12596 "-ERR syntax error\r\n"
12597 );
12598 f.run(&[b"SADD", b"LIMIT", b"2"]);
12600 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
12601 }
12602
12603 #[test]
12606 fn sunioncard_and_sdiffcard_count_without_building() {
12607 let mut f = Fixture::new();
12608 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
12609 f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
12610
12611 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
12612 assert_eq!(
12613 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
12614 ":2\r\n"
12615 );
12616 assert_eq!(
12617 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
12618 ":6\r\n",
12619 "a limit of zero is no limit"
12620 );
12621 assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
12622 assert_eq!(
12623 f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
12624 ":4\r\n",
12625 "a missing key adds nothing to a union"
12626 );
12627
12628 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
12629 assert_eq!(
12630 f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
12631 ":1\r\n"
12632 );
12633 assert_eq!(
12634 f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
12635 ":2\r\n",
12636 "a difference is not symmetric"
12637 );
12638 assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
12639 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
12640 assert_eq!(
12641 f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
12642 ":0\r\n",
12643 "nothing taken away from nothing"
12644 );
12645
12646 for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
12649 assert_eq!(
12650 f.run(&[name, b"0", b"a"]),
12651 "-ERR numkeys should be greater than 0\r\n"
12652 );
12653 assert_eq!(
12654 f.run(&[name, b"abc", b"a"]),
12655 "-ERR numkeys should be greater than 0\r\n"
12656 );
12657 assert_eq!(
12658 f.run(&[name, b"-1", b"a"]),
12659 "-ERR numkeys should be greater than 0\r\n"
12660 );
12661 assert_eq!(
12662 f.run(&[name, b"3", b"a", b"b"]),
12663 "-ERR Number of keys can't be greater than number of args\r\n"
12664 );
12665 assert_eq!(
12666 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
12667 "-ERR LIMIT can't be negative\r\n"
12668 );
12669 assert_eq!(
12670 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
12671 "-ERR LIMIT can't be negative\r\n",
12672 "a LIMIT that is not a number gets the negative message too"
12673 );
12674 assert_eq!(
12675 f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
12676 "-ERR syntax error\r\n"
12677 );
12678 assert_eq!(
12679 f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
12680 "-ERR syntax error\r\n"
12681 );
12682 assert_eq!(
12683 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
12684 "-ERR syntax error\r\n"
12685 );
12686 }
12687
12688 f.run(&[b"SADD", b"LIMIT", b"2"]);
12690 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
12691 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
12692 }
12693
12694 #[test]
12695 fn the_algebra_answers_wrongtype_before_it_writes_anything() {
12696 let mut f = Fixture::new();
12697 f.run(&[b"SADD", b"a", b"1"]);
12698 f.run(&[b"SADD", b"d", b"old"]);
12699 f.run(&[b"SET", b"str", b"v"]);
12700
12701 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12702 for bad in [
12703 &[b"SINTER".as_slice(), b"a", b"str"][..],
12704 &[b"SUNION".as_slice(), b"str"][..],
12705 &[b"SDIFF".as_slice(), b"a", b"str"][..],
12706 &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
12707 &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
12708 &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
12709 &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
12710 ] {
12711 let reply = f.run(bad);
12712 assert_eq!(reply, wrong, "for {:?}", bad[0]);
12713 }
12714 assert_eq!(
12715 f.run(&[b"SMEMBERS", b"d"]),
12716 "*1\r\n$3\r\nold\r\n",
12717 "and the destination was left alone every time"
12718 );
12719 }
12720
12721 #[cfg_attr(miri, ignore = "the volume is the claim")]
12733 #[test]
12734 fn churning_sets_does_not_grow_the_server() {
12735 let mut f = Fixture::new();
12736 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
12737 let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
12738 .chain(std::iter::once(&b"s"[..]))
12739 .chain(members.iter().map(Vec::as_slice))
12740 .collect();
12741
12742 f.run(&args);
12743 f.run(&[b"DEL", b"s"]);
12744 f.server.compact_step();
12745 let after_first = f.server.memory_bytes();
12746
12747 for _ in 0..200 {
12748 f.run(&args);
12749 f.run(&[b"DEL", b"s"]);
12750 f.server.compact_step();
12751 }
12752 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
12753 assert!(
12754 f.server.memory_bytes() <= after_first * 2,
12755 "held {} after two hundred passes against {after_first} after one",
12756 f.server.memory_bytes()
12757 );
12758 }
12759
12760 #[test]
12768 fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
12769 let mut f = Fixture::new();
12770 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
12771 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
12772 assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
12773 assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
12774 assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
12775 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
12776
12777 assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
12779 assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
12780 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
12781
12782 f.run(&[b"SET", b"num", b"12345"]);
12783 assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
12784 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
12785 assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
12786 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
12787 assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
12788 }
12789
12790 #[test]
12795 fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
12796 let mut f = Fixture::new();
12797 f.run(&[b"SET", b"mykey", b"foobar"]);
12798 assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
12799 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
12800 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
12801 assert_eq!(
12802 f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
12803 ":6\r\n"
12804 );
12805 assert_eq!(
12806 f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
12807 ":25\r\n"
12808 );
12809 assert_eq!(
12810 f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
12811 ":17\r\n"
12812 );
12813 assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
12814
12815 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
12818
12819 assert_eq!(
12821 f.run(&[b"BITCOUNT", b"mykey", b"0"]),
12822 "-ERR syntax error\r\n"
12823 );
12824 assert_eq!(
12825 f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
12826 "-ERR syntax error\r\n"
12827 );
12828 }
12829
12830 #[test]
12836 fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
12837 let mut f = Fixture::new();
12838 f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
12839 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
12840 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
12841 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
12842 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
12843 assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
12844
12845 f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
12846 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
12847 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
12848 assert_eq!(
12849 f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
12850 ":8\r\n"
12851 );
12852
12853 assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
12856 assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
12857 }
12858
12859 #[test]
12861 fn the_eight_combinations_write_what_a_real_server_writes() {
12862 let mut f = Fixture::new();
12863 f.run(&[b"SET", b"a", b"abc"]);
12864 f.run(&[b"SET", b"b", b"abd"]);
12865 let cases: &[(&[u8], &str)] = &[
12866 (b"AND", "ab`"),
12867 (b"OR", "abg"),
12868 (b"XOR", "\u{0}\u{0}\u{7}"),
12869 (b"DIFF", "\u{0}\u{0}\u{3}"),
12870 (b"DIFF1", "\u{0}\u{0}\u{4}"),
12871 (b"ANDOR", "ab`"),
12872 (b"ONE", "\u{0}\u{0}\u{7}"),
12873 ];
12874 for (op, want) in cases {
12875 assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
12876 assert_eq!(
12877 f.run(&[b"GET", b"d"]),
12878 format!("$3\r\n{want}\r\n"),
12879 "{op:?}"
12880 );
12881 }
12882 assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
12884 assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
12885
12886 assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
12889 assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
12890
12891 f.run(&[b"SET", b"dest", b"x"]);
12894 assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
12895 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
12896 }
12897
12898 #[test]
12900 fn bitop_names_the_operation_in_its_own_complaints() {
12901 let mut f = Fixture::new();
12902 f.run(&[b"SET", b"a", b"abc"]);
12903 assert_eq!(
12904 f.run(&[b"BITOP", b"nope", b"d", b"a"]),
12905 "-ERR syntax error\r\n"
12906 );
12907 assert_eq!(
12908 f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
12909 "-ERR BITOP NOT must be called with a single source key.\r\n"
12910 );
12911 for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
12912 assert_eq!(
12913 f.run(&[b"BITOP", op, b"d", b"a"]),
12914 format!(
12915 "-ERR BITOP {} must be called with at least two source keys.\r\n",
12916 String::from_utf8_lossy(op)
12917 )
12918 );
12919 }
12920 f.run(&[b"LPUSH", b"l", b"x"]);
12921 assert_eq!(
12922 f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
12923 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
12924 );
12925 }
12926
12927 #[test]
12929 fn bitfield_reads_and_writes_packed_fields() {
12930 let mut f = Fixture::new();
12931 assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
12932 assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
12933
12934 assert_eq!(
12935 f.run(&[
12936 b"BITFIELD",
12937 b"bf",
12938 b"INCRBY",
12939 b"u2",
12940 b"100",
12941 b"1",
12942 b"GET",
12943 b"u4",
12944 b"0"
12945 ]),
12946 "*2\r\n:1\r\n:0\r\n"
12947 );
12948 assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
12951
12952 assert_eq!(
12954 f.run(&[
12955 b"BITFIELD",
12956 b"bf",
12957 b"SET",
12958 b"u8",
12959 b"#0",
12960 b"255",
12961 b"GET",
12962 b"u8",
12963 b"#0"
12964 ]),
12965 "*2\r\n:0\r\n:255\r\n"
12966 );
12967
12968 assert_eq!(
12969 f.run(&[
12970 b"BITFIELD",
12971 b"bf",
12972 b"OVERFLOW",
12973 b"SAT",
12974 b"INCRBY",
12975 b"i8",
12976 b"0",
12977 b"120",
12978 b"INCRBY",
12979 b"i8",
12980 b"0",
12981 b"120"
12982 ]),
12983 "*2\r\n:119\r\n:127\r\n"
12984 );
12985 assert_eq!(
12986 f.run(&[
12987 b"BITFIELD",
12988 b"bf2",
12989 b"OVERFLOW",
12990 b"FAIL",
12991 b"INCRBY",
12992 b"u2",
12993 b"0",
12994 b"5"
12995 ]),
12996 "*1\r\n$-1\r\n"
12997 );
12998 assert_eq!(
12999 f.run(&[
13000 b"BITFIELD",
13001 b"bf3",
13002 b"OVERFLOW",
13003 b"WRAP",
13004 b"INCRBY",
13005 b"u2",
13006 b"0",
13007 b"5"
13008 ]),
13009 "*1\r\n:1\r\n"
13010 );
13011 assert_eq!(
13012 f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
13013 "*1\r\n:4611686018427387904\r\n"
13014 );
13015 }
13016
13017 #[test]
13023 fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
13024 let mut f = Fixture::new();
13025 let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
13026 assert_eq!(
13027 f.run(&[
13028 b"BITFIELD",
13029 b"bad",
13030 b"SET",
13031 b"u8",
13032 b"0",
13033 b"1",
13034 b"GET",
13035 b"u99",
13036 b"0"
13037 ]),
13038 bad_type
13039 );
13040 assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
13041 assert_eq!(
13042 f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
13043 bad_type
13044 );
13045 assert_eq!(
13046 f.run(&[b"BITFIELD", b"bad", b"GET"]),
13047 "-ERR syntax error\r\n"
13048 );
13049 assert_eq!(
13050 f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
13051 "-ERR syntax error\r\n"
13052 );
13053 assert_eq!(
13054 f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
13055 "-ERR syntax error\r\n"
13056 );
13057 assert_eq!(
13058 f.run(&[
13059 b"BITFIELD",
13060 b"bad",
13061 b"OVERFLOW",
13062 b"NOPE",
13063 b"GET",
13064 b"u8",
13065 b"0"
13066 ]),
13067 "-ERR Invalid OVERFLOW type specified\r\n"
13068 );
13069 assert_eq!(
13070 f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
13071 "-ERR value is not an integer or out of range\r\n"
13072 );
13073 for at in [&b"#-1"[..], b"abc"] {
13074 assert_eq!(
13075 f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
13076 "-ERR bit offset is not an integer or out of range\r\n"
13077 );
13078 }
13079 }
13080
13081 #[test]
13083 fn bitfield_ro_answers_gets_and_refuses_the_rest() {
13084 let mut f = Fixture::new();
13085 f.run(&[b"SET", b"n", b"123"]);
13086 assert_eq!(
13087 f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
13088 "*1\r\n:49\r\n"
13089 );
13090 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
13092
13093 assert_eq!(
13095 f.run(&[
13096 b"BITFIELD_RO",
13097 b"n",
13098 b"OVERFLOW",
13099 b"SAT",
13100 b"GET",
13101 b"u8",
13102 b"0"
13103 ]),
13104 "*1\r\n:49\r\n"
13105 );
13106 for sub in [&b"SET"[..], b"INCRBY"] {
13107 assert_eq!(
13108 f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
13109 "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
13110 );
13111 }
13112
13113 assert_eq!(
13114 f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
13115 "*1\r\n:0\r\n"
13116 );
13117 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13118 }
13119
13120 #[test]
13122 fn an_offset_off_the_end_of_the_world_is_refused() {
13123 let mut f = Fixture::new();
13124 let bad = "-ERR bit offset is not an integer or out of range\r\n";
13125 for arg in [&b"abc"[..], b"-1", b"4294967296"] {
13126 assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
13127 assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
13128 }
13129 for arg in [&b"2"[..], b"-1"] {
13130 assert_eq!(
13131 f.run(&[b"BITPOS", b"k", arg]),
13132 "-ERR The bit argument must be 1 or 0.\r\n"
13133 );
13134 }
13135 assert_eq!(
13136 f.run(&[b"BITPOS", b"k", b"abc"]),
13137 "-ERR value is not an integer or out of range\r\n"
13138 );
13139 assert_eq!(
13140 f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
13141 "-ERR value is not an integer or out of range\r\n"
13142 );
13143 let bad_bit = "-ERR bit is not an integer or out of range\r\n";
13144 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
13145 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
13146 }
13147
13148 #[test]
13150 fn every_bitmap_command_says_wrongtype() {
13151 let mut f = Fixture::new();
13152 f.run(&[b"LPUSH", b"l", b"x"]);
13153 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13154 let cases: &[&[&[u8]]] = &[
13155 &[b"SETBIT", b"l", b"0", b"1"],
13156 &[b"GETBIT", b"l", b"0"],
13157 &[b"BITCOUNT", b"l"],
13158 &[b"BITPOS", b"l", b"1"],
13159 &[b"BITOP", b"AND", b"d", b"l"],
13160 &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
13161 &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
13162 ];
13163 for case in cases {
13164 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
13165 }
13166 }
13167
13168 #[test]
13171 fn a_sketch_is_added_to_and_counted() {
13172 let mut f = Fixture::new();
13173 assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
13175 assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
13176 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
13177 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
13178 assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
13181 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
13182
13183 assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
13184 assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
13185 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
13186 }
13187
13188 #[test]
13189 fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
13190 let mut f = Fixture::new();
13191 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13192 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";
13194 let mut reply = b"$27\r\n".to_vec();
13195 reply.extend_from_slice(want);
13196 reply.extend_from_slice(b"\r\n");
13197 assert_eq!(f.raw(&[b"GET", b"h"]), reply);
13198 }
13199
13200 #[test]
13201 fn counting_several_keys_counts_their_union() {
13202 let mut f = Fixture::new();
13203 f.run(&[b"PFADD", b"a", b"x", b"y"]);
13204 f.run(&[b"PFADD", b"b", b"y", b"z"]);
13205 assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
13206 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
13207 assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
13210 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
13211 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13212 }
13213
13214 #[test]
13215 fn a_merge_keeps_what_the_destination_had() {
13216 let mut f = Fixture::new();
13217 f.run(&[b"PFADD", b"a", b"x", b"y"]);
13218 f.run(&[b"PFADD", b"b", b"z"]);
13219 assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
13220 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
13221 f.run(&[b"PFADD", b"c", b"w"]);
13223 assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
13224 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
13225 assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
13228 assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
13229 }
13230
13231 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
13240 #[test]
13241 fn the_debug_forms_answer_four_different_shapes() {
13242 let mut f = Fixture::new();
13243 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13244 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
13245 assert_eq!(
13246 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
13247 "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
13248 );
13249 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
13250 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
13251 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
13252 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
13253 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
13254 assert_eq!(
13256 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
13257 "-ERR HLL encoding is not sparse\r\n"
13258 );
13259
13260 let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
13262 assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
13263 assert_eq!(reply.matches(":0\r\n").count(), 16381);
13264 assert_eq!(reply.matches(":1\r\n").count(), 2);
13265 assert_eq!(reply.matches(":2\r\n").count(), 1);
13266
13267 assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
13268 }
13269
13270 #[test]
13271 fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
13272 let mut f = Fixture::new();
13273 f.run(&[b"SET", b"plain", b"not a sketch"]);
13274 let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
13275 assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
13276 assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
13277 assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
13278 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
13279
13280 f.run(&[b"RPUSH", b"l", b"x"]);
13283 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13284 assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
13285 assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
13286 assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
13287 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
13288 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
13289 }
13290
13291 #[test]
13292 fn pfdebug_has_its_own_complaints() {
13293 let mut f = Fixture::new();
13294 f.run(&[b"PFADD", b"h", b"a"]);
13295 assert_eq!(
13298 f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
13299 "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
13300 );
13301 let gone = "-ERR The specified key does not exist\r\n";
13303 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
13304 assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
13305 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
13306 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
13307 assert_eq!(
13308 f.run(&[b"PFDEBUG"]),
13309 "-ERR wrong number of arguments for 'pfdebug' command\r\n"
13310 );
13311 assert_eq!(
13312 f.run(&[b"PFSELFTEST", b"x"]),
13313 "-ERR wrong number of arguments for 'pfselftest' command\r\n"
13314 );
13315 }
13316
13317 #[test]
13318 fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
13319 let mut f = Fixture::new();
13320 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13321 let reply = f.raw(&[b"GET", b"h"]);
13324 let short = reply[5..reply.len() - 3].to_vec();
13325 f.run(&[b"SET", b"h", &short]);
13326 assert_eq!(
13327 f.run(&[b"PFCOUNT", b"h"]),
13328 "-INVALIDOBJ Corrupted HLL object detected\r\n"
13329 );
13330 }
13331
13332 #[test]
13333 fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
13334 let mut f = Fixture::new();
13335 f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
13338 if cfg!(miri) {
13345 f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
13346 f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
13347 } else {
13348 for i in 0..10_000u32 {
13349 let ele = format!("e{i}");
13350 f.run(&[b"PFADD", b"big", ele.as_bytes()]);
13351 }
13352 }
13353 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
13354 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
13355
13356 for key in [&b"small"[..], b"big"] {
13357 let mut copy = key.to_vec();
13358 copy.push(b'2');
13359 let bytes = payload(&f.raw(&[b"DUMP", key]));
13360 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
13361 assert_eq!(f.raw(&[b"GET", ©]), f.raw(&[b"GET", key]));
13364 assert_eq!(
13365 f.run(&[b"PFDEBUG", b"ENCODING", ©]),
13366 f.run(&[b"PFDEBUG", b"ENCODING", key])
13367 );
13368 assert_eq!(f.run(&[b"PFCOUNT", ©]), f.run(&[b"PFCOUNT", key]));
13369 }
13370 assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
13371 assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
13372 }
13373
13374 fn bulk(s: &str) -> String {
13378 format!("${}\r\n{s}\r\n", s.len())
13379 }
13380
13381 fn bulks(parts: &[&str]) -> String {
13384 let mut s = format!("*{}\r\n", parts.len());
13385 for p in parts {
13386 s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
13387 }
13388 s
13389 }
13390
13391 #[test]
13392 fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
13393 let mut f = Fixture::new();
13394 assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
13398 assert_eq!(
13399 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13400 bulks(&["c", "b", "a"])
13401 );
13402 assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
13403 assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
13404 assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
13405 assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
13406 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
13407 assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
13408 }
13409
13410 #[test]
13411 fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
13412 let mut f = Fixture::new();
13413 assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
13414 assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
13415 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
13416 f.run(&[b"RPUSH", b"k", b"a"]);
13417 assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
13418 assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
13419 assert_eq!(
13420 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13421 bulks(&["z", "a", "y"])
13422 );
13423 }
13424
13425 #[test]
13428 fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
13429 let mut f = Fixture::new();
13430 assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
13431 assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
13432 assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
13433 assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
13434 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13435 assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
13438 assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
13439 assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
13441 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
13442 }
13443
13444 #[test]
13445 fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
13446 let mut f = Fixture::new();
13447 f.run(&[b"RPUSH", b"k", b"a"]);
13448 let range = "-ERR value is out of range, must be positive\r\n";
13449 assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
13450 assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
13451 assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
13452 assert_eq!(
13455 f.run(&[b"LPOP", b"k", b"1", b"2"]),
13456 "-ERR wrong number of arguments for 'lpop' command\r\n"
13457 );
13458 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
13459 }
13460
13461 #[test]
13462 fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
13463 let mut f = Fixture::new();
13464 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13465 assert_eq!(
13466 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13467 bulks(&["a", "b", "c"])
13468 );
13469 assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
13470 assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
13471 assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
13472 assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
13473 assert_eq!(
13474 f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
13475 bulks(&["a", "b", "c"])
13476 );
13477 assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
13480 assert_eq!(
13481 f.run(&[b"LRANGE", b"k", b"a", b"b"]),
13482 "-ERR value is not an integer or out of range\r\n"
13483 );
13484 }
13485
13486 #[test]
13487 fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
13488 let mut f = Fixture::new();
13489 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13490 assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
13491 assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
13492 assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
13493 assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
13494 assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
13495 assert_eq!(
13496 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13497 bulks(&["a", "b", "z"])
13498 );
13499 assert_eq!(
13502 f.run(&[b"LSET", b"k", b"99", b"z"]),
13503 "-ERR index out of range\r\n"
13504 );
13505 assert_eq!(
13506 f.run(&[b"LSET", b"nope", b"0", b"z"]),
13507 "-ERR no such key\r\n"
13508 );
13509 }
13510
13511 #[test]
13512 fn linsert_says_three_things_with_one_signed_number() {
13513 let mut f = Fixture::new();
13514 assert_eq!(
13517 f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
13518 ":0\r\n"
13519 );
13520 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
13521 assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
13522 assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
13523 assert_eq!(
13524 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13525 bulks(&["X", "a", "b", "Y"])
13526 );
13527 assert_eq!(
13528 f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
13529 ":-1\r\n"
13530 );
13531 assert_eq!(
13532 f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
13533 "-ERR syntax error\r\n"
13534 );
13535 }
13536
13537 #[test]
13538 fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
13539 let mut f = Fixture::new();
13540 f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
13541 assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
13542 assert_eq!(
13543 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13544 bulks(&["b", "c", "a"])
13545 );
13546 assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
13547 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
13548 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
13549 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
13550 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
13551 assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
13552 }
13553
13554 #[test]
13555 fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
13556 let mut f = Fixture::new();
13557 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
13558 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
13559 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
13560 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
13563 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
13564 assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
13565 }
13566
13567 #[test]
13568 fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
13569 let mut f = Fixture::new();
13570 f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
13571 assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
13572 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
13573 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
13574 assert_eq!(
13575 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
13576 "*2\r\n:0\r\n:3\r\n"
13577 );
13578 assert_eq!(
13579 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
13580 "*3\r\n:6\r\n:3\r\n:0\r\n"
13581 );
13582 assert_eq!(
13585 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
13586 "*1\r\n:0\r\n"
13587 );
13588 assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
13591 assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
13592 assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
13593 assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
13594 }
13595
13596 #[test]
13597 fn lpos_words_its_three_mistakes_the_way_redis_does() {
13598 let mut f = Fixture::new();
13599 f.run(&[b"RPUSH", b"p", b"a"]);
13600 assert_eq!(
13603 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
13604 "-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"
13605 );
13606 assert_eq!(
13607 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
13608 "-ERR COUNT can't be negative\r\n"
13609 );
13610 assert_eq!(
13611 f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
13612 "-ERR MAXLEN can't be negative\r\n"
13613 );
13614 assert_eq!(
13615 f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
13616 "-ERR syntax error\r\n"
13617 );
13618 assert_eq!(
13619 f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
13620 "-ERR syntax error\r\n"
13621 );
13622 }
13623
13624 #[test]
13625 fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
13626 let mut f = Fixture::new();
13627 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13628 assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
13629 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
13630 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
13631 assert_eq!(
13632 f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
13633 "$1\r\na\r\n"
13634 );
13635 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
13636 f.run(&[b"DEL", b"r"]);
13639 f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
13640 assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
13641 assert_eq!(
13642 f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
13643 bulks(&["3", "1", "2"])
13644 );
13645 assert_eq!(
13646 f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
13647 "$-1\r\n"
13648 );
13649 assert_eq!(
13650 f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
13651 "-ERR syntax error\r\n"
13652 );
13653 }
13654
13655 #[test]
13656 fn a_move_checks_the_destination_before_it_takes_anything() {
13657 let mut f = Fixture::new();
13658 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
13659 f.run(&[b"SET", b"str", b"v"]);
13660 assert_eq!(
13661 f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
13662 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13663 );
13664 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
13666 }
13667
13668 #[test]
13669 fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
13670 for (from, to, order, want) in [
13674 ("LEFT", "RIGHT", "OBO", ["a", "b"]),
13675 ("LEFT", "RIGHT", "BULK", ["a", "b"]),
13676 ("LEFT", "LEFT", "OBO", ["b", "a"]),
13677 ("LEFT", "LEFT", "BULK", ["a", "b"]),
13678 ("RIGHT", "LEFT", "OBO", ["d", "e"]),
13679 ("RIGHT", "LEFT", "BULK", ["d", "e"]),
13680 ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
13681 ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
13682 ] {
13683 let mut f = Fixture::new();
13684 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
13685 let how = format!("{from} {to} {order}");
13686 let reply = f.run(&[
13687 b"LMOVEM",
13688 b"s",
13689 b"d",
13690 from.as_bytes(),
13691 to.as_bytes(),
13692 b"COUNT",
13693 b"2",
13694 order.as_bytes(),
13695 ]);
13696 assert_eq!(reply, bulks(&want), "the reply for {how}");
13697 assert_eq!(
13698 f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
13699 bulks(&want),
13700 "the destination for {how}"
13701 );
13702 }
13703 }
13704
13705 #[test]
13706 fn a_block_move_of_one_needs_no_count_at_all() {
13707 let mut f = Fixture::new();
13708 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
13709 assert_eq!(
13710 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
13711 bulks(&["a"])
13712 );
13713 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
13714 assert_eq!(
13717 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
13718 "-ERR syntax error\r\n"
13719 );
13720 assert_eq!(
13721 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
13722 "-ERR syntax error\r\n"
13723 );
13724 }
13725
13726 #[test]
13727 fn a_block_move_with_exactly_takes_all_of_them_or_none() {
13728 let mut f = Fixture::new();
13729 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
13730 assert_eq!(
13734 f.run(&[
13735 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
13736 ]),
13737 "*-1\r\n"
13738 );
13739 assert_eq!(
13740 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
13741 bulks(&["a", "b", "c"])
13742 );
13743 assert_eq!(
13745 f.run(&[
13746 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
13747 ]),
13748 bulks(&["a", "b", "c"])
13749 );
13750 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
13751 assert_eq!(
13752 f.run(&[
13753 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
13754 ]),
13755 "*-1\r\n"
13756 );
13757 }
13758
13759 #[test]
13760 fn a_block_move_onto_itself_rotates_by_the_count() {
13761 let mut f = Fixture::new();
13762 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
13763 assert_eq!(
13764 f.run(&[
13765 b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
13766 ]),
13767 bulks(&["a", "b"])
13768 );
13769 assert_eq!(
13770 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
13771 bulks(&["c", "a", "b"])
13772 );
13773 }
13774
13775 #[test]
13776 fn a_block_move_reads_the_count_before_the_ordering_word() {
13777 let mut f = Fixture::new();
13778 f.run(&[b"RPUSH", b"s", b"a", b"b"]);
13779 f.run(&[b"SET", b"str", b"v"]);
13780 let count = "-ERR count should be greater than 0\r\n";
13781 assert_eq!(
13782 f.run(&[
13783 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
13784 ]),
13785 count
13786 );
13787 assert_eq!(
13788 f.run(&[
13789 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
13790 ]),
13791 count
13792 );
13793 assert_eq!(
13794 f.run(&[
13795 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
13796 ]),
13797 "-ERR syntax error\r\n"
13798 );
13799 assert_eq!(
13800 f.run(&[
13801 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
13802 ]),
13803 "-ERR syntax error\r\n"
13804 );
13805 assert_eq!(
13808 f.run(&[
13809 b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
13810 ]),
13811 count
13812 );
13813 assert_eq!(
13814 f.run(&[
13815 b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
13816 ]),
13817 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13818 );
13819 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
13820 }
13821
13822 #[test]
13823 fn lmpop_answers_from_the_first_key_that_has_anything() {
13824 let mut f = Fixture::new();
13825 f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
13826 assert_eq!(
13829 f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
13830 "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
13831 );
13832 assert_eq!(
13833 f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
13834 "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
13835 );
13836 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
13837 assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
13840 }
13841
13842 #[test]
13843 fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
13844 let mut f = Fixture::new();
13845 f.run(&[b"RPUSH", b"k", b"a"]);
13846 assert_eq!(
13847 f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
13848 "-ERR numkeys should be greater than 0\r\n"
13849 );
13850 assert_eq!(
13851 f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
13852 "-ERR numkeys should be greater than 0\r\n"
13853 );
13854 assert_eq!(
13855 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
13856 "-ERR count should be greater than 0\r\n"
13857 );
13858 assert_eq!(
13861 f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
13862 "-ERR syntax error\r\n"
13863 );
13864 assert_eq!(
13865 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
13866 "-ERR syntax error\r\n"
13867 );
13868 assert_eq!(
13869 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
13870 "-ERR syntax error\r\n"
13871 );
13872 assert_eq!(
13873 f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
13874 "-ERR syntax error\r\n"
13875 );
13876 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
13877 }
13878
13879 #[test]
13880 fn every_list_command_says_wrongtype_and_writes_nothing() {
13881 let mut f = Fixture::new();
13882 f.run(&[b"SET", b"str", b"v"]);
13883 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13884 for cmd in [
13885 &[b"LPUSH".as_slice(), b"str", b"a"][..],
13886 &[b"RPUSH", b"str", b"a"],
13887 &[b"LPUSHX", b"str", b"a"],
13888 &[b"RPUSHX", b"str", b"a"],
13889 &[b"LPOP", b"str"],
13890 &[b"LPOP", b"str", b"2"],
13891 &[b"RPOP", b"str"],
13892 &[b"LLEN", b"str"],
13893 &[b"LRANGE", b"str", b"0", b"-1"],
13894 &[b"LINDEX", b"str", b"0"],
13895 &[b"LSET", b"str", b"0", b"a"],
13896 &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
13897 &[b"LREM", b"str", b"0", b"a"],
13898 &[b"LTRIM", b"str", b"0", b"-1"],
13899 &[b"LPOS", b"str", b"a"],
13900 &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
13901 &[b"RPOPLPUSH", b"str", b"d"],
13902 &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
13903 &[b"LMPOP", b"1", b"str", b"LEFT"],
13904 ] {
13905 assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
13906 }
13907 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
13908 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
13909 }
13910
13911 #[test]
13915 fn a_timeout_has_three_ways_of_being_wrong() {
13916 let mut f = Fixture::new();
13917 let not_float = "-ERR timeout is not a float or out of range\r\n";
13918 let range = "-ERR timeout is out of range\r\n";
13919 for (bad, want) in [
13920 (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
13921 (&[b"BLPOP", b"k", b"nan"], not_float),
13922 (&[b"BLPOP", b"k", b""], not_float),
13923 (&[b"BLPOP", b"k", b" 1"], not_float),
13926 (&[b"BLPOP", b"k", b"1 "], not_float),
13927 (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
13928 (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
13929 (&[b"BLPOP", b"k", b"1e400"], range),
13932 (&[b"BLPOP", b"k", b"inf"], range),
13933 (&[b"BLPOP", b"k", b"9999999999999999"], range),
13934 (&[b"BRPOP", b"k", b"abc"], not_float),
13935 (
13936 &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
13937 not_float,
13938 ),
13939 (
13940 &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
13941 "-ERR timeout is negative\r\n",
13942 ),
13943 (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
13944 ] {
13945 assert_eq!(f.run(bad), want, "for {bad:?}");
13946 }
13947 }
13948
13949 #[test]
13952 fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
13953 let mut f = Fixture::new();
13954 for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
13955 let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
13956 assert_eq!(flow, Flow::Block, "for {timeout:?}");
13957 assert!(out.is_empty(), "for {timeout:?}");
13958 }
13959 let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
13963 assert_eq!(flow, Flow::Block);
13964 assert!(out.is_empty());
13965 }
13966
13967 #[test]
13968 fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
13969 let mut f = Fixture::new();
13970 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
13971
13972 assert_eq!(
13975 f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
13976 (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
13977 );
13978 assert_eq!(
13979 f.run(&[b"BRPOP", b"L", b"0"]),
13980 "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
13981 );
13982 assert_eq!(
13983 f.run(&[
13984 b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
13985 ]),
13986 "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
13987 );
13988 assert_eq!(
13989 f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
13990 "$1\r\nd\r\n"
13991 );
13992 assert_eq!(
13993 f.run(&[b"EXISTS", b"L"]),
13994 ":0\r\n",
13995 "and the key went with it"
13996 );
13997 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
13998 f.run(&[b"RPUSH", b"D", b"x"]);
14001 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
14002 assert_eq!(
14003 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
14004 "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
14005 );
14006 }
14007
14008 #[test]
14009 fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
14010 let mut f = Fixture::new();
14011 f.run(&[b"RPUSH", b"k", b"a"]);
14012 for (bad, want) in [
14013 (
14014 &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
14015 "-ERR numkeys should be greater than 0\r\n",
14016 ),
14017 (
14018 &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
14019 "-ERR numkeys should be greater than 0\r\n",
14020 ),
14021 (
14024 &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
14025 "-ERR syntax error\r\n",
14026 ),
14027 (
14028 &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
14029 "-ERR syntax error\r\n",
14030 ),
14031 (
14032 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
14033 "-ERR syntax error\r\n",
14034 ),
14035 (
14036 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
14037 "-ERR syntax error\r\n",
14038 ),
14039 (
14042 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
14043 "-ERR count should be greater than 0\r\n",
14044 ),
14045 (
14046 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
14047 "-ERR count should be greater than 0\r\n",
14048 ),
14049 ] {
14050 assert_eq!(f.run(bad), want, "for {bad:?}");
14051 }
14052 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
14053 }
14054
14055 #[test]
14056 fn a_blocking_move_reads_its_directions_before_its_timeout() {
14057 let mut f = Fixture::new();
14058 assert_eq!(
14061 f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
14062 "-ERR syntax error\r\n"
14063 );
14064 assert_eq!(
14065 f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
14066 "-ERR syntax error\r\n"
14067 );
14068 }
14069
14070 #[test]
14074 fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
14075 let mut f = Fixture::new();
14076 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
14077 assert_eq!(
14078 f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
14079 (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
14080 );
14081 assert_eq!(
14082 f.run(&[
14083 b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
14084 ]),
14085 bulks(&["e", "d"])
14086 );
14087 assert_eq!(
14088 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
14089 bulks(&["a", "e", "d"])
14090 );
14091 assert_eq!(
14093 f.run(&[
14094 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
14095 ]),
14096 bulks(&["b", "c"])
14097 );
14098 assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
14099 }
14100
14101 #[test]
14105 fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
14106 let mut f = Fixture::new();
14107 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
14108 assert_eq!(
14110 f.flow(&[
14111 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
14112 ]),
14113 (Flow::Continue, bulks(&["a", "b"]))
14114 );
14115
14116 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
14117 assert_eq!(
14120 f.flow(&[
14121 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
14122 ])
14123 .0,
14124 Flow::Block
14125 );
14126 assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
14127 }
14128
14129 #[test]
14130 fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
14131 let mut f = Fixture::new();
14132 let syntax = "-ERR syntax error\r\n";
14133 assert_eq!(
14135 f.run(&[
14136 b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
14137 ]),
14138 syntax
14139 );
14140 assert_eq!(
14142 f.run(&[
14143 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
14144 ]),
14145 "-ERR timeout is not a float or out of range\r\n"
14146 );
14147 assert_eq!(
14148 f.run(&[
14149 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
14150 ]),
14151 "-ERR timeout is negative\r\n"
14152 );
14153 assert_eq!(
14155 f.run(&[
14156 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
14157 ]),
14158 "-ERR count should be greater than 0\r\n"
14159 );
14160 assert_eq!(
14161 f.run(&[
14162 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
14163 ]),
14164 syntax
14165 );
14166 assert_eq!(
14169 f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
14170 syntax
14171 );
14172 assert_eq!(
14173 f.run(&[
14174 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
14175 ]),
14176 syntax
14177 );
14178 }
14179
14180 #[test]
14183 fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
14184 let mut f = Fixture::new();
14185 f.run(&[b"SET", b"S", b"v"]);
14186 f.run(&[b"RPUSH", b"D", b"x"]);
14187 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14188
14189 assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
14190 assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
14193 assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
14194 assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
14195 assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
14196 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
14199 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
14200 assert_eq!(
14201 f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
14202 wrong
14203 );
14204 assert_eq!(
14205 f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
14206 wrong
14207 );
14208
14209 assert_eq!(
14213 f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
14214 .0,
14215 Flow::Block
14216 );
14217 assert_eq!(
14221 f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
14222 .0,
14223 Flow::Block
14224 );
14225 f.run(&[b"RPUSH", b"E", b"1", b"2"]);
14226 assert_eq!(
14227 f.flow(&[
14228 b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
14229 ])
14230 .0,
14231 Flow::Block
14232 );
14233 }
14234
14235 #[cfg_attr(miri, ignore = "the volume is the claim")]
14240 #[test]
14241 fn churning_lists_does_not_grow_the_server() {
14242 let mut f = Fixture::new();
14243 let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
14244 let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
14245 .into_iter()
14246 .chain(vals.iter().map(Vec::as_slice))
14247 .collect();
14248
14249 f.run(&args);
14250 f.run(&[b"DEL", b"k"]);
14251 f.server.compact_step();
14252 let after_first = f.server.memory_bytes();
14253
14254 for _ in 0..200 {
14255 f.run(&args);
14256 f.run(&[b"LTRIM", b"k", b"1", b"0"]);
14257 f.server.compact_step();
14258 }
14259 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14260 assert!(
14261 f.server.memory_bytes() <= after_first * 2,
14262 "held {} after two hundred passes against {after_first} after one",
14263 f.server.memory_bytes()
14264 );
14265 }
14266
14267 #[test]
14270 fn a_sorted_set_takes_scores_and_gives_them_back() {
14271 let mut f = Fixture::new();
14272 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
14273 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
14274 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
14275 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
14276 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
14277 assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
14278 assert_eq!(
14279 f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
14280 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
14281 );
14282 assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
14283 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
14284 assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
14286 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
14287 }
14288
14289 #[test]
14290 fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
14291 let mut f = Fixture::new();
14292 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
14293 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
14294 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
14295 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
14296
14297 f.out = Out::new(Proto::Resp3);
14298 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
14299 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
14300 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
14301 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
14302 }
14303
14304 #[test]
14305 fn the_zadd_options_gate_what_gets_written() {
14306 let mut f = Fixture::new();
14307 f.run(&[b"ZADD", b"z", b"5", b"a"]);
14308 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
14310 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
14311 assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
14312 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
14313 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
14315 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
14316 assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
14317 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
14319 assert_eq!(
14320 f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
14321 ":2\r\n"
14322 );
14323 }
14324
14325 #[test]
14326 fn zadd_incr_answers_a_score_or_nothing_at_all() {
14327 let mut f = Fixture::new();
14328 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
14329 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
14330 assert_eq!(
14333 f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
14334 "$-1\r\n"
14335 );
14336 assert_eq!(
14337 f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
14338 "$-1\r\n"
14339 );
14340 assert_eq!(
14341 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
14342 "$-1\r\n"
14343 );
14344 assert_eq!(
14345 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
14346 "$1\r\n8\r\n"
14347 );
14348 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
14349 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
14350 }
14351
14352 #[test]
14353 fn the_two_infinities_will_not_be_added_together() {
14354 let mut f = Fixture::new();
14355 f.run(&[b"ZADD", b"z", b"inf", b"m"]);
14356 let nan = "-ERR resulting score is not a number (NaN)\r\n";
14357 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
14358 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
14359 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
14360 assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
14362 }
14363
14364 #[test]
14365 fn zadd_says_its_mistakes_the_way_redis_says_them() {
14366 let mut f = Fixture::new();
14367 assert_eq!(
14370 f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
14371 "-ERR syntax error\r\n"
14372 );
14373 assert_eq!(
14374 f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
14375 "-ERR XX and NX options at the same time are not compatible\r\n"
14376 );
14377 let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
14378 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
14379 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
14380 assert_eq!(
14381 f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
14382 "-ERR INCR option supports a single increment-element pair\r\n"
14383 );
14384 assert_eq!(
14386 f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
14387 "-ERR syntax error\r\n"
14388 );
14389 assert_eq!(
14391 f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
14392 "-ERR value is not a valid float\r\n"
14393 );
14394 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
14395 }
14396
14397 #[test]
14398 fn a_rank_says_where_a_member_sits_from_either_end() {
14399 let mut f = Fixture::new();
14400 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14401 assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
14402 assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
14403 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
14404 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
14405 assert_eq!(
14407 f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
14408 "*2\r\n:1\r\n$1\r\n2\r\n"
14409 );
14410 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
14411 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
14412 assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
14413 assert_eq!(
14416 f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
14417 "-ERR syntax error\r\n"
14418 );
14419 assert_eq!(
14420 f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
14421 "-ERR wrong number of arguments for 'zrevrank' command\r\n"
14422 );
14423 }
14424
14425 #[test]
14426 fn the_two_counts_read_their_two_kinds_of_bound() {
14427 let mut f = Fixture::new();
14428 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14429 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
14430 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
14431 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
14432 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
14433 assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
14434 assert_eq!(
14435 f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
14436 "-ERR min or max is not a float\r\n"
14437 );
14438
14439 f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
14440 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
14441 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
14442 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
14443 assert_eq!(
14446 f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
14447 "-ERR min or max not valid string range item\r\n"
14448 );
14449 }
14450
14451 #[test]
14457 fn one_range_command_selects_by_rank_or_score_or_name() {
14458 let mut f = Fixture::new();
14459 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14460 assert_eq!(
14461 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
14462 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
14463 );
14464 assert_eq!(
14465 f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
14466 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14467 );
14468 assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
14469 assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
14470 assert_eq!(
14473 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
14474 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14475 );
14476 assert_eq!(
14477 f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
14478 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14479 );
14480 assert_eq!(
14483 f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
14484 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
14485 );
14486 assert_eq!(
14487 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
14488 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
14489 );
14490 assert_eq!(
14491 f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
14492 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14493 );
14494 }
14495
14496 #[test]
14499 fn the_older_range_spellings_name_their_high_end_first() {
14500 let mut f = Fixture::new();
14501 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14502 assert_eq!(
14503 f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
14504 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14505 );
14506 assert_eq!(
14507 f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
14508 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
14509 );
14510 assert_eq!(
14511 f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
14512 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14513 );
14514 assert_eq!(
14515 f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
14516 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
14517 );
14518 assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
14522 assert_eq!(
14523 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
14524 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
14525 );
14526 assert_eq!(
14527 f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
14528 "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
14529 );
14530 for cmd in [
14533 &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
14534 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
14535 &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
14536 ] {
14537 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
14538 }
14539 }
14540
14541 #[test]
14544 fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
14545 let mut f = Fixture::new();
14546 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14547 assert_eq!(
14548 f.run(&[
14549 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
14550 ]),
14551 "*1\r\n$1\r\nb\r\n"
14552 );
14553 assert_eq!(
14555 f.run(&[
14556 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
14557 ]),
14558 "*0\r\n"
14559 );
14560 assert_eq!(
14561 f.run(&[
14562 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
14563 ]),
14564 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
14565 );
14566 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";
14568 assert_eq!(
14569 f.run(&[
14570 b"ZRANGEBYSCORE",
14571 b"z",
14572 b"1",
14573 b"3",
14574 b"WITHSCORES",
14575 b"LIMIT",
14576 b"0",
14577 b"2"
14578 ]),
14579 both
14580 );
14581 assert_eq!(
14582 f.run(&[
14583 b"ZRANGEBYSCORE",
14584 b"z",
14585 b"1",
14586 b"3",
14587 b"LIMIT",
14588 b"0",
14589 b"2",
14590 b"WITHSCORES"
14591 ]),
14592 both
14593 );
14594 let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
14597 assert_eq!(
14598 f.run(&[
14599 b"ZREVRANGE",
14600 b"z",
14601 b"0",
14602 b"-1",
14603 b"WITHSCORES",
14604 b"LIMIT",
14605 b"0",
14606 b"1"
14607 ]),
14608 needs_by
14609 );
14610 assert_eq!(
14611 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
14612 needs_by
14613 );
14614 let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
14615 assert_eq!(
14616 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
14617 not_bylex
14618 );
14619 assert_eq!(
14620 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
14621 not_bylex
14622 );
14623 for cmd in [
14626 &[
14627 b"ZRANGE".as_slice(),
14628 b"z",
14629 b"0",
14630 b"-1",
14631 b"BYSCORE",
14632 b"BYLEX",
14633 ][..],
14634 &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
14635 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
14636 ] {
14637 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
14638 }
14639 assert_eq!(
14640 f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
14641 "-ERR min or max is not a float\r\n"
14642 );
14643 assert_eq!(
14644 f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
14645 "-ERR min or max not valid string range item\r\n"
14646 );
14647 assert_eq!(
14648 f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
14649 "-ERR value is not an integer or out of range\r\n"
14650 );
14651 }
14652
14653 #[test]
14656 fn withscores_nests_on_resp3_and_flattens_on_resp2() {
14657 let mut f = Fixture::new();
14658 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14659 assert_eq!(
14660 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
14661 "*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"
14662 );
14663 f.out = Out::new(Proto::Resp3);
14664 assert_eq!(
14665 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
14666 "*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"
14667 );
14668 assert_eq!(
14669 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
14670 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
14671 );
14672 }
14673
14674 #[test]
14676 fn a_range_store_writes_the_window_into_another_key() {
14677 let mut f = Fixture::new();
14678 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14679 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
14680 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
14683 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
14684 assert_eq!(
14685 f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
14686 ":2\r\n"
14687 );
14688 assert_eq!(
14689 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
14690 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
14691 );
14692 assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
14695 assert_eq!(
14696 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
14697 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
14698 );
14699 assert_eq!(
14702 f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
14703 "-ERR syntax error\r\n"
14704 );
14705 }
14706
14707 #[test]
14710 fn the_three_removals_share_their_window_with_the_reads() {
14711 let mut f = Fixture::new();
14712 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14713 assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
14714 assert_eq!(
14715 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
14716 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14717 );
14718 assert_eq!(
14719 f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
14720 ":1\r\n"
14721 );
14722 assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
14723 assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
14725 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
14726 assert_eq!(
14727 f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
14728 ":0\r\n"
14729 );
14730 assert_eq!(
14731 f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
14732 "-ERR value is not an integer or out of range\r\n"
14733 );
14734 }
14735
14736 #[test]
14738 fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
14739 let mut f = Fixture::new();
14740 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14741 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
14742 assert_eq!(
14743 f.run(&[b"ZUNION", b"2", b"z", b"y"]),
14744 "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
14745 );
14746 assert_eq!(
14749 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
14750 "*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"
14751 );
14752 assert_eq!(
14753 f.run(&[
14754 b"ZUNION",
14755 b"2",
14756 b"z",
14757 b"y",
14758 b"WEIGHTS",
14759 b"2",
14760 b"3",
14761 b"WITHSCORES"
14762 ]),
14763 "*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"
14764 );
14765 assert_eq!(
14766 f.run(&[
14767 b"ZUNION",
14768 b"2",
14769 b"z",
14770 b"y",
14771 b"AGGREGATE",
14772 b"MIN",
14773 b"WITHSCORES"
14774 ]),
14775 "*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"
14776 );
14777 assert_eq!(
14778 f.run(&[
14779 b"ZUNION",
14780 b"2",
14781 b"z",
14782 b"y",
14783 b"AGGREGATE",
14784 b"MAX",
14785 b"WITHSCORES"
14786 ]),
14787 "*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"
14788 );
14789 assert_eq!(
14790 f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
14791 "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
14792 );
14793 assert_eq!(
14794 f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
14795 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
14796 );
14797 assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
14798 f.run(&[b"SADD", b"p", b"a", b"d"]);
14801 assert_eq!(
14802 f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
14803 "*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"
14804 );
14805 for cmd in [
14808 &[
14809 b"ZDIFF".as_slice(),
14810 b"2",
14811 b"z",
14812 b"y",
14813 b"WEIGHTS",
14814 b"1",
14815 b"1",
14816 ][..],
14817 &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
14818 ] {
14819 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
14820 }
14821 }
14822
14823 #[test]
14825 fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
14826 let mut f = Fixture::new();
14827 f.run(&[b"ZADD", b"z", b"1", b"a"]);
14828 f.run(&[b"ZADD", b"y", b"2", b"b"]);
14829 assert_eq!(
14831 f.run(&[b"ZUNION", b"0", b"z"]),
14832 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
14833 );
14834 assert_eq!(
14835 f.run(&[b"ZUNION", b"-1", b"z"]),
14836 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
14837 );
14838 assert_eq!(
14839 f.run(&[b"ZINTERCARD", b"0", b"z"]),
14840 "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
14841 );
14842 assert_eq!(
14845 f.run(&[b"ZUNION", b"3", b"z", b"y"]),
14846 "-ERR syntax error\r\n"
14847 );
14848 assert_eq!(
14849 f.run(&[b"ZUNION", b"x", b"z"]),
14850 "-ERR value is not an integer or out of range\r\n"
14851 );
14852 assert_eq!(
14855 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
14856 "-ERR syntax error\r\n"
14857 );
14858 assert_eq!(
14859 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
14860 "-ERR weight value is not a float\r\n"
14861 );
14862 assert_eq!(
14863 f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
14864 "-ERR syntax error\r\n"
14865 );
14866 }
14867
14868 #[test]
14870 fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
14871 let mut f = Fixture::new();
14872 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14873 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
14874 assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
14875 assert_eq!(
14876 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
14877 "*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"
14878 );
14879 assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
14880 assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
14881 assert_eq!(
14884 f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
14885 ":0\r\n"
14886 );
14887 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
14888 assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
14890 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
14891 for cmd in [
14892 &[
14893 b"ZUNIONSTORE".as_slice(),
14894 b"d",
14895 b"2",
14896 b"z",
14897 b"y",
14898 b"WITHSCORES",
14899 ][..],
14900 &[
14901 b"ZDIFFSTORE",
14902 b"d",
14903 b"2",
14904 b"z",
14905 b"y",
14906 b"WEIGHTS",
14907 b"1",
14908 b"1",
14909 ],
14910 ] {
14911 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
14912 }
14913 }
14914
14915 #[test]
14917 fn intercard_counts_and_stops_at_its_limit() {
14918 let mut f = Fixture::new();
14919 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14920 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
14921 assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
14922 assert_eq!(
14924 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
14925 ":2\r\n"
14926 );
14927 assert_eq!(
14928 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
14929 ":1\r\n"
14930 );
14931 let bad = "-ERR LIMIT can't be negative\r\n";
14934 assert_eq!(
14935 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
14936 bad
14937 );
14938 assert_eq!(
14939 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
14940 bad
14941 );
14942 for cmd in [
14943 &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
14944 &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
14945 &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
14946 ] {
14947 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
14948 }
14949 }
14950
14951 #[test]
14953 fn a_draw_answers_one_member_or_an_array_of_them() {
14954 let mut f = Fixture::new();
14955 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14956 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
14959 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
14960 assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
14961 assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
14962 let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
14965 assert!(all.starts_with("*3\r\n"), "{all}");
14966 for m in ["a", "b", "c"] {
14967 assert!(all.contains(m), "{all}");
14968 }
14969 assert!(
14972 f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
14973 "five draws with replacement"
14974 );
14975 assert!(
14976 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
14977 .starts_with("*4\r\n"),
14978 "two pairs, flat on RESP2"
14979 );
14980 f.out = Out::new(Proto::Resp3);
14981 let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
14982 assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
14983 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
14984 f.out = Out::new(Proto::Resp2);
14985 assert_eq!(
14986 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
14987 "-ERR syntax error\r\n"
14988 );
14989 assert_eq!(
14990 f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
14991 "-ERR value is not an integer or out of range\r\n"
14992 );
14993 }
14994
14995 #[test]
14997 fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
14998 let mut f = Fixture::new();
14999 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15000 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";
15001 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
15002 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
15003 assert_eq!(
15004 f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
15005 "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
15006 );
15007 assert_eq!(
15008 f.run(&[b"ZSCAN", b"nokey", b"0"]),
15009 "*2\r\n$1\r\n0\r\n*0\r\n"
15010 );
15011 f.out = Out::new(Proto::Resp3);
15014 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
15015 f.out = Out::new(Proto::Resp2);
15016 assert_eq!(
15017 f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
15018 "-ERR NOVALUES option can only be used in HSCAN\r\n"
15019 );
15020 assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
15021 assert_eq!(
15022 f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
15023 "-ERR syntax error\r\n"
15024 );
15025 }
15026
15027 #[test]
15029 fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
15030 let mut f = Fixture::new();
15031 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15032 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
15034 assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
15035 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
15036 assert_eq!(
15038 f.run(&[b"ZPOPMIN", b"z", b"2"]),
15039 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
15040 );
15041 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
15044 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
15045 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
15046 assert_eq!(
15048 f.run(&[b"ZPOPMIN", b"z", b"9"]),
15049 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15050 );
15051 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15052
15053 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
15054 f.out = Out::new(Proto::Resp3);
15055 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
15056 assert_eq!(
15057 f.run(&[b"ZPOPMIN", b"z", b"1"]),
15058 "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
15059 );
15060 f.out = Out::new(Proto::Resp2);
15061 let bad = "-ERR value is out of range, must be positive\r\n";
15064 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
15065 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
15066 assert_eq!(
15067 f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
15068 "-ERR syntax error\r\n"
15069 );
15070 }
15071
15072 #[test]
15074 fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
15075 let mut f = Fixture::new();
15076 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15077 assert_eq!(
15078 f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
15079 "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
15080 );
15081 assert_eq!(
15084 f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
15085 "*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"
15086 );
15087 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
15089 f.out = Out::new(Proto::Resp3);
15090 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
15091 f.out = Out::new(Proto::Resp2);
15092 let numkeys = "-ERR numkeys should be greater than 0\r\n";
15093 for bad in [
15094 &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
15095 &[b"ZMPOP", b"-1", b"z", b"MIN"],
15096 &[b"ZMPOP", b"x", b"z", b"MIN"],
15097 ] {
15098 assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
15099 }
15100 let count = "-ERR count should be greater than 0\r\n";
15101 for bad in [
15102 &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
15103 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
15104 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
15105 ] {
15106 assert_eq!(f.run(bad), count, "{:?}", bad[5]);
15107 }
15108 let syntax = "-ERR syntax error\r\n";
15109 for bad in [
15110 &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
15113 &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
15114 &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
15115 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
15116 ] {
15117 assert_eq!(f.run(bad), syntax, "{bad:?}");
15118 }
15119 }
15120
15121 #[test]
15124 fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
15125 let mut f = Fixture::new();
15126 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15127 assert_eq!(
15128 f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
15129 (
15130 Flow::Continue,
15131 "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
15132 )
15133 );
15134 assert_eq!(
15135 f.run(&[b"BZPOPMAX", b"z", b"0"]),
15136 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
15137 );
15138 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
15139 assert_eq!(
15140 f.run(&[
15141 b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
15142 ]),
15143 "*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"
15144 );
15145 f.out = Out::new(Proto::Resp3);
15146 assert_eq!(
15147 f.run(&[b"BZPOPMIN", b"z", b"0"]),
15148 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
15149 );
15150 f.out = Out::new(Proto::Resp2);
15151 assert_eq!(
15153 f.flow(&[b"BZPOPMIN", b"z", b"0"]),
15154 (Flow::Block, String::new())
15155 );
15156 assert_eq!(
15157 f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
15158 (Flow::Block, String::new())
15159 );
15160 assert_eq!(
15163 f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
15164 "-ERR timeout is not a float or out of range\r\n"
15165 );
15166 assert_eq!(
15167 f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
15168 "-ERR numkeys should be greater than 0\r\n"
15169 );
15170 assert_eq!(
15171 f.run(&[b"BZPOPMIN", b"z", b"-1"]),
15172 "-ERR timeout is negative\r\n"
15173 );
15174 }
15175
15176 #[test]
15180 fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
15181 let mut f = Fixture::new();
15182 assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
15183 assert_eq!(f.server.parked(), 1);
15184 f.run(&[b"SET", b"z", b"v"]);
15187 let mut out = Out::new(Proto::Resp2);
15188 assert!(!f.server.serve_waiter(7, 0, &mut out));
15189 assert!(out.as_slice().is_empty());
15190 f.run(&[b"DEL", b"z"]);
15191 f.run(&[b"ZADD", b"z", b"5", b"m"]);
15192 assert!(f.server.serve_waiter(7, 0, &mut out));
15193 assert_eq!(
15194 core::str::from_utf8(out.as_slice()).expect("ascii"),
15195 "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
15196 );
15197 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15200 }
15201
15202 #[test]
15203 fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
15204 let mut f = Fixture::new();
15205 f.run(&[b"SET", b"s", b"v"]);
15206 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
15207 for cmd in [
15208 &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
15209 &[b"ZINCRBY", b"s", b"1", b"a"],
15210 &[b"ZCARD", b"s"],
15211 &[b"ZSCORE", b"s", b"a"],
15212 &[b"ZMSCORE", b"s", b"a"],
15213 &[b"ZREM", b"s", b"a"],
15214 &[b"ZRANK", b"s", b"a"],
15215 &[b"ZREVRANK", b"s", b"a"],
15216 &[b"ZCOUNT", b"s", b"1", b"2"],
15217 &[b"ZLEXCOUNT", b"s", b"-", b"+"],
15218 &[b"ZRANGE", b"s", b"0", b"-1"],
15219 &[b"ZREVRANGE", b"s", b"0", b"-1"],
15220 &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
15221 &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
15222 &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
15223 &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
15224 &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
15225 &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
15226 &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
15227 &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
15228 &[b"ZUNION", b"1", b"s"],
15229 &[b"ZINTER", b"1", b"s"],
15230 &[b"ZDIFF", b"1", b"s"],
15231 &[b"ZUNIONSTORE", b"d", b"1", b"s"],
15232 &[b"ZINTERSTORE", b"d", b"1", b"s"],
15233 &[b"ZDIFFSTORE", b"d", b"1", b"s"],
15234 &[b"ZINTERCARD", b"1", b"s"],
15235 &[b"ZRANDMEMBER", b"s"],
15236 &[b"ZSCAN", b"s", b"0"],
15237 &[b"ZPOPMIN", b"s"],
15238 &[b"ZPOPMAX", b"s", b"2"],
15239 &[b"ZMPOP", b"1", b"s", b"MIN"],
15240 &[b"BZPOPMIN", b"s", b"0"],
15241 &[b"BZPOPMAX", b"s", b"0"],
15242 &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
15243 ] {
15244 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
15245 }
15246 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
15247 }
15248
15249 #[cfg_attr(miri, ignore = "the volume is the claim")]
15254 #[test]
15255 fn churning_sorted_sets_does_not_grow_the_server() {
15256 let mut f = Fixture::new();
15257 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
15258 let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
15259 let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
15260 for i in 0..200 {
15261 args.push(&scores[i]);
15262 args.push(&members[i]);
15263 }
15264
15265 f.run(&args);
15266 f.run(&[b"DEL", b"z"]);
15267 f.server.compact_step();
15268 let after_first = f.server.memory_bytes();
15269
15270 for _ in 0..200 {
15271 f.run(&args);
15272 f.run(&[b"DEL", b"z"]);
15273 f.server.compact_step();
15274 }
15275 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
15276 assert!(
15277 f.server.memory_bytes() <= after_first * 2,
15278 "held {} after two hundred passes against {after_first} after one",
15279 f.server.memory_bytes()
15280 );
15281 }
15282
15283 fn sicily(f: &mut Fixture) {
15291 f.run(&[
15292 b"GEOADD",
15293 b"Sicily",
15294 b"13.361389",
15295 b"38.115556",
15296 b"Palermo",
15297 b"15.087269",
15298 b"37.502669",
15299 b"Catania",
15300 ]);
15301 f.run(&[
15302 b"GEOADD",
15303 b"Sicily",
15304 b"13.583333",
15305 b"37.316667",
15306 b"Agrigento",
15307 ]);
15308 }
15309
15310 #[test]
15311 fn places_go_in_as_scores_and_come_back_as_positions() {
15312 let mut f = Fixture::new();
15313 assert_eq!(
15314 f.run(&[
15315 b"GEOADD",
15316 b"Sicily",
15317 b"13.361389",
15318 b"38.115556",
15319 b"Palermo",
15320 b"15.087269",
15321 b"37.502669",
15322 b"Catania"
15323 ]),
15324 ":2\r\n"
15325 );
15326 assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
15330 assert_eq!(
15331 f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
15332 "$16\r\n3479099956230698\r\n"
15333 );
15334 assert_eq!(
15335 f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
15336 "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
15337 );
15338 assert_eq!(
15339 f.run(&[
15340 b"GEOHASH",
15341 b"Sicily",
15342 b"Palermo",
15343 b"Catania",
15344 b"NonExisting"
15345 ]),
15346 "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
15347 );
15348 assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
15352 assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
15353 }
15354
15355 #[test]
15356 fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
15357 let mut f = Fixture::new();
15358 sicily(&mut f);
15359 assert_eq!(
15360 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
15361 "$11\r\n166274.1516\r\n"
15362 );
15363 assert_eq!(
15364 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
15365 "$8\r\n166.2742\r\n"
15366 );
15367 assert_eq!(
15368 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
15369 "$8\r\n103.3182\r\n"
15370 );
15371 assert_eq!(
15375 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
15376 "$-1\r\n"
15377 );
15378 assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
15379 assert_eq!(
15380 f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
15381 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
15382 );
15383 assert_eq!(
15384 f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
15385 "-ERR syntax error\r\n"
15386 );
15387 }
15388
15389 #[test]
15390 fn a_search_finds_what_is_inside_it_nearest_first() {
15391 let mut f = Fixture::new();
15392 sicily(&mut f);
15393 let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
15394 assert_eq!(
15395 f.run(&[
15396 b"GEOSEARCH",
15397 b"Sicily",
15398 b"FROMLONLAT",
15399 b"15",
15400 b"37",
15401 b"BYRADIUS",
15402 b"200",
15403 b"km",
15404 b"ASC"
15405 ]),
15406 all
15407 );
15408 assert_eq!(
15411 f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
15412 all
15413 );
15414 assert_eq!(
15415 f.run(&[
15416 b"GEORADIUS_RO",
15417 b"Sicily",
15418 b"15",
15419 b"37",
15420 b"200",
15421 b"km",
15422 b"ASC"
15423 ]),
15424 all
15425 );
15426 assert_eq!(
15429 f.run(&[
15430 b"GEORADIUS",
15431 b"Sicily",
15432 b"15",
15433 b"37",
15434 b"200",
15435 b"km",
15436 b"DESC",
15437 b"COUNT",
15438 b"1"
15439 ]),
15440 "*1\r\n$7\r\nPalermo\r\n"
15441 );
15442 assert_eq!(
15443 f.run(&[
15444 b"GEORADIUS",
15445 b"Sicily",
15446 b"15",
15447 b"37",
15448 b"200",
15449 b"km",
15450 b"COUNT",
15451 b"1"
15452 ]),
15453 "*1\r\n$7\r\nCatania\r\n"
15454 );
15455 let empty = "*0\r\n";
15458 assert_eq!(
15459 f.run(&[
15460 b"GEOSEARCH",
15461 b"Sicily",
15462 b"FROMLONLAT",
15463 b"15",
15464 b"37",
15465 b"BYRADIUS",
15466 b"1",
15467 b"km"
15468 ]),
15469 empty
15470 );
15471 assert_eq!(
15472 f.run(&[
15473 b"GEOSEARCH",
15474 b"nokey",
15475 b"FROMLONLAT",
15476 b"15",
15477 b"37",
15478 b"BYRADIUS",
15479 b"1",
15480 b"km"
15481 ]),
15482 empty
15483 );
15484 assert_eq!(
15485 f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
15486 empty
15487 );
15488 }
15489
15490 #[test]
15491 fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
15492 let mut f = Fixture::new();
15493 sicily(&mut f);
15494 assert_eq!(
15495 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
15496 "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
15497 );
15498 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";
15501 assert_eq!(
15502 f.run(&[
15503 b"GEORADIUSBYMEMBER_RO",
15504 b"Sicily",
15505 b"Agrigento",
15506 b"100",
15507 b"km",
15508 b"WITHDIST"
15509 ]),
15510 with_dist
15511 );
15512 assert_eq!(
15513 f.run(&[
15514 b"GEOSEARCH",
15515 b"Sicily",
15516 b"FROMMEMBER",
15517 b"Agrigento",
15518 b"BYRADIUS",
15519 b"100",
15520 b"km",
15521 b"ASC",
15522 b"WITHDIST"
15523 ]),
15524 with_dist
15525 );
15526 assert_eq!(
15527 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
15528 "-ERR could not decode requested zset member\r\n"
15529 );
15530 }
15531
15532 #[test]
15533 fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
15534 let mut f = Fixture::new();
15535 sicily(&mut f);
15536 assert_eq!(
15540 f.run(&[
15541 b"GEOSEARCH",
15542 b"Sicily",
15543 b"FROMLONLAT",
15544 b"15",
15545 b"37",
15546 b"BYBOX",
15547 b"400",
15548 b"400",
15549 b"km",
15550 b"ASC",
15551 b"WITHCOORD",
15552 b"WITHDIST",
15553 b"WITHHASH"
15554 ]),
15555 "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
15556 $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
15557 *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
15558 $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
15559 *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
15560 $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
15561 );
15562 }
15563
15564 #[test]
15565 fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
15566 let mut f = Fixture::new();
15567 sicily(&mut f);
15568 let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
15569 $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
15570 $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
15571 assert_eq!(
15572 f.run(&[
15573 b"GEOSEARCHSTORE",
15574 b"dst",
15575 b"Sicily",
15576 b"FROMLONLAT",
15577 b"15",
15578 b"37",
15579 b"BYRADIUS",
15580 b"200",
15581 b"km",
15582 b"ASC"
15583 ]),
15584 ":3\r\n"
15585 );
15586 assert_eq!(
15587 f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
15588 hashes
15589 );
15590 assert_eq!(
15593 f.run(&[
15594 b"GEORADIUS",
15595 b"Sicily",
15596 b"15",
15597 b"37",
15598 b"200",
15599 b"km",
15600 b"STORE",
15601 b"dst3"
15602 ]),
15603 ":3\r\n"
15604 );
15605 assert_eq!(
15606 f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
15607 hashes
15608 );
15609 assert_eq!(
15618 f.run(&[
15619 b"GEOSEARCHSTORE",
15620 b"dst2",
15621 b"Sicily",
15622 b"FROMLONLAT",
15623 b"15",
15624 b"37",
15625 b"BYRADIUS",
15626 b"200",
15627 b"km",
15628 b"ASC",
15629 b"STOREDIST"
15630 ]),
15631 ":3\r\n"
15632 );
15633 for (member, want) in [
15634 ("Catania", 56.441_257_870_158_19),
15635 ("Agrigento", 130.423_487_067_147_14),
15636 ("Palermo", 190.442_429_847_757_92),
15637 ] {
15638 let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
15639 let got: f64 = reply
15640 .trim_start_matches(|c: char| c != '\n')
15641 .trim()
15642 .parse()
15643 .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
15644 assert!(
15645 (got - want).abs() < 1e-9,
15646 "{member} scored {got} not {want}"
15647 );
15648 }
15649 assert_eq!(
15652 f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
15653 "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
15654 );
15655 assert_eq!(
15659 f.run(&[
15660 b"GEOSEARCHSTORE",
15661 b"dst",
15662 b"nokey",
15663 b"FROMLONLAT",
15664 b"15",
15665 b"37",
15666 b"BYRADIUS",
15667 b"200",
15668 b"km"
15669 ]),
15670 ":0\r\n"
15671 );
15672 assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
15673 }
15674
15675 #[test]
15676 fn the_gates_on_geoadd_are_the_ones_zadd_has() {
15677 let mut f = Fixture::new();
15678 sicily(&mut f);
15679 assert_eq!(
15682 f.run(&[
15683 b"GEOADD",
15684 b"Sicily",
15685 b"XX",
15686 b"CH",
15687 b"13.361389",
15688 b"38.115556",
15689 b"Palermo"
15690 ]),
15691 ":0\r\n"
15692 );
15693 assert_eq!(
15694 f.run(&[
15695 b"GEOADD",
15696 b"Sicily",
15697 b"NX",
15698 b"13.361389",
15699 b"38.9",
15700 b"Palermo"
15701 ]),
15702 ":0\r\n"
15703 );
15704 assert_eq!(
15705 f.run(&[
15706 b"GEOADD",
15707 b"Sicily",
15708 b"CH",
15709 b"13.361389",
15710 b"38.9",
15711 b"Palermo"
15712 ]),
15713 ":1\r\n"
15714 );
15715 assert_eq!(
15718 f.run(&[
15719 b"GEOADD",
15720 b"new",
15721 b"13.361389",
15722 b"38.115556",
15723 b"here",
15724 b"181",
15725 b"38",
15726 b"there"
15727 ]),
15728 "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
15729 );
15730 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
15731 assert_eq!(
15732 f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
15733 "-ERR value is not a valid float\r\n"
15734 );
15735 assert_eq!(
15738 f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
15739 "-ERR syntax error\r\n"
15740 );
15741 assert_eq!(
15742 f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
15743 "-ERR syntax error\r\n"
15744 );
15745 assert_eq!(
15746 f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
15747 "-ERR syntax error\r\n"
15748 );
15749 assert_eq!(
15750 f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
15751 "-ERR wrong number of arguments for 'geoadd' command\r\n"
15752 );
15753 }
15754
15755 #[test]
15758 fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
15759 let mut f = Fixture::new();
15760 sicily(&mut f);
15761 let cases: &[(&[&[u8]], &str)] = &[
15762 (
15763 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
15764 "-ERR need numeric radius\r\n",
15765 ),
15766 (
15767 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
15768 "-ERR radius cannot be negative\r\n",
15769 ),
15770 (
15771 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
15772 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
15773 ),
15774 (
15775 &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
15776 "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
15777 ),
15778 (
15779 &[
15780 b"GEOSEARCH",
15781 b"Sicily",
15782 b"FROMLONLAT",
15783 b"15",
15784 b"37",
15785 b"BYBOX",
15786 b"x",
15787 b"1",
15788 b"km",
15789 ],
15790 "-ERR need numeric width\r\n",
15791 ),
15792 (
15793 &[
15794 b"GEOSEARCH",
15795 b"Sicily",
15796 b"FROMLONLAT",
15797 b"15",
15798 b"37",
15799 b"BYBOX",
15800 b"1",
15801 b"y",
15802 b"km",
15803 ],
15804 "-ERR need numeric height\r\n",
15805 ),
15806 (
15807 &[
15808 b"GEOSEARCH",
15809 b"Sicily",
15810 b"FROMLONLAT",
15811 b"15",
15812 b"37",
15813 b"BYBOX",
15814 b"-1",
15815 b"1",
15816 b"km",
15817 ],
15818 "-ERR height or width cannot be negative\r\n",
15819 ),
15820 (
15821 &[
15822 b"GEOSEARCH",
15823 b"Sicily",
15824 b"FROMLONLAT",
15825 b"15",
15826 b"37",
15827 b"BYRADIUS",
15828 b"1",
15829 b"km",
15830 b"ANY",
15831 ],
15832 "-ERR the ANY argument requires COUNT argument\r\n",
15833 ),
15834 (
15835 &[
15836 b"GEOSEARCH",
15837 b"Sicily",
15838 b"FROMLONLAT",
15839 b"15",
15840 b"37",
15841 b"BYRADIUS",
15842 b"1",
15843 b"km",
15844 b"COUNT",
15845 b"0",
15846 ],
15847 "-ERR COUNT must be > 0\r\n",
15848 ),
15849 (
15850 &[
15851 b"GEOSEARCH",
15852 b"Sicily",
15853 b"BYRADIUS",
15854 b"1",
15855 b"km",
15856 b"BYBOX",
15857 b"1",
15858 b"1",
15859 b"km",
15860 ],
15861 "-ERR syntax error\r\n",
15862 ),
15863 (
15864 &[
15865 b"GEOSEARCH",
15866 b"Sicily",
15867 b"FROMMEMBER",
15868 b"Palermo",
15869 b"FROMLONLAT",
15870 b"1",
15871 b"2",
15872 b"BYRADIUS",
15873 b"1",
15874 b"km",
15875 ],
15876 "-ERR syntax error\r\n",
15877 ),
15878 (
15881 &[
15882 b"geosearch",
15883 b"Sicily",
15884 b"BYRADIUS",
15885 b"1",
15886 b"km",
15887 b"ASC",
15888 b"WITHDIST",
15889 ],
15890 "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
15891 ),
15892 (
15893 &[
15894 b"GEOSEARCH",
15895 b"Sicily",
15896 b"FROMLONLAT",
15897 b"15",
15898 b"37",
15899 b"ASC",
15900 b"WITHDIST",
15901 ],
15902 "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
15903 ),
15904 (
15907 &[
15908 b"GEOSEARCHSTORE",
15909 b"d",
15910 b"Sicily",
15911 b"FROMLONLAT",
15912 b"15",
15913 b"37",
15914 b"BYRADIUS",
15915 b"1",
15916 b"km",
15917 b"WITHCOORD",
15918 ],
15919 "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
15920 ),
15921 (
15922 &[
15923 b"GEORADIUS",
15924 b"Sicily",
15925 b"15",
15926 b"37",
15927 b"1",
15928 b"km",
15929 b"WITHDIST",
15930 b"STORE",
15931 b"d",
15932 ],
15933 "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
15934 ),
15935 (
15938 &[
15939 b"GEORADIUS_RO",
15940 b"Sicily",
15941 b"15",
15942 b"37",
15943 b"1",
15944 b"km",
15945 b"STORE",
15946 b"d",
15947 ],
15948 "-ERR syntax error\r\n",
15949 ),
15950 (
15951 &[
15952 b"GEOSEARCH",
15953 b"Sicily",
15954 b"FROMLONLAT",
15955 b"15",
15956 b"37",
15957 b"BYRADIUS",
15958 b"1",
15959 b"km",
15960 b"STOREDIST",
15961 ],
15962 "-ERR syntax error\r\n",
15963 ),
15964 ];
15965 for (parts, want) in cases {
15966 assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
15967 }
15968 }
15969
15970 #[test]
15973 fn every_geo_command_says_wrongtype() {
15974 let mut f = Fixture::new();
15975 f.run(&[b"SET", b"s", b"v"]);
15976 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
15977 let cases: &[&[&[u8]]] = &[
15978 &[b"GEOADD", b"s", b"13", b"38", b"m"],
15979 &[b"GEOPOS", b"s", b"m"],
15980 &[b"GEOHASH", b"s", b"m"],
15981 &[b"GEODIST", b"s", b"a", b"b"],
15982 &[
15983 b"GEOSEARCH",
15984 b"s",
15985 b"FROMLONLAT",
15986 b"15",
15987 b"37",
15988 b"BYRADIUS",
15989 b"1",
15990 b"km",
15991 ],
15992 &[
15993 b"GEOSEARCHSTORE",
15994 b"d",
15995 b"s",
15996 b"FROMLONLAT",
15997 b"15",
15998 b"37",
15999 b"BYRADIUS",
16000 b"1",
16001 b"km",
16002 ],
16003 &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
16004 &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
16005 &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
16006 &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
16007 ];
16008 for case in cases {
16009 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
16010 }
16011 assert_eq!(
16014 f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
16015 wrong
16016 );
16017 }
16018
16019 #[test]
16022 fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
16023 let mut f = Fixture::new();
16024 assert_eq!(
16027 f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
16028 ":3\r\n"
16029 );
16030 assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
16031 assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
16032 assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
16033 assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
16035 assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
16036 assert_eq!(
16037 f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
16038 "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
16039 );
16040 assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
16042 assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
16043 }
16044
16045 #[test]
16048 fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
16049 let mut f = Fixture::new();
16050 assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
16051 assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
16052 f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
16053 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
16054 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
16055 assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
16057 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
16058 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
16059
16060 f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
16064 assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
16065 assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
16066 assert_eq!(
16069 f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
16070 "-ERR array index overflow\r\n"
16071 );
16072 assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
16073 }
16074
16075 #[test]
16078 fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
16079 let mut f = Fixture::new();
16080 f.run(&[b"ARSET", b"a", b"1", b"x"]);
16081 assert_eq!(
16082 f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
16083 "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
16084 );
16085 assert_eq!(
16088 f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
16089 "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
16090 );
16091 assert_eq!(
16093 f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
16094 "*2\r\n$-1\r\n$-1\r\n"
16095 );
16096 assert_eq!(
16100 f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
16101 "-ERR range exceeds maximum of 1000000 items\r\n"
16102 );
16103 }
16104
16105 #[test]
16108 fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
16109 let mut f = Fixture::new();
16110 assert_eq!(
16111 f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
16112 "-ERR invalid array index\r\n"
16113 );
16114 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
16115 f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
16116 assert_eq!(
16117 f.run(&[b"ARDEL", b"a", b"0", b"01"]),
16118 "-ERR invalid array index\r\n"
16119 );
16120 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
16121 assert_eq!(
16124 f.run(&[b"ARGET", b"a", b"-1"]),
16125 "-ERR invalid array index\r\n"
16126 );
16127 assert_eq!(
16130 f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
16131 "-ERR wrong number of arguments for 'armset' command\r\n"
16132 );
16133 assert_eq!(
16134 f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
16135 "-ERR wrong number of arguments for 'ardelrange' command\r\n"
16136 );
16137 }
16138
16139 #[test]
16140 fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
16141 let mut f = Fixture::new();
16142 f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
16143 assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
16144 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
16145 assert_eq!(
16148 f.run(&[
16149 b"ARDELRANGE",
16150 b"a",
16151 b"100",
16152 b"200",
16153 b"0",
16154 b"18446744073709551614"
16155 ]),
16156 ":2\r\n"
16157 );
16158 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
16159 assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
16160 assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
16161 }
16162
16163 #[test]
16166 fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
16167 let mut f = Fixture::new();
16168 let long = vec![b'v'; 200];
16169 f.run(&[
16170 b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
16171 b"short", b"5", &long, b"6", b"-0",
16172 ]);
16173 assert_eq!(
16177 f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
16178 format!(
16179 "*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",
16180 String::from_utf8_lossy(&long)
16181 )
16182 );
16183 }
16184
16185 #[test]
16186 fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
16187 let mut f = Fixture::new();
16188 f.run(&[b"ARSET", b"a", b"0", b"x"]);
16189 assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
16190 assert_eq!(
16191 f.run(&[b"OBJECT", b"ENCODING", b"a"]),
16192 "$12\r\nsliced-array\r\n"
16193 );
16194 assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
16196 assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
16197 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
16198 assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
16199 assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
16200 assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
16201 }
16202
16203 #[test]
16204 fn every_array_command_refuses_a_key_holding_something_else() {
16205 let mut f = Fixture::new();
16206 f.run(&[b"SET", b"s", b"v"]);
16207 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16208 for cmd in [
16209 &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
16210 &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
16211 &[b"ARGET".as_ref(), b"s", b"0"][..],
16212 &[b"ARMGET".as_ref(), b"s", b"0"][..],
16213 &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
16214 &[b"ARLEN".as_ref(), b"s"][..],
16215 &[b"ARCOUNT".as_ref(), b"s"][..],
16216 &[b"ARDEL".as_ref(), b"s", b"0"][..],
16217 &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
16218 &[b"ARINSERT".as_ref(), b"s", b"x"][..],
16219 &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
16220 &[b"ARNEXT".as_ref(), b"s"][..],
16221 &[b"ARSEEK".as_ref(), b"s", b"1"][..],
16222 &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
16223 &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
16224 &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
16225 &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
16226 &[b"ARINFO".as_ref(), b"s"][..],
16227 ] {
16228 assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
16229 }
16230 }
16231
16232 #[test]
16236 fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
16237 let mut f = Fixture::new();
16238 f.run(&[b"SET", b"s", b"v"]);
16239 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16240 let bad = "-ERR invalid array index\r\n";
16241 assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
16242 assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
16243 assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
16244 assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
16245 assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
16246 assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
16247 f.run(&[b"ARSET", b"a", b"0", b"x"]);
16249 assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
16250 assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
16251 }
16252
16253 #[test]
16254 fn an_append_follows_a_cursor_the_client_can_move() {
16255 let mut f = Fixture::new();
16256 assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
16257 assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
16258 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
16259 assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
16260 assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
16261
16262 assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
16265 assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
16266 assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
16267 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
16268 assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
16269 assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
16270 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
16271
16272 assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
16275 assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
16276 assert_eq!(
16277 f.run(&[b"ARINSERT", b"a", b"x"]),
16278 "-ERR insert index overflow\r\n"
16279 );
16280 assert_eq!(
16281 f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
16282 "-ERR invalid array index\r\n"
16283 );
16284 }
16285
16286 #[test]
16287 fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
16288 let mut f = Fixture::new();
16289 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
16290 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
16291 assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
16292 assert_eq!(
16293 f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
16294 "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
16295 );
16296 assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
16299 assert_eq!(
16300 f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
16301 "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
16302 );
16303 assert_eq!(
16306 f.run(&[b"ARRING", b"r", b"0", b"x"]),
16307 "-ERR size must be positive\r\n"
16308 );
16309 assert_eq!(
16310 f.run(&[b"ARRING", b"r", b"big", b"x"]),
16311 "-ERR invalid size\r\n"
16312 );
16313 }
16314
16315 #[test]
16316 fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
16317 let mut f = Fixture::new();
16318 assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
16319 f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
16320 assert_eq!(
16321 f.run(&[b"ARLASTITEMS", b"r", b"3"]),
16322 "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
16323 );
16324 assert_eq!(
16325 f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
16326 "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
16327 );
16328 assert_eq!(
16329 f.run(&[b"ARLASTITEMS", b"r", b"99"]),
16330 "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
16331 "more than there is gets what there is"
16332 );
16333 assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
16336 assert_eq!(
16337 f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
16338 "-ERR syntax error\r\n"
16339 );
16340 assert_eq!(
16341 f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
16342 "-ERR invalid COUNT\r\n"
16343 );
16344
16345 f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
16348 assert_eq!(
16349 f.run(&[b"ARLASTITEMS", b"h", b"5"]),
16350 "*2\r\n$-1\r\n$1\r\nz\r\n"
16351 );
16352 }
16353
16354 #[test]
16355 fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
16356 let mut f = Fixture::new();
16357 assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
16358 f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
16359 assert_eq!(
16362 f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
16363 "*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"
16364 );
16365 assert_eq!(
16366 f.run(&[
16367 b"ARSCAN",
16368 b"a",
16369 b"18446744073709551614",
16370 b"0",
16371 b"LIMIT",
16372 b"1"
16373 ]),
16374 "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
16375 );
16376 assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
16377 assert_eq!(
16378 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
16379 "-ERR LIMIT must be positive\r\n"
16380 );
16381 assert_eq!(
16382 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
16383 "-ERR syntax error\r\n"
16384 );
16385 assert_eq!(
16386 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
16387 "-ERR wrong number of arguments for 'arscan' command\r\n"
16388 );
16389 }
16390
16391 #[test]
16392 fn a_grep_answers_the_indexes_whose_elements_match() {
16393 let mut f = Fixture::new();
16394 assert_eq!(
16395 f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
16396 "*0\r\n"
16397 );
16398 f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
16399
16400 assert_eq!(
16403 f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
16404 "*3\r\n:0\r\n:1\r\n:2\r\n"
16405 );
16406 assert_eq!(
16407 f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
16408 "*3\r\n:2\r\n:1\r\n:0\r\n"
16409 );
16410 assert_eq!(
16411 f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
16412 "*2\r\n:1\r\n:2\r\n"
16413 );
16414
16415 assert_eq!(
16418 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
16419 "*1\r\n:0\r\n"
16420 );
16421 assert_eq!(
16422 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
16423 "*2\r\n:0\r\n:3\r\n"
16424 );
16425 assert_eq!(
16426 f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
16427 "*1\r\n:2\r\n"
16428 );
16429 assert_eq!(
16430 f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
16431 "*2\r\n:1\r\n:2\r\n"
16432 );
16433
16434 let both: &[&[u8]] = &[
16437 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
16438 ];
16439 assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
16440 assert_eq!(
16441 f.run(&[
16442 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
16443 ]),
16444 "*0\r\n"
16445 );
16446 assert_eq!(
16447 f.run(&[
16448 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
16449 ]),
16450 "*2\r\n:0\r\n:1\r\n"
16451 );
16452
16453 assert_eq!(
16456 f.run(&[
16457 b"ARGREP",
16458 b"a",
16459 b"-",
16460 b"+",
16461 b"MATCH",
16462 b"a",
16463 b"WITHVALUES",
16464 b"LIMIT",
16465 b"2"
16466 ]),
16467 "*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"
16468 );
16469 assert_eq!(
16470 f.run(&[
16471 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
16472 ]),
16473 "*1\r\n:3\r\n"
16474 );
16475 }
16476
16477 #[test]
16479 fn a_grep_reports_a_broken_command_the_way_redis_does() {
16480 let mut f = Fixture::new();
16481 f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
16482 let syntax = "-ERR syntax error\r\n";
16483
16484 assert_eq!(
16487 f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
16488 "-ERR invalid array index\r\n"
16489 );
16490 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
16491 assert_eq!(
16493 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
16494 syntax
16495 );
16496 assert_eq!(
16497 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
16498 syntax
16499 );
16500 assert_eq!(
16501 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
16502 syntax,
16503 "a command with no predicate in it at all"
16504 );
16505 assert_eq!(
16506 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
16507 "-ERR LIMIT must be positive\r\n"
16508 );
16509 assert_eq!(
16510 f.run(&[
16511 b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
16512 ]),
16513 "-ERR value is not an integer or out of range\r\n"
16514 );
16515 assert_eq!(
16516 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
16517 "-ERR regular expression is empty\r\n"
16518 );
16519 assert_eq!(
16520 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
16521 "-ERR invalid regular expression: Missing ')'\r\n"
16522 );
16523 assert_eq!(
16524 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
16525 "-ERR regular expression backreferences are not supported\r\n"
16526 );
16527 let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
16530 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
16531 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
16532 }
16533
16534 #[test]
16535 fn an_op_reduces_a_range_to_one_number() {
16536 let mut f = Fixture::new();
16537 f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
16538 assert_eq!(
16539 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
16540 "$4\r\n-0.5\r\n"
16541 );
16542 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
16543 assert_eq!(
16544 f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
16545 "$3\r\n2.5\r\n"
16546 );
16547 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
16548 assert_eq!(
16549 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
16550 ":1\r\n"
16551 );
16552 f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
16555 assert_eq!(
16556 f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
16557 "$19\r\n0.30000000000000004\r\n"
16558 );
16559 assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
16560 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
16561
16562 f.run(&[b"ARSET", b"w", b"0", b"word"]);
16565 assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
16566 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
16567 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
16568
16569 assert_eq!(
16570 f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
16571 "-ERR unknown operation\r\n"
16572 );
16573 assert_eq!(
16574 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
16575 "-ERR MATCH requires a value argument\r\n"
16576 );
16577 assert_eq!(
16578 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
16579 "-ERR wrong number of arguments for 'arop' command\r\n"
16580 );
16581 }
16582
16583 #[test]
16584 fn the_info_is_a_map_and_a_missing_key_is_an_error() {
16585 let mut f = Fixture::new();
16586 assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
16587 f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
16588 let short = f.run(&[b"ARINFO", b"a"]);
16589 assert!(
16590 short.starts_with("*14\r\n"),
16591 "seven pairs on RESP2: {short}"
16592 );
16593 assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
16594 assert!(
16595 short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
16596 "{short}"
16597 );
16598 assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
16599 let full = f.run(&[b"ARINFO", b"a", b"full"]);
16600 assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
16601 assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
16604 assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
16605 assert!(
16606 full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
16607 "{full}"
16608 );
16609 assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
16610
16611 let mut g = Fixture::new();
16613 g.run(&[b"HELLO", b"3"]);
16614 g.run(&[b"ARINSERT", b"a", b"x"]);
16615 let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
16616 assert!(map.starts_with("%12\r\n"), "{map}");
16617 assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
16618 assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
16619 }
16620
16621 #[test]
16622 fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
16623 let mut f = Fixture::new();
16624 for (score, want) in [
16627 ("3", "3"),
16628 ("3.5", "3.5"),
16629 ("0.3", "0.3"),
16630 ("1e30", "1e+30"),
16631 ("1e19", "1e+19"),
16632 ("1e-7", "1e-7"),
16633 ("0.000001", "0.000001"),
16634 ("4611686018427387904", "4611686018427387904"),
16635 ("-0", "-0"),
16636 ] {
16637 f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
16638 assert_eq!(
16639 f.run(&[b"ZSCORE", b"z", b"m"]),
16640 format!("${}\r\n{want}\r\n", want.len()),
16641 "score {score}"
16642 );
16643 }
16644
16645 let mut g = Fixture::new();
16648 g.run(&[b"HELLO", b"3"]);
16649 g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
16650 assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
16651 assert_eq!(
16656 g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
16657 "$31\r\n1000000000000000000000000000000\r\n"
16658 );
16659 assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
16660 assert_eq!(
16661 g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
16662 "$20\r\n10000000000000000000\r\n"
16663 );
16664 }
16665
16666 #[test]
16669 fn a_node_comes_back_with_the_fields_it_went_in_with() {
16670 let mut f = Fixture::new();
16671 assert_eq!(
16672 f.run(&[
16673 b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
16674 ]),
16675 ":1\r\n"
16676 );
16677 assert_eq!(
16683 f.run(&[b"G.NGET", b"social", b"ada"]),
16684 "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
16685 );
16686 assert_eq!(
16689 f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
16690 ":0\r\n"
16691 );
16692 assert_eq!(
16693 f.run(&[b"G.NGET", b"social", b"ada"]),
16694 "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
16695 );
16696 assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
16699 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
16700 assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
16701 assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
16702
16703 assert_eq!(
16706 f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
16707 "-ERR syntax error\r\n"
16708 );
16709 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
16710
16711 let mut g = Fixture::new();
16713 g.run(&[b"HELLO", b"3"]);
16714 g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
16715 assert_eq!(
16716 g.run(&[b"G.NGET", b"social", b"ada"]),
16717 "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
16718 );
16719 }
16720
16721 #[test]
16722 fn an_edge_creates_the_ends_it_needs() {
16723 let mut f = Fixture::new();
16724 assert_eq!(
16725 f.run(&[
16726 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
16727 ]),
16728 ":1\r\n"
16729 );
16730 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
16732 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
16733 assert_eq!(
16734 f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
16735 "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
16736 );
16737 assert_eq!(
16738 f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
16739 "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
16740 );
16741 assert_eq!(
16744 f.run(&[
16745 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
16746 ]),
16747 ":0\r\n"
16748 );
16749 assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
16750 assert_eq!(
16752 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
16753 ":1\r\n"
16754 );
16755 assert_eq!(
16756 f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
16757 ":1\r\n"
16758 );
16759
16760 assert_eq!(
16761 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
16762 ":1\r\n"
16763 );
16764 assert_eq!(
16765 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
16766 ":0\r\n"
16767 );
16768 assert_eq!(
16771 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
16772 ":0\r\n"
16773 );
16774 assert_eq!(
16775 f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
16776 ":0\r\n"
16777 );
16778 assert_eq!(
16779 f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
16780 ":0\r\n"
16781 );
16782 }
16783
16784 #[test]
16787 fn a_hop_answers_a_cursor_and_a_page() {
16788 let mut f = Fixture::new();
16789 for i in 0..25u32 {
16790 let dst = format!("n{i}");
16791 f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
16792 }
16793 let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
16795 assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
16796
16797 let mut seen = 0;
16798 let mut cursor = String::from("0");
16799 loop {
16800 let page = f.run(&[
16801 b"G.OUT",
16802 b"social",
16803 b"hub",
16804 b"FOLLOWS",
16805 b"COUNT",
16806 b"7",
16807 b"CURSOR",
16808 cursor.as_bytes(),
16809 ]);
16810 let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
16811 cursor = head
16812 .rsplit("\r\n")
16813 .next()
16814 .expect("the cursor line")
16815 .to_string();
16816 seen += rest
16817 .split_once("\r\n")
16818 .expect("the page length")
16819 .0
16820 .parse::<usize>()
16821 .expect("a length");
16822 if cursor == "0" {
16823 break;
16824 }
16825 }
16826 assert_eq!(seen, 25, "every neighbour once across the pages");
16827
16828 assert_eq!(
16831 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
16832 "*2\r\n$1\r\n0\r\n*0\r\n"
16833 );
16834 assert_eq!(
16835 f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
16836 "*2\r\n$1\r\n0\r\n*0\r\n"
16837 );
16838 assert_eq!(
16839 f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
16840 "*2\r\n$1\r\n0\r\n*0\r\n"
16841 );
16842 assert_eq!(
16843 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
16844 "-ERR COUNT must be a positive integer\r\n"
16845 );
16846 assert_eq!(
16847 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
16848 "-ERR syntax error\r\n"
16849 );
16850 }
16851
16852 #[test]
16853 fn a_degree_counts_one_way_or_both() {
16854 let mut f = Fixture::new();
16855 f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
16856 f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
16857 f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
16858 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
16859 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
16860 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
16861 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
16862 assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
16863 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
16864 assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
16865 assert_eq!(
16866 f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
16867 "-ERR syntax error\r\n"
16868 );
16869 }
16870
16871 #[test]
16874 fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
16875 let mut f = Fixture::new();
16876 for (src, dst) in [
16877 ("ada", "grace"),
16878 ("ada", "alan"),
16879 ("grace", "edsger"),
16880 ("alan", "edsger"),
16881 ("edsger", "barbara"),
16882 ] {
16883 f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
16884 }
16885 assert_eq!(
16888 f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
16889 "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
16890 );
16891 assert_eq!(
16892 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
16893 "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
16894 );
16895 let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
16896 assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
16897 assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
16898 assert_eq!(
16900 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
16901 "*1\r\n$5\r\ngrace\r\n"
16902 );
16903 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
16905 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
16906 assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
16907 assert_eq!(
16908 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
16909 "-ERR DEPTH must be a positive integer\r\n"
16910 );
16911 assert_eq!(
16912 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
16913 "-ERR syntax error\r\n"
16914 );
16915 }
16916
16917 #[test]
16920 fn a_path_is_the_shortest_one_and_goes_over_any_label() {
16921 let mut f = Fixture::new();
16922 for i in 0..6u32 {
16925 let src = format!("n{i}");
16926 let dst = format!("n{}", i + 1);
16927 f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
16928 }
16929 assert_eq!(
16930 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
16931 "*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"
16932 );
16933 f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
16934 assert_eq!(
16935 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
16936 "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
16937 );
16938 assert_eq!(
16941 f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
16942 "*1\r\n$2\r\nn2\r\n"
16943 );
16944 assert_eq!(
16945 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
16946 "*0\r\n"
16947 );
16948 assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
16950 f.run(&[b"G.NADD", b"road", b"island"]);
16953 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
16954 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
16955 assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
16956 assert_eq!(
16957 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
16958 "-ERR syntax error\r\n"
16959 );
16960 }
16961
16962 #[test]
16966 fn the_keyspace_sees_a_graph_key_like_any_other() {
16967 let mut f = Fixture::new();
16968 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
16969 assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
16970 assert_eq!(
16971 f.run(&[b"OBJECT", b"ENCODING", b"social"]),
16972 "$9\r\nadjacency\r\n"
16973 );
16974 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
16975 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
16976 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
16977 let held = f.server.memory_bytes();
16981 for i in 0..200u32 {
16982 let dst = format!("n{i}");
16983 f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
16984 }
16985 assert!(
16986 f.server.memory_bytes() > held,
16987 "two hundred edges cost something: {held} then {}",
16988 f.server.memory_bytes()
16989 );
16990 f.run(&[b"DEL", b"big"]);
16991
16992 assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
16995 assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
16996 assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
16997 assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
16998 assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
16999 f.run(&[b"SELECT", b"1"]);
17000 assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
17001
17002 assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
17003 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17004 f.run(&[b"G.NADD", b"g", b"n"]);
17005 assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
17006 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17007 }
17008
17009 #[test]
17012 fn a_graph_cannot_be_copied_or_dumped() {
17013 let mut f = Fixture::new();
17014 f.run(&[b"G.NADD", b"social", b"ada"]);
17015 assert_eq!(
17016 f.run(&[b"COPY", b"social", b"other"]),
17017 "-ERR COPY is not supported for a graph\r\n"
17018 );
17019 assert_eq!(
17020 f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
17021 "-ERR COPY is not supported for a graph\r\n"
17022 );
17023 assert_eq!(
17024 f.run(&[b"DUMP", b"social"]),
17025 "-ERR DUMP is not supported for a graph\r\n"
17026 );
17027 assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
17029 }
17030
17031 #[test]
17034 fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
17035 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
17036 let mut f = Fixture::new();
17037 f.run(&[b"G.NADD", b"social", b"ada"]);
17038 assert_eq!(f.run(&[b"GET", b"social"]), wrong);
17039 assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
17040 assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
17041
17042 f.run(&[b"SET", b"str", b"v"]);
17043 for cmd in [
17044 vec![b"G.NADD".as_ref(), b"str", b"n"],
17045 vec![b"G.NGET".as_ref(), b"str", b"n"],
17046 vec![b"G.NDEL".as_ref(), b"str", b"n"],
17047 vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
17048 vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
17049 vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
17050 vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
17051 vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
17052 vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
17053 vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
17054 ] {
17055 assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
17056 }
17057 }
17058
17059 #[test]
17062 fn a_graph_goes_when_its_last_node_does() {
17063 let mut f = Fixture::new();
17064 f.run(&[
17065 b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
17066 ]);
17067 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
17068 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
17070 assert_eq!(
17071 f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
17072 ":0\r\n"
17073 );
17074 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
17075 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
17076
17077 assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
17078 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
17079 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17080 assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
17081
17082 f.run(&[b"G.NADD", b"social", b"first"]);
17085 f.run(&[b"G.NADD", b"social", b"second"]);
17086 f.run(&[b"G.NDEL", b"social", b"first"]);
17087 f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
17088 assert_eq!(
17089 f.run(&[b"G.OUT", b"social", b"third", b"F"]),
17090 "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
17091 );
17092 }
17093
17094 #[test]
17099 fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
17100 let mut f = Fixture::new();
17101 let doc = br#"{"a":1,"b":{"c":true}}"#;
17102 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
17103 assert_eq!(
17106 f.run(&[b"JSON.GET", b"doc"]),
17107 bulk(r#"{"a":1,"b":{"c":true}}"#)
17108 );
17109 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
17110 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
17111 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
17112 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
17115 assert_eq!(
17116 f.run(&[b"JSON.GET", b"doc", b".nope"]),
17117 "-ERR Path does not exist\r\n"
17118 );
17119 assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
17120 assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
17123 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
17124 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
17125 assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
17126 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
17127 }
17128
17129 #[test]
17135 fn the_two_json_errors_that_carry_no_prefix() {
17136 let mut f = Fixture::new();
17137 f.run(&[b"SET", b"plain", b"x"]);
17138 let wrong = "-Existing key has wrong Redis type\r\n";
17139 assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
17140 assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
17141 assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
17142 assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
17143 assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
17144
17145 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
17146 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
17150 assert_eq!(
17151 f.run(&[b"JSON.GET", b"doc"]),
17152 bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
17153 );
17154 assert_eq!(
17155 f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
17156 "-Err wrong static path\r\n"
17157 );
17158 }
17159
17160 #[test]
17162 fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
17163 let mut f = Fixture::new();
17164 assert_eq!(
17166 f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
17167 "-ERR new objects must be created at the root\r\n"
17168 );
17169 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17170 assert_eq!(
17173 f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
17174 "-ERR new objects must be created at the root\r\n"
17175 );
17176 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
17177 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
17178
17179 f.run(&[
17180 b"JSON.SET",
17181 b"doc",
17182 b"$",
17183 br#"{"o":{},"arr":[1,2],"s":"x"}"#,
17184 ]);
17185 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
17187 assert_eq!(
17190 f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
17191 "$-1\r\n"
17192 );
17193 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
17194 assert_eq!(
17196 f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
17197 "-ERR array index out of range\r\n"
17198 );
17199 assert_eq!(
17200 f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
17201 "-ERR array index out of range\r\n"
17202 );
17203 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
17204 assert_eq!(
17207 f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
17208 "$-1\r\n"
17209 );
17210 assert_eq!(
17211 f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
17212 "$-1\r\n"
17213 );
17214 assert_eq!(
17215 f.run(&[b"JSON.GET", b"doc"]),
17216 bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
17217 );
17218 assert!(
17222 f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
17223 .starts_with("-this is not the start of a value")
17224 );
17225 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
17226 }
17227
17228 #[test]
17231 fn the_json_commands_that_do_not_answer_text() {
17232 let mut f = Fixture::new();
17233 let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
17234 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
17235
17236 assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
17237 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
17238 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
17239 assert_eq!(
17240 f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
17241 format!("*1\r\n{}", bulk("integer"))
17242 );
17243 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
17246 assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
17247
17248 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
17251 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
17252 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
17255 assert_eq!(
17256 f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
17257 "-ERR Path does not exist or not a bool\r\n"
17258 );
17259 assert_eq!(
17260 f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
17261 "-ERR Path does not exist or not a bool\r\n"
17262 );
17263 assert_eq!(
17264 f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
17265 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17266 );
17267
17268 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
17271 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
17272 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
17273 assert_eq!(
17274 f.run(&[b"JSON.GET", b"doc"]),
17275 bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
17276 );
17277
17278 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
17281 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
17282 assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
17287 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
17288 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
17289 assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
17290 assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
17291 assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
17292 assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
17293 assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
17294 }
17295
17296 #[test]
17303 fn json_get_lays_out_the_wrapper_it_builds() {
17304 let mut f = Fixture::new();
17305 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
17306
17307 assert_eq!(
17308 f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
17309 bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
17310 );
17311 assert_eq!(
17313 f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
17314 bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
17315 );
17316 let fmt: &[&[u8]] = &[b"INDENT", b" ", b"NEWLINE", b"\n", b"SPACE", b" "];
17317 let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
17318 one.extend_from_slice(fmt);
17319 one.push(b"$.b");
17320 assert_eq!(
17321 f.run(&one),
17322 bulk("[\n [\n 1,\n {\n \"c\": 2\n }\n ]\n]")
17323 );
17324 let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
17325 two.extend_from_slice(fmt);
17326 two.push(b"$.a");
17327 two.push(b"$.nope");
17328 assert_eq!(
17329 f.run(&two),
17330 bulk("{\n \"$.a\": [\n 1\n ],\n \"$.nope\": []\n}")
17331 );
17332 let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
17335 root.push(b".a");
17336 assert_eq!(f.run(&root), bulk("1"));
17337 }
17338
17339 #[test]
17342 fn json_mget_answers_once_per_key_whatever_is_under_them() {
17343 let mut f = Fixture::new();
17344 f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
17345 f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
17346 f.run(&[b"SET", b"plain", b"x"]);
17347 assert_eq!(
17348 f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
17349 format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
17350 );
17351 assert_eq!(
17354 f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
17355 format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
17356 );
17357 assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
17360 }
17361
17362 #[test]
17370 fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
17371 let mut f = Fixture::new();
17372 let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
17373 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
17374
17375 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
17376 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
17377 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
17378 assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
17379 assert_eq!(
17380 f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
17381 format!("*2\r\n{}{}", bulk("x"), bulk("y"))
17382 );
17383 assert_eq!(
17386 f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
17387 "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
17388 );
17389
17390 assert_eq!(
17393 f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
17394 "-ERR Path does not exist\r\n"
17395 );
17396 assert_eq!(
17397 f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
17398 "-ERR Path does not exist\r\n"
17399 );
17400 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
17401 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
17404 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
17407
17408 assert_eq!(
17412 f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
17413 "-ERR Path does not exist or not an array\r\n"
17414 );
17415 assert_eq!(
17416 f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
17417 "-ERR Path does not exist or not an object\r\n"
17418 );
17419 assert_eq!(
17420 f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
17421 "-WRONGTYPE wrong type of path value - expected object\r\n"
17422 );
17423 assert_eq!(
17424 f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
17425 "-WRONGTYPE wrong type of path value - expected string\r\n"
17426 );
17427
17428 assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
17431 assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
17432 assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
17433 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
17434 assert_eq!(
17435 f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
17436 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17437 );
17438 assert_eq!(
17440 f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
17441 "-ERR Path does not exist or not an object\r\n"
17442 );
17443 }
17444
17445 #[test]
17451 fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
17452 let mut f = Fixture::new();
17453 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
17454
17455 assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
17456 assert_eq!(
17457 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
17458 "*1\r\n:6\r\n"
17459 );
17460 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
17461
17462 assert_eq!(
17465 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
17466 ":7\r\n"
17467 );
17468 assert_eq!(
17469 f.run(&[b"JSON.GET", b"doc", b".a"]),
17470 bulk("[1,2,3,4,5,0,6]")
17471 );
17472 assert_eq!(
17473 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
17474 ":8\r\n"
17475 );
17476 assert_eq!(
17478 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
17479 "-ERR index out of bounds\r\n"
17480 );
17481 assert_eq!(
17482 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
17483 "-ERR index out of bounds\r\n"
17484 );
17485
17486 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
17489 assert_eq!(
17490 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
17491 ":3\r\n"
17492 );
17493 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
17494 assert_eq!(
17495 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
17496 ":2\r\n"
17497 );
17498 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
17499 assert_eq!(
17500 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
17501 ":0\r\n"
17502 );
17503 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
17504
17505 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
17508 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
17509 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
17510 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
17511 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
17512
17513 for call in [
17516 &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
17517 &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
17518 &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
17519 &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
17520 ] {
17521 for path in [&b".n"[..], &b".nope"[..]] {
17522 let args: Vec<&[u8]> = call
17523 .iter()
17524 .map(|a| if *a == b"PATH" { path } else { *a })
17525 .collect();
17526 assert_eq!(
17527 f.run(&args),
17528 "-ERR Path does not exist or not an array\r\n",
17529 "{} {}",
17530 String::from_utf8_lossy(call[0]),
17531 String::from_utf8_lossy(path)
17532 );
17533 }
17534 }
17535
17536 assert_eq!(
17539 f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
17540 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17541 );
17542 assert_eq!(
17543 f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
17544 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17545 );
17546
17547 assert!(
17552 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
17553 .starts_with("-this is not the start of a value")
17554 );
17555 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
17556 }
17557
17558 #[test]
17566 fn json_arrinsert_is_all_or_nothing_across_the_matches() {
17567 let mut f = Fixture::new();
17568 let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
17569 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
17570 assert_eq!(
17571 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
17572 "-ERR index out of bounds\r\n"
17573 );
17574 assert_eq!(
17575 f.run(&[b"JSON.GET", b"doc"]),
17576 bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
17577 );
17578 assert_eq!(
17580 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
17581 "*3\r\n:4\r\n:3\r\n:2\r\n"
17582 );
17583 assert_eq!(
17584 f.run(&[b"JSON.GET", b"doc"]),
17585 bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
17586 );
17587 }
17588
17589 #[test]
17597 fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
17598 let mut f = Fixture::new();
17599 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
17600
17601 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
17602 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
17603 assert_eq!(
17604 f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
17605 "*1\r\n:1\r\n"
17606 );
17607
17608 assert_eq!(
17611 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
17612 ":3\r\n"
17613 );
17614 assert_eq!(
17617 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
17618 ":-1\r\n"
17619 );
17620
17621 for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
17624 assert_eq!(
17625 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
17626 ":3\r\n",
17627 "{}",
17628 String::from_utf8_lossy(start)
17629 );
17630 }
17631 assert_eq!(
17632 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
17633 ":0\r\n"
17634 );
17635 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
17638 assert_eq!(
17639 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
17640 ":-1\r\n"
17641 );
17642
17643 f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
17647 assert_eq!(
17648 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
17649 ":0\r\n"
17650 );
17651 assert_eq!(
17652 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
17653 ":1\r\n"
17654 );
17655 assert_eq!(
17656 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
17657 ":-1\r\n"
17658 );
17659
17660 assert_eq!(
17664 f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
17665 "-ERR Path does not exist\r\n"
17666 );
17667 assert_eq!(
17668 f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
17669 "-WRONGTYPE wrong type of path value - expected array\r\n"
17670 );
17671 assert_eq!(
17672 f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
17673 "-ERR Path does not exist\r\n"
17674 );
17675 assert_eq!(
17676 f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
17677 "-ERR Path does not exist\r\n"
17678 );
17679 }
17680
17681 #[test]
17684 fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
17685 let mut f = Fixture::new();
17686 let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
17687 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
17688
17689 assert_eq!(
17692 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
17693 bulk("9").as_str()
17694 );
17695 assert_eq!(
17697 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
17698 bulk("[11]").as_str()
17699 );
17700 assert_eq!(
17703 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
17704 bulk("13.0").as_str()
17705 );
17706 assert_eq!(
17707 f.run(&[b"JSON.TYPE", b"doc", b".i"]),
17708 bulk("number").as_str()
17709 );
17710 assert_eq!(
17711 f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
17712 bulk("3.0").as_str()
17713 );
17714 assert_eq!(
17715 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
17716 bulk("-8").as_str()
17717 );
17718 f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
17721 assert_eq!(
17722 f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
17723 bulk("1.224744871391589").as_str()
17724 );
17725 assert_eq!(
17726 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
17727 "-ERR result is not a number\r\n"
17728 );
17729 f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
17733 assert_eq!(
17734 f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
17735 "-ERR numeric overflow\r\n"
17736 );
17737 f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
17738 assert_eq!(
17739 f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
17740 "-ERR numeric overflow\r\n"
17741 );
17742 f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
17744 assert_eq!(
17745 f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
17746 "-ERR result is not a number\r\n"
17747 );
17748
17749 assert_eq!(
17753 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
17754 bulk("[null]").as_str()
17755 );
17756 assert_eq!(
17757 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
17758 bulk("[]").as_str()
17759 );
17760 assert_eq!(
17761 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
17762 "-ERR Path does not exist or does not contains a number\r\n"
17763 );
17764 assert_eq!(
17765 f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
17766 "-ERR Path does not exist or does not contains a number\r\n"
17767 );
17768 assert_eq!(
17771 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
17772 "-bad input number\r\n"
17773 );
17774 assert_eq!(
17775 f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
17776 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17777 );
17778 assert_eq!(
17779 f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
17780 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17781 );
17782 }
17783
17784 #[test]
17787 fn json_strappend_reads_its_shape_off_the_argument_count() {
17788 let mut f = Fixture::new();
17789 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
17790
17791 assert_eq!(
17792 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
17793 ":3\r\n"
17794 );
17795 assert_eq!(
17796 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
17797 "*1\r\n:4\r\n"
17798 );
17799 assert_eq!(
17802 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
17803 ":6\r\n"
17804 );
17805 f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
17808 assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
17809 assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
17810
17811 assert_eq!(
17815 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
17816 "-WRONGTYPE wrong type of path value - expected string\r\n"
17817 );
17818 assert_eq!(
17819 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
17820 "*1\r\n$-1\r\n"
17821 );
17822 assert_eq!(
17823 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
17824 "-ERR Path does not exist or not a string\r\n"
17825 );
17826 assert_eq!(
17827 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
17828 "*0\r\n"
17829 );
17830 assert_eq!(
17831 f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
17832 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17833 );
17834 }
17835
17836 #[test]
17839 fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
17840 let mut f = Fixture::new();
17841 let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
17844
17845 f.run(&[b"JSON.SET", b"doc", b"$", three]);
17846 assert_eq!(
17847 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
17848 ":4\r\n"
17849 );
17850 f.run(&[b"JSON.SET", b"doc", b"$", three]);
17851 assert_eq!(
17852 f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
17853 ":2\r\n"
17854 );
17855 f.run(&[b"JSON.SET", b"doc", b"$", three]);
17856 assert_eq!(
17857 f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
17858 ":1\r\n"
17859 );
17860 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
17861 assert_eq!(
17862 f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
17863 bulk("1").as_str()
17864 );
17865 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
17866 assert_eq!(
17867 f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
17868 bulk("13").as_str()
17869 );
17870 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
17871 assert_eq!(
17872 f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
17873 ":4\r\n"
17874 );
17875 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
17876 assert_eq!(
17877 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
17878 bulk("false").as_str()
17879 );
17880 assert_eq!(
17883 f.run(&[b"JSON.GET", b"doc", b".a"]),
17884 bulk("[false,true,false]").as_str()
17885 );
17886
17887 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
17890 assert_eq!(
17891 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
17892 ":3\r\n"
17893 );
17894 assert_eq!(
17895 f.run(&[b"JSON.GET", b"doc", b".a"]),
17896 bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
17897 );
17898 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
17901 assert_eq!(
17902 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
17903 "-ERR Path does not exist or not an array\r\n"
17904 );
17905 assert_eq!(
17906 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
17907 "-ERR Path does not exist or not a bool\r\n"
17908 );
17909 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
17913 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
17914 assert_eq!(
17915 f.run(&[b"JSON.GET", b"doc", b".a"]),
17916 bulk("[[],[2]]").as_str()
17917 );
17918 }
17919
17920 #[test]
17923 fn a_write_reaches_a_match_that_sits_inside_another_match() {
17924 let mut f = Fixture::new();
17925 let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
17926
17927 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
17928 assert_eq!(
17929 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
17930 "*3\r\n:3\r\n:2\r\n:3\r\n"
17931 );
17932 assert_eq!(
17933 f.run(&[b"JSON.GET", b"doc", b"$"]),
17934 bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
17935 );
17936
17937 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
17940 assert_eq!(
17941 f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
17942 "*3\r\n:1\r\n:1\r\n:1\r\n"
17943 );
17944 assert_eq!(
17945 f.run(&[b"JSON.GET", b"doc", b"$"]),
17946 bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
17947 );
17948
17949 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
17952 assert_eq!(
17953 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
17954 bulk("[null,8,8]").as_str()
17955 );
17956 }
17957
17958 #[test]
17961 fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
17962 let mut f = Fixture::new();
17963 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
17964
17965 assert_eq!(
17968 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
17969 bulk("[null]").as_str()
17970 );
17971 assert_eq!(
17972 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
17973 bulk("[null]").as_str()
17974 );
17975 assert_eq!(
17976 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
17977 bulk("[]").as_str()
17978 );
17979 assert_eq!(
17980 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
17981 "-ERR Path does not exist or does not contains a number\r\n"
17982 );
17983 assert_eq!(
17985 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
17986 "-bad input number\r\n"
17987 );
17988
17989 assert_eq!(
17991 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
17992 "*1\r\n$-1\r\n"
17993 );
17994 assert_eq!(
17995 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
17996 "-ERR Path does not exist or not a string\r\n"
17997 );
17998 assert_eq!(
17999 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
18000 "-WRONGTYPE wrong type of path value - expected string\r\n"
18001 );
18002
18003 assert_eq!(
18005 f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
18006 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18007 );
18008 assert_eq!(
18009 f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
18010 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18011 );
18012 }
18013
18014 #[test]
18017 fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
18018 let mut f = Fixture::new();
18019
18020 assert_eq!(
18024 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
18025 "+OK\r\n"
18026 );
18027 assert_eq!(
18028 f.run(&[b"JSON.GET", b"doc", b"$"]),
18029 bulk(r#"[{"x":null,"y":1}]"#).as_str()
18030 );
18031
18032 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
18035 assert_eq!(
18036 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
18037 "+OK\r\n"
18038 );
18039 assert_eq!(
18040 f.run(&[b"JSON.GET", b"doc", b"$"]),
18041 bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
18042 );
18043
18044 assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
18046 assert_eq!(
18047 f.run(&[b"JSON.GET", b"doc", b"$"]),
18048 bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
18049 );
18050
18051 assert_eq!(
18055 f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
18056 "+OK\r\n"
18057 );
18058 assert_eq!(
18059 f.run(&[b"JSON.GET", b"doc", b"$"]),
18060 bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
18061 );
18062
18063 assert_eq!(
18067 f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
18068 "+OK\r\n"
18069 );
18070 assert_eq!(
18071 f.run(&[b"JSON.GET", b"doc", b"$.new"]),
18072 bulk(r#"[{"z":null}]"#).as_str()
18073 );
18074 assert_eq!(
18075 f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
18076 "$-1\r\n"
18077 );
18078 assert_eq!(
18079 f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
18080 "-Err wrong static path\r\n"
18081 );
18082
18083 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
18085 assert_eq!(
18086 f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
18087 "+OK\r\n"
18088 );
18089 assert_eq!(
18090 f.run(&[b"JSON.GET", b"doc", b"$"]),
18091 bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
18092 );
18093
18094 assert_eq!(
18096 f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
18097 "-ERR syntax error\r\n"
18098 );
18099 assert_eq!(
18100 f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
18101 "-ERR new objects must be created at the root\r\n"
18102 );
18103 f.run(&[b"SET", b"str", b"x"]);
18104 assert_eq!(
18105 f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
18106 "-Existing key has wrong Redis type\r\n"
18107 );
18108 }
18109
18110 #[test]
18113 fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
18114 let mut f = Fixture::new();
18115 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
18116 assert_eq!(
18117 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
18118 "+OK\r\n"
18119 );
18120 assert_eq!(
18124 f.run(&[b"JSON.GET", b"doc", b"$"]),
18125 bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
18126 );
18127
18128 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
18131 assert_eq!(
18132 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
18133 "+OK\r\n"
18134 );
18135 assert_eq!(
18136 f.run(&[b"JSON.GET", b"doc", b"$"]),
18137 bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
18138 );
18139 }
18140
18141 #[test]
18144 fn a_filter_path_reads_and_writes_the_members_it_keeps() {
18145 let mut f = Fixture::new();
18146 let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
18147 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18148
18149 assert_eq!(
18150 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
18151 bulk(r#"["a","c"]"#).as_str()
18152 );
18153 assert_eq!(
18156 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
18157 bulk(r#"["a","c"]"#).as_str()
18158 );
18159 assert_eq!(
18161 f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
18162 bulk(r#""a""#).as_str()
18163 );
18164 assert_eq!(
18165 f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
18166 "*1\r\n$6\r\nobject\r\n"
18167 );
18168
18169 assert_eq!(
18173 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
18174 bulk("[9,10]").as_str()
18175 );
18176 assert_eq!(
18177 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
18178 "+OK\r\n"
18179 );
18180 assert_eq!(
18181 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
18182 "-Err wrong static path\r\n"
18183 );
18184 assert_eq!(
18185 f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
18186 ":2\r\n"
18187 );
18188 assert_eq!(
18189 f.run(&[b"JSON.GET", b"doc", b"$"]),
18190 bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
18191 );
18192
18193 assert!(
18196 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
18197 .starts_with("-ERR")
18198 );
18199 assert!(
18200 f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
18201 .starts_with("-ERR")
18202 );
18203 }
18204
18205 #[test]
18208 fn a_filter_takes_the_membership_operators_and_the_methods_too() {
18209 let mut f = Fixture::new();
18210 let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
18211 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18212
18213 for (path, want) in [
18214 (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
18215 (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
18216 (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
18217 (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
18218 (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
18219 (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
18220 (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
18221 (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
18222 (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
18223 (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
18224 (b"$.box[?(@.n~)].t", "[]"),
18225 (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
18226 (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
18227 (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
18228 (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
18229 ] {
18230 assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
18231 }
18232
18233 assert_eq!(
18236 f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
18237 "+OK\r\n"
18238 );
18239 assert_eq!(
18240 f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
18241 bulk(r#"["b"]"#).as_str()
18242 );
18243 }
18244
18245 #[test]
18248 fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
18249 let mut f = Fixture::new();
18250 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
18251 assert_eq!(
18252 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
18253 "+OK\r\n"
18254 );
18255 assert_eq!(
18256 f.run(&[b"JSON.GET", b"doc", b"$"]),
18257 bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
18258 );
18259 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
18262 assert_eq!(
18263 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
18264 "+OK\r\n"
18265 );
18266 assert_eq!(
18267 f.run(&[b"JSON.GET", b"doc", b"$"]),
18268 bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
18269 );
18270 }
18271
18272 #[test]
18275 fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
18276 let mut f = Fixture::new();
18277 assert_eq!(
18278 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
18279 "+OK\r\n"
18280 );
18281 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
18282 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
18283
18284 assert_eq!(
18286 f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
18287 "+OK\r\n"
18288 );
18289 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
18290
18291 f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
18296 assert_eq!(
18297 f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
18298 "$-1\r\n"
18299 );
18300 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
18301 assert_eq!(
18302 f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
18303 "$-1\r\n"
18304 );
18305 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
18306
18307 f.run(&[b"SET", b"str", b"x"]);
18312 assert_eq!(
18313 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
18314 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
18315 );
18316 assert_eq!(
18317 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
18318 "-Existing key has wrong Redis type\r\n"
18319 );
18320 assert_eq!(
18321 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
18322 "-ERR new objects must be created at the root\r\n"
18323 );
18324 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
18325
18326 assert_eq!(
18331 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
18332 "-Err wrong static path\r\n"
18333 );
18334 assert_eq!(
18335 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
18336 "-ERR array index out of range\r\n"
18337 );
18338 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
18339
18340 f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
18344 assert_eq!(
18345 f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
18346 "+OK\r\n"
18347 );
18348 assert_eq!(
18349 f.run(&[b"JSON.GET", b"c", b"$"]),
18350 bulk(r#"[{"n":3}]"#).as_str()
18351 );
18352
18353 assert_eq!(
18356 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
18357 "-ERR wrong number of arguments for 'json.mset' command\r\n"
18358 );
18359 }
18360
18361 #[test]
18364 fn json_resp_answers_the_document_as_resp_types() {
18365 let mut f = Fixture::new();
18366 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
18367 assert_eq!(
18368 f.run(&[b"JSON.RESP", b"doc"]),
18369 "*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"
18370 );
18371 assert_eq!(
18373 f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
18374 "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
18375 );
18376
18377 f.run(&[
18378 b"JSON.SET",
18379 b"doc",
18380 b"$",
18381 br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
18382 ]);
18383 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
18384 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
18385 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
18388 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
18389 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
18390
18391 assert_eq!(
18394 f.run(&[b"JSON.RESP", b"doc", b".nope"]),
18395 "-ERR Path does not exist\r\n"
18396 );
18397 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
18398 assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
18399 assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
18400 }
18401
18402 #[test]
18406 fn json_debug_answers_a_byte_count_and_its_own_help() {
18407 let mut f = Fixture::new();
18408 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
18409 let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
18410 assert!(one.starts_with(':'), "{one}");
18411 assert_eq!(
18412 f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
18413 format!("*1\r\n{one}")
18414 );
18415 let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
18416 assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
18417
18418 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
18422 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
18423 assert_eq!(
18424 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
18425 "-ERR Path does not exist\r\n"
18426 );
18427 assert_eq!(
18428 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
18429 "*0\r\n"
18430 );
18431
18432 assert_eq!(
18433 f.run(&[b"JSON.DEBUG", b"HELP"]),
18434 "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
18435 $34\r\nHELP - this message\r\n"
18436 );
18437 assert_eq!(
18438 f.run(&[b"JSON.DEBUG", b"NOPE"]),
18439 "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
18440 );
18441 assert_eq!(
18442 f.run(&[b"JSON.DEBUG", b"MEMORY"]),
18443 "-ERR wrong number of arguments for 'json.debug' command\r\n"
18444 );
18445 }
18446
18447 #[test]
18452 fn the_first_vadd_decides_how_wide_the_set_is() {
18453 let mut f = Fixture::new();
18454 assert_eq!(
18455 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
18456 ":1\r\n"
18457 );
18458 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
18459 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
18460 assert_eq!(
18463 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
18464 ":0\r\n"
18465 );
18466 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
18467 assert_eq!(
18470 f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
18471 "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
18472 );
18473 assert_eq!(
18476 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
18477 ":1\r\n"
18478 );
18479 assert_eq!(
18480 f.run(&[b"VEMB", b"v", b"nowhere"]),
18481 "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
18482 );
18483 assert_eq!(
18487 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
18488 "-ERR asked quantization mismatch with existing vector set\r\n"
18489 );
18490 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
18493 assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
18494 assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
18495 }
18496
18497 #[test]
18500 fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
18501 let mut f = Fixture::new();
18502 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
18503 assert_eq!(
18509 f.run(&[b"VEMB", b"v", b"a"]),
18510 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
18511 );
18512 f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
18514 assert_eq!(
18515 f.run(&[b"VEMB", b"n", b"a"]),
18516 "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
18517 );
18518 f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
18521 assert_eq!(
18522 f.run(&[b"VEMB", b"b", b"a"]),
18523 "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
18524 );
18525 assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
18526 assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
18527
18528 let mut f = Fixture::new();
18532 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
18533 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
18534 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
18535 assert_eq!(
18536 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
18537 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
18538 $8\r\nopposite\r\n$1\r\n0\r\n"
18539 );
18540 assert_eq!(
18543 f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
18544 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
18545 );
18546 assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
18549 assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
18550 assert_eq!(
18553 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
18554 "*1\r\n$6\r\nacross\r\n"
18555 );
18556 assert_eq!(
18557 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
18558 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
18559 );
18560 assert_eq!(
18564 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
18565 "*1\r\n$6\r\nacross\r\n"
18566 );
18567
18568 let mut g = Fixture::new();
18571 g.run(&[b"HELLO", b"3"]);
18572 g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
18573 assert_eq!(
18574 g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
18575 "%1\r\n$4\r\neast\r\n,1\r\n"
18576 );
18577 }
18578
18579 #[test]
18581 fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
18582 let mut f = Fixture::new();
18583 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
18584 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
18585 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
18586 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
18587 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
18590 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
18591 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
18593 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
18594 assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
18597 assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
18598 assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
18599 assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
18602 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
18603 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
18604 assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
18605
18606 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
18608 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
18609 assert_eq!(
18610 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
18611 "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
18612 );
18613 }
18614
18615 #[test]
18618 fn vrem_takes_the_attribute_with_it() {
18619 let mut f = Fixture::new();
18620 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
18621 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
18622 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
18623 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
18624 assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
18625 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
18628
18629 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
18632 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
18633 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
18634 f.run(&[b"VREM", b"v", b"east"]);
18635 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
18636 assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
18637 }
18638
18639 #[test]
18642 fn vinfo_says_partition_first() {
18643 let mut f = Fixture::new();
18644 f.run(&[
18645 b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
18646 ]);
18647 f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
18648 let info = f.run(&[b"VINFO", b"v"]);
18649 assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
18650 assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
18653 assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
18654 assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
18655 assert!(
18658 info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
18659 "{info}"
18660 );
18661 let mut f = Fixture::new();
18662 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
18663 assert!(
18664 f.run(&[b"VINFO", b"v"])
18665 .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
18666 );
18667 assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
18668 }
18669
18670 fn named() -> Fixture {
18672 let mut f = Fixture::new();
18673 for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
18674 .iter()
18675 .enumerate()
18676 {
18677 let x = (i + 1).to_string();
18678 f.run(&[
18679 b"VADD",
18680 b"r",
18681 b"VALUES",
18682 b"2",
18683 x.as_bytes(),
18684 b"1",
18685 name.as_bytes(),
18686 ]);
18687 }
18688 f
18689 }
18690
18691 #[test]
18694 fn vrange_walks_the_names_and_not_the_vectors() {
18695 let mut f = named();
18696 assert_eq!(
18697 f.run(&[b"VRANGE", b"r", b"-", b"+"]),
18698 "*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"
18699 );
18700 assert_eq!(
18701 f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
18702 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
18703 "the high end is a name and not a prefix, so delta is past it"
18704 );
18705 assert_eq!(
18706 f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
18707 "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
18708 );
18709 assert_eq!(
18710 f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
18711 "*1\r\n$4\r\nbeta\r\n"
18712 );
18713 assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
18714 f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
18717 assert_eq!(
18718 f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
18719 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
18720 );
18721 assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
18722 }
18723
18724 #[test]
18727 fn a_vrange_count_of_zero_asks_for_nothing() {
18728 let mut f = named();
18729 assert_eq!(
18730 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
18731 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
18732 );
18733 assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
18734 assert!(
18735 f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
18736 .starts_with("*5\r\n"),
18737 "a negative count is no limit at all"
18738 );
18739 }
18740
18741 #[test]
18744 fn vrange_says_which_end_it_could_not_read() {
18745 let mut f = named();
18746 assert_eq!(
18747 f.run(&[b"VRANGE", b"r", b"x", b"y"]),
18748 "-ERR invalid start range format\r\n"
18749 );
18750 assert_eq!(
18751 f.run(&[b"VRANGE", b"r", b"+", b"x"]),
18752 "-ERR invalid end range format\r\n",
18753 "the high end is spelled wrong, which is worth saying before the \
18754 low end being on the wrong side"
18755 );
18756 assert_eq!(
18757 f.run(&[b"VRANGE", b"r", b"+", b"-"]),
18758 "-ERR '-' can only be used as first argument, '+' only as second\r\n"
18759 );
18760 assert_eq!(
18763 f.run(&[b"VRANGE", b"r", b"[", b"+"]),
18764 "-ERR invalid start range format\r\n"
18765 );
18766 assert_eq!(
18767 f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
18768 "-ERR invalid COUNT value\r\n"
18769 );
18770 assert_eq!(
18771 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
18772 "-ERR wrong number of arguments for 'VRANGE' command\r\n"
18773 );
18774 f.run(&[b"SET", b"s", b"x"]);
18775 assert!(
18776 f.run(&[b"VRANGE", b"s", b"-", b"+"])
18777 .starts_with("-WRONGTYPE")
18778 );
18779 }
18780
18781 #[test]
18784 fn reduce_is_refused_and_not_ignored() {
18785 let mut f = Fixture::new();
18786 let reduce = f.run(&[
18787 b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
18788 ]);
18789 assert!(
18790 reduce.starts_with("-ERR REDUCE is not supported."),
18791 "{reduce}"
18792 );
18793 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
18794 }
18795
18796 #[test]
18799 fn vsim_filter_reads_the_attributes() {
18800 let mut f = Fixture::new();
18801 for (name, x, y, attr) in [
18802 ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
18803 ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
18804 ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
18805 ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
18806 ] {
18807 f.run(&[
18808 b"VADD",
18809 b"v",
18810 b"VALUES",
18811 b"2",
18812 x.as_bytes(),
18813 y.as_bytes(),
18814 name.as_bytes(),
18815 b"SETATTR",
18816 attr.as_bytes(),
18817 ]);
18818 }
18819 assert_eq!(
18822 f.run(&[
18823 b"VSIM",
18824 b"v",
18825 b"VALUES",
18826 b"2",
18827 b"9",
18828 b"1",
18829 b"COUNT",
18830 b"2",
18831 b"FILTER",
18832 b".lang == \"en\"",
18833 ]),
18834 "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
18835 );
18836 assert_eq!(
18839 f.run(&[
18840 b"VSIM",
18841 b"v",
18842 b"VALUES",
18843 b"2",
18844 b"9",
18845 b"1",
18846 b"FILTER",
18847 b".lang == 'en' and .year > 1980",
18848 ]),
18849 "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
18850 );
18851 assert_eq!(
18853 f.run(&[
18854 b"VSIM",
18855 b"v",
18856 b"VALUES",
18857 b"2",
18858 b"9",
18859 b"1",
18860 b"FILTER",
18861 b".lang in ['fr', 'de']",
18862 ]),
18863 "*1\r\n$1\r\nb\r\n"
18864 );
18865 assert_eq!(
18866 f.run(&[
18867 b"VSIM",
18868 b"v",
18869 b"VALUES",
18870 b"2",
18871 b"9",
18872 b"1",
18873 b"FILTER",
18874 b".rating > 3"
18875 ]),
18876 "*0\r\n"
18877 );
18878 assert_eq!(
18881 f.run(&[
18882 b"VSIM",
18883 b"v",
18884 b"VALUES",
18885 b"2",
18886 b"9",
18887 b"1",
18888 b"TRUTH",
18889 b"FILTER",
18890 b".year < 1980",
18891 ]),
18892 "*1\r\n$1\r\nc\r\n"
18893 );
18894 f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
18897 assert_eq!(
18898 f.run(&[
18899 b"VSIM",
18900 b"v",
18901 b"VALUES",
18902 b"2",
18903 b"9",
18904 b"1",
18905 b"COUNT",
18906 b"1",
18907 b"FILTER",
18908 b".lang == \"en\"",
18909 ]),
18910 "*1\r\n$1\r\nb\r\n"
18911 );
18912 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
18915 assert_eq!(
18916 f.run(&[
18917 b"VSIM",
18918 b"v",
18919 b"VALUES",
18920 b"2",
18921 b"9",
18922 b"1",
18923 b"COUNT",
18924 b"1",
18925 b"FILTER",
18926 b".lang == \"en\"",
18927 ]),
18928 "*1\r\n$1\r\nb\r\n"
18929 );
18930
18931 let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
18934 assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
18935 assert_eq!(
18936 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
18937 "-ERR invalid FILTER expression\r\n"
18938 );
18939 assert_eq!(
18942 f.run(&[
18943 b"VSIM",
18944 b"v",
18945 b"VALUES",
18946 b"2",
18947 b"9",
18948 b"1",
18949 b"COUNT",
18950 b"1",
18951 b"FILTER-EF",
18952 b"500",
18953 b"FILTER",
18954 b".lang == 'en'",
18955 ]),
18956 "*1\r\n$1\r\nb\r\n"
18957 );
18958 assert_eq!(
18959 f.run(&[
18960 b"VSIM",
18961 b"v",
18962 b"VALUES",
18963 b"2",
18964 b"9",
18965 b"1",
18966 b"COUNT",
18967 b"1",
18968 b"FILTER-EF",
18969 b"0"
18970 ]),
18971 "*1\r\n$1\r\nb\r\n"
18972 );
18973 assert_eq!(
18974 f.run(&[
18975 b"VSIM",
18976 b"v",
18977 b"VALUES",
18978 b"2",
18979 b"9",
18980 b"1",
18981 b"FILTER-EF",
18982 b"lots"
18983 ]),
18984 "-ERR EF must be a positive integer\r\n"
18985 );
18986 }
18987
18988 #[test]
18991 fn the_keyspace_sees_a_vector_set_key_like_any_other() {
18992 let mut f = Fixture::new();
18993 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
18994 assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
18995 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
18996 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
18997 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
18998 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
18999 assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
19000 assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
19001 assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
19002 assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
19003 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
19004
19005 f.run(&[b"SET", b"s", b"1"]);
19007 assert_eq!(
19008 f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
19009 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19010 );
19011 assert_eq!(
19012 f.run(&[b"VCARD", b"s"]),
19013 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19014 );
19015 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19016 assert_eq!(
19017 f.run(&[b"GET", b"v"]),
19018 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19019 );
19020 f.run(&[b"G.NADD", b"social", b"ada"]);
19024 assert_eq!(
19025 f.run(&[b"VCARD", b"social"]),
19026 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19027 );
19028 assert_eq!(
19029 f.run(&[b"G.NGET", b"v", b"ada"]),
19030 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19031 );
19032 }
19033
19034 #[test]
19037 fn vrandmember_has_the_two_shapes_srandmember_has() {
19038 let mut f = Fixture::new();
19039 for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
19040 let x = (i + 1).to_string();
19041 f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
19042 }
19043 let one = f.run(&[b"VRANDMEMBER", b"v"]);
19045 assert!(one.starts_with("$1\r\n"), "{one}");
19046 let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
19048 assert!(all.starts_with("*3\r\n"), "{all}");
19049 for name in ["a", "b", "c"] {
19050 assert!(all.contains(name), "{all} is missing {name}");
19051 }
19052 all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
19053 assert!(all.starts_with("*2\r\n"), "{all}");
19054 let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
19056 assert!(many.starts_with("*5\r\n"), "{many}");
19057 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
19059 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
19060 }
19061
19062 #[test]
19065 fn vlinks_reports_one_layer_of_partition_neighbours() {
19066 let mut f = Fixture::new();
19067 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19068 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
19069 assert_eq!(
19072 f.run(&[b"VLINKS", b"v", b"east"]),
19073 "*1\r\n*1\r\n$5\r\nnorth\r\n"
19074 );
19075 assert_eq!(
19076 f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
19077 "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
19078 );
19079 assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
19080 assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
19081 }
19082
19083 #[test]
19086 fn fp32_and_values_are_the_same_vector() {
19087 let mut f = Fixture::new();
19088 let mut blob = Vec::new();
19089 for x in [3.0f32, 4.0] {
19090 blob.extend_from_slice(&x.to_le_bytes());
19091 }
19092 assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
19093 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
19094 assert_eq!(
19095 f.run(&[b"VEMB", b"v", b"a"]),
19096 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
19097 );
19098 assert_eq!(
19104 f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
19105 "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
19106 );
19107 assert_eq!(
19109 f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
19110 "-ERR invalid vector specification\r\n"
19111 );
19112 assert_eq!(
19114 f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
19115 "-ERR syntax error\r\n"
19116 );
19117 assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
19118 }
19119
19120 #[test]
19125 fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
19126 let mut f = Fixture::new();
19127 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
19128 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
19129 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
19130 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
19131 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
19132 assert_eq!(
19135 f.run(&[b"BF.INFO", b"b"]),
19136 "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
19137 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
19138 +Expansion rate\r\n:2\r\n"
19139 );
19140 assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
19141 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
19142 assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
19145 assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
19146 }
19147
19148 #[test]
19154 fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
19155 let mut f = Fixture::new();
19156 f.run(&[b"SET", b"s", b"text"]);
19157 assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
19158 assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
19159 for cmd in [
19160 vec![&b"BF.ADD"[..], b"s", b"x"],
19161 vec![&b"BF.MADD"[..], b"s", b"x"],
19162 vec![&b"BF.CARD"[..], b"s"],
19163 vec![&b"BF.INFO"[..], b"s"],
19164 vec![&b"BF.DEBUG"[..], b"s"],
19165 vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
19166 ] {
19167 let name = String::from_utf8_lossy(cmd[0]).into_owned();
19168 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
19169 }
19170 assert_eq!(
19173 f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
19174 "-ERR bad error rate\r\n"
19175 );
19176 assert!(
19177 f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
19178 .starts_with("-WRONGTYPE")
19179 );
19180 }
19181
19182 #[test]
19185 fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
19186 let mut f = Fixture::new();
19187 assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
19188 for i in 0..10u32 {
19189 assert_eq!(
19190 f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
19191 ":1\r\n"
19192 );
19193 }
19194 assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
19195 assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
19196 assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
19197 assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
19200 assert_eq!(
19201 f.run(&[b"BF.DEBUG", b"g"]),
19202 "*3\r\n$7\r\nsize:11\r\n\
19203 $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
19204 $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
19205 );
19206
19207 assert_eq!(
19209 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
19210 "+OK\r\n"
19211 );
19212 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
19213 assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
19214 assert_eq!(
19215 f.run(&[b"BF.ADD", b"n", b"c"]),
19216 "-ERR non scaling filter is full\r\n"
19217 );
19218 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
19221 assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
19224 f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
19225 assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
19226 assert_eq!(
19229 f.run(&[
19230 b"BF.RESERVE",
19231 b"q",
19232 b"0.01",
19233 b"2",
19234 b"NONSCALING",
19235 b"EXPANSION",
19236 b"2"
19237 ]),
19238 "-Nonscaling filters cannot expand\r\n"
19239 );
19240 }
19241
19242 #[test]
19245 fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
19246 let mut f = Fixture::new();
19247 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
19248 assert_eq!(
19249 f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
19250 "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
19251 );
19252 assert_eq!(
19253 f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
19254 "*2\r\n:1\r\n:0\r\n"
19255 );
19256 }
19257
19258 #[test]
19261 fn insert_is_a_reserve_and_a_madd_with_different_errors() {
19262 let mut f = Fixture::new();
19263 assert_eq!(
19264 f.run(&[
19265 b"BF.INSERT",
19266 b"i",
19267 b"CAPACITY",
19268 b"50",
19269 b"ERROR",
19270 b"0.001",
19271 b"ITEMS",
19272 b"a",
19273 b"b"
19274 ]),
19275 "*2\r\n:1\r\n:1\r\n"
19276 );
19277 assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
19278 assert_eq!(
19280 f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
19281 "-ERR not found\r\n"
19282 );
19283 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
19284 assert_eq!(
19287 f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
19288 "-Bad capacity\r\n"
19289 );
19290 assert_eq!(
19291 f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
19292 "-Bad error rate\r\n"
19293 );
19294 assert_eq!(
19295 f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
19296 "-Bad expansion\r\n"
19297 );
19298 assert_eq!(
19303 f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
19304 "*1\r\n:1\r\n"
19305 );
19306 assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
19307 assert_eq!(
19311 f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
19312 "*1\r\n:1\r\n"
19313 );
19314 assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
19315 assert_eq!(
19316 f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
19317 "*1\r\n:1\r\n"
19318 );
19319 assert_eq!(
19320 f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
19321 "-ERR not found\r\n"
19322 );
19323 assert_eq!(
19325 f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
19326 "-Unknown argument received\r\n"
19327 );
19328 assert_eq!(
19330 f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
19331 "*1\r\n:1\r\n"
19332 );
19333 assert!(
19335 f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
19336 .contains("wrong number of arguments")
19337 );
19338 }
19339
19340 #[test]
19343 fn a_dump_replays_into_a_filter_that_answers_the_same() {
19344 let mut f = Fixture::new();
19345 f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
19346 for i in 0..25u32 {
19347 f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
19348 }
19349 assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
19350
19351 let mut iter = b"0".to_vec();
19354 let mut chunks = 0;
19355 loop {
19356 let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
19357 let text = String::from_utf8_lossy(&raw).into_owned();
19358 let next = text
19359 .split("\r\n")
19360 .nth(1)
19361 .and_then(|n| n.strip_prefix(':'))
19362 .expect("a two element reply of an iterator and a chunk")
19363 .to_owned();
19364 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
19365 let data = &body[body
19366 .windows(2)
19367 .position(|w| w == b"\r\n")
19368 .expect("a length line")
19369 + 2..body.len() - 2];
19370 if next == "0" {
19371 assert!(data.is_empty(), "the last chunk is empty");
19372 break;
19373 }
19374 let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
19375 assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
19376 iter = next.into_bytes();
19377 chunks += 1;
19378 }
19379 assert_eq!(chunks, 3, "a header and one chunk per link");
19380
19381 assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
19382 assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
19383 for i in 0..25u32 {
19384 assert_eq!(
19385 f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
19386 ":1\r\n"
19387 );
19388 }
19389
19390 assert_eq!(
19393 f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
19394 "-ERR received bad data\r\n"
19395 );
19396 assert_eq!(
19397 f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
19398 "-ERR received bad data\r\n"
19399 );
19400 assert_eq!(
19402 f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
19403 "-ERR invalid offset - no link found\r\n"
19404 );
19405 assert_eq!(
19406 f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
19407 "-ERR Second argument must be numeric\r\n"
19408 );
19409 assert_eq!(
19412 f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
19413 "-Second argument must be numeric\r\n"
19414 );
19415 }
19416
19417 #[test]
19420 fn reserve_reads_its_numbers_the_way_string2ll_does() {
19421 let mut f = Fixture::new();
19422 for (args, want) in [
19423 (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
19424 (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
19425 (
19426 vec![&b"0"[..], b"10"],
19427 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19428 ),
19429 (
19430 vec![&b"1"[..], b"10"],
19431 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19432 ),
19433 (
19434 vec![&b"inf"[..], b"10"],
19435 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19436 ),
19437 (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
19438 (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
19439 (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
19440 (
19441 vec![&b"0.01"[..], b"0"],
19442 "-ERR capacity must be in the range [1, 1073741824]\r\n",
19443 ),
19444 (
19445 vec![&b"0.01"[..], b"1073741825"],
19446 "-ERR capacity must be in the range [1, 1073741824]\r\n",
19447 ),
19448 ] {
19449 let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
19450 cmd.extend(args.iter().copied());
19451 assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
19452 }
19453 assert_eq!(
19454 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
19455 "-ERR no expansion\r\n"
19456 );
19457 assert_eq!(
19458 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
19459 "-ERR bad expansion\r\n"
19460 );
19461 assert_eq!(
19462 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
19463 "-ERR expansion must be in the range [0, 32768]\r\n"
19464 );
19465 assert_eq!(
19467 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
19468 "+OK\r\n"
19469 );
19470 assert_eq!(
19471 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
19472 "-ERR item exists\r\n"
19473 );
19474 assert_eq!(
19475 f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
19476 "-Invalid information value\r\n"
19477 );
19478 assert!(
19479 f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
19480 .contains("wrong number of arguments")
19481 );
19482 }
19483
19484 #[test]
19486 fn the_bloom_family_answers_in_resp3_spelling_too() {
19487 let mut f = Fixture::new();
19488 f.out.set_proto(Proto::Resp3);
19489 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
19490 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
19491 assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
19492 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
19493 assert_eq!(
19494 f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
19495 "*2\r\n#t\r\n#f\r\n"
19496 );
19497 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
19499 assert_eq!(
19500 f.run(&[b"BF.INFO", b"b"]),
19501 "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
19502 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
19503 +Expansion rate\r\n:2\r\n"
19504 );
19505 assert_eq!(
19508 f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
19509 "%1\r\n+Capacity\r\n:100\r\n"
19510 );
19511 }
19512
19513 fn cf_header(
19522 items: u64,
19523 buckets: u64,
19524 deletes: u64,
19525 filters: u64,
19526 geometry: [u16; 3],
19527 ) -> Vec<u8> {
19528 let mut out = Vec::with_capacity(38);
19529 for n in [items, buckets, deletes, filters] {
19530 out.extend_from_slice(&n.to_le_bytes());
19531 }
19532 for n in geometry {
19533 out.extend_from_slice(&n.to_le_bytes());
19534 }
19535 out
19536 }
19537
19538 #[test]
19542 fn cf_add_makes_the_filter_and_counts_the_copies() {
19543 let mut f = Fixture::new();
19544 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
19545 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
19546 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
19547 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
19550 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
19551 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
19552 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
19553 assert_eq!(
19554 f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
19555 "*2\r\n:1\r\n:0\r\n"
19556 );
19557 assert_eq!(
19560 f.run(&[b"CF.INFO", b"d"]),
19561 "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
19562 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
19563 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
19564 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
19565 );
19566 assert_eq!(
19567 f.run(&[b"CF.DEBUG", b"d"]),
19568 "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
19569 max_iterations:20 expansion:1\r\n"
19570 );
19571 assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
19572 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
19573
19574 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
19576 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
19577 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
19578 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
19579 assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
19580
19581 assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
19584 assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
19585 assert_eq!(
19586 f.run(&[b"CF.COMPACT", b"gone"]),
19587 "-Cuckoo filter was not found\r\n"
19588 );
19589 assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
19590 assert!(
19594 f.run(&[b"CF.COMPACT", b"a", b"b"])
19595 .contains("wrong number of arguments")
19596 );
19597 }
19598
19599 #[test]
19602 fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
19603 let mut f = Fixture::new();
19604 f.run(&[b"SET", b"s", b"text"]);
19605 assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
19606 assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
19607 assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
19608 assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
19612 assert_eq!(
19613 f.run(&[b"CF.COMPACT", b"s"]),
19614 "-Cuckoo filter was not found\r\n"
19615 );
19616 for cmd in [
19617 vec![&b"CF.ADD"[..], b"s", b"x"],
19618 vec![&b"CF.ADDNX"[..], b"s", b"x"],
19619 vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
19620 vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
19621 vec![&b"CF.INFO"[..], b"s"],
19622 vec![&b"CF.DEBUG"[..], b"s"],
19623 vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
19624 vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
19625 vec![&b"CF.RESERVE"[..], b"s", b"64"],
19626 ] {
19627 let name = String::from_utf8_lossy(cmd[0]).into_owned();
19628 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
19629 }
19630 }
19631
19632 #[test]
19635 fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
19636 let mut f = Fixture::new();
19637 assert_eq!(
19638 f.run(&[
19639 b"CF.RESERVE",
19640 b"r",
19641 b"64",
19642 b"BUCKETSIZE",
19643 b"1",
19644 b"MAXITERATIONS",
19645 b"7",
19646 b"EXPANSION",
19647 b"4"
19648 ]),
19649 "+OK\r\n"
19650 );
19651 assert_eq!(
19652 f.run(&[b"CF.DEBUG", b"r"]),
19653 "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
19654 max_iterations:7 expansion:4\r\n"
19655 );
19656 assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
19657
19658 assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
19659 assert_eq!(
19660 f.run(&[b"CF.RESERVE", b"q", b"1"]),
19661 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
19662 );
19663 assert_eq!(
19666 f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
19667 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
19668 );
19669 assert_eq!(
19670 f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
19671 "+OK\r\n"
19672 );
19673
19674 assert_eq!(
19679 f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
19680 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
19681 );
19682 assert_eq!(
19683 f.run(&[
19684 b"CF.RESERVE",
19685 b"q2",
19686 b"64",
19687 b"EXPANSION",
19688 b"xx",
19689 b"BUCKETSIZE",
19690 b"0"
19691 ]),
19692 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
19693 );
19694 assert_eq!(
19695 f.run(&[
19696 b"CF.RESERVE",
19697 b"q2",
19698 b"64",
19699 b"MAXITERATIONS",
19700 b"0",
19701 b"BUCKETSIZE",
19702 b"0"
19703 ]),
19704 "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
19705 );
19706 assert_eq!(
19710 f.run(&[
19711 b"CF.RESERVE",
19712 b"q3",
19713 b"64",
19714 b"BUCKETSIZE",
19715 b"1",
19716 b"BUCKETSIZE",
19717 b"0"
19718 ]),
19719 "+OK\r\n"
19720 );
19721 assert_eq!(
19724 f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
19725 "+OK\r\n"
19726 );
19727 assert_eq!(
19728 f.run(&[b"CF.DEBUG", b"q4"]),
19729 "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
19730 max_iterations:20 expansion:1\r\n"
19731 );
19732 assert!(
19735 f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
19736 .contains("wrong number of arguments")
19737 );
19738 }
19739
19740 #[test]
19743 fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
19744 let mut f = Fixture::new();
19745 assert_eq!(
19746 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
19747 "*2\r\n:1\r\n:1\r\n"
19748 );
19749 assert_eq!(
19750 f.run(&[b"CF.DEBUG", b"i"]),
19751 "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
19752 max_iterations:20 expansion:1\r\n"
19753 );
19754 assert_eq!(
19757 f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
19758 "*2\r\n:0\r\n:1\r\n"
19759 );
19760 assert_eq!(
19761 f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
19762 "-ERR not found\r\n"
19763 );
19764 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
19765
19766 assert_eq!(
19767 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
19768 "-Bad capacity\r\n"
19769 );
19770 assert_eq!(
19773 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
19774 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
19775 );
19776 assert_eq!(
19780 f.run(&[
19781 b"CF.INSERT",
19782 b"i",
19783 b"CAPACITY",
19784 b"8",
19785 b"CAPACITY",
19786 b"2",
19787 b"ITEMS",
19788 b"a"
19789 ]),
19790 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
19791 );
19792 assert_eq!(
19796 f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
19797 "*1\r\n:1\r\n"
19798 );
19799 assert_eq!(
19800 f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
19801 "*1\r\n:1\r\n"
19802 );
19803 assert_eq!(
19804 f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
19805 "-Unknown argument received\r\n"
19806 );
19807 assert_eq!(
19809 f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
19810 "*1\r\n:1\r\n"
19811 );
19812 assert!(
19814 f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
19815 .contains("wrong number of arguments")
19816 );
19817 assert!(
19818 f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
19819 .contains("wrong number of arguments")
19820 );
19821 }
19822
19823 #[test]
19826 fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
19827 let mut f = Fixture::new();
19828 f.run(&[
19829 b"CF.RESERVE",
19830 b"s",
19831 b"4",
19832 b"BUCKETSIZE",
19833 b"1",
19834 b"EXPANSION",
19835 b"0",
19836 ]);
19837 for i in 0..4u32 {
19838 assert_eq!(
19839 f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
19840 ":1\r\n"
19841 );
19842 }
19843 assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
19844 assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
19845 assert_eq!(
19848 f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
19849 "*2\r\n:-1\r\n:-1\r\n"
19850 );
19851 assert_eq!(
19852 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
19853 "*2\r\n:0\r\n:-1\r\n"
19854 );
19855
19856 let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
19862 assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
19863 assert_eq!(
19864 f.run(&[b"CF.ADD", b"g", b"q"]),
19865 "-Maximum expansions reached\r\n"
19866 );
19867 assert_eq!(
19868 f.run(&[b"CF.INFO", b"g"]),
19869 "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
19870 +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
19871 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
19872 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
19873 );
19874 }
19875
19876 #[test]
19880 fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
19881 let mut f = Fixture::new();
19882 f.run(&[
19883 b"CF.RESERVE",
19884 b"src",
19885 b"8",
19886 b"BUCKETSIZE",
19887 b"2",
19888 b"EXPANSION",
19889 b"2",
19890 ]);
19891 for i in 0..40u32 {
19892 f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
19893 }
19894 let mut pos = b"0".to_vec();
19898 let mut chunks = 0;
19899 loop {
19900 let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
19901 let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
19902 let next = head
19903 .split("\r\n")
19904 .nth(1)
19905 .and_then(|n| n.strip_prefix(':'))
19906 .expect("a two element reply of a position and a chunk")
19907 .to_owned();
19908 if next == "0" {
19909 assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
19910 break;
19911 }
19912 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
19913 let at = body
19914 .windows(2)
19915 .position(|w| w == b"\r\n")
19916 .expect("a length line")
19917 + 2;
19918 let data = &body[at..body.len() - 2];
19919 assert_eq!(
19920 f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
19921 "+OK\r\n",
19922 "loading chunk {chunks}"
19923 );
19924 pos = next.into_bytes();
19925 chunks += 1;
19926 }
19927 assert!(chunks >= 2, "a header and at least one chunk");
19928
19929 assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
19930 assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
19931 for i in 0..40u32 {
19932 assert_eq!(
19933 f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
19934 ":1\r\n"
19935 );
19936 }
19937
19938 f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
19941 assert_eq!(
19942 f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
19943 "*2\r\n:0\r\n$-1\r\n"
19944 );
19945
19946 assert_eq!(
19950 f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
19951 "-Invalid position\r\n"
19952 );
19953 assert_eq!(
19954 f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
19955 "-Invalid position\r\n"
19956 );
19957 assert_eq!(
19958 f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
19959 "-Invalid position\r\n"
19960 );
19961 assert_eq!(
19962 f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
19963 "-Couldn't load chunk!\r\n"
19964 );
19965 let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
19967 assert_eq!(
19968 f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
19969 "-ERR item exists\r\n"
19970 );
19971 assert_eq!(
19975 f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
19976 "-Invalid header\r\n"
19977 );
19978 for (why, bad) in [
19979 ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
19980 ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
19981 (
19982 "a bucket count that is not a power of two",
19983 cf_header(0, 3, 0, 1, [2, 20, 1]),
19984 ),
19985 ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
19986 ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
19987 (
19988 "a growth nobody could reach",
19989 cf_header(0, 8, 0, 1, [2, 20, 32769]),
19990 ),
19991 (
19992 "a chain that cannot grow and did",
19993 cf_header(0, 8, 0, 2, [2, 20, 0]),
19994 ),
19995 (
19998 "a filter count that wraps",
19999 cf_header(0, 8, 0, 65_536, [2, 20, 1]),
20000 ),
20001 ] {
20002 assert_eq!(
20003 f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
20004 "-Couldn't create filter!\r\n",
20005 "{why}"
20006 );
20007 }
20008 }
20009
20010 #[test]
20013 fn the_cuckoo_family_answers_in_resp3_spelling_too() {
20014 let mut f = Fixture::new();
20015 f.out.set_proto(Proto::Resp3);
20016 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
20017 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
20018 assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
20019 assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
20020 assert_eq!(
20021 f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
20022 "*2\r\n#t\r\n#f\r\n"
20023 );
20024 assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
20025 assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
20026 assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
20028 assert_eq!(
20029 f.run(&[b"CF.INFO", b"c"]),
20030 "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
20031 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
20032 +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
20033 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
20034 );
20035
20036 f.run(&[
20041 b"CF.RESERVE",
20042 b"s",
20043 b"4",
20044 b"BUCKETSIZE",
20045 b"1",
20046 b"EXPANSION",
20047 b"0",
20048 ]);
20049 assert_eq!(
20050 f.run(&[
20051 b"CF.INSERT",
20052 b"s",
20053 b"ITEMS",
20054 b"a",
20055 b"b",
20056 b"c",
20057 b"d",
20058 b"e",
20059 b"f"
20060 ]),
20061 "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
20062 );
20063 assert_eq!(
20064 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
20065 "*2\r\n:0\r\n:-1\r\n"
20066 );
20067 assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
20068 assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
20071 }
20072
20073 #[test]
20078 fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
20079 let mut f = Fixture::new();
20080 assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
20081 assert_eq!(
20082 f.run(&[b"CMS.INFO", b"d"]),
20083 "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
20084 );
20085 assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
20086 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
20087 assert_eq!(
20090 f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
20091 "+OK\r\n"
20092 );
20093 assert_eq!(
20094 f.run(&[b"CMS.INFO", b"p"]),
20095 "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
20096 );
20097 assert_eq!(
20100 f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
20101 "-CMS: key already exists\r\n"
20102 );
20103 assert_eq!(
20104 f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
20105 "-CMS: invalid width\r\n"
20106 );
20107 assert_eq!(
20108 f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
20109 "-CMS: invalid depth\r\n"
20110 );
20111 assert_eq!(
20112 f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
20113 "-CMS: invalid overestimation value\r\n"
20114 );
20115 assert_eq!(
20116 f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
20117 "-CMS: invalid prob value\r\n"
20118 );
20119 assert_eq!(
20123 f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
20124 "-CMS: invalid init arguments\r\n"
20125 );
20126 assert_eq!(
20130 f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
20131 "-CMS: Insufficient memory to create the key\r\n"
20132 );
20133 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
20134 }
20135
20136 #[test]
20139 fn increments_are_parsed_whole_and_the_counters_saturate() {
20140 let mut f = Fixture::new();
20141 f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
20142 assert_eq!(
20143 f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
20144 "*2\r\n:3\r\n:4\r\n"
20145 );
20146 assert_eq!(
20149 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
20150 "*2\r\n:4\r\n:5\r\n"
20151 );
20152 assert_eq!(
20154 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
20155 "-CMS: Cannot parse number\r\n"
20156 );
20157 assert_eq!(
20158 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
20159 "-CMS: Number cannot be negative\r\n"
20160 );
20161 assert_eq!(
20162 f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
20163 "*2\r\n:5\r\n:4\r\n"
20164 );
20165 f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
20168 assert_eq!(
20169 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
20170 "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
20171 );
20172 assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
20173 f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
20176 f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
20177 f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
20178 assert_eq!(
20179 f.run(&[b"CMS.INFO", b"w"]),
20180 "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
20181 );
20182 assert!(
20185 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
20186 .contains("wrong number of arguments")
20187 );
20188 assert_eq!(
20189 f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
20190 "-CMS: key does not exist\r\n"
20191 );
20192 assert_eq!(
20193 f.run(&[b"CMS.QUERY", b"nope", b"a"]),
20194 "-CMS: key does not exist\r\n"
20195 );
20196 }
20197
20198 #[test]
20201 fn a_merge_lands_whole_or_not_at_all() {
20202 let mut f = Fixture::new();
20203 for name in [&b"m1"[..], b"m2", b"dst"] {
20204 f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
20205 }
20206 f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
20207 f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
20208 assert_eq!(
20209 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
20210 "+OK\r\n"
20211 );
20212 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
20213 assert_eq!(
20216 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
20217 "+OK\r\n"
20218 );
20219 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
20220 assert_eq!(
20221 f.run(&[
20222 b"CMS.MERGE",
20223 b"dst",
20224 b"2",
20225 b"m1",
20226 b"m2",
20227 b"WEIGHTS",
20228 b"2",
20229 b"3"
20230 ]),
20231 "+OK\r\n"
20232 );
20233 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
20234 assert_eq!(
20237 f.run(&[
20238 b"CMS.MERGE",
20239 b"dst",
20240 b"1",
20241 b"m1",
20242 b"WEIGHTS",
20243 b"4611686018427387904"
20244 ]),
20245 "-CMS: MERGE overflow\r\n"
20246 );
20247 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
20248 f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
20251 assert_eq!(
20252 f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
20253 "-CMS: key does not exist\r\n"
20254 );
20255 assert_eq!(
20256 f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
20257 "-CMS: Number of keys must be positive\r\n"
20258 );
20259 assert_eq!(
20260 f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
20261 "-CMS: wrong number of keys\r\n"
20262 );
20263 assert_eq!(
20264 f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
20265 "-CMS: wrong number of keys/weights\r\n"
20266 );
20267 assert_eq!(
20268 f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
20269 "-CMS: width/depth is not equal\r\n"
20270 );
20271 assert_eq!(
20272 f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
20273 "-CMS: key does not exist\r\n"
20274 );
20275 }
20276
20277 #[test]
20280 fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
20281 let mut f = Fixture::new();
20282 f.run(&[b"SET", b"s", b"text"]);
20283 for cmd in [
20284 vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
20285 vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
20286 vec![&b"CMS.QUERY"[..], b"s", b"a"],
20287 vec![&b"CMS.INFO"[..], b"s"],
20288 vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
20289 ] {
20290 let name = String::from_utf8_lossy(cmd[0]).into_owned();
20291 let reply = f.run(&cmd);
20292 assert!(
20295 reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
20296 "{name}: {reply}"
20297 );
20298 }
20299 f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
20300 assert_eq!(
20304 f.run(&[b"COPY", b"c", b"c2"]),
20305 "-ERR not supported for this module key\r\n"
20306 );
20307 assert_eq!(
20308 f.run(&[b"DUMP", b"c"]),
20309 "-ERR DUMP is not supported for this module key\r\n"
20310 );
20311 f.run(&[b"G.NADD", b"g", b"a"]);
20313 assert_eq!(
20314 f.run(&[b"COPY", b"g", b"g2"]),
20315 "-ERR COPY is not supported for a graph\r\n"
20316 );
20317 assert_eq!(
20318 f.run(&[b"DUMP", b"g"]),
20319 "-ERR DUMP is not supported for a graph\r\n"
20320 );
20321 assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
20324 assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
20325 assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
20326 assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
20327 assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
20328 }
20329
20330 #[test]
20335 fn a_reserve_takes_three_arguments_or_six() {
20336 let mut f = Fixture::new();
20337 assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
20338 assert_eq!(
20339 f.run(&[b"TOPK.INFO", b"t"]),
20340 "*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"
20341 );
20342 for cmd in [
20345 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
20346 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
20347 ] {
20348 assert!(f.run(&cmd).contains("wrong number of arguments"));
20349 }
20350 assert_eq!(
20351 f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
20352 "+OK\r\n"
20353 );
20354 assert_eq!(
20357 f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
20358 "-TopK: key already exists\r\n"
20359 );
20360 assert_eq!(
20361 f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
20362 "-TopK: invalid k\r\n"
20363 );
20364 assert_eq!(
20365 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
20366 "-TopK: invalid width\r\n"
20367 );
20368 assert_eq!(
20369 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
20370 "-TopK: invalid depth\r\n"
20371 );
20372 assert_eq!(
20374 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
20375 "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
20376 );
20377 assert_eq!(
20378 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
20379 "+OK\r\n"
20380 );
20381 assert_eq!(
20383 f.run(&[
20384 b"TOPK.RESERVE",
20385 b"w",
20386 b"1",
20387 b"4294967295",
20388 b"4294967295",
20389 b"0.9"
20390 ]),
20391 "-ERR Insufficient memory to create topk data structure\r\n"
20392 );
20393 }
20394
20395 #[test]
20397 fn the_kept_set_is_what_query_and_list_answer_from() {
20398 let mut f = Fixture::new();
20399 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
20400 assert_eq!(
20403 f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
20404 "*2\r\n$-1\r\n$-1\r\n"
20405 );
20406 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
20407 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
20410 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
20411 assert_eq!(
20412 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
20413 "*3\r\n:1\r\n:0\r\n:1\r\n"
20414 );
20415 assert_eq!(
20417 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
20418 "*3\r\n:11\r\n:1\r\n:6\r\n"
20419 );
20420 assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
20421 assert_eq!(
20422 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
20423 "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
20424 );
20425 assert_eq!(
20428 f.run(&[b"TOPK.LIST", b"t", b"w"]),
20429 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
20430 );
20431 assert_eq!(
20432 f.run(&[b"TOPK.LIST", b"t", b""]),
20433 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
20434 );
20435 assert_eq!(
20436 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
20437 "-WITHCOUNT keyword expected\r\n"
20438 );
20439 assert_eq!(
20442 f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
20443 "-WITHCOUNT keyword expected\r\n"
20444 );
20445 assert_eq!(
20446 f.run(&[b"TOPK.LIST", b"missing"]),
20447 "-TopK: key does not exist\r\n"
20448 );
20449 f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
20451 assert_eq!(
20452 f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
20453 "*1\r\n$-1\r\n"
20454 );
20455 assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
20456 assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
20457 }
20458
20459 #[test]
20462 fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
20463 let mut f = Fixture::new();
20464 f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
20465 assert_eq!(
20469 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
20470 format!(
20471 "*2\r\n$-1\r\n-{}\r\n",
20472 "TopK: increment must be an integer greater or equal to 0 and smaller or equal to 100,000"
20473 )
20474 );
20475 assert_eq!(
20476 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
20477 "*3\r\n:3\r\n:0\r\n:0\r\n"
20478 );
20479 assert_eq!(
20481 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
20482 "*1\r\n$-1\r\n"
20483 );
20484 assert!(
20485 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
20486 .contains("smaller or equal to 100,000")
20487 );
20488 assert!(
20490 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
20491 .contains("wrong number of arguments")
20492 );
20493 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
20494 }
20495
20496 #[test]
20498 fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
20499 let mut f = Fixture::new();
20500 f.run(&[b"HELLO", b"3"]);
20501 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
20502 f.run(&[b"TOPK.ADD", b"t", b"a"]);
20503 assert_eq!(
20504 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
20505 "*2\r\n#t\r\n#f\r\n"
20506 );
20507 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
20509 assert_eq!(
20510 f.run(&[b"TOPK.INFO", b"t"]),
20511 "%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"
20512 );
20513 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
20514 }
20515
20516 #[test]
20519 fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
20520 let mut f = Fixture::new();
20521 f.run(&[b"SET", b"s", b"text"]);
20522 for cmd in [
20523 vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
20524 vec![&b"TOPK.ADD"[..], b"s", b"a"],
20525 vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
20526 vec![&b"TOPK.QUERY"[..], b"s", b"a"],
20527 vec![&b"TOPK.COUNT"[..], b"s", b"a"],
20528 vec![&b"TOPK.LIST"[..], b"s"],
20529 vec![&b"TOPK.INFO"[..], b"s"],
20530 ] {
20531 let name = String::from_utf8_lossy(cmd[0]).into_owned();
20532 let reply = f.run(&cmd);
20533 assert!(
20534 reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
20535 "{name}: {reply}"
20536 );
20537 }
20538 f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
20539 assert_eq!(
20540 f.run(&[b"COPY", b"t", b"t2"]),
20541 "-ERR not supported for this module key\r\n"
20542 );
20543 assert_eq!(
20544 f.run(&[b"DUMP", b"t"]),
20545 "-ERR DUMP is not supported for this module key\r\n"
20546 );
20547 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
20548 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
20549 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
20550 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
20551 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
20552 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
20553 assert_eq!(
20556 f.run(&[b"TOPK.INFO", b"t3"]),
20557 "-TopK: key does not exist\r\n"
20558 );
20559 }
20560
20561 #[test]
20566 fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
20567 let mut f = Fixture::new();
20568 assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
20569 assert_eq!(
20571 f.run(&[b"TDIGEST.INFO", b"t"]),
20572 "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
20573 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
20574 +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
20575 );
20576 assert_eq!(
20577 f.run(&[b"TDIGEST.CREATE", b"t"]),
20578 "-ERR T-Digest: key already exists\r\n"
20579 );
20580 assert!(
20582 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
20583 .contains("wrong number of arguments")
20584 );
20585 assert_eq!(
20586 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
20587 "+OK\r\n"
20588 );
20589 assert_eq!(
20590 f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
20591 "+OK\r\n"
20592 );
20593 assert_eq!(
20597 f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
20598 "-ERR T-Digest: error parsing compression parameter\r\n"
20599 );
20600 assert_eq!(
20601 f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
20602 "-ERR T-Digest: wrong keyword\r\n"
20603 );
20604 assert_eq!(
20605 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
20606 "-ERR T-Digest: error parsing compression parameter\r\n"
20607 );
20608 assert_eq!(
20609 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
20610 "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
20611 );
20612 assert_eq!(
20615 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
20616 "-ERR T-Digest: allocation failed\r\n"
20617 );
20618 assert_eq!(
20620 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
20621 "-ERR T-Digest: allocation failed\r\n"
20622 );
20623 assert_eq!(
20626 f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
20627 "-ERR T-Digest: key already exists\r\n"
20628 );
20629 }
20630
20631 #[test]
20634 fn the_quantile_family_answers_what_the_module_answers() {
20635 let mut f = Fixture::new();
20636 f.run(&[b"TDIGEST.CREATE", b"s"]);
20637 assert_eq!(
20638 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
20639 "+OK\r\n"
20640 );
20641 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
20642 assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
20643 assert_eq!(
20645 f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
20646 "*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"
20647 );
20648 assert_eq!(
20649 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
20650 "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
20651 );
20652 assert_eq!(
20655 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
20656 "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
20657 );
20658 assert_eq!(
20659 f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
20660 "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
20661 );
20662 assert_eq!(
20663 f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
20664 "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
20665 );
20666 assert_eq!(
20667 f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
20668 "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
20669 );
20670 assert_eq!(
20671 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
20672 "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
20673 );
20674 assert_eq!(
20675 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
20676 "$3\r\n2.5\r\n"
20677 );
20678 assert_eq!(
20679 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
20680 "$3\r\n2.5\r\n"
20681 );
20682 assert_eq!(
20684 f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
20685 "-ERR T-Digest: quantile should be in [0,1]\r\n"
20686 );
20687 assert_eq!(
20688 f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
20689 "-ERR T-Digest: error parsing quantile\r\n"
20690 );
20691 assert_eq!(
20692 f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
20693 "-ERR T-Digest: error parsing cdf\r\n"
20694 );
20695 assert_eq!(
20696 f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
20697 "-ERR T-Digest: error parsing value\r\n"
20698 );
20699 assert_eq!(
20700 f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
20701 "-ERR T-Digest: rank needs to be non negative\r\n"
20702 );
20703 assert_eq!(
20704 f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
20705 "-ERR T-Digest: error parsing rank\r\n"
20706 );
20707 assert_eq!(
20710 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
20711 "-ERR T-Digest: error parsing low_cut_percentile\r\n"
20712 );
20713 assert_eq!(
20714 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
20715 "-ERR T-Digest: error parsing high_cut_percentile\r\n"
20716 );
20717 assert_eq!(
20718 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
20719 "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
20720 );
20721 assert_eq!(
20722 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
20723 "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
20724 );
20725 }
20726
20727 #[test]
20730 fn an_empty_digest_has_an_answer_for_everything() {
20731 let mut f = Fixture::new();
20732 f.run(&[b"TDIGEST.CREATE", b"e"]);
20733 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
20734 assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
20735 assert_eq!(
20736 f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
20737 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
20738 );
20739 assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
20740 assert_eq!(
20741 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
20742 "$3\r\nnan\r\n"
20743 );
20744 assert_eq!(
20747 f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
20748 "*2\r\n:-2\r\n:-2\r\n"
20749 );
20750 assert_eq!(
20751 f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
20752 "*2\r\n:-2\r\n:-2\r\n"
20753 );
20754 assert_eq!(
20755 f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
20756 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
20757 );
20758 f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
20760 assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
20761 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
20762 f.run(&[b"TDIGEST.CREATE", b"e2"]);
20765 assert_eq!(
20766 f.run(&[b"TDIGEST.INFO", b"e"]),
20767 f.run(&[b"TDIGEST.INFO", b"e2"])
20768 );
20769 }
20770
20771 #[test]
20774 fn a_sample_is_read_the_way_redis_reads_a_double() {
20775 let mut f = Fixture::new();
20776 f.run(&[b"TDIGEST.CREATE", b"a"]);
20777 for bad in [
20780 &b"nan"[..],
20781 b"1e400",
20782 b"-1e400",
20783 b"1e309",
20784 b"1e-400",
20785 b"",
20786 b" 1",
20787 b"1 ",
20788 b"1e",
20789 b"--1",
20790 ] {
20791 assert_eq!(
20792 f.run(&[b"TDIGEST.ADD", b"a", bad]),
20793 "-ERR T-Digest: error parsing val parameter\r\n",
20794 "{}",
20795 String::from_utf8_lossy(bad)
20796 );
20797 }
20798 for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
20801 assert_eq!(
20802 f.run(&[b"TDIGEST.ADD", b"a", word]),
20803 "-ERR T-Digest: val parameter needs to be a finite number\r\n",
20804 "{}",
20805 String::from_utf8_lossy(word)
20806 );
20807 }
20808 for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
20811 assert_eq!(
20812 f.run(&[b"TDIGEST.ADD", b"a", good]),
20813 "+OK\r\n",
20814 "{}",
20815 String::from_utf8_lossy(good)
20816 );
20817 }
20818 assert!(
20820 f.run(&[b"TDIGEST.INFO", b"a"])
20821 .contains("Observations\r\n:6\r\n")
20822 );
20823 assert_eq!(
20826 f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
20827 "-ERR T-Digest: error parsing val parameter\r\n"
20828 );
20829 assert!(
20830 f.run(&[b"TDIGEST.INFO", b"a"])
20831 .contains("Observations\r\n:6\r\n")
20832 );
20833 }
20834
20835 #[test]
20838 fn a_merge_sweeps_the_destination_between_its_inputs() {
20839 let mut f = Fixture::new();
20840 f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
20841 f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
20842 f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
20843 f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
20844 assert_eq!(
20845 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
20846 "+OK\r\n"
20847 );
20848 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
20853 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
20854 assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
20855 assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
20856 assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
20857 assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
20858 assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
20859 assert!(
20862 f.run(&[b"TDIGEST.INFO", b"m1"])
20863 .contains("Merged nodes\r\n:3\r\n")
20864 );
20865 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
20868 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
20869 assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
20870 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
20871 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
20874 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
20875 assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
20876 assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
20877 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
20879 assert!(
20880 f.run(&[b"TDIGEST.INFO", b"d"])
20881 .contains("Compression\r\n:500\r\n")
20882 );
20883 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
20885 assert!(
20886 f.run(&[b"TDIGEST.INFO", b"d"])
20887 .contains("Observations\r\n:12\r\n")
20888 );
20889 assert_eq!(
20891 f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
20892 "-ERR T-Digest: error parsing numkeys\r\n"
20893 );
20894 assert_eq!(
20895 f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
20896 "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
20897 );
20898 assert!(
20899 f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
20900 .contains("wrong number of arguments")
20901 );
20902 assert!(
20903 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
20904 .contains("wrong number of arguments")
20905 );
20906 assert_eq!(
20907 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
20908 "-ERR T-Digest: wrong keyword\r\n"
20909 );
20910 assert_eq!(
20913 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
20914 "-ERR T-Digest: key does not exist\r\n"
20915 );
20916 assert!(
20917 f.run(&[b"TDIGEST.INFO", b"d"])
20918 .contains("Observations\r\n:12\r\n")
20919 );
20920 assert_eq!(
20923 f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
20924 "-ERR T-Digest: key does not exist\r\n"
20925 );
20926 }
20927
20928 #[test]
20930 fn a_digest_answers_doubles_and_a_map_on_resp3() {
20931 let mut f = Fixture::new();
20932 f.run(&[b"HELLO", b"3"]);
20933 f.run(&[b"TDIGEST.CREATE", b"s"]);
20934 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
20935 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
20936 assert_eq!(
20937 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
20938 "*2\r\n,1\r\n,4\r\n"
20939 );
20940 assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
20941 assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
20943 assert_eq!(
20944 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
20945 "*1\r\n,-inf\r\n"
20946 );
20947 f.run(&[b"TDIGEST.CREATE", b"e"]);
20948 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
20949 assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
20951 assert_eq!(
20954 f.run(&[b"TDIGEST.INFO", b"s"]),
20955 "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
20956 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
20957 +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
20958 );
20959 }
20960
20961 #[test]
20964 fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
20965 let mut f = Fixture::new();
20966 f.run(&[b"SET", b"s", b"text"]);
20967 for cmd in [
20968 vec![&b"TDIGEST.CREATE"[..], b"s"],
20969 vec![&b"TDIGEST.RESET"[..], b"s"],
20970 vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
20971 vec![&b"TDIGEST.MIN"[..], b"s"],
20972 vec![&b"TDIGEST.MAX"[..], b"s"],
20973 vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
20974 vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
20975 vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
20976 vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
20977 vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
20978 vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
20979 vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
20980 vec![&b"TDIGEST.INFO"[..], b"s"],
20981 ] {
20982 let name = String::from_utf8_lossy(cmd[0]).into_owned();
20983 let reply = f.run(&cmd);
20984 assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
20985 }
20986 f.run(&[b"TDIGEST.CREATE", b"t"]);
20988 assert!(
20989 f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
20990 .starts_with("-WRONGTYPE")
20991 );
20992 assert!(
20993 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
20994 .starts_with("-WRONGTYPE")
20995 );
20996 assert_eq!(
20997 f.run(&[b"COPY", b"t", b"t2"]),
20998 "-ERR not supported for this module key\r\n"
20999 );
21000 assert_eq!(
21001 f.run(&[b"DUMP", b"t"]),
21002 "-ERR DUMP is not supported for this module key\r\n"
21003 );
21004 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
21005 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
21006 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
21007 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
21008 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
21009 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
21010 assert_eq!(
21013 f.run(&[b"TDIGEST.INFO", b"t3"]),
21014 "-ERR T-Digest: key does not exist\r\n"
21015 );
21016 assert_eq!(
21019 f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
21020 "-ERR T-Digest: key does not exist\r\n"
21021 );
21022 }
21023
21024 fn without_memory(reply: &str) -> String {
21034 let head = "+memoryUsage\r\n:";
21035 let at = reply.find(head).expect("every TS.INFO reports memory");
21036 let rest = &reply[at + head.len()..];
21037 let end = rest.find("\r\n").expect("and it is a whole number");
21038 format!("{}{}", &reply[..at + head.len()], &rest[end..])
21039 }
21040
21041 #[test]
21044 fn a_series_is_made_empty_and_reports_on_itself() {
21045 let mut f = Fixture::new();
21046 assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
21047 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
21048 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
21049 assert_eq!(
21053 without_memory(&f.run(&[b"TS.INFO", b"t"])),
21054 "*28\r\n\
21055 +totalSamples\r\n:0\r\n\
21056 +memoryUsage\r\n:\r\n\
21057 +firstTimestamp\r\n:0\r\n\
21058 +lastTimestamp\r\n:0\r\n\
21059 +retentionTime\r\n:0\r\n\
21060 +chunkCount\r\n:1\r\n\
21061 +chunkSize\r\n:4096\r\n\
21062 +chunkType\r\n+compressed\r\n\
21063 +duplicatePolicy\r\n+block\r\n\
21064 +labels\r\n*0\r\n\
21065 +sourceKey\r\n$-1\r\n\
21066 +rules\r\n*0\r\n\
21067 +ignoreMaxTimeDiff\r\n:0\r\n\
21068 +ignoreMaxValDiff\r\n$1\r\n0\r\n"
21069 );
21070 assert_eq!(
21073 f.run(&[b"TS.CREATE", b"t"]),
21074 "-ERR TSDB: key already exists\r\n"
21075 );
21076 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21077 assert_eq!(
21078 f.run(&[b"TS.CREATE", b"str"]),
21079 "-ERR TSDB: key already exists\r\n"
21080 );
21081 assert_eq!(
21084 f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
21085 "-ERR TSDB: Couldn't parse RETENTION\r\n"
21086 );
21087 let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
21093 assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
21094 assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
21095 assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
21096 assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
21097 assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
21098 assert_eq!(
21099 f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
21100 "-ERR TSDB: the key is not a TSDB key\r\n"
21101 );
21102 assert_eq!(
21104 f.run(&[b"TS.INFO", b"nope"]),
21105 "-ERR TSDB: the key does not exist\r\n"
21106 );
21107 assert_eq!(
21108 f.run(&[b"TS.GET", b"nope"]),
21109 "-ERR TSDB: the key does not exist\r\n"
21110 );
21111 assert_eq!(
21112 f.run(&[b"TS.ALTER", b"nope"]),
21113 "-ERR TSDB: the key does not exist\r\n"
21114 );
21115 assert_eq!(
21116 f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
21117 "-ERR TSDB: the key does not exist\r\n"
21118 );
21119 }
21120
21121 #[test]
21124 fn the_options_are_a_keyword_scan_and_not_a_grammar() {
21125 let mut f = Fixture::new();
21126 assert_eq!(
21127 f.run(&[
21128 b"TS.CREATE",
21129 b"t",
21130 b"RETENTION",
21131 b"5000",
21132 b"ENCODING",
21133 b"UNCOMPRESSED",
21134 b"CHUNK_SIZE",
21135 b"128",
21136 b"DUPLICATE_POLICY",
21137 b"LAST",
21138 b"IGNORE",
21139 b"10",
21140 b"0.5",
21141 b"LABELS",
21142 b"room",
21143 b"kitchen"
21144 ]),
21145 "+OK\r\n"
21146 );
21147 let info = f.run(&[b"TS.INFO", b"t"]);
21148 assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
21149 assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
21150 assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
21151 assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
21152 assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
21153 assert!(
21156 info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
21157 "{info}"
21158 );
21159 assert!(
21160 info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
21161 "{info}"
21162 );
21163
21164 assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
21166 assert_eq!(
21170 f.run(&[
21171 b"TS.CREATE",
21172 b"g",
21173 b"LABELS",
21174 b"a",
21175 b"b",
21176 b"RETENTION",
21177 b"5"
21178 ]),
21179 "+OK\r\n"
21180 );
21181 let greedy = f.run(&[b"TS.INFO", b"g"]);
21182 assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
21183 assert!(
21184 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"),
21185 "{greedy}"
21186 );
21187
21188 assert_eq!(
21190 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
21191 "-ERR TSDB: Couldn't parse LABELS\r\n"
21192 );
21193 assert_eq!(
21194 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
21195 "-ERR TSDB: Couldn't parse LABELS\r\n"
21196 );
21197 assert_eq!(
21198 f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
21199 "-ERR TSDB: Couldn't parse RETENTION\r\n"
21200 );
21201 assert_eq!(
21204 f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
21205 "-TSDB: Couldn't parse RETENTION\r\n"
21206 );
21207 assert_eq!(
21208 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
21209 "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
21210 );
21211 assert_eq!(
21212 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
21213 "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
21214 );
21215 assert_eq!(
21216 f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
21217 "-ERR TSDB: unknown ENCODING parameter\r\n"
21218 );
21219 assert!(
21222 f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
21223 .contains("wrong number of arguments for 'ts.create' command")
21224 );
21225 assert_eq!(
21226 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
21227 "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
21228 );
21229 assert_eq!(
21230 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
21231 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
21232 );
21233 assert_eq!(
21234 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
21235 "-ERR TSDB: Couldn't parse IGNORE\r\n"
21236 );
21237 assert_eq!(
21238 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
21239 "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
21240 );
21241 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
21242
21243 assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
21246 let after = f.run(&[b"TS.INFO", b"t"]);
21247 assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
21248 assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
21249 assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
21250 assert_eq!(
21251 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
21252 "-ERR TSDB: unknown ENCODING parameter\r\n"
21253 );
21254 assert_eq!(
21256 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
21257 "+OK\r\n"
21258 );
21259 assert!(
21260 f.run(&[b"TS.INFO", b"t"])
21261 .contains("+chunkType\r\n+uncompressed\r\n")
21262 );
21263 }
21264
21265 #[test]
21268 fn samples_land_where_they_are_put_and_the_newest_comes_back() {
21269 let mut f = Fixture::new();
21270 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
21271 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
21273 assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
21274 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
21277 assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
21278 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
21279 assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
21282 assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
21283
21284 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21287 assert_eq!(
21288 f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
21289 "-ERR TSDB: invalid value\r\n"
21290 );
21291 for bad in [
21295 &b".5"[..],
21296 b"1.",
21297 b"+1",
21298 b" 1",
21299 b"0x10",
21300 b"inf",
21301 b"1e400",
21302 b"--1",
21303 b"1e",
21304 ] {
21305 assert_eq!(
21306 f.run(&[b"TS.ADD", b"v", b"1", bad]),
21307 "-ERR TSDB: invalid value\r\n",
21308 "{}",
21309 String::from_utf8_lossy(bad)
21310 );
21311 }
21312 assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
21314
21315 assert_eq!(
21318 f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
21319 "-ERR TSDB: invalid timestamp\r\n"
21320 );
21321 assert_eq!(
21322 f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
21323 "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
21324 );
21325
21326 assert_eq!(
21329 f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
21330 "-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"
21331 );
21332 assert_eq!(
21333 f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
21334 ":300\r\n"
21335 );
21336 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
21337 assert_eq!(
21340 f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
21341 ":1\r\n"
21342 );
21343 assert_eq!(
21344 f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
21345 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
21346 );
21347
21348 assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
21351 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
21352 assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
21353 assert_eq!(
21354 f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
21355 "-ERR TSDB: Timestamp is older than retention\r\n"
21356 );
21357 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
21359 assert!(
21360 f.run(&[b"TS.INFO", b"r"])
21361 .contains("+totalSamples\r\n:1\r\n")
21362 );
21363
21364 assert_eq!(
21367 f.run(&[
21368 b"TS.CREATE",
21369 b"i",
21370 b"DUPLICATE_POLICY",
21371 b"LAST",
21372 b"IGNORE",
21373 b"10",
21374 b"0.5"
21375 ]),
21376 "+OK\r\n"
21377 );
21378 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
21379 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
21380 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
21381 }
21382
21383 #[test]
21386 fn a_madd_answers_each_triple_and_creates_nothing() {
21387 let mut f = Fixture::new();
21388 assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
21389 assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
21390 assert_eq!(
21391 f.run(&[
21392 b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
21393 ]),
21394 "*3\r\n:100\r\n:100\r\n:200\r\n"
21395 );
21396 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21399 assert_eq!(
21400 f.run(&[
21401 b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
21402 ]),
21403 "*3\r\n\
21404 -ERR TSDB: the key is not a TSDB key\r\n\
21405 -ERR TSDB: the key is not a TSDB key\r\n\
21406 :300\r\n"
21407 );
21408 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
21409 assert_eq!(
21411 f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
21412 "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
21413 );
21414 assert!(
21416 f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
21417 .contains("wrong number of arguments for 'ts.madd' command")
21418 );
21419 }
21420
21421 #[test]
21423 fn an_increment_walks_the_newest_value_up_and_down() {
21424 let mut f = Fixture::new();
21425 assert_eq!(
21426 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
21427 ":100\r\n"
21428 );
21429 assert_eq!(
21430 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
21431 ":100\r\n"
21432 );
21433 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
21436 assert_eq!(
21437 f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
21438 ":200\r\n"
21439 );
21440 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
21441 assert_eq!(
21444 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
21445 "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
21446 );
21447 assert_eq!(
21451 f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
21452 ":1\r\n"
21453 );
21454 assert_eq!(
21455 f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
21456 ":1\r\n"
21457 );
21458 assert_eq!(
21459 f.run(&[b"TS.INCRBY", b"t", b"nan"]),
21460 "-ERR TSDB: invalid increase/decrease value\r\n"
21461 );
21462 assert_eq!(
21463 f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
21464 "-ERR TSDB: invalid increase/decrease value\r\n"
21465 );
21466 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21469 assert_eq!(
21470 f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
21471 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21472 );
21473 assert_eq!(
21478 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
21479 "-ERR TSDB: invalid timestamp\r\n"
21480 );
21481 assert_eq!(
21484 f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
21485 format!(":{}\r\n", f.server.now_ms())
21486 );
21487 assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
21489 assert_eq!(
21490 f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
21491 "-ERR TSDB: cannot increment/decrement NaN value\r\n"
21492 );
21493 }
21494
21495 #[test]
21497 fn deleting_takes_out_a_span_and_answers_how_many_went() {
21498 let mut f = Fixture::new();
21499 for at in [b"100".as_slice(), b"200", b"300", b"400"] {
21500 f.run(&[b"TS.ADD", b"t", at, b"1"]);
21501 }
21502 assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
21503 assert!(
21504 f.run(&[b"TS.INFO", b"t"])
21505 .contains("+totalSamples\r\n:2\r\n")
21506 );
21507 assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
21509 assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
21511 let empty = f.run(&[b"TS.INFO", b"t"]);
21514 assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
21515 assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
21516 assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
21517 assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
21518 assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
21519 assert_eq!(
21521 f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
21522 "-ERR TSDB: wrong fromTimestamp\r\n"
21523 );
21524 assert_eq!(
21525 f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
21526 "-ERR TSDB: wrong toTimestamp\r\n"
21527 );
21528 assert_eq!(
21529 f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
21530 "-ERR TSDB: wrong fromTimestamp\r\n"
21531 );
21532 }
21533
21534 #[test]
21537 fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
21538 let mut f = Fixture::new();
21539 f.out = Out::new(Proto::Resp3);
21540 assert_eq!(
21541 f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
21542 "+OK\r\n"
21543 );
21544 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
21545 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
21547 assert_eq!(
21548 without_memory(&f.run(&[b"TS.INFO", b"t"])),
21549 "%14\r\n\
21550 +totalSamples\r\n:1\r\n\
21551 +memoryUsage\r\n:\r\n\
21552 +firstTimestamp\r\n:100\r\n\
21553 +lastTimestamp\r\n:100\r\n\
21554 +retentionTime\r\n:0\r\n\
21555 +chunkCount\r\n:1\r\n\
21556 +chunkSize\r\n:4096\r\n\
21557 +chunkType\r\n+compressed\r\n\
21558 +duplicatePolicy\r\n+block\r\n\
21559 +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
21560 +sourceKey\r\n_\r\n\
21561 +rules\r\n%0\r\n\
21562 +ignoreMaxTimeDiff\r\n:0\r\n\
21563 +ignoreMaxValDiff\r\n,0\r\n"
21564 );
21565 }
21566
21567 #[test]
21570 fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
21571 let mut f = Fixture::new();
21572 for (at, v) in [
21573 (b"100".as_slice(), b"1".as_slice()),
21574 (b"200", b"2"),
21575 (b"300", b"3"),
21576 (b"400", b"4"),
21577 ] {
21578 f.run(&[b"TS.ADD", b"t", at, v]);
21579 }
21580 assert_eq!(
21581 f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
21582 "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
21583 *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
21584 );
21585 assert_eq!(
21587 f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
21588 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
21589 );
21590 assert_eq!(
21593 f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
21594 "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
21595 );
21596 assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
21598 assert_eq!(
21600 f.run(&[
21601 b"TS.RANGE",
21602 b"t",
21603 b"-",
21604 b"+",
21605 b"FILTER_BY_VALUE",
21606 b"2",
21607 b"3"
21608 ]),
21609 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
21610 );
21611 assert_eq!(
21612 f.run(&[
21613 b"TS.RANGE",
21614 b"t",
21615 b"-",
21616 b"+",
21617 b"FILTER_BY_TS",
21618 b"100",
21619 b"400"
21620 ]),
21621 "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
21622 );
21623 assert_eq!(
21625 f.run(&[
21626 b"TS.RANGE",
21627 b"t",
21628 b"-",
21629 b"+",
21630 b"ZZZ",
21631 b"FILTER_BY_TS",
21632 b"400"
21633 ]),
21634 "*1\r\n*2\r\n:400\r\n+4\r\n"
21635 );
21636 assert_eq!(
21638 f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
21639 "*1\r\n*2\r\n:100\r\n+1\r\n"
21640 );
21641 }
21642
21643 #[test]
21645 fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
21646 let mut f = Fixture::new();
21647 for (at, v) in [
21648 (b"100".as_slice(), b"1".as_slice()),
21649 (b"200", b"2"),
21650 (b"300", b"3"),
21651 (b"400", b"4"),
21652 ] {
21653 f.run(&[b"TS.ADD", b"t", at, v]);
21654 }
21655 assert_eq!(
21656 f.run(&[
21657 b"TS.RANGE",
21658 b"t",
21659 b"-",
21660 b"+",
21661 b"AGGREGATION",
21662 b"avg",
21663 b"200"
21664 ]),
21665 "*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"
21666 );
21667 assert_eq!(
21670 f.run(&[
21671 b"TS.RANGE",
21672 b"t",
21673 b"-",
21674 b"+",
21675 b"AGGREGATION",
21676 b"min,max,count",
21677 b"200"
21678 ]),
21679 "*3\r\n\
21680 *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
21681 *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
21682 *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
21683 );
21684 assert_eq!(
21686 f.run(&[
21687 b"TS.RANGE",
21688 b"t",
21689 b"-",
21690 b"+",
21691 b"AGGREGATION",
21692 b"avg",
21693 b"200",
21694 b"BUCKETTIMESTAMP",
21695 b"+"
21696 ]),
21697 "*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"
21698 );
21699 assert_eq!(
21701 f.run(&[
21702 b"TS.RANGE",
21703 b"t",
21704 b"100",
21705 b"400",
21706 b"ALIGN",
21707 b"100",
21708 b"AGGREGATION",
21709 b"sum",
21710 b"200"
21711 ]),
21712 "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
21713 );
21714 assert_eq!(
21717 f.run(&[
21718 b"TS.RANGE",
21719 b"t",
21720 b"-",
21721 b"+",
21722 b"AGGREGATION",
21723 b"count",
21724 b"200"
21725 ]),
21726 "*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"
21727 );
21728 assert_eq!(
21729 f.run(&[
21730 b"TS.RANGE",
21731 b"t",
21732 b"-",
21733 b"+",
21734 b"AGGREGATION",
21735 b"count",
21736 b"200",
21737 b"COUNT",
21738 b"1"
21739 ]),
21740 "*1\r\n*2\r\n:0\r\n+1\r\n"
21741 );
21742 }
21743
21744 #[test]
21747 fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
21748 let mut f = Fixture::new();
21749 for (at, v) in [
21750 (b"0".as_slice(), b"1".as_slice()),
21751 (b"100", b"2"),
21752 (b"500", b"nan"),
21753 (b"600", b"3"),
21754 ] {
21755 f.run(&[b"TS.ADD", b"g", at, v]);
21756 }
21757 assert_eq!(
21760 f.run(&[
21761 b"TS.RANGE",
21762 b"g",
21763 b"-",
21764 b"+",
21765 b"AGGREGATION",
21766 b"avg",
21767 b"100"
21768 ]),
21769 "*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"
21770 );
21771 assert_eq!(
21773 f.run(&[
21774 b"TS.RANGE",
21775 b"g",
21776 b"-",
21777 b"+",
21778 b"AGGREGATION",
21779 b"sum",
21780 b"100",
21781 b"EMPTY"
21782 ]),
21783 "*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\
21784 *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\
21785 *2\r\n:600\r\n+3\r\n"
21786 );
21787 assert_eq!(
21792 f.run(&[
21793 b"TS.RANGE",
21794 b"g",
21795 b"-",
21796 b"+",
21797 b"AGGREGATION",
21798 b"last",
21799 b"100",
21800 b"EMPTY"
21801 ]),
21802 "*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\
21803 *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\
21804 *2\r\n:600\r\n+3\r\n"
21805 );
21806 assert_eq!(
21807 f.run(&[
21808 b"TS.REVRANGE",
21809 b"g",
21810 b"-",
21811 b"+",
21812 b"AGGREGATION",
21813 b"last",
21814 b"100",
21815 b"EMPTY"
21816 ]),
21817 "*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\
21818 *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\
21819 *2\r\n:0\r\n+1\r\n"
21820 );
21821 assert_eq!(
21824 f.run(&[
21825 b"TS.RANGE",
21826 b"g",
21827 b"500",
21828 b"600",
21829 b"AGGREGATION",
21830 b"last",
21831 b"100",
21832 b"EMPTY"
21833 ]),
21834 "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
21835 );
21836 }
21837
21838 #[test]
21841 fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
21842 let mut f = Fixture::new();
21843 f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
21844 f.run(&[b"SET", b"str", b"x"]);
21845 let cases: &[(&[&[u8]], &str)] = &[
21846 (
21847 &[b"TS.RANGE", b"t"],
21848 "-ERR wrong number of arguments for 'ts.range' command\r\n",
21849 ),
21850 (
21852 &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
21853 "-ERR TSDB: the key does not exist\r\n",
21854 ),
21855 (
21856 &[b"TS.RANGE", b"str", b"-", b"+"],
21857 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
21858 ),
21859 (
21860 &[b"TS.RANGE", b"t", b"abc", b"+"],
21861 "-ERR TSDB: wrong fromTimestamp\r\n",
21862 ),
21863 (
21864 &[b"TS.RANGE", b"t", b"-", b"abc"],
21865 "-ERR TSDB: wrong toTimestamp\r\n",
21866 ),
21867 (
21868 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
21869 "-ERR TSDB: COUNT argument is missing\r\n",
21870 ),
21871 (
21872 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
21873 "-ERR TSDB: Couldn't parse COUNT\r\n",
21874 ),
21875 (
21876 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
21877 "-ERR TSDB: Invalid COUNT value\r\n",
21878 ),
21879 (
21880 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
21881 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
21882 ),
21883 (
21884 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
21885 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
21886 ),
21887 (
21888 &[
21889 b"TS.RANGE",
21890 b"t",
21891 b"-",
21892 b"+",
21893 b"AGGREGATION",
21894 b"nope",
21895 b"100",
21896 ],
21897 "-ERR TSDB: Unknown aggregation type\r\n",
21898 ),
21899 (
21900 &[
21901 b"TS.RANGE",
21902 b"t",
21903 b"-",
21904 b"+",
21905 b"AGGREGATION",
21906 b"avg,,min",
21907 b"100",
21908 ],
21909 "-ERR TSDB: Empty aggregation type in list\r\n",
21910 ),
21911 (
21913 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
21914 "-ERR TSDB: Unknown aggregation type\r\n",
21915 ),
21916 (
21917 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
21918 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
21919 ),
21920 (
21921 &[
21922 b"TS.RANGE",
21923 b"t",
21924 b"-",
21925 b"+",
21926 b"AGGREGATION",
21927 b"avg",
21928 b"100",
21929 b"X",
21930 b"EMPTY",
21931 ],
21932 "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
21933 ),
21934 (
21935 &[
21936 b"TS.RANGE",
21937 b"t",
21938 b"-",
21939 b"+",
21940 b"AGGREGATION",
21941 b"avg",
21942 b"100",
21943 b"BUCKETTIMESTAMP",
21944 b"z",
21945 ],
21946 "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
21947 ),
21948 (
21949 &[
21950 b"TS.RANGE",
21951 b"t",
21952 b"-",
21953 b"+",
21954 b"AGGREGATION",
21955 b"avg",
21956 b"100",
21957 b"X",
21958 b"Y",
21959 b"BUCKETTIMESTAMP",
21960 b"-",
21961 ],
21962 "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
21963 AGGREGATION flag\r\n",
21964 ),
21965 (
21966 &[
21967 b"TS.RANGE",
21968 b"t",
21969 b"-",
21970 b"+",
21971 b"ALIGN",
21972 b"z",
21973 b"AGGREGATION",
21974 b"avg",
21975 b"100",
21976 ],
21977 "-ERR TSDB: unknown ALIGN parameter\r\n",
21978 ),
21979 (
21980 &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
21981 "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
21982 ),
21983 (
21984 &[
21985 b"TS.RANGE",
21986 b"t",
21987 b"-",
21988 b"+",
21989 b"ALIGN",
21990 b"-",
21991 b"AGGREGATION",
21992 b"avg",
21993 b"100",
21994 ],
21995 "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
21996 ),
21997 (
21998 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
21999 "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
22000 ),
22001 (
22002 &[
22003 b"TS.RANGE",
22004 b"t",
22005 b"-",
22006 b"+",
22007 b"FILTER_BY_VALUE",
22008 b"x",
22009 b"2",
22010 ],
22011 "-ERR TSDB: Couldn't parse MIN\r\n",
22012 ),
22013 (
22014 &[
22015 b"TS.RANGE",
22016 b"t",
22017 b"-",
22018 b"+",
22019 b"FILTER_BY_VALUE",
22020 b"1",
22021 b"y",
22022 ],
22023 "-ERR TSDB: Couldn't parse MAX\r\n",
22024 ),
22025 (
22026 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
22027 "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
22028 ),
22029 ];
22030 for (argv, want) in cases {
22031 let got = f.run(argv);
22032 assert_eq!(&got, want, "{:?}", argv.last());
22033 }
22034 f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
22038 f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
22039 assert_eq!(
22040 f.run(&[
22041 b"TS.RANGE",
22042 b"wide",
22043 b"-",
22044 b"+",
22045 b"AGGREGATION",
22046 b"avg",
22047 b"1",
22048 b"EMPTY"
22049 ]),
22050 "-ERR TSDB: the requested range holds too many empty buckets\r\n"
22051 );
22052 }
22053
22054 #[test]
22056 fn resp3_writes_a_read_value_as_a_double() {
22057 let mut f = Fixture::new();
22058 f.out = Out::new(Proto::Resp3);
22059 for (at, v) in [
22060 (b"0".as_slice(), b"1".as_slice()),
22061 (b"100", b"2"),
22062 (b"500", b"nan"),
22063 (b"600", b"3"),
22064 ] {
22065 f.run(&[b"TS.ADD", b"g", at, v]);
22066 }
22067 assert_eq!(
22068 f.run(&[
22069 b"TS.RANGE",
22070 b"g",
22071 b"0",
22072 b"100",
22073 b"AGGREGATION",
22074 b"avg,min",
22075 b"200"
22076 ]),
22077 "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
22078 );
22079 assert_eq!(
22080 f.run(&[
22081 b"TS.RANGE",
22082 b"g",
22083 b"500",
22084 b"600",
22085 b"AGGREGATION",
22086 b"last",
22087 b"100",
22088 b"EMPTY"
22089 ]),
22090 "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
22091 );
22092 }
22093
22094 fn joined() -> Fixture {
22097 let mut f = Fixture::new();
22098 f.run(&[b"TS.CREATE", b"z"]);
22099 for (at, v) in [
22100 (b"10".as_slice(), b"1".as_slice()),
22101 (b"20", b"2"),
22102 (b"40", b"4"),
22103 (b"50", b"5"),
22104 ] {
22105 f.run(&[b"TS.ADD", b"x", at, v]);
22106 }
22107 for (at, v) in [
22108 (b"20".as_slice(), b"20".as_slice()),
22109 (b"30", b"30"),
22110 (b"50", b"50"),
22111 (b"60", b"60"),
22112 ] {
22113 f.run(&[b"TS.ADD", b"y", at, v]);
22114 }
22115 f
22116 }
22117
22118 #[test]
22122 fn an_nrange_joins_its_keys_on_the_timestamp() {
22123 let mut f = joined();
22124 assert_eq!(
22126 f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
22127 "*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\
22128 *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
22129 );
22130 assert_eq!(
22132 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
22133 "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
22134 *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
22135 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
22136 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
22137 *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
22138 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22139 );
22140 assert_eq!(
22143 f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
22144 "*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"
22145 );
22146 assert_eq!(
22147 f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
22148 "*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"
22149 );
22150 assert_eq!(
22153 f.run(&[
22154 b"TS.NREVRANGE",
22155 b"2",
22156 b"x",
22157 b"y",
22158 b"-",
22159 b"+",
22160 b"COUNT",
22161 b"1"
22162 ]),
22163 "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22164 );
22165 assert_eq!(
22166 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
22167 "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
22168 );
22169 assert_eq!(
22171 f.run(&[
22172 b"TS.NRANGE",
22173 b"2",
22174 b"x",
22175 b"y",
22176 b"-",
22177 b"+",
22178 b"FILTER_BY_VALUE",
22179 b"2",
22180 b"30"
22181 ]),
22182 "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
22183 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
22184 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
22185 *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
22186 );
22187 }
22188
22189 #[test]
22193 fn an_nrange_aggregation_names_one_reduction_a_key() {
22194 let mut f = joined();
22195 assert_eq!(
22196 f.run(&[
22197 b"TS.NRANGE",
22198 b"2",
22199 b"x",
22200 b"y",
22201 b"-",
22202 b"+",
22203 b"AGGREGATION",
22204 b"sum",
22205 b"sum",
22206 b"20"
22207 ]),
22208 "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
22209 *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
22210 *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
22211 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22212 );
22213 assert_eq!(
22215 f.run(&[
22216 b"TS.NRANGE",
22217 b"2",
22218 b"x",
22219 b"y",
22220 b"-",
22221 b"+",
22222 b"AGGREGATION",
22223 b"sum,count",
22224 b"avg",
22225 b"20"
22226 ]),
22227 "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
22228 *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
22229 *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
22230 *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
22231 );
22232 assert_eq!(
22235 f.run(&[
22236 b"TS.NRANGE",
22237 b"2",
22238 b"x",
22239 b"y",
22240 b"-",
22241 b"+",
22242 b"AGGREGATION",
22243 b"avg",
22244 b"sum",
22245 b"100",
22246 b"EMPTY",
22247 b"BUCKETTIMESTAMP",
22248 b"end"
22249 ]),
22250 "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
22251 );
22252 assert_eq!(
22255 f.run(&[
22256 b"TS.NRANGE",
22257 b"2",
22258 b"x",
22259 b"y",
22260 b"-",
22261 b"+",
22262 b"AGGREGATION",
22263 b"avg",
22264 b"COUNT",
22265 b"100"
22266 ]),
22267 "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
22268 );
22269 }
22270
22271 #[test]
22274 fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
22275 let mut f = joined();
22276 f.run(&[b"SET", b"str", b"hi"]);
22277 let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
22278 let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
22279 must be equal to numkeys\r\n";
22280 let cases: &[(&[&[u8]], &str)] = &[
22281 (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
22282 (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
22283 (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
22284 (
22287 &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
22288 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
22289 ),
22290 (
22291 &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
22292 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
22293 ),
22294 (
22297 &[
22298 b"TS.NRANGE",
22299 b"2",
22300 b"x",
22301 b"y",
22302 b"abc",
22303 b"+",
22304 b"AGGREGATION",
22305 b"nope",
22306 b"sum",
22307 b"100",
22308 ],
22309 "-ERR TSDB: Unknown aggregation type\r\n",
22310 ),
22311 (
22312 &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
22313 "-ERR TSDB: wrong fromTimestamp\r\n",
22314 ),
22315 (
22316 &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
22317 "-ERR TSDB: wrong toTimestamp\r\n",
22318 ),
22319 (
22323 &[
22324 b"TS.NRANGE",
22325 b"2",
22326 b"x",
22327 b"y",
22328 b"-",
22329 b"+",
22330 b"AGGREGATION",
22331 b"avg",
22332 ],
22333 numkeys,
22334 ),
22335 (
22336 &[
22337 b"TS.NRANGE",
22338 b"2",
22339 b"x",
22340 b"y",
22341 b"-",
22342 b"+",
22343 b"AGGREGATION",
22344 b"100",
22345 b"sum",
22346 b"100",
22347 ],
22348 numkeys,
22349 ),
22350 (
22351 &[
22352 b"TS.NRANGE",
22353 b"2",
22354 b"x",
22355 b"y",
22356 b"-",
22357 b"+",
22358 b"AGGREGATION",
22359 b"avg",
22360 b"sum",
22361 b"sum",
22362 b"100",
22363 ],
22364 numkeys,
22365 ),
22366 (
22367 &[
22368 b"TS.NRANGE",
22369 b"2",
22370 b"x",
22371 b"y",
22372 b"-",
22373 b"+",
22374 b"AGGREGATION",
22375 b"avg",
22376 b"sum",
22377 b"abc",
22378 ],
22379 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22380 ),
22381 (
22382 &[
22383 b"TS.NRANGE",
22384 b"2",
22385 b"x",
22386 b"y",
22387 b"-",
22388 b"+",
22389 b"AGGREGATION",
22390 b"avg",
22391 b"sum",
22392 b"0",
22393 ],
22394 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
22395 ),
22396 (
22399 &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
22400 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22401 ),
22402 (
22403 &[
22404 b"TS.NRANGE",
22405 b"1",
22406 b"x",
22407 b"-",
22408 b"+",
22409 b"AGGREGATION",
22410 b"100",
22411 b"200",
22412 ],
22413 "-ERR TSDB: Unknown aggregation type\r\n",
22414 ),
22415 (
22417 &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
22418 "-ERR TSDB: the key does not exist\r\n",
22419 ),
22420 (
22421 &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
22422 "-ERR WRONGTYPE Operation against a key \
22423 holding the wrong kind of value\r\n",
22424 ),
22425 ];
22426 for (argv, want) in cases {
22427 let got = f.run(argv);
22428 assert_eq!(&got, want, "{argv:?}");
22429 }
22430 }
22431
22432 #[test]
22434 fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
22435 let mut f = joined();
22436 assert_eq!(
22437 f.run(&[b"TS.READ", b"x", b"-"]),
22438 "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
22439 *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
22440 );
22441 assert_eq!(
22444 f.run(&[b"TS.READ", b"x", b"+"]),
22445 "*1\r\n*2\r\n:50\r\n+5\r\n"
22446 );
22447 assert_eq!(
22448 f.run(&[b"TS.READ", b"x", b"25"]),
22449 "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
22450 );
22451 assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
22454 assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
22455 assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
22456 assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
22457 assert_eq!(
22461 f.run(&[b"TS.READ", b"x", b"abc"]),
22462 "-TSDB: invalid timestamp\r\n"
22463 );
22464 assert_eq!(
22465 f.run(&[b"TS.READ", b"x", b"-1"]),
22466 "-TSDB: invalid timestamp\r\n"
22467 );
22468 f.run(&[b"SET", b"str", b"hi"]);
22469 assert_eq!(
22470 f.run(&[b"TS.READ", b"str", b"-"]),
22471 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22472 );
22473 assert_eq!(
22476 f.run(&[b"TS.READ", b"x"]),
22477 "-ERR wrong number of arguments for 'ts.read' command\r\n"
22478 );
22479 assert_eq!(
22480 f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
22481 "-ERR wrong number of arguments for 'ts.read' command\r\n"
22482 );
22483 }
22484
22485 #[test]
22488 fn getkeys_reads_the_count_of_a_joined_read() {
22489 let mut f = Fixture::new();
22490 assert_eq!(
22491 f.run(&[
22492 b"COMMAND",
22493 b"GETKEYS",
22494 b"TS.NRANGE",
22495 b"2",
22496 b"a",
22497 b"b",
22498 b"-",
22499 b"+"
22500 ]),
22501 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
22502 );
22503 assert_eq!(
22504 f.run(&[
22505 b"COMMAND",
22506 b"GETKEYS",
22507 b"TS.NREVRANGE",
22508 b"1",
22509 b"a",
22510 b"-",
22511 b"+"
22512 ]),
22513 "*1\r\n$1\r\na\r\n"
22514 );
22515 for n in [b"0".as_slice(), b"9", b"abc"] {
22518 assert_eq!(
22519 f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
22520 "-ERR Invalid arguments specified for command\r\n"
22521 );
22522 }
22523 }
22524
22525 fn labelled() -> Fixture {
22527 let mut f = Fixture::new();
22528 f.run(&[
22529 b"TS.CREATE",
22530 b"a",
22531 b"LABELS",
22532 b"room",
22533 b"kitchen",
22534 b"x",
22535 b"1",
22536 ]);
22537 f.run(&[
22538 b"TS.CREATE",
22539 b"b",
22540 b"LABELS",
22541 b"room",
22542 b"bedroom",
22543 b"x",
22544 b"2",
22545 ]);
22546 f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
22547 f.run(&[b"TS.CREATE", b"d"]);
22548 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
22549 f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
22550 f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
22551 f
22552 }
22553
22554 #[test]
22557 fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
22558 let mut f = labelled();
22559 let cases: &[(&[&[u8]], &str)] = &[
22560 (
22563 &[b"TS.QUERYINDEX", b"room=kitchen"],
22564 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
22565 ),
22566 (
22567 &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
22568 "*1\r\n$1\r\na\r\n",
22569 ),
22570 (
22571 &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
22572 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
22573 ),
22574 (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
22577 (
22579 &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
22580 "*1\r\n$1\r\nc\r\n",
22581 ),
22582 (
22583 &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
22584 "*1\r\n$1\r\na\r\n",
22585 ),
22586 (
22587 &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
22588 "-ERR TSDB: please provide at least one matcher\r\n",
22589 ),
22590 (
22593 &[b"TS.QUERYINDEX", b"room==kitchen"],
22594 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
22595 ),
22596 (
22597 &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
22598 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
22599 ),
22600 (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
22601 (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
22604 (
22605 &[b"TS.QUERYINDEX", b"=(1)"],
22606 "-ERR TSDB: failed parsing labels\r\n",
22607 ),
22608 (
22609 &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
22610 "-ERR TSDB: failed parsing labels\r\n",
22611 ),
22612 (
22613 &[b"TS.QUERYINDEX", b"room=(kitchen"],
22614 "-ERR TSDB: failed parsing labels\r\n",
22615 ),
22616 (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
22617 (
22618 &[b"TS.QUERYINDEX", b"nonsense"],
22619 "-ERR TSDB: failed parsing labels\r\n",
22620 ),
22621 (
22623 &[b"TS.QUERYINDEX", b"room!=kitchen"],
22624 "-ERR TSDB: please provide at least one matcher\r\n",
22625 ),
22626 (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
22628 (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
22629 (
22630 &[b"TS.QUERYINDEX"],
22631 "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
22632 ),
22633 ];
22634 for (argv, want) in cases {
22635 let got = f.run(argv);
22636 assert_eq!(&got, want, "{:?}", argv.last());
22637 }
22638 }
22639
22640 #[test]
22642 fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
22643 let mut f = labelled();
22644 let cases: &[(&[&[u8]], &str)] = &[
22645 (
22646 &[b"TS.QUERYLABELS", b"LABELS"],
22647 "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
22648 ),
22649 (
22650 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
22651 "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
22652 ),
22653 (
22654 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
22655 "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
22656 ),
22657 (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
22660 (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
22661 (
22662 &[b"TS.QUERYLABELS", b"VALUES"],
22663 "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
22664 ),
22665 (
22666 &[b"TS.QUERYLABELS", b"ZZZ"],
22667 "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
22668 ),
22669 (
22670 &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
22671 "-ERR TSDB: unknown argument, expected FILTER\r\n",
22672 ),
22673 (
22674 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
22675 "-ERR TSDB: FILTER given with no filter expressions\r\n",
22676 ),
22677 (
22681 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
22682 "-ERR TSDB: please provide at least one matcher\r\n",
22683 ),
22684 (
22685 &[
22686 b"TS.QUERYLABELS",
22687 b"LABELS",
22688 b"FILTER",
22689 b"room=kitchen",
22690 b"x=",
22691 ],
22692 "*1\r\n$4\r\nroom\r\n",
22693 ),
22694 ];
22695 for (argv, want) in cases {
22696 let got = f.run(argv);
22697 assert_eq!(&got, want, "{:?}", argv.last());
22698 }
22699 }
22700
22701 #[test]
22704 fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
22705 let mut f = labelled();
22706 let cases: &[(&[&[u8]], &str)] = &[
22707 (
22710 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
22711 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
22712 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
22713 ),
22714 (
22715 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
22716 "*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\
22717 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
22718 *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",
22719 ),
22720 (
22722 &[
22723 b"TS.MGET",
22724 b"SELECTED_LABELS",
22725 b"x",
22726 b"FILTER",
22727 b"room=kitchen",
22728 ],
22729 "*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\
22730 *2\r\n:100\r\n+1.5\r\n\
22731 *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",
22732 ),
22733 (
22736 &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
22737 "*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",
22738 ),
22739 (
22740 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
22741 "*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\
22742 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
22743 ),
22744 (
22747 &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
22748 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
22749 ),
22750 (
22751 &[b"TS.MGET", b"a", b"b", b"c"],
22752 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
22753 ),
22754 (
22755 &[b"TS.MGET", b"FILTER"],
22756 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
22757 ),
22758 (
22761 &[
22762 b"TS.MGET",
22763 b"WITHLABELS",
22764 b"SELECTED_LABELS",
22765 b"x",
22766 b"FILTER",
22767 b"bad",
22768 ],
22769 "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
22770 ),
22771 (
22772 &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
22773 "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
22774 ),
22775 ];
22776 for (argv, want) in cases {
22777 let got = f.run(argv);
22778 assert_eq!(&got, want, "{:?}", argv.last());
22779 }
22780 }
22781
22782 #[test]
22785 fn resp3_writes_the_label_surface_as_sets_and_maps() {
22786 let mut f = labelled();
22787 f.out = Out::new(Proto::Resp3);
22788 let cases: &[(&[&[u8]], &str)] = &[
22789 (
22790 &[b"TS.QUERYINDEX", b"room=kitchen"],
22791 "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
22792 ),
22793 (
22794 &[b"TS.QUERYLABELS", b"LABELS"],
22795 "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
22796 ),
22797 (
22798 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
22799 "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
22800 ),
22801 (
22804 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
22805 "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
22806 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
22807 ),
22808 (
22809 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
22810 "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
22811 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
22812 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
22813 ),
22814 (
22815 &[
22816 b"TS.MGET",
22817 b"SELECTED_LABELS",
22818 b"x",
22819 b"FILTER",
22820 b"room=kitchen",
22821 ],
22822 "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
22823 *2\r\n:100\r\n,1.5\r\n\
22824 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
22825 ),
22826 (
22829 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
22830 "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
22831 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
22832 ),
22833 ];
22834 for (argv, want) in cases {
22835 let got = f.run(argv);
22836 assert_eq!(&got, want, "{:?}", argv.last());
22837 }
22838 }
22839
22840 fn spanned() -> Fixture {
22843 let mut f = labelled();
22844 f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
22845 f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
22846 f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
22847 f
22848 }
22849
22850 #[test]
22853 fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
22854 let mut f = spanned();
22855 let cases: &[(&[&[u8]], &str)] = &[
22856 (
22857 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
22858 "*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\
22859 *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",
22860 ),
22861 (
22864 &[
22865 b"TS.MREVRANGE",
22866 b"-",
22867 b"+",
22868 b"WITHLABELS",
22869 b"FILTER",
22870 b"room=kitchen",
22871 ],
22872 "*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\
22873 *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\
22874 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
22875 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
22876 ),
22877 (
22880 &[
22881 b"TS.MRANGE",
22882 b"-",
22883 b"+",
22884 b"SELECTED_LABELS",
22885 b"x",
22886 b"FILTER",
22887 b"room=kitchen",
22888 ],
22889 "*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\
22890 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
22891 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
22892 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
22893 ),
22894 (
22897 &[
22898 b"TS.MRANGE",
22899 b"-",
22900 b"+",
22901 b"FILTER",
22902 b"room=kitchen",
22903 b"GROUPBY",
22904 b"room",
22905 b"REDUCE",
22906 b"sum",
22907 ],
22908 "*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\
22909 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
22910 ),
22911 (
22914 &[
22915 b"TS.MRANGE",
22916 b"-",
22917 b"+",
22918 b"WITHLABELS",
22919 b"FILTER",
22920 b"room=kitchen",
22921 b"GROUPBY",
22922 b"room",
22923 b"REDUCE",
22924 b"max",
22925 ],
22926 "*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\
22927 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
22928 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
22929 *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",
22930 ),
22931 (
22933 &[
22934 b"TS.MREVRANGE",
22935 b"-",
22936 b"+",
22937 b"COUNT",
22938 b"1",
22939 b"FILTER",
22940 b"room=kitchen",
22941 b"GROUPBY",
22942 b"room",
22943 b"REDUCE",
22944 b"count",
22945 ],
22946 "*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",
22947 ),
22948 (
22950 &[
22951 b"TS.MRANGE",
22952 b"-",
22953 b"+",
22954 b"FILTER",
22955 b"room=kitchen",
22956 b"GROUPBY",
22957 b"nope",
22958 b"REDUCE",
22959 b"sum",
22960 ],
22961 "*0\r\n",
22962 ),
22963 (
22964 &[
22965 b"TS.MRANGE",
22966 b"-",
22967 b"+",
22968 b"AGGREGATION",
22969 b"sum,avg",
22970 b"100",
22971 b"FILTER",
22972 b"room=bedroom",
22973 ],
22974 "*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",
22975 ),
22976 (
22978 &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
22979 "-ERR TSDB: missing FILTER argument\r\n",
22980 ),
22981 (
22982 &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
22983 "-ERR TSDB: missing labels for filter argument\r\n",
22984 ),
22985 (
22986 &[
22987 b"TS.MRANGE",
22988 b"-",
22989 b"+",
22990 b"GROUPBY",
22991 b"room",
22992 b"REDUCE",
22993 b"sum",
22994 b"FILTER",
22995 b"room=kitchen",
22996 ],
22997 "-ERR TSDB: GROUPBY should always come after filter\r\n",
22998 ),
22999 (
23002 &[
23003 b"TS.MRANGE",
23004 b"-",
23005 b"+",
23006 b"FILTER",
23007 b"room=kitchen",
23008 b"GROUPBY",
23009 b"room",
23010 b"REDUCE",
23011 b"sum",
23012 b"x",
23013 ],
23014 "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
23015 ),
23016 (
23018 &[
23019 b"TS.MRANGE",
23020 b"-",
23021 b"+",
23022 b"FILTER",
23023 b"nope",
23024 b"GROUPBY",
23025 b"room",
23026 b"REDUCE",
23027 b"sum",
23028 b"x",
23029 ],
23030 "-ERR TSDB: failed parsing labels\r\n",
23031 ),
23032 (
23033 &[
23034 b"TS.MRANGE",
23035 b"-",
23036 b"+",
23037 b"FILTER",
23038 b"room=kitchen",
23039 b"GROUPBY",
23040 b"room",
23041 b"REDUCE",
23042 b"twa",
23043 ],
23044 "-ERR TSDB: Invalid reducer type\r\n",
23045 ),
23046 (
23047 &[
23048 b"TS.MRANGE",
23049 b"-",
23050 b"+",
23051 b"AGGREGATION",
23052 b"sum,avg",
23053 b"100",
23054 b"FILTER",
23055 b"room=kitchen",
23056 b"GROUPBY",
23057 b"room",
23058 b"REDUCE",
23059 b"sum",
23060 ],
23061 "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
23062 ),
23063 (
23066 &[
23067 b"TS.MRANGE",
23068 b"-",
23069 b"+",
23070 b"SELECTED_LABELS",
23071 b"COUNT",
23072 b"FILTER",
23073 b"room=kitchen",
23074 ],
23075 "-ERR TSDB: Couldn't parse COUNT\r\n",
23076 ),
23077 ];
23078 for (argv, want) in cases {
23079 let got = f.run(argv);
23080 assert_eq!(&got, want, "{argv:?}");
23081 }
23082 }
23083
23084 #[test]
23087 fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
23088 let mut f = spanned();
23089 f.out = Out::new(Proto::Resp3);
23090 let cases: &[(&[&[u8]], &str)] = &[
23091 (
23092 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
23093 "%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\
23094 *1\r\n*2\r\n:200\r\n,2\r\n",
23095 ),
23096 (
23099 &[
23100 b"TS.MRANGE",
23101 b"-",
23102 b"+",
23103 b"AGGREGATION",
23104 b"sum,avg",
23105 b"100",
23106 b"FILTER",
23107 b"room=bedroom",
23108 ],
23109 "%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\
23110 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
23111 ),
23112 (
23113 &[
23114 b"TS.MRANGE",
23115 b"-",
23116 b"+",
23117 b"FILTER",
23118 b"room=kitchen",
23119 b"GROUPBY",
23120 b"room",
23121 b"REDUCE",
23122 b"sum",
23123 ],
23124 "%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\
23125 $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\
23126 *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",
23127 ),
23128 (
23131 &[
23132 b"TS.MRANGE",
23133 b"-",
23134 b"+",
23135 b"WITHLABELS",
23136 b"FILTER",
23137 b"room=kitchen",
23138 b"GROUPBY",
23139 b"room",
23140 b"REDUCE",
23141 b"max",
23142 ],
23143 "%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\
23144 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
23145 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
23146 *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",
23147 ),
23148 (
23149 &[
23150 b"TS.MRANGE",
23151 b"-",
23152 b"+",
23153 b"FILTER",
23154 b"room=kitchen",
23155 b"GROUPBY",
23156 b"nope",
23157 b"REDUCE",
23158 b"sum",
23159 ],
23160 "%0\r\n",
23161 ),
23162 ];
23163 for (argv, want) in cases {
23164 let got = f.run(argv);
23165 assert_eq!(&got, want, "{argv:?}");
23166 }
23167 }
23168
23169 #[test]
23171 fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
23172 let mut f = Fixture::new();
23173 f.run(&[b"TS.CREATE", b"src"]);
23174 f.run(&[b"TS.CREATE", b"dst"]);
23175 f.run(&[b"SET", b"plain", b"v"]);
23176 let cases: &[(&[&[u8]], &str)] = &[
23177 (
23181 &[
23182 b"TS.CREATERULE",
23183 b"src",
23184 b"dst",
23185 b"AGGREGATION",
23186 b"nope",
23187 b"x",
23188 ],
23189 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
23190 ),
23191 (
23192 &[
23193 b"TS.CREATERULE",
23194 b"src",
23195 b"dst",
23196 b"AGGREGATION",
23197 b"nope",
23198 b"10",
23199 ],
23200 "-ERR TSDB: Unknown aggregation type\r\n",
23201 ),
23202 (
23203 &[
23204 b"TS.CREATERULE",
23205 b"src",
23206 b"dst",
23207 b"AGGREGATION",
23208 b"avg",
23209 b"0",
23210 ],
23211 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
23212 ),
23213 (
23214 &[
23215 b"TS.CREATERULE",
23216 b"src",
23217 b"dst",
23218 b"AGGREGATION",
23219 b"avg",
23220 b"10",
23221 b"x",
23222 ],
23223 "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
23224 ),
23225 (
23226 &[
23227 b"TS.CREATERULE",
23228 b"src",
23229 b"src",
23230 b"AGGREGATION",
23231 b"avg",
23232 b"10",
23233 ],
23234 "-ERR TSDB: the source key and destination key should be different\r\n",
23235 ),
23236 (
23240 &[
23241 b"TS.CREATERULE",
23242 b"nope",
23243 b"plain",
23244 b"AGGREGATION",
23245 b"avg",
23246 b"10",
23247 ],
23248 "-ERR TSDB: the key does not exist\r\n",
23249 ),
23250 (
23251 &[
23252 b"TS.CREATERULE",
23253 b"src",
23254 b"nope",
23255 b"AGGREGATION",
23256 b"avg",
23257 b"10",
23258 ],
23259 "-ERR TSDB: the key does not exist\r\n",
23260 ),
23261 (
23264 &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
23265 "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
23266 ),
23267 (
23268 &[
23269 b"TS.CREATERULE",
23270 b"src",
23271 b"dst",
23272 b"AGGREGATION",
23273 b"avg",
23274 b"10",
23275 ],
23276 "+OK\r\n",
23277 ),
23278 (
23281 &[
23282 b"TS.CREATERULE",
23283 b"src",
23284 b"dst",
23285 b"AGGREGATION",
23286 b"avg",
23287 b"10",
23288 ],
23289 "-ERR TSDB: the destination key already has a src rule\r\n",
23290 ),
23291 (
23294 &[
23295 b"TS.CREATERULE",
23296 b"dst",
23297 b"src",
23298 b"AGGREGATION",
23299 b"avg",
23300 b"10",
23301 ],
23302 "-ERR TSDB: the source key already has a source rule\r\n",
23303 ),
23304 (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
23305 (
23306 &[b"TS.DELETERULE", b"src", b"dst"],
23307 "-ERR TSDB: compaction rule does not exist\r\n",
23308 ),
23309 (
23313 &[b"TS.DELETERULE", b"src", b"nope"],
23314 "-ERR TSDB: compaction rule does not exist\r\n",
23315 ),
23316 (
23317 &[b"TS.DELETERULE", b"nope", b"dst"],
23318 "-ERR TSDB: the key does not exist\r\n",
23319 ),
23320 ];
23321 for (argv, want) in cases {
23322 let got = f.run(argv);
23323 assert_eq!(&got, want, "{argv:?}");
23324 }
23325 }
23326
23327 #[test]
23329 fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
23330 let mut f = Fixture::new();
23331 f.run(&[b"TS.CREATE", b"src"]);
23332 f.run(&[b"TS.CREATE", b"dst"]);
23333 f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
23336 f.run(&[
23337 b"TS.CREATERULE",
23338 b"src",
23339 b"dst",
23340 b"AGGREGATION",
23341 b"sum",
23342 b"100",
23343 ]);
23344 f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
23345 assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
23346 assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
23349 f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
23350 assert_eq!(
23351 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23352 "*1\r\n*2\r\n:0\r\n+2\r\n"
23353 );
23354 f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
23357 assert_eq!(
23358 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23359 "*1\r\n*2\r\n:0\r\n+11\r\n"
23360 );
23361 assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
23364 assert_eq!(
23365 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23366 "*1\r\n*2\r\n:0\r\n+8\r\n"
23367 );
23368 assert_eq!(
23369 f.run(&[b"TS.GET", b"dst", b"LATEST"]),
23370 "*2\r\n:100\r\n+4\r\n"
23371 );
23372 assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
23374 f.run(&[b"DEL", b"dst"]);
23375 assert_eq!(
23376 f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
23377 "-ERR TSDB: compaction rule does not exist\r\n"
23378 );
23379 }
23380
23381 #[test]
23384 fn xadd_ids_only_ever_go_up() {
23385 let mut f = Fixture::new();
23386 assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
23388 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
23390 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
23391 assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
23392 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
23393
23394 assert!(
23395 f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
23396 .contains("equal or smaller")
23397 );
23398 assert!(
23399 f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
23400 .contains("must be greater than 0-0")
23401 );
23402 assert!(
23403 f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
23404 .contains("Invalid stream ID")
23405 );
23406 assert!(
23409 f.run(&[b"XADD", b"s", b"*", b"a"])
23410 .contains("wrong number of arguments")
23411 );
23412
23413 assert_eq!(
23416 f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
23417 "$-1\r\n"
23418 );
23419 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
23420 assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
23421 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
23422 }
23423
23424 #[test]
23427 fn trimming_reads_its_options_the_way_redis_does() {
23428 let mut f = Fixture::new();
23429 for i in 1..=10u32 {
23430 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
23431 }
23432 assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
23433 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
23434 assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
23435 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
23436
23437 assert!(
23441 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
23442 .contains("not an integer")
23443 );
23444 assert!(
23445 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
23446 .contains("MAXLEN argument must be >= 0")
23447 );
23448 assert!(
23451 f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
23452 .contains("without specifying a trimming strategy")
23453 );
23454 assert!(
23455 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
23456 .contains("without the special ~ option")
23457 );
23458 assert!(
23459 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
23460 .contains("at the same time are not compatible")
23461 );
23462 assert!(
23464 f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
23465 .contains("syntax error")
23466 );
23467 assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
23468 }
23469
23470 #[test]
23472 fn xrange_looks_the_key_up_before_it_reads_the_count() {
23473 let mut f = Fixture::new();
23474 f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
23475 f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
23476
23477 assert_eq!(
23478 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
23479 "*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\
23480 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
23481 );
23482 assert_eq!(
23483 f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
23484 "*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"
23485 );
23486 assert_eq!(
23490 f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
23491 "*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\
23492 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
23493 );
23494 assert_eq!(
23495 f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
23496 "*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"
23497 );
23498 assert!(
23499 f.run(&[b"XRANGE", b"s", b"(-", b"+"])
23500 .contains("Invalid stream ID")
23501 );
23502
23503 assert_eq!(
23507 f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
23508 "*0\r\n"
23509 );
23510 assert_eq!(
23511 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
23512 "*-1\r\n"
23513 );
23514 f.run(&[b"SET", b"str", b"v"]);
23515 assert!(
23516 f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
23517 .starts_with("-WRONGTYPE")
23518 );
23519 assert_eq!(
23521 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
23522 "*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"
23523 );
23524 }
23525
23526 #[test]
23528 fn a_bad_id_late_in_the_list_stops_the_whole_command() {
23529 let mut f = Fixture::new();
23530 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
23531 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
23532 assert!(
23533 f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
23534 .contains("Invalid stream ID")
23535 );
23536 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
23537 assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
23538 assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
23539 assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
23540 assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
23541 }
23542
23543 #[test]
23545 fn xgroup_has_an_arity_per_subcommand() {
23546 let mut f = Fixture::new();
23547 assert!(
23548 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
23549 .contains("requires the key")
23550 );
23551 assert_eq!(
23552 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
23553 "+OK\r\n"
23554 );
23555 assert!(
23558 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
23559 .starts_with("-BUSYGROUP")
23560 );
23561 assert_eq!(
23562 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
23563 ":1\r\n"
23564 );
23565 assert_eq!(
23566 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
23567 ":0\r\n"
23568 );
23569 assert_eq!(
23570 f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
23571 ":0\r\n"
23572 );
23573
23574 let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
23576 assert!(
23577 short.contains("wrong number of arguments for 'xgroup|destroy' command"),
23578 "{short}"
23579 );
23580 let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
23582 assert!(
23583 odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
23584 "{odd}"
23585 );
23586 assert!(
23587 f.run(&[b"XGROUP", b"NOSUCH", b"s"])
23588 .contains("Try XGROUP HELP")
23589 );
23590
23591 assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
23592 assert!(
23593 f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
23594 .starts_with("-NOGROUP")
23595 );
23596 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
23597 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
23598 assert!(
23599 f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
23600 .contains("requires the key")
23601 );
23602 }
23603
23604 #[test]
23606 fn xreadgroup_hands_out_and_xack_takes_back() {
23607 let mut f = Fixture::new();
23608 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
23609 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
23610 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
23611
23612 let first = f.run(&[
23613 b"XREADGROUP",
23614 b"GROUP",
23615 b"g",
23616 b"c1",
23617 b"COUNT",
23618 b"1",
23619 b"STREAMS",
23620 b"s",
23621 b">",
23622 ]);
23623 assert_eq!(
23624 first,
23625 "*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"
23626 );
23627 assert_eq!(
23630 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
23631 "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
23632 );
23633 assert_eq!(
23634 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
23635 "*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"
23636 );
23637
23638 assert_eq!(
23639 f.run(&[b"XPENDING", b"s", b"g"]),
23640 "*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"
23641 );
23642 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
23643 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
23644 assert_eq!(
23646 f.run(&[b"XPENDING", b"s", b"g"]),
23647 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
23648 );
23649
23650 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
23653 f.run(&[b"XDEL", b"s", b"2-1"]);
23654 assert_eq!(
23655 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
23656 "*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"
23657 );
23658
23659 assert!(
23662 f.run(&[
23663 b"XREADGROUP",
23664 b"GROUP",
23665 b"nope",
23666 b"c",
23667 b"STREAMS",
23668 b"s",
23669 b"+"
23670 ])
23671 .starts_with("-NOGROUP")
23672 );
23673 assert!(
23674 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
23675 .contains("meaningless in the context of XREADGROUP")
23676 );
23677 assert!(
23678 f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
23679 .contains("only supported by XREADGROUP")
23680 );
23681 assert!(
23682 f.run(&[
23683 b"XREADGROUP",
23684 b"GROUP",
23685 b"g",
23686 b"c",
23687 b"STREAMS",
23688 b"s",
23689 b"a",
23690 b"b"
23691 ])
23692 .contains("Unbalanced 'xreadgroup' list of streams")
23693 );
23694 }
23695
23696 #[test]
23699 fn xread_with_no_block_writes_the_null_itself() {
23700 let mut f = Fixture::new();
23701 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
23702 assert_eq!(
23703 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
23704 "*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"
23705 );
23706 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
23709 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
23710 f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
23711 assert_eq!(
23712 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
23713 "*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"
23714 );
23715 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
23717 assert_eq!(
23719 f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
23720 "*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"
23721 );
23722 assert_eq!(
23725 f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
23726 "*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"
23727 );
23728 assert!(
23730 f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
23731 .contains("not an integer")
23732 );
23733 assert!(
23734 f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
23735 .contains("timeout is negative")
23736 );
23737 assert!(
23738 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
23739 .contains("Unbalanced 'xread' list of streams")
23740 );
23741 }
23742
23743 #[test]
23745 fn a_blocked_xread_wakes_on_the_next_entry() {
23746 let mut f = Fixture::new();
23747 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
23748 let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
23749 assert_eq!(flow, Flow::Block);
23750 assert!(reply.is_empty());
23751
23752 f.session = Session::new(8);
23757 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
23758 assert_eq!(flow, Flow::Block);
23759 assert_eq!(f.server.parked(), 2);
23760
23761 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
23762 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";
23763 for client in [7, 8] {
23764 let mut out = Out::new(Proto::Resp2);
23765 assert!(f.server.serve_waiter(client, 0, &mut out));
23766 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
23767 }
23768
23769 f.server.forget_waiters(7);
23772 f.server.forget_waiters(8);
23773 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
23774 assert_eq!(flow, Flow::Block);
23775 let mut out = Out::new(Proto::Resp2);
23776 assert!(!f.server.serve_waiter(8, 0, &mut out));
23777 assert!(out.as_slice().is_empty());
23778 assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
23779 assert_eq!(
23780 core::str::from_utf8(out.as_slice()).expect("ascii"),
23781 "*-1\r\n"
23782 );
23783 }
23784
23785 #[test]
23787 fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
23788 let mut f = Fixture::new();
23789 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
23790 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
23791 let (flow, _) = f.flow(&[
23792 b"XREADGROUP",
23793 b"GROUP",
23794 b"g",
23795 b"c",
23796 b"BLOCK",
23797 b"0",
23798 b"STREAMS",
23799 b"s",
23800 b">",
23801 ]);
23802 assert_eq!(flow, Flow::Block);
23803
23804 f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
23805 let mut out = Out::new(Proto::Resp2);
23806 assert!(f.server.serve_waiter(7, 0, &mut out));
23807 assert_eq!(
23810 core::str::from_utf8(out.as_slice()).expect("ascii"),
23811 "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
23812 );
23813 }
23814
23815 #[test]
23817 fn xclaim_reads_ids_until_one_will_not_parse() {
23818 let mut f = Fixture::new();
23819 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
23820 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
23821 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
23822 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
23823
23824 assert!(
23827 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
23828 .contains("Unrecognized XCLAIM option '-'")
23829 );
23830 assert_eq!(
23831 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
23832 "*1\r\n$3\r\n1-1\r\n"
23833 );
23834 f.run(&[b"XDEL", b"s", b"2-1"]);
23837 assert_eq!(
23838 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
23839 "*0\r\n"
23840 );
23841 assert!(
23842 f.run(&[b"XPENDING", b"s", b"g"])
23843 .starts_with("*4\r\n:1\r\n")
23844 );
23845 assert!(
23846 f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
23847 .starts_with("-NOGROUP")
23848 );
23849 assert!(
23850 f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
23851 .contains("Invalid min-idle-time argument for XCLAIM")
23852 );
23853 }
23854
23855 #[test]
23857 fn xautoclaim_reports_what_it_dropped() {
23858 let mut f = Fixture::new();
23859 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
23860 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
23861 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
23862 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
23863 f.run(&[b"XDEL", b"s", b"1-1"]);
23864
23865 assert_eq!(
23868 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
23869 "*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"
23870 );
23871 assert!(
23872 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
23873 .contains("COUNT must be > 0")
23874 );
23875 assert!(
23876 f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
23877 .starts_with("-NOGROUP")
23878 );
23879 }
23880
23881 #[test]
23883 fn xdelex_answers_one_integer_an_id() {
23884 let mut f = Fixture::new();
23885 for i in 1..=4 {
23886 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
23887 }
23888 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
23889 f.run(&[
23890 b"XREADGROUP",
23891 b"GROUP",
23892 b"g",
23893 b"c",
23894 b"COUNT",
23895 b"2",
23896 b"STREAMS",
23897 b"s",
23898 b">",
23899 ]);
23900
23901 assert_eq!(
23903 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
23904 "*2\r\n:1\r\n:-1\r\n"
23905 );
23906 assert!(
23909 f.run(&[b"XPENDING", b"s", b"g"])
23910 .starts_with("*4\r\n:2\r\n")
23911 );
23912 assert_eq!(
23914 f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
23915 "*1\r\n:1\r\n"
23916 );
23917 assert_eq!(
23919 f.run(&[b"XPENDING", b"s", b"g"]),
23920 "*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"
23921 );
23922
23923 assert_eq!(
23927 f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
23928 "*2\r\n:2\r\n:2\r\n"
23929 );
23930
23931 assert_eq!(
23933 f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
23934 "*2\r\n:-1\r\n:-1\r\n"
23935 );
23936 assert!(
23938 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
23939 .starts_with("-ERR Invalid stream ID")
23940 );
23941 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
23942
23943 assert!(
23944 f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
23945 .contains("Number of IDs must be a positive integer")
23946 );
23947 assert!(
23948 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
23949 .contains("The `numids` parameter must match the number of arguments")
23950 );
23951 assert!(
23954 f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
23955 .starts_with("-ERR syntax error")
23956 );
23957 assert!(
23958 f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
23959 .starts_with("-ERR syntax error")
23960 );
23961 f.run(&[b"SET", b"str", b"v"]);
23963 assert!(
23964 f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
23965 .starts_with("-WRONGTYPE")
23966 );
23967 }
23968
23969 #[test]
23971 fn xackdel_reports_what_the_group_was_holding() {
23972 let mut f = Fixture::new();
23973 for i in 1..=3 {
23974 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
23975 }
23976 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
23977 f.run(&[
23978 b"XREADGROUP",
23979 b"GROUP",
23980 b"g",
23981 b"c",
23982 b"COUNT",
23983 b"1",
23984 b"STREAMS",
23985 b"s",
23986 b">",
23987 ]);
23988
23989 assert_eq!(
23993 f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
23994 "*2\r\n:1\r\n:-1\r\n"
23995 );
23996 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
23997
23998 assert_eq!(
24000 f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
24001 "*1\r\n:-1\r\n"
24002 );
24003 assert_eq!(
24004 f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
24005 "*1\r\n:-1\r\n"
24006 );
24007
24008 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
24011 f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
24012 assert_eq!(
24013 f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
24014 "*1\r\n:2\r\n"
24015 );
24016 assert_eq!(
24017 f.run(&[b"XPENDING", b"s", b"g"]),
24018 "*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"
24019 );
24020 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
24021 }
24022
24023 #[test]
24025 fn xnack_releases_an_entry_for_the_next_claim() {
24026 let mut f = Fixture::new();
24027 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24028 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24029 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24030 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24031 f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
24034
24035 assert_eq!(
24036 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
24037 ":1\r\n"
24038 );
24039 assert_eq!(
24043 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
24044 "*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"
24045 );
24046 assert_eq!(
24048 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
24049 "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
24050 );
24051 assert_eq!(
24053 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
24054 "*-1\r\n"
24055 );
24056 assert_eq!(
24058 f.run(&[
24059 b"XAUTOCLAIM",
24060 b"s",
24061 b"g",
24062 b"c2",
24063 b"99999999",
24064 b"-",
24065 b"JUSTID"
24066 ]),
24067 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
24068 );
24069
24070 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24074 assert!(
24075 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24076 .contains(":-1\r\n:1\r\n")
24077 );
24078 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24080 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24081 assert!(
24082 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24083 .contains(":-1\r\n:0\r\n")
24084 );
24085 f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
24087 assert!(
24088 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24089 .contains(":9223372036854775807\r\n")
24090 );
24091 f.run(&[
24092 b"XNACK",
24093 b"s",
24094 b"g",
24095 b"FATAL",
24096 b"IDS",
24097 b"1",
24098 b"1-1",
24099 b"RETRYCOUNT",
24100 b"3",
24101 ]);
24102 assert!(
24103 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24104 .contains(":-1\r\n:3\r\n")
24105 );
24106
24107 f.run(&[b"XACK", b"s", b"g", b"2-1"]);
24111 assert_eq!(
24112 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
24113 ":0\r\n"
24114 );
24115 assert_eq!(
24116 f.run(&[
24117 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
24118 ]),
24119 ":1\r\n"
24120 );
24121 assert!(
24122 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24123 .contains(":-1\r\n:0\r\n")
24124 );
24125 assert_eq!(
24127 f.run(&[
24128 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
24129 ]),
24130 ":0\r\n"
24131 );
24132
24133 assert_eq!(
24136 f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
24137 "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
24138 );
24139 assert!(
24140 f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
24141 .starts_with("-ERR")
24142 );
24143 assert!(
24145 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
24146 .contains("numids must be a positive integer")
24147 );
24148 assert!(
24149 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
24150 .contains("number of IDs doesn't match numids")
24151 );
24152 assert!(
24155 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
24156 .contains("Unrecognized XNACK option '2-1'")
24157 );
24158 }
24159
24160 #[test]
24162 fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
24163 let mut f = Fixture::new();
24164 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24165 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24166 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24167 f.run(&[
24168 b"XREADGROUP",
24169 b"GROUP",
24170 b"g",
24171 b"c1",
24172 b"COUNT",
24173 b"1",
24174 b"STREAMS",
24175 b"s",
24176 b">",
24177 ]);
24178
24179 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
24180 assert!(info.starts_with("*20\r\n"), "{info}");
24183 assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
24184 assert!(
24185 info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
24186 "{info}"
24187 );
24188 assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
24189 assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
24190
24191 let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
24192 assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
24193 assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
24194 assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
24195 assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
24196
24197 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
24201 let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
24202 assert!(consumers.starts_with("*2\r\n"), "{consumers}");
24203 assert!(
24204 consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
24205 "{consumers}"
24206 );
24207 let c1 = consumers.find("c1").unwrap();
24209 let c2 = consumers.find("c2").unwrap();
24210 assert!(c1 < c2, "{consumers}");
24211
24212 let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
24213 assert!(full.starts_with("*18\r\n"), "{full}");
24214 assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
24215 assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
24216
24217 assert!(
24218 f.run(&[b"XINFO", b"STREAM", b"missing"])
24219 .contains("no such key")
24220 );
24221 assert!(
24222 f.run(&[b"XINFO", b"GROUPS", b"missing"])
24223 .contains("no such key")
24224 );
24225 assert!(
24226 f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
24227 .starts_with("-NOGROUP")
24228 );
24229 assert!(
24230 f.run(&[b"XINFO", b"NOSUCH", b"s"])
24231 .contains("Try XINFO HELP")
24232 );
24233 assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
24234 assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
24235 }
24236
24237 #[test]
24239 fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
24240 let mut f = Fixture::new();
24241 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24242 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24243 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24244
24245 let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
24246 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");
24247 assert_eq!(
24248 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
24249 "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
24250 );
24251 assert_eq!(
24253 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
24254 "*0\r\n"
24255 );
24256 assert_eq!(
24257 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
24258 list
24259 );
24260 assert!(
24262 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
24263 .contains("syntax error")
24264 );
24265 assert!(
24266 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
24267 .contains("syntax error")
24268 );
24269 assert_eq!(
24270 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
24271 "*0\r\n"
24272 );
24273 assert!(
24274 f.run(&[b"XPENDING", b"missing", b"g"])
24275 .starts_with("-NOGROUP")
24276 );
24277 }
24278
24279 #[test]
24281 fn xsetid_will_not_go_below_what_is_there() {
24282 let mut f = Fixture::new();
24283 f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
24284 assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
24285 assert_eq!(
24286 f.run(&[
24287 b"XSETID",
24288 b"s",
24289 b"10-1",
24290 b"ENTRIESADDED",
24291 b"7",
24292 b"MAXDELETEDID",
24293 b"9-1"
24294 ]),
24295 "+OK\r\n"
24296 );
24297 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
24298 assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
24299 assert!(
24300 info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
24301 "{info}"
24302 );
24303
24304 assert!(
24305 f.run(&[b"XSETID", b"s", b"1-1"])
24306 .contains("smaller than the target stream top item")
24307 );
24308 assert!(
24309 f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
24310 .contains("entries_added must be positive")
24311 );
24312 assert!(
24313 f.run(&[b"XSETID", b"missing", b"1-1"])
24314 .contains("no such key")
24315 );
24316 }
24317
24318 #[test]
24320 fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
24321 let mut f = Fixture::new();
24322 f.run(&[b"HELLO", b"3"]);
24323 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24324 assert_eq!(
24327 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
24328 "%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"
24329 );
24330 assert_eq!(
24333 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
24334 "*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"
24335 );
24336 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
24337 }
24338
24339 struct Mem {
24345 blobs: Vec<Vec<u8>>,
24346 }
24347
24348 impl yo_kv::cold::Blocks for Mem {
24349 fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
24350 self.blobs.push(bytes.to_vec());
24351 Ok(yo_common::Addr::new(
24352 yo_common::Space::Log,
24353 (self.blobs.len() - 1) as u64,
24354 ))
24355 }
24356
24357 fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
24358 self.blobs
24359 .get(at.offset() as usize)
24360 .map(Vec::as_slice)
24361 .ok_or_else(|| {
24362 yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
24363 })
24364 }
24365
24366 fn bytes(&self) -> u64 {
24367 self.blobs.iter().map(|b| b.len() as u64).sum()
24368 }
24369 }
24370
24371 fn filled(attach: bool) -> (Fixture, usize) {
24384 let mut f = Fixture::new();
24385 if attach {
24386 f.server
24387 .striped(0)
24388 .hold_stripe(0)
24389 .attach(Box::new(Mem { blobs: Vec::new() }));
24390 }
24391 let val = vec![b'v'; 256];
24392 for i in 0..24000u32 {
24393 let k = format!("key:{i:08}");
24394 f.run(&[b"SET", k.as_bytes(), &val]);
24395 }
24396 let full = f.server.memory_bytes();
24397 assert!(full > 3 * 1024 * 1024, "the arena is several segments");
24398 (f, full)
24399 }
24400
24401 fn press(f: &mut Fixture, limit: usize) {
24407 let val = vec![b'v'; 256];
24408 for i in 0..3000u32 {
24409 let k = format!("new:{i:08}");
24410 assert_eq!(
24411 f.run(&[b"SET", k.as_bytes(), &val]),
24412 "+OK\r\n",
24413 "write {i} was refused"
24414 );
24415 f.server.refresh_memory();
24416 if f.server.memory_bytes() <= limit {
24417 return;
24418 }
24419 }
24420 panic!(
24421 "it never got under: {} against {limit}",
24422 f.server.memory_bytes()
24423 );
24424 }
24425
24426 #[test]
24427 fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
24428 let mut f = Fixture::new();
24429 assert_eq!(
24430 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
24431 "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
24432 "no limit is the default"
24433 );
24434 for (typed, bytes) in [
24437 (&b"0"[..], "0"),
24438 (b"1024", "1024"),
24439 (b"1k", "1000"),
24440 (b"1gb", "1073741824"),
24441 (b"-1", "-1"),
24442 ] {
24443 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
24444 assert_eq!(
24445 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
24446 format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
24447 "set {}",
24448 String::from_utf8_lossy(typed)
24449 );
24450 }
24451 for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
24452 assert_eq!(
24453 f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
24454 "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
24455 "refused {}",
24456 String::from_utf8_lossy(bad)
24457 );
24458 }
24459 let info = f.run(&[b"INFO", b"memory"]);
24461 assert!(info.contains("maxstore:-1"), "{info}");
24462 assert!(info.contains("yo_memory_regime:evict"), "{info}");
24463 assert!(info.contains("yo_store_bytes:0"), "{info}");
24464 }
24465
24466 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24467 #[test]
24468 fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
24469 let (mut f, full) = filled(true);
24473 let keys = f.run(&[b"DBSIZE"]);
24474 assert!(
24475 f.run(&[b"INFO", b"memory"])
24476 .contains("yo_memory_regime:migrate"),
24477 "a database with somewhere to put values migrates"
24478 );
24479
24480 let limit = full - 2 * 1024 * 1024;
24481 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
24482 f.run(&[
24483 b"CONFIG",
24484 b"SET",
24485 b"maxmemory",
24486 limit.to_string().as_bytes(),
24487 ]);
24488 press(&mut f, limit);
24489
24490 assert!(
24491 f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
24492 "nothing was thrown away"
24493 );
24494 let after: usize = f.run(&[b"DBSIZE"])[1..]
24495 .trim_end()
24496 .parse()
24497 .expect("a count");
24498 let before: usize = keys[1..].trim_end().parse().expect("a count");
24499 assert!(after > before, "the keys that came in are all still here");
24500 assert!(
24501 f.server.store_bytes() > 0,
24502 "and what came out of memory went to the file"
24503 );
24504 let val = format!("$256\r\n{}\r\n", "v".repeat(256));
24507 assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
24508 assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
24509 }
24510
24511 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24512 #[test]
24513 fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
24514 let (mut f, full) = filled(true);
24518 f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
24519 assert!(
24520 f.run(&[b"INFO", b"memory"])
24521 .contains("yo_memory_regime:evict"),
24522 "nothing may go to the file"
24523 );
24524
24525 let limit = full - 2 * 1024 * 1024;
24526 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
24527 f.run(&[
24528 b"CONFIG",
24529 b"SET",
24530 b"maxmemory",
24531 limit.to_string().as_bytes(),
24532 ]);
24533 press(&mut f, limit);
24534
24535 assert!(
24536 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
24537 "keys were thrown away, which is what was asked for"
24538 );
24539 assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
24540 }
24541
24542 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24543 #[test]
24544 fn a_full_file_goes_back_to_evicting() {
24545 let (mut f, full) = filled(true);
24549 f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
24550 let limit = full - 2 * 1024 * 1024;
24551 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
24552 f.run(&[
24553 b"CONFIG",
24554 b"SET",
24555 b"maxmemory",
24556 limit.to_string().as_bytes(),
24557 ]);
24558 press(&mut f, limit);
24559
24560 assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
24561 assert!(
24562 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
24563 "and then it started evicting"
24564 );
24565 assert!(
24566 f.run(&[b"INFO", b"memory"])
24567 .contains("yo_memory_regime:evict"),
24568 "and it says so"
24569 );
24570 }
24571 #[test]
24587 fn the_string_group_answers_the_same_however_many_stripes_there_are() {
24588 let script: &[&[&[u8]]] = &[
24589 &[b"SET", b"k1", b"v1"],
24592 &[b"SET", b"k2", b"v2"],
24593 &[b"GET", b"k1"],
24594 &[b"GET", b"nothing"],
24595 &[b"GETSET", b"k1", b"v1b"],
24596 &[b"SETNX", b"k1", b"no"],
24597 &[b"SETNX", b"k3", b"yes"],
24598 &[b"APPEND", b"k3", b"!"],
24599 &[b"STRLEN", b"k3"],
24600 &[b"SETRANGE", b"k3", b"1", b"XY"],
24601 &[b"GETRANGE", b"k3", b"0", b"-1"],
24602 &[b"INCR", b"n1"],
24603 &[b"INCRBY", b"n1", b"41"],
24604 &[b"DECRBY", b"n1", b"2"],
24605 &[b"INCRBYFLOAT", b"f1", b"1.5"],
24606 &[b"SETEX", b"e1", b"100", b"v"],
24607 &[b"PSETEX", b"e2", b"100000", b"v"],
24608 &[b"GETEX", b"e1", b"PERSIST"],
24609 &[b"GETDEL", b"k2"],
24610 &[b"GET", b"k2"],
24611 &[b"DIGEST", b"k1"],
24612 &[b"DELEX", b"k3"],
24613 &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
24616 &[b"MGET", b"a", b"b", b"c", b"missing"],
24617 &[b"MSETNX", b"d", b"4", b"e", b"5"],
24618 &[b"MSETNX", b"e", b"6", b"f", b"7"],
24619 &[b"MGET", b"d", b"e", b"f"],
24620 &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
24621 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
24622 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
24623 &[b"MGET", b"g", b"h"],
24624 &[b"SET", b"s1", b"ohmytext"],
24625 &[b"SET", b"s2", b"mynewtext"],
24626 &[b"LCS", b"s1", b"s2"],
24627 &[b"LCS", b"s1", b"s2", b"LEN"],
24628 &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
24629 &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
24630 &[b"LCS", b"s1", b"gone"],
24631 &[b"MSET", b"odd"],
24633 &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
24634 &[b"MGET"],
24635 ];
24636
24637 let mut one = Fixture::new();
24638 let mut many = Fixture::striped(8);
24639 for parts in script {
24640 let a = one.run(parts);
24641 let b = many.run(parts);
24642 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24643 }
24644 }
24645
24646 #[test]
24651 fn a_striped_database_spreads_the_keys_it_is_given() {
24652 let mut f = Fixture::striped(8);
24653 for i in 0..256 {
24654 let key = format!("key:{i}");
24655 f.run(&[b"SET", key.as_bytes(), b"v"]);
24656 }
24657 assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
24658 }
24659
24660 #[test]
24663 fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
24664 let mut one = Fixture::new();
24665 let mut many = Fixture::striped(8);
24666 for f in [&mut one, &mut many] {
24667 f.run(&[b"SET", b"str", b"v"]);
24668 f.server
24674 .striped(0)
24675 .hold(b"list")
24676 .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
24677 .expect("a new list");
24678 }
24679 assert_eq!(
24680 one.run(&[b"MGET", b"str", b"list", b"gone"]),
24681 many.run(&[b"MGET", b"str", b"list", b"gone"])
24682 );
24683 }
24684
24685 #[test]
24693 fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
24694 let script: &[&[&[u8]]] = &[
24695 &[b"SET", b"k1", b"v1"],
24696 &[b"SET", b"k2", b"v2"],
24697 &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
24698 &[b"TYPE", b"k1"],
24699 &[b"TYPE", b"gone"],
24700 &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
24701 &[b"EXPIRE", b"k1", b"100"],
24702 &[b"TTL", b"k1"],
24703 &[b"EXPIRE", b"k1", b"200", b"NX"],
24704 &[b"PERSIST", b"k1"],
24705 &[b"TTL", b"k1"],
24706 &[b"PEXPIREAT", b"k2", b"1900000000000"],
24707 &[b"EXPIRETIME", b"k2"],
24708 &[b"PEXPIRETIME", b"k2"],
24709 &[b"PERSIST", b"k2"],
24710 &[b"OBJECT", b"ENCODING", b"k1"],
24711 &[b"OBJECT", b"REFCOUNT", b"k1"],
24712 &[b"OBJECT", b"IDLETIME", b"k1"],
24713 &[b"OBJECT", b"FREQ", b"k1"],
24714 &[b"OBJECT", b"ENCODING", b"gone"],
24715 &[b"OBJECT", b"HELP"],
24716 &[b"RENAME", b"k1", b"k9"],
24717 &[b"GET", b"k9"],
24718 &[b"RENAME", b"gone", b"x"],
24719 &[b"RENAMENX", b"k9", b"k2"],
24720 &[b"RENAMENX", b"k9", b"k8"],
24721 &[b"GET", b"k8"],
24722 &[b"COPY", b"k8", b"c1"],
24723 &[b"COPY", b"k8", b"c1"],
24724 &[b"COPY", b"k8", b"c1", b"REPLACE"],
24725 &[b"COPY", b"k8", b"k8"],
24726 &[b"COPY", b"gone", b"c2"],
24727 &[b"COPY", b"k8", b"k8", b"DB", b"1"],
24728 &[b"COPY", b"k8", b"c9", b"DB", b"9"],
24729 &[b"MOVE", b"c1", b"1"],
24730 &[b"MOVE", b"c1", b"1"],
24731 &[b"MOVE", b"k8", b"0"],
24732 &[b"DEL", b"k2", b"gone"],
24733 &[b"UNLINK", b"k8", b"k8"],
24734 &[b"DBSIZE"],
24735 ];
24736
24737 let mut one = Fixture::new();
24738 let mut many = Fixture::striped(8);
24739 for parts in script {
24740 let a = one.run(parts);
24741 let b = many.run(parts);
24742 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24743 }
24744
24745 for f in [&mut one, &mut many] {
24751 f.run(&[b"SET", b"d1", b"payload"]);
24752 let payload = f
24753 .server
24754 .striped(0)
24755 .hold(b"d1")
24756 .dump(b"d1")
24757 .expect("a key that is there");
24758 assert!(
24759 f.run(&[b"DUMP", b"d1"])
24760 .starts_with(&format!("${}", payload.len())),
24761 "a payload of the length the store gave"
24762 );
24763 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
24764 assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
24765 assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
24766 assert_eq!(
24767 f.run(&[b"RESTORE", b"d2", b"0", &payload]),
24768 "-BUSYKEY Target key name already exists.\r\n"
24769 );
24770 assert_eq!(
24771 f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
24772 "-ERR DUMP payload version or checksum are wrong\r\n"
24773 );
24774 }
24775 }
24776
24777 #[test]
24784 fn a_scan_of_a_striped_database_walks_all_of_it() {
24785 let n = if cfg!(miri) { 80 } else { 500 };
24789 let mut f = Fixture::striped(8);
24790 for i in 0..n {
24791 let key = format!("key:{i}");
24792 f.run(&[b"SET", key.as_bytes(), b"v"]);
24793 }
24794
24795 let mut seen = Vec::new();
24796 let mut cursor = "0".to_owned();
24797 let mut calls = 0;
24798 loop {
24799 let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
24800 let (next, keys) = scan_reply(&reply);
24801 seen.extend(keys);
24802 cursor = next;
24803 calls += 1;
24804 assert!(calls < 5_000, "a scan that will not finish");
24805 if cursor == "0" {
24806 break;
24807 }
24808 }
24809 seen.sort();
24810 assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
24811 assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
24812
24813 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
24817 let (_, keys) = scan_reply(&reply);
24818 assert_eq!(keys.len(), 10, "key:40 through key:49");
24819 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
24820 let (_, keys) = scan_reply(&reply);
24821 assert!(keys.is_empty(), "nothing here is a list");
24822 }
24823
24824 #[test]
24830 fn a_random_key_can_come_from_any_stripe() {
24831 let mut f = Fixture::striped(8);
24832 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
24833 for i in 0..200 {
24834 let key = format!("key:{i}");
24835 f.run(&[b"SET", key.as_bytes(), b"v"]);
24836 }
24837 let mut homes = std::collections::HashSet::new();
24838 for _ in 0..200 {
24839 let got = f.run(&[b"RANDOMKEY"]);
24840 let key = got.split("\r\n").nth(1).expect("a key").to_owned();
24841 assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
24842 homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
24843 }
24844 assert_eq!(homes.len(), 8, "some stripe was never drawn from");
24845 }
24846
24847 fn apart(f: &mut Fixture, src: &str) -> String {
24851 let home = f.server.striped(0).stripe_of(src.as_bytes());
24852 for i in 0..1_000 {
24853 let dst = format!("dst:{i}");
24854 if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
24855 return dst;
24856 }
24857 }
24858 panic!("eight stripes and a thousand keys all landed in one place");
24859 }
24860
24861 #[test]
24864 fn a_rename_across_stripes_takes_everything_with_it() {
24865 let mut f = Fixture::striped(8);
24866 let dst = apart(&mut f, "src");
24867 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
24868
24869 f.run(&[b"SET", src, b"v"]);
24870 f.run(&[b"EXPIRE", src, b"100"]);
24871 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
24872 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
24873 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
24874 assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
24875
24876 f.server
24881 .striped(0)
24882 .hold(src)
24883 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
24884 .expect("a new list");
24885 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
24886 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
24887 assert_eq!(
24888 f.server.striped(0).hold(dst).llen(dst).expect("a list"),
24889 2,
24890 "the members are on the stripe the key moved to"
24891 );
24892
24893 f.run(&[b"SET", src, b"v"]);
24896 assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
24897 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
24898 assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
24899 }
24900
24901 #[test]
24903 fn a_copy_across_stripes_leaves_the_source_where_it_was() {
24904 let mut f = Fixture::striped(8);
24905 let dst = apart(&mut f, "src");
24906 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
24907
24908 f.run(&[b"SET", src, b"v"]);
24909 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
24910 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
24911 assert_eq!(
24912 f.run(&[b"COPY", src, dst]),
24913 ":0\r\n",
24914 "the destination is taken"
24915 );
24916 f.run(&[b"SET", src, b"w"]);
24917 assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
24918 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
24919
24920 f.run(&[b"DEL", src, dst]);
24924 f.server
24925 .striped(0)
24926 .hold(src)
24927 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
24928 .expect("a new list");
24929 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
24930 f.server
24931 .striped(0)
24932 .hold(src)
24933 .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
24934 .expect("a list that is there");
24935 assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
24936 assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
24937 }
24938
24939 #[test]
24947 fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
24948 let script: &[&[&[u8]]] = &[
24949 &[b"SET", b"k1", b"foobar"],
24950 &[b"SETBIT", b"b1", b"7", b"1"],
24951 &[b"SETBIT", b"b1", b"7", b"0"],
24952 &[b"GETBIT", b"k1", b"6"],
24953 &[b"GETBIT", b"k1", b"100"],
24954 &[b"BITCOUNT", b"k1"],
24955 &[b"BITCOUNT", b"k1", b"0", b"0"],
24956 &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
24957 &[b"BITPOS", b"k1", b"1"],
24958 &[b"BITPOS", b"k1", b"0", b"2"],
24959 &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
24960 &[
24961 b"BITFIELD",
24962 b"bf",
24963 b"SET",
24964 b"u8",
24965 b"0",
24966 b"255",
24967 b"GET",
24968 b"u8",
24969 b"0",
24970 ],
24971 &[
24972 b"BITFIELD",
24973 b"bf",
24974 b"OVERFLOW",
24975 b"SAT",
24976 b"INCRBY",
24977 b"u8",
24978 b"0",
24979 b"10",
24980 ],
24981 &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
24982 &[b"SET", b"s1", b"abc"],
24985 &[b"SET", b"s2", b"abd"],
24986 &[b"SET", b"s3", b"a"],
24987 &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
24988 &[b"GET", b"d1"],
24989 &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
24990 &[b"GET", b"d2"],
24991 &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
24992 &[b"STRLEN", b"d3"],
24993 &[b"BITOP", b"NOT", b"d4", b"s1"],
24994 &[b"STRLEN", b"d4"],
24995 &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
24996 &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
24997 &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
24998 &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
24999 &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
25002 &[b"EXISTS", b"d1"],
25003 &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
25004 &[b"GET", b"d9"],
25005 &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
25009 &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
25010 &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
25011 &[b"BITOP", b"DIFF", b"d1", b"s1"],
25012 &[b"BITOP", b"NOPE", b"d1", b"s1"],
25013 &[b"BITCOUNT", b"list"],
25014 &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
25015 ];
25016
25017 let mut one = Fixture::new();
25018 let mut many = Fixture::striped(8);
25019 for f in [&mut one, &mut many] {
25020 plant_list(f, b"list");
25021 }
25022 for parts in script {
25023 let a = one.run(parts);
25024 let b = many.run(parts);
25025 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25026 }
25027 }
25028
25029 fn plant_list(f: &mut Fixture, key: &[u8]) {
25035 f.server
25036 .striped(0)
25037 .hold(key)
25038 .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
25039 .expect("a new list");
25040 }
25041
25042 #[test]
25049 fn a_bitop_across_stripes_reads_every_source() {
25050 let mut f = Fixture::striped(8);
25051 let other = apart(&mut f, "src");
25052 let (src, far) = (b"src".as_slice(), other.as_bytes());
25053 assert_ne!(
25054 f.server.striped(0).stripe_of(src),
25055 f.server.striped(0).stripe_of(far),
25056 "the two keys are the point of the test"
25057 );
25058
25059 f.run(&[b"SET", src, b"abc"]);
25060 f.run(&[b"SET", far, b"abd"]);
25061 assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
25062 assert_eq!(
25063 f.run(&[b"GET", far]),
25064 "$3\r\nab`\r\n",
25065 "a destination that is also a source"
25066 );
25067 f.run(&[b"SET", far, b"abd"]);
25068 assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
25069 assert_eq!(
25070 f.run(&[b"GET", src]),
25071 "$3\r\n\0\0\x07\r\n",
25072 "and the other way round"
25073 );
25074
25075 f.run(&[b"SET", src, b"abc"]);
25079 f.run(&[b"DEL", far]);
25080 assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
25081 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
25082 f.run(&[b"SET", src, b"abc"]);
25083 f.run(&[b"DEL", far]);
25084 plant_list(&mut f, far);
25085 assert_eq!(
25086 f.run(&[b"BITOP", b"OR", b"out", src, far]),
25087 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25088 );
25089 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
25090 }
25091
25092 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
25098 #[test]
25099 fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
25100 let script: &[&[&[u8]]] = &[
25101 &[b"PFADD", b"h1", b"a", b"b", b"c"],
25102 &[b"PFADD", b"h1", b"a"],
25103 &[b"PFADD", b"h2"],
25104 &[b"PFADD", b"h2", b"c", b"d", b"e"],
25105 &[b"PFCOUNT", b"h1"],
25106 &[b"PFCOUNT", b"h2"],
25107 &[b"PFCOUNT", b"missing"],
25108 &[b"PFCOUNT", b"h1", b"h2"],
25110 &[b"PFCOUNT", b"h1", b"missing"],
25111 &[b"PFMERGE", b"m", b"h1", b"h2"],
25112 &[b"PFCOUNT", b"m"],
25113 &[b"STRLEN", b"m"],
25114 &[b"PFMERGE", b"m"],
25115 &[b"PFCOUNT", b"m"],
25116 &[b"PFMERGE", b"m2", b"missing"],
25117 &[b"PFCOUNT", b"m2"],
25118 &[b"PFDEBUG", b"ENCODING", b"h1"],
25121 &[b"PFDEBUG", b"DECODE", b"h1"],
25122 &[b"PFDEBUG", b"TODENSE", b"h1"],
25123 &[b"PFDEBUG", b"ENCODING", b"h1"],
25124 &[b"PFDEBUG", b"TODENSE", b"h1"],
25125 &[b"PFCOUNT", b"h1", b"h2"],
25126 &[b"PFSELFTEST"],
25127 &[b"SET", b"plain", b"not a sketch at all"],
25129 &[b"PFADD", b"plain", b"a"],
25130 &[b"PFCOUNT", b"plain"],
25131 &[b"PFCOUNT", b"h1", b"plain"],
25132 &[b"PFMERGE", b"plain", b"h1"],
25133 &[b"PFMERGE", b"m", b"plain"],
25134 &[b"PFDEBUG", b"ENCODING", b"gone"],
25135 &[b"PFDEBUG", b"NOPE", b"h1"],
25136 ];
25137
25138 let mut one = Fixture::new();
25139 let mut many = Fixture::striped(8);
25140 for parts in script {
25141 let a = one.run(parts);
25142 let b = many.run(parts);
25143 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25144 }
25145 }
25146
25147 #[test]
25156 fn the_set_group_answers_the_same_however_many_stripes_there_are() {
25157 const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
25158 let script: &[&[&[u8]]] = &[
25159 &[b"SADD", b"s1", b"a", b"b", b"c"],
25160 &[b"SADD", b"s1", b"a"],
25161 &[b"SADD", b"s2", b"b", b"c", b"d"],
25162 &[b"SADD", b"ints", b"1", b"2", b"3"],
25163 &[b"SCARD", b"s1"],
25164 &[b"SISMEMBER", b"s1", b"a"],
25165 &[b"SISMEMBER", b"s1", b"z"],
25166 &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
25167 &[b"SMEMBERS", b"s1"],
25168 &[b"SREM", b"s1", b"c"],
25169 &[b"SADD", b"s1", b"c"],
25170 &[b"SSCAN", b"s1", b"0"],
25171 &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
25172 &[b"SADD", b"one", b"m"],
25175 &[b"SRANDMEMBER", b"one"],
25176 &[b"SRANDMEMBER", b"one", b"-3"],
25177 &[b"SRANDMEMBER", b"gone"],
25178 &[b"SPOP", b"one"],
25179 &[b"SPOP", b"one"],
25180 &[b"SPOP", b"gone", b"2"],
25181 &[b"SMOVE", b"s1", b"s2", b"a"],
25183 &[b"SMOVE", b"s1", b"s2", b"zzz"],
25184 &[b"SMOVE", b"gone", b"s2", b"a"],
25185 &[b"SMEMBERS", b"s1"],
25186 &[b"SMEMBERS", b"s2"],
25187 &[b"SINTER", b"s1", b"s2"],
25189 &[b"SUNION", b"s1", b"s2"],
25190 &[b"SDIFF", b"s2", b"s1"],
25191 &[b"SINTER", b"s1", b"gone"],
25192 &[b"SUNION", b"s1", b"gone"],
25193 &[b"SDIFF", b"gone", b"s1"],
25194 &[b"SINTER", b"ints", b"s1"],
25195 &[b"SINTERCARD", b"2", b"s1", b"s2"],
25196 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
25197 &[b"SUNIONCARD", b"2", b"s1", b"s2"],
25198 &[b"SDIFFCARD", b"2", b"s2", b"s1"],
25199 &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
25200 &[b"SMEMBERS", b"d1"],
25201 &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
25202 &[b"SCARD", b"d2"],
25203 &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
25204 &[b"SCARD", b"d3"],
25205 &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
25208 &[b"EXISTS", b"d4"],
25209 &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
25211 &[b"SCARD", b"s2"],
25212 &[b"SET", b"str", b"v"],
25214 &[b"SADD", b"str", b"a"],
25215 &[b"SINTER", b"s1", b"str"],
25216 &[b"SINTERSTORE", b"d5", b"s1", b"str"],
25217 &[b"EXISTS", b"d5"],
25218 &[b"SMOVE", b"str", b"s2", b"a"],
25219 &[b"SMOVE", b"s1", b"str", b"b"],
25220 &[b"SMOVE", b"gone", b"str", b"b"],
25221 &[b"SINTERCARD", b"0", b"s1"],
25222 &[b"SINTERCARD", b"3", b"s1", b"s2"],
25223 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
25224 &[b"SPOP", b"s1", b"-1"],
25225 ];
25226
25227 let mut one = Fixture::new();
25228 let mut many = Fixture::striped(8);
25229 for parts in script {
25230 let a = one.run(parts);
25231 let b = many.run(parts);
25232 let name = String::from_utf8_lossy(parts[0]).to_uppercase();
25233 if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
25234 assert_eq!(sorted(&a), sorted(&b), "{name}");
25235 } else {
25236 assert_eq!(a, b, "{name}");
25237 }
25238 }
25239 }
25240
25241 #[test]
25243 fn a_set_operation_across_stripes_reads_every_set() {
25244 let mut f = Fixture::striped(8);
25245 let second = apart(&mut f, "s1");
25246 let third = apart(&mut f, &second);
25247 let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
25248
25249 f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
25250 f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
25251 assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
25252 assert_eq!(
25253 sorted(&f.run(&[b"SUNION", s1, s2])),
25254 ["a", "b", "c", "d"],
25255 "a union of two stripes is both of them"
25256 );
25257 assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
25258 assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
25259 assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
25260 assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
25261
25262 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
25264 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
25265 assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
25266 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
25267 assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
25268 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
25269
25270 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
25273 assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
25274 f.run(&[b"SET", s3, b"v"]);
25275 assert_eq!(
25276 f.run(&[b"SINTER", s1, s3]),
25277 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25278 );
25279 assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
25280 }
25281
25282 #[test]
25284 fn a_move_across_stripes_takes_the_member_with_it() {
25285 let mut f = Fixture::striped(8);
25286 let other = apart(&mut f, "src");
25287 let (src, dst) = (b"src".as_slice(), other.as_bytes());
25288
25289 f.run(&[b"SADD", src, b"a", b"b"]);
25290 f.run(&[b"SADD", dst, b"c"]);
25291 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
25292 assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
25293 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
25294 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
25295
25296 f.run(&[b"DEL", dst]);
25299 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
25300 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
25301 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
25302
25303 f.run(&[b"SET", dst, b"v"]);
25307 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
25308 f.run(&[b"SADD", src, b"b"]);
25309 assert_eq!(
25310 f.run(&[b"SMOVE", src, dst, b"b"]),
25311 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25312 );
25313 }
25314
25315 #[test]
25317 fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
25318 let mut f = Fixture::striped(8);
25319 let other = apart(&mut f, "src");
25320 let (src, far) = (b"src".as_slice(), other.as_bytes());
25321
25322 for i in 0..150 {
25323 let ele = format!("e:{i}");
25324 f.run(&[b"PFADD", src, ele.as_bytes()]);
25325 }
25326 for i in 150..200 {
25327 let ele = format!("e:{i}");
25328 f.run(&[b"PFADD", far, ele.as_bytes()]);
25329 }
25330 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
25333 assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
25334 assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
25335
25336 let dest = apart(&mut f, &other);
25339 assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
25340 assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
25341 assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
25342 assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
25343 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
25344 }
25345
25346 #[test]
25353 fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
25354 let script: &[&[&[u8]]] = &[
25355 &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
25356 &[b"ZADD", b"z1", b"NX", b"9", b"a"],
25357 &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
25358 &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
25359 &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
25360 &[b"ZINCRBY", b"z1", b"1.5", b"b"],
25361 &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
25362 &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
25363 &[b"ZADD", b"one", b"1", b"m"],
25364 &[b"ZCARD", b"z1"],
25365 &[b"ZCARD", b"gone"],
25366 &[b"ZSCORE", b"z1", b"a"],
25367 &[b"ZSCORE", b"z1", b"zz"],
25368 &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
25369 &[b"ZRANK", b"z1", b"c"],
25370 &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
25371 &[b"ZREVRANK", b"z1", b"c"],
25372 &[b"ZRANK", b"z1", b"gone"],
25373 &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
25374 &[b"ZCOUNT", b"z1", b"(1", b"3"],
25375 &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
25376 &[b"ZRANGE", b"z1", b"0", b"-1"],
25378 &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
25379 &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
25380 &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
25381 &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
25382 &[b"ZREVRANGE", b"z1", b"0", b"-1"],
25383 &[
25384 b"ZRANGEBYSCORE",
25385 b"z1",
25386 b"-inf",
25387 b"+inf",
25388 b"LIMIT",
25389 b"1",
25390 b"1",
25391 ],
25392 &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
25393 &[b"ZSCAN", b"z1", b"0"],
25394 &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
25395 &[b"ZRANDMEMBER", b"one"],
25398 &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
25399 &[b"ZRANDMEMBER", b"gone"],
25400 &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
25402 &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
25403 &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
25404 &[b"EXISTS", b"d0"],
25405 &[b"ZUNION", b"2", b"z1", b"z2"],
25407 &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
25408 &[
25409 b"ZUNION",
25410 b"2",
25411 b"z1",
25412 b"z2",
25413 b"WEIGHTS",
25414 b"2",
25415 b"3",
25416 b"AGGREGATE",
25417 b"MAX",
25418 b"WITHSCORES",
25419 ],
25420 &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
25421 &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
25422 &[b"ZDIFF", b"2", b"gone", b"z1"],
25423 &[b"ZINTERCARD", b"2", b"z1", b"z2"],
25424 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
25425 &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
25426 &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
25427 &[
25428 b"ZINTERSTORE",
25429 b"d2",
25430 b"2",
25431 b"z1",
25432 b"z2",
25433 b"AGGREGATE",
25434 b"MIN",
25435 ],
25436 &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
25437 &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
25438 &[b"ZCARD", b"d3"],
25439 &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
25442 &[b"EXISTS", b"d4"],
25443 &[b"SADD", b"plain", b"a", b"x"],
25446 &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
25447 &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
25448 &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
25450 &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
25451 &[b"ZREM", b"d5", b"x", b"nothere"],
25453 &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
25454 &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
25455 &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
25456 &[b"ZPOPMIN", b"z1"],
25457 &[b"ZPOPMAX", b"z1", b"2"],
25458 &[b"ZPOPMIN", b"gone"],
25459 &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
25460 &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
25461 &[b"SET", b"str", b"v"],
25463 &[b"ZADD", b"str", b"1", b"a"],
25464 &[b"ZSCORE", b"str", b"a"],
25465 &[b"ZADD", b"z1", b"nan", b"a"],
25466 &[b"ZUNION", b"2", b"z1", b"str"],
25467 &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
25468 &[b"EXISTS", b"d6"],
25469 &[b"ZINTERCARD", b"0", b"z1"],
25470 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
25471 &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
25472 &[b"ZMPOP", b"1", b"str", b"MIN"],
25473 &[b"ZPOPMIN", b"z1", b"-1"],
25474 ];
25475
25476 let mut one = Fixture::new();
25477 let mut many = Fixture::striped(8);
25478 for parts in script {
25479 let a = one.run(parts);
25480 let b = many.run(parts);
25481 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25482 }
25483 }
25484
25485 #[test]
25487 fn a_sorted_set_operation_across_stripes_reads_every_input() {
25488 let mut f = Fixture::striped(8);
25489 let second = apart(&mut f, "z1");
25490 let third = apart(&mut f, &second);
25491 let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
25492
25493 f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
25494 f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
25495 assert_eq!(
25498 f.run(&[b"ZUNION", b"2", z1, z2]),
25499 "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
25500 );
25501 assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
25502 assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
25503 assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
25504 assert_eq!(
25505 f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
25506 ":1\r\n"
25507 );
25508
25509 assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
25512 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
25513 assert_eq!(
25514 f.run(&[
25515 b"ZUNIONSTORE",
25516 z3,
25517 b"2",
25518 z1,
25519 z2,
25520 b"WEIGHTS",
25521 b"2",
25522 b"3",
25523 b"AGGREGATE",
25524 b"MAX"
25525 ]),
25526 ":3\r\n"
25527 );
25528 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
25529 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
25530 assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
25531 assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
25532 assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
25533
25534 let popped = format!(
25537 "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
25538 second.len()
25539 );
25540 assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
25541 f.run(&[b"ZADD", z2, b"3", b"b"]);
25542
25543 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
25546 assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
25547 f.run(&[b"SET", z3, b"v"]);
25548 assert_eq!(
25549 f.run(&[b"ZUNION", b"2", z1, z3]),
25550 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25551 );
25552 assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
25553
25554 assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
25558 assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
25559 assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
25560 }
25561
25562 #[test]
25564 fn a_range_store_across_stripes_copies_the_window() {
25565 let mut f = Fixture::striped(8);
25566 let other = apart(&mut f, "src");
25567 let third = apart(&mut f, &other);
25568 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
25569
25570 f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
25571 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
25572 assert_eq!(
25573 f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
25574 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
25575 );
25576 assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
25577
25578 assert_eq!(
25581 f.run(&[
25582 b"ZRANGESTORE",
25583 dst,
25584 src,
25585 b"+inf",
25586 b"-inf",
25587 b"BYSCORE",
25588 b"REV",
25589 b"LIMIT",
25590 b"0",
25591 b"2"
25592 ]),
25593 ":2\r\n"
25594 );
25595 assert_eq!(
25596 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
25597 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
25598 );
25599
25600 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
25603 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
25604 f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
25605 f.run(&[b"SET", plain, b"v"]);
25606 assert_eq!(
25607 f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
25608 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25609 );
25610 assert_eq!(
25611 f.run(&[b"ZCARD", dst]),
25612 ":3\r\n",
25613 "and left the destination"
25614 );
25615 }
25616
25617 #[test]
25624 fn the_list_group_answers_the_same_however_many_stripes_there_are() {
25625 let script: &[&[&[u8]]] = &[
25626 &[b"RPUSH", b"l1", b"a", b"b", b"c"],
25627 &[b"LPUSH", b"l1", b"z"],
25628 &[b"RPUSHX", b"l1", b"d"],
25629 &[b"LPUSHX", b"gone", b"x"],
25630 &[b"RPUSHX", b"gone", b"x"],
25631 &[b"LLEN", b"l1"],
25632 &[b"LLEN", b"gone"],
25633 &[b"LRANGE", b"l1", b"0", b"-1"],
25634 &[b"LRANGE", b"l1", b"1", b"2"],
25635 &[b"LRANGE", b"l1", b"5", b"9"],
25636 &[b"LINDEX", b"l1", b"0"],
25637 &[b"LINDEX", b"l1", b"-1"],
25638 &[b"LINDEX", b"l1", b"99"],
25639 &[b"LSET", b"l1", b"0", b"y"],
25640 &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
25641 &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
25642 &[b"LPOS", b"l1", b"b"],
25643 &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
25644 &[b"LPOS", b"l1", b"nothere"],
25645 &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
25646 &[b"LREM", b"l1", b"1", b"aa"],
25647 &[b"LTRIM", b"l1", b"0", b"3"],
25648 &[b"LRANGE", b"l1", b"0", b"-1"],
25649 &[b"LPOP", b"l1"],
25650 &[b"RPOP", b"l1"],
25651 &[b"LPOP", b"l1", b"2"],
25652 &[b"LPOP", b"gone"],
25653 &[b"LPOP", b"gone", b"2"],
25654 &[b"EXISTS", b"l1"],
25655 &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
25658 &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
25659 &[b"RPOPLPUSH", b"src", b"dst"],
25660 &[b"LRANGE", b"dst", b"0", b"-1"],
25661 &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
25662 &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
25663 &[
25664 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
25665 ],
25666 &[
25667 b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
25668 ],
25669 &[b"LRANGE", b"dst", b"0", b"-1"],
25670 &[
25671 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
25672 ],
25673 &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
25674 &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
25675 &[b"LMPOP", b"1", b"gone", b"LEFT"],
25676 &[b"RPUSH", b"q", b"a", b"b", b"c"],
25679 &[b"BLPOP", b"gone", b"q", b"0"],
25680 &[b"BRPOP", b"q", b"0"],
25681 &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
25682 &[b"RPUSH", b"q", b"x", b"y", b"z"],
25683 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
25684 &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
25685 &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
25686 &[b"BLPOP", b"q", b"0"],
25687 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
25688 &[b"SET", b"plain", b"v"],
25690 &[b"LPUSH", b"plain", b"a"],
25691 &[b"LLEN", b"plain"],
25692 &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
25693 &[b"LRANGE", b"dst", b"0", b"-1"],
25694 &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
25695 &[b"LSET", b"gone", b"0", b"v"],
25696 &[b"LSET", b"dst", b"99", b"v"],
25697 &[b"LPOP", b"dst", b"-1"],
25698 &[b"LMPOP", b"0", b"dst", b"LEFT"],
25699 &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
25700 ];
25701
25702 let mut one = Fixture::new();
25703 let mut many = Fixture::striped(8);
25704 for parts in script {
25705 let a = one.run(parts);
25706 let b = many.run(parts);
25707 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25708 }
25709 }
25710
25711 #[test]
25713 fn a_list_move_across_stripes_takes_the_elements_with_it() {
25714 let mut f = Fixture::striped(8);
25715 let other = apart(&mut f, "src");
25716 let third = apart(&mut f, &other);
25717 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
25718
25719 f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
25720 assert_eq!(
25721 f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
25722 "$1\r\na\r\n"
25723 );
25724 assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
25725 assert_eq!(
25726 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
25727 "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
25728 "one went on each end of the destination"
25729 );
25730 assert_eq!(
25731 f.run(&[b"LRANGE", src, b"0", b"-1"]),
25732 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
25733 );
25734
25735 assert_eq!(
25737 f.run(&[
25738 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
25739 ]),
25740 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
25741 );
25742 assert_eq!(
25743 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
25744 "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
25745 );
25746 assert_eq!(
25747 f.run(&[b"EXISTS", src]),
25748 ":0\r\n",
25749 "and the source is gone with its last element"
25750 );
25751
25752 f.run(&[b"RPUSH", src, b"e", b"f"]);
25755 assert_eq!(
25756 f.run(&[
25757 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
25758 ]),
25759 "*-1\r\n"
25760 );
25761 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
25762 assert_eq!(
25763 f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
25764 "$-1\r\n"
25765 );
25766 assert_eq!(
25767 f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
25768 "*-1\r\n"
25769 );
25770
25771 f.run(&[b"SET", plain, b"v"]);
25775 assert_eq!(
25776 f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
25777 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25778 );
25779 assert_eq!(
25780 f.run(&[b"LLEN", src]),
25781 ":2\r\n",
25782 "and left the source alone"
25783 );
25784 assert_eq!(
25785 f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
25786 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25787 );
25788 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
25789 }
25790
25791 #[test]
25797 fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
25798 let mut f = Fixture::striped(8);
25799 let other = apart(&mut f, "q");
25800 let (q, far) = (b"q".as_slice(), other.as_bytes());
25801
25802 assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
25803 assert_eq!(f.server.parked(), 1);
25804 f.run(&[b"RPUSH", far, b"v"]);
25805 let mut out = Out::new(Proto::Resp2);
25806 assert!(f.server.serve_waiter(7, 0, &mut out));
25807 let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
25808 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
25809 assert_eq!(
25810 f.run(&[b"EXISTS", far]),
25811 ":0\r\n",
25812 "and it took the element with it"
25813 );
25814
25815 f.server.forget_waiters(7);
25818 assert_eq!(
25819 f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
25820 Flow::Block
25821 );
25822 f.run(&[b"RPUSH", q, b"w"]);
25823 let mut out = Out::new(Proto::Resp2);
25824 assert!(f.server.serve_waiter(7, 0, &mut out));
25825 assert_eq!(
25826 core::str::from_utf8(out.as_slice()).expect("ascii"),
25827 "$1\r\nw\r\n"
25828 );
25829 assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
25830 }
25831
25832 #[test]
25838 fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
25839 let script: &[&[&[u8]]] = &[
25840 &[b"XADD", b"s", b"1-1", b"a", b"1"],
25841 &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
25842 &[b"XADD", b"s", b"3-1", b"d", b"4"],
25843 &[b"XADD", b"s", b"1-1", b"e", b"5"],
25844 &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
25845 &[b"XLEN", b"s"],
25846 &[b"XLEN", b"gone"],
25847 &[b"XRANGE", b"s", b"-", b"+"],
25848 &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
25849 &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
25850 &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
25851 &[b"XREVRANGE", b"s", b"+", b"-"],
25852 &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
25853 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
25854 &[b"XREAD", b"STREAMS", b"s", b"$"],
25855 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
25857 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
25858 &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
25859 &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
25860 &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
25861 &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
25862 &[
25863 b"XREADGROUP",
25864 b"GROUP",
25865 b"g",
25866 b"c1",
25867 b"COUNT",
25868 b"1",
25869 b"STREAMS",
25870 b"s",
25871 b"0",
25872 ],
25873 &[
25874 b"XREADGROUP",
25875 b"GROUP",
25876 b"nope",
25877 b"c1",
25878 b"STREAMS",
25879 b"s",
25880 b">",
25881 ],
25882 &[b"XPENDING", b"s", b"g"],
25883 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
25884 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
25885 &[b"XPENDING", b"s", b"nope"],
25886 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
25887 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
25888 &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
25889 &[b"XACK", b"s", b"g", b"1-1"],
25890 &[b"XACK", b"s", b"g", b"1-1"],
25891 &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
25892 &[b"XPENDING", b"s", b"g"],
25893 &[b"XINFO", b"STREAM", b"s"],
25894 &[b"XINFO", b"GROUPS", b"s"],
25895 &[b"XINFO", b"CONSUMERS", b"s", b"g"],
25896 &[b"XINFO", b"STREAM", b"gone"],
25897 &[b"XDEL", b"s", b"3-1"],
25899 &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
25900 &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
25901 &[b"XADD", b"s", b"9-1", b"z", b"9"],
25902 &[b"XTRIM", b"s", b"MAXLEN", b"1"],
25903 &[b"XTRIM", b"s", b"MINID", b"9"],
25904 &[b"XSETID", b"s", b"99-1"],
25905 &[b"XSETID", b"s", b"1-1"],
25906 &[b"XLEN", b"s"],
25907 &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
25908 &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
25909 &[b"XGROUP", b"DESTROY", b"s", b"g"],
25910 &[b"XGROUP", b"DESTROY", b"s", b"g"],
25911 &[b"SET", b"plain", b"v"],
25913 &[b"XADD", b"plain", b"1-1", b"a", b"1"],
25914 &[b"XLEN", b"plain"],
25915 &[b"XREAD", b"STREAMS", b"plain", b"0"],
25916 &[b"XRANGE", b"s", b"bogus", b"+"],
25917 &[b"XADD", b"s", b"1-1", b"a"],
25918 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
25919 &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
25920 ];
25921
25922 let mut one = Fixture::new();
25923 let mut many = Fixture::striped(8);
25924 for parts in script {
25925 let a = one.run(parts);
25926 let b = many.run(parts);
25927 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25928 }
25929 }
25930
25931 #[test]
25937 fn a_stream_read_across_stripes_reads_every_key() {
25938 let mut f = Fixture::striped(8);
25939 let other = apart(&mut f, "s1");
25940 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
25941
25942 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
25943 f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
25944 let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
25945 assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
25946 assert!(got.contains("1-1"), "the first one is in there: {got}");
25947 assert!(got.contains("2-1"), "and so is the second: {got}");
25948
25949 f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
25952 let got = f.run(&[
25953 b"XREADGROUP",
25954 b"GROUP",
25955 b"g",
25956 b"c",
25957 b"STREAMS",
25958 s1,
25959 s2,
25960 b">",
25961 b">",
25962 ]);
25963 assert!(got.starts_with("-NOGROUP"), "{got}");
25964 assert_eq!(
25965 f.run(&[b"XPENDING", s1, b"g"]),
25966 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
25967 "and read nothing from the key that did have the group"
25968 );
25969
25970 f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
25971 let got = f.run(&[
25972 b"XREADGROUP",
25973 b"GROUP",
25974 b"g",
25975 b"c",
25976 b"STREAMS",
25977 s1,
25978 s2,
25979 b">",
25980 b">",
25981 ]);
25982 assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
25983 }
25984
25985 #[test]
25987 fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
25988 let mut f = Fixture::striped(8);
25989 let other = apart(&mut f, "s1");
25990 let (s1, far) = (b"s1".as_slice(), other.as_bytes());
25991 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
25992 f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
25993
25994 assert_eq!(
25995 f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
25996 .0,
25997 Flow::Block
25998 );
25999 f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
26000 let mut out = Out::new(Proto::Resp2);
26001 assert!(f.server.serve_waiter(7, 0, &mut out));
26002 let want = format!(
26003 "*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",
26004 other.len()
26005 );
26006 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
26007 }
26008
26009 #[test]
26011 fn the_json_group_answers_the_same_however_many_stripes_there_are() {
26012 let script: &[&[&[u8]]] = &[
26013 &[
26014 b"JSON.SET",
26015 b"d",
26016 b"$",
26017 br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
26018 ],
26019 &[b"JSON.SET", b"d", b"$.a", b"2"],
26020 &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
26021 &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
26022 &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
26023 &[b"JSON.GET", b"d"],
26024 &[b"JSON.GET", b"d", b"$.b"],
26025 &[b"JSON.GET", b"gone", b"$"],
26026 &[b"JSON.TYPE", b"d", b"$.b"],
26027 &[b"JSON.TYPE", b"d", b"$.s"],
26028 &[b"JSON.TOGGLE", b"d", b"$.t"],
26029 &[b"JSON.ARRLEN", b"d", b"$.b"],
26030 &[b"JSON.OBJLEN", b"d", b"$"],
26031 &[b"JSON.OBJKEYS", b"d", b"$"],
26032 &[b"JSON.STRLEN", b"d", b"$.s"],
26033 &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
26034 &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
26035 &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
26036 &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
26037 &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
26038 &[b"JSON.ARRPOP", b"d", b"$.b"],
26039 &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
26040 &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
26041 &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
26042 &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
26043 &[b"JSON.RESP", b"d", b"$.b"],
26044 &[b"JSON.DEBUG", b"MEMORY", b"d"],
26045 &[b"JSON.CLEAR", b"d", b"$.b"],
26046 &[b"JSON.DEL", b"d", b"$.m"],
26047 &[b"JSON.FORGET", b"d", b"$.nothere"],
26048 &[
26050 b"JSON.MSET",
26051 b"m1",
26052 b"$",
26053 b"1",
26054 b"m2",
26055 b"$",
26056 b"2",
26057 b"m3",
26058 b"$",
26059 b"3",
26060 ],
26061 &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
26062 &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
26063 &[b"JSON.GET", b"m1", b"$"],
26064 &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
26065 &[b"JSON.GET", b"m2", b"$"],
26066 &[b"SET", b"plain", b"v"],
26068 &[b"JSON.GET", b"plain", b"$"],
26069 &[b"JSON.SET", b"plain", b"$", b"1"],
26070 &[b"JSON.MGET", b"m1", b"plain", b"$"],
26071 &[b"JSON.SET", b"d", b"$.b", b"["],
26072 &[b"JSON.DEL", b"plain"],
26073 ];
26074
26075 let mut one = Fixture::new();
26076 let mut many = Fixture::striped(8);
26077 for parts in script {
26078 let a = one.run(parts);
26079 let b = many.run(parts);
26080 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26081 }
26082 }
26083
26084 #[test]
26091 fn a_json_multi_write_across_stripes_reaches_every_key() {
26092 let mut f = Fixture::striped(8);
26093 let second = apart(&mut f, "m1");
26094 let third = apart(&mut f, &second);
26095 let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
26096
26097 assert_eq!(
26098 f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
26099 "+OK\r\n"
26100 );
26101 assert_eq!(
26102 f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
26103 "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
26104 );
26105
26106 assert_eq!(
26109 f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
26110 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
26111 );
26112 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
26113
26114 assert_eq!(
26118 f.run(&[
26119 b"JSON.MSET",
26120 m1,
26121 b"$",
26122 b"9",
26123 m2,
26124 b"$.deep",
26125 b"9",
26126 m3,
26127 b"$",
26128 b"7"
26129 ]),
26130 "$-1\r\n"
26131 );
26132 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
26133 assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
26134 assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
26135 }
26136
26137 #[test]
26139 fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
26140 let script: &[&[&[u8]]] = &[
26141 &[
26142 b"GEOADD",
26143 b"g",
26144 b"13.361389",
26145 b"38.115556",
26146 b"palermo",
26147 b"15.087269",
26148 b"37.502669",
26149 b"catania",
26150 ],
26151 &[
26152 b"GEOADD",
26153 b"g",
26154 b"NX",
26155 b"13.361389",
26156 b"38.115556",
26157 b"palermo",
26158 ],
26159 &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
26160 &[b"GEOPOS", b"g", b"palermo", b"nothere"],
26161 &[b"GEOHASH", b"g", b"palermo", b"catania"],
26162 &[b"GEODIST", b"g", b"palermo", b"catania"],
26163 &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
26164 &[b"GEODIST", b"g", b"palermo", b"nothere"],
26165 &[
26166 b"GEOSEARCH",
26167 b"g",
26168 b"FROMLONLAT",
26169 b"15",
26170 b"37",
26171 b"BYRADIUS",
26172 b"200",
26173 b"KM",
26174 b"ASC",
26175 b"WITHCOORD",
26176 b"WITHDIST",
26177 b"WITHHASH",
26178 ],
26179 &[
26180 b"GEOSEARCH",
26181 b"g",
26182 b"FROMMEMBER",
26183 b"palermo",
26184 b"BYBOX",
26185 b"400",
26186 b"400",
26187 b"KM",
26188 b"DESC",
26189 ],
26190 &[
26191 b"GEORADIUS",
26192 b"g",
26193 b"15",
26194 b"37",
26195 b"200",
26196 b"KM",
26197 b"COUNT",
26198 b"1",
26199 ],
26200 &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
26201 &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
26202 &[
26203 b"GEOSEARCHSTORE",
26204 b"dst",
26205 b"g",
26206 b"FROMLONLAT",
26207 b"15",
26208 b"37",
26209 b"BYRADIUS",
26210 b"200",
26211 b"KM",
26212 ],
26213 &[b"ZRANGE", b"dst", b"0", b"-1"],
26214 &[
26215 b"GEOSEARCHSTORE",
26216 b"dst",
26217 b"g",
26218 b"FROMLONLAT",
26219 b"15",
26220 b"37",
26221 b"BYRADIUS",
26222 b"1",
26223 b"M",
26224 b"STOREDIST",
26225 ],
26226 &[b"EXISTS", b"dst"],
26227 &[
26228 b"GEORADIUS",
26229 b"g",
26230 b"15",
26231 b"37",
26232 b"200",
26233 b"KM",
26234 b"STORE",
26235 b"dst",
26236 ],
26237 &[b"ZCARD", b"dst"],
26238 &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
26240 &[b"SET", b"plain", b"v"],
26241 &[b"GEOPOS", b"plain", b"a"],
26242 &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
26243 &[
26244 b"GEOSEARCHSTORE",
26245 b"dst",
26246 b"g",
26247 b"FROMLONLAT",
26248 b"15",
26249 b"37",
26250 b"BYRADIUS",
26251 b"200",
26252 b"KM",
26253 b"WITHCOORD",
26254 ],
26255 ];
26256
26257 let mut one = Fixture::new();
26258 let mut many = Fixture::striped(8);
26259 for parts in script {
26260 let a = one.run(parts);
26261 let b = many.run(parts);
26262 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26263 }
26264 }
26265
26266 #[test]
26268 fn a_geo_search_store_across_stripes_writes_what_it_found() {
26269 let mut f = Fixture::striped(8);
26270 let other = apart(&mut f, "g");
26271 let third = apart(&mut f, &other);
26272 let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
26273
26274 f.run(&[
26275 b"GEOADD",
26276 g,
26277 b"13.361389",
26278 b"38.115556",
26279 b"palermo",
26280 b"15.087269",
26281 b"37.502669",
26282 b"catania",
26283 ]);
26284 assert_eq!(
26285 f.run(&[
26286 b"GEOSEARCHSTORE",
26287 dst,
26288 g,
26289 b"FROMLONLAT",
26290 b"15",
26291 b"37",
26292 b"BYRADIUS",
26293 b"200",
26294 b"KM",
26295 b"ASC",
26296 ]),
26297 ":2\r\n"
26298 );
26299 assert_eq!(
26300 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
26301 "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
26302 "the geohash is the score, so the order is not the search order"
26303 );
26304 assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
26305
26306 assert_eq!(
26309 f.run(&[
26310 b"GEOSEARCHSTORE",
26311 dst,
26312 g,
26313 b"FROMMEMBER",
26314 b"palermo",
26315 b"BYRADIUS",
26316 b"200",
26317 b"KM",
26318 b"STOREDIST",
26319 ]),
26320 ":2\r\n"
26321 );
26322 assert_eq!(
26323 f.run(&[b"ZSCORE", dst, b"palermo"]),
26324 "$1\r\n0\r\n",
26325 "the centre is nought away from itself"
26326 );
26327
26328 assert_eq!(
26332 f.run(&[
26333 b"GEOSEARCHSTORE",
26334 dst,
26335 g,
26336 b"FROMLONLAT",
26337 b"0",
26338 b"0",
26339 b"BYRADIUS",
26340 b"1",
26341 b"M",
26342 ]),
26343 ":0\r\n"
26344 );
26345 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
26346 f.run(&[
26347 b"GEOSEARCHSTORE",
26348 dst,
26349 g,
26350 b"FROMLONLAT",
26351 b"15",
26352 b"37",
26353 b"BYRADIUS",
26354 b"200",
26355 b"KM",
26356 ]);
26357 f.run(&[b"SET", plain, b"v"]);
26358 assert_eq!(
26359 f.run(&[
26360 b"GEOSEARCHSTORE",
26361 dst,
26362 plain,
26363 b"FROMLONLAT",
26364 b"15",
26365 b"37",
26366 b"BYRADIUS",
26367 b"200",
26368 b"KM",
26369 ]),
26370 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26371 );
26372 assert_eq!(
26373 f.run(&[b"ZCARD", dst]),
26374 ":2\r\n",
26375 "and left the destination"
26376 );
26377 }
26378
26379 #[test]
26385 fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
26386 let script: &[&[&[u8]]] = &[
26387 &[
26388 b"TS.CREATE",
26389 b"ts:a",
26390 b"LABELS",
26391 b"sensor",
26392 b"a",
26393 b"room",
26394 b"1",
26395 ],
26396 &[b"TS.CREATE", b"ts:a"],
26397 &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
26398 &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
26399 &[
26400 b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
26401 ],
26402 &[
26403 b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
26404 ],
26405 &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
26406 &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
26407 &[b"TS.GET", b"ts:a"],
26408 &[b"TS.GET", b"gone"],
26409 &[b"TS.RANGE", b"ts:a", b"-", b"+"],
26410 &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
26411 &[
26412 b"TS.RANGE",
26413 b"ts:a",
26414 b"-",
26415 b"+",
26416 b"AGGREGATION",
26417 b"avg",
26418 b"2000",
26419 ],
26420 &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
26421 &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
26422 &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
26423 &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
26424 &[b"TS.READ", b"ts:a", b"0"],
26425 &[b"TS.READ", b"ts:a", b"+"],
26426 &[b"TS.QUERYINDEX", b"sensor=a"],
26428 &[b"TS.QUERYINDEX", b"room=1"],
26429 &[b"TS.QUERYINDEX", b"room=9"],
26430 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
26431 &[
26432 b"TS.QUERYLABELS",
26433 b"VALUES",
26434 b"sensor",
26435 b"FILTER",
26436 b"room=1",
26437 ],
26438 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
26439 &[
26440 b"TS.MGET",
26441 b"SELECTED_LABELS",
26442 b"sensor",
26443 b"FILTER",
26444 b"sensor=a",
26445 ],
26446 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
26447 &[
26448 b"TS.MREVRANGE",
26449 b"-",
26450 b"+",
26451 b"WITHLABELS",
26452 b"FILTER",
26453 b"sensor=a",
26454 ],
26455 &[
26456 b"TS.MRANGE",
26457 b"-",
26458 b"+",
26459 b"FILTER",
26460 b"room=1",
26461 b"GROUPBY",
26462 b"room",
26463 b"REDUCE",
26464 b"max",
26465 ],
26466 &[b"TS.INFO", b"ts:a"],
26467 &[
26469 b"TS.CREATERULE",
26470 b"ts:a",
26471 b"ts:down",
26472 b"AGGREGATION",
26473 b"avg",
26474 b"1000",
26475 ],
26476 &[b"TS.CREATE", b"ts:down"],
26477 &[
26478 b"TS.CREATERULE",
26479 b"ts:a",
26480 b"ts:down",
26481 b"AGGREGATION",
26482 b"avg",
26483 b"1000",
26484 ],
26485 &[b"TS.ADD", b"ts:a", b"5000", b"4"],
26486 &[b"TS.ADD", b"ts:a", b"6000", b"5"],
26487 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
26488 &[b"TS.GET", b"ts:down", b"LATEST"],
26489 &[b"TS.INFO", b"ts:down"],
26490 &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
26491 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
26492 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
26493 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
26494 &[b"TS.DEL", b"ts:a", b"0", b"1000"],
26495 &[b"SET", b"plain", b"v"],
26497 &[b"TS.ADD", b"plain", b"1", b"1"],
26498 &[b"TS.GET", b"plain"],
26499 &[b"TS.READ", b"plain", b"0"],
26500 &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
26501 &[b"TS.RANGE", b"gone", b"-", b"+"],
26502 &[b"TS.INFO", b"gone"],
26503 ];
26504
26505 let mut one = Fixture::new();
26506 let mut many = Fixture::striped(8);
26507 for parts in script {
26508 let a = one.run(parts);
26509 let b = many.run(parts);
26510 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26511 }
26512 }
26513
26514 #[test]
26524 fn a_compaction_rule_across_stripes_reaches_both_ends() {
26525 let mut many = Fixture::striped(8);
26526 let other = apart(&mut many, "src");
26527 let (src, dst) = (b"src".as_slice(), other.as_bytes());
26528 let mut one = Fixture::new();
26529 let mut both = |parts: &[&[u8]]| {
26530 let a = one.run(parts);
26531 let b = many.run(parts);
26532 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26533 a
26534 };
26535
26536 both(&[b"TS.CREATE", src]);
26537 both(&[b"TS.CREATE", dst]);
26538 assert_eq!(
26539 both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
26540 "+OK\r\n"
26541 );
26542 both(&[b"TS.ADD", src, b"1000", b"1"]);
26543 both(&[b"TS.ADD", src, b"1500", b"3"]);
26544 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
26547 let open = both(&[b"TS.GET", dst, b"LATEST"]);
26548 assert!(open.contains(":1000"), "the open bucket is folded: {open}");
26549
26550 both(&[b"TS.ADD", src, b"2000", b"5"]);
26553 let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
26554 assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
26555 assert!(got.contains(":1000"), "{got}");
26556
26557 both(&[b"TS.DEL", src, b"1000", b"1999"]);
26559 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
26560
26561 assert!(
26564 both(&[b"TS.INFO", dst]).contains("src"),
26565 "the source is named"
26566 );
26567 assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
26568 assert_eq!(
26569 both(&[b"TS.DELETERULE", src, dst]),
26570 "-ERR TSDB: compaction rule does not exist\r\n"
26571 );
26572 }
26573
26574 #[test]
26576 fn a_label_query_across_stripes_finds_every_series() {
26577 let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
26578 let mut many = Fixture::striped(8);
26579 let mut homes: Vec<usize> = names
26580 .iter()
26581 .map(|name| many.server.striped(0).stripe_of(name))
26582 .collect();
26583 homes.sort_unstable();
26584 homes.dedup();
26585 assert!(homes.len() > 1, "the six keys are not all on one stripe");
26586
26587 let mut one = Fixture::new();
26588 let mut both = |parts: &[&[u8]]| {
26589 let a = one.run(parts);
26590 let b = many.run(parts);
26591 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26592 a
26593 };
26594 for name in &names {
26595 both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
26596 both(&[b"TS.ADD", name, b"1000", b"1"]);
26597 }
26598
26599 let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
26600 assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
26601 assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
26602 assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
26603 assert_eq!(
26604 both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
26605 "*1\r\n$4\r\nroom\r\n"
26606 );
26607 }
26608
26609 #[test]
26616 fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
26617 let script: &[&[&[u8]]] = &[
26618 &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
26619 &[b"HMSET", b"h", b"c", b"3"],
26620 &[b"HSETNX", b"h", b"a", b"9"],
26621 &[b"HSETNX", b"h", b"d", b"4"],
26622 &[b"HGET", b"h", b"a"],
26623 &[b"HGET", b"h", b"nope"],
26624 &[b"HMGET", b"h", b"a", b"nope"],
26625 &[b"HLEN", b"h"],
26626 &[b"HEXISTS", b"h", b"a"],
26627 &[b"HSTRLEN", b"h", b"a"],
26628 &[b"HGETALL", b"h"],
26629 &[b"HKEYS", b"h"],
26630 &[b"HVALS", b"h"],
26631 &[b"HINCRBY", b"h", b"a", b"5"],
26632 &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
26633 &[b"HSCAN", b"h", b"0"],
26634 &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
26635 &[b"HSCAN", b"h", b"0", b"NOVALUES"],
26636 &[b"HDEL", b"h", b"d"],
26637 &[b"HSET", b"one", b"f", b"v"],
26638 &[b"HRANDFIELD", b"one"],
26639 &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
26640 &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
26642 &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
26643 &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
26644 &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
26645 &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
26646 &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
26647 &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
26648 &[b"HGET", b"h", b"b"],
26649 &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
26651 &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
26652 &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
26653 &[b"HGET", b"h", b"e"],
26654 &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
26656 &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
26657 &[b"HGETALL", b"imp"],
26658 &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
26659 &[b"HIMPORT", b"DISCARD", b"fs"],
26660 &[b"SET", b"plain", b"v"],
26662 &[b"HSET", b"plain", b"a", b"1"],
26663 &[b"HGETALL", b"plain"],
26664 &[b"HGET", b"gone", b"a"],
26665 &[b"HINCRBY", b"h", b"a", b"nan"],
26666 ];
26667
26668 let mut one = Fixture::new();
26669 let mut many = Fixture::striped(8);
26670 one.server.set_clock_ms(1_700_000_000_000);
26674 many.server.set_clock_ms(1_700_000_000_000);
26675 for parts in script {
26676 let a = one.run(parts);
26677 let b = many.run(parts);
26678 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26679 }
26680 }
26681
26682 #[test]
26684 fn the_array_group_answers_the_same_however_many_stripes_there_are() {
26685 let script: &[&[&[u8]]] = &[
26686 &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
26687 &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
26688 &[b"ARGET", b"a", b"1"],
26689 &[b"ARGET", b"a", b"99"],
26690 &[b"ARMGET", b"a", b"0", b"5", b"99"],
26691 &[b"ARGETRANGE", b"a", b"0", b"7"],
26692 &[b"ARLEN", b"a"],
26693 &[b"ARCOUNT", b"a"],
26694 &[b"ARINSERT", b"a", b"m", b"n"],
26695 &[b"ARSCAN", b"a", b"0", b"20"],
26696 &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
26697 &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
26698 &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
26699 &[b"ARLASTITEMS", b"a", b"2"],
26700 &[b"ARLASTITEMS", b"a", b"2", b"REV"],
26701 &[b"ARNEXT", b"a"],
26702 &[b"ARSEEK", b"a", b"3"],
26703 &[b"AROP", b"a", b"0", b"20", b"USED"],
26704 &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
26705 &[b"ARINFO", b"a"],
26706 &[b"ARINFO", b"a", b"FULL"],
26707 &[b"ARDEL", b"a", b"0"],
26708 &[b"ARDELRANGE", b"a", b"1", b"2"],
26709 &[b"ARCOUNT", b"a"],
26710 &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
26711 &[b"ARGETRANGE", b"r", b"0", b"9"],
26712 &[b"SET", b"plain", b"v"],
26714 &[b"ARGET", b"plain", b"0"],
26715 &[b"ARSET", b"plain", b"0", b"v"],
26716 &[b"ARGET", b"gone", b"0"],
26717 &[b"ARSET", b"a", b"bad", b"v"],
26718 ];
26719
26720 let mut one = Fixture::new();
26721 let mut many = Fixture::striped(8);
26722 for parts in script {
26723 let a = one.run(parts);
26724 let b = many.run(parts);
26725 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26726 }
26727 }
26728
26729 #[test]
26735 fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
26736 let script: &[&[&[u8]]] = &[
26737 &[b"G.NADD", b"g", b"n1", b"name", b"one"],
26738 &[b"G.NADD", b"g", b"n2", b"name", b"two"],
26739 &[b"G.NADD", b"g", b"n3"],
26740 &[b"G.NGET", b"g", b"n1"],
26741 &[b"G.NGET", b"g", b"gone"],
26742 &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
26743 &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
26744 &[b"G.OUT", b"g", b"n1", b"knows"],
26745 &[b"G.IN", b"g", b"n2", b"knows"],
26746 &[b"G.DEG", b"g", b"n1", b"knows"],
26747 &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
26748 &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
26749 &[b"G.PATH", b"g", b"n1", b"n3"],
26750 &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
26751 &[b"G.NDEL", b"g", b"n3"],
26752 &[b"G.NGET", b"g", b"n3"],
26753 &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
26755 &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
26756 &[b"VCARD", b"v"],
26757 &[b"VDIM", b"v"],
26758 &[b"VEMB", b"v", b"e1"],
26759 &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
26760 &[b"VSIM", b"v", b"ELE", b"e1"],
26761 &[b"VISMEMBER", b"v", b"e1"],
26762 &[b"VISMEMBER", b"v", b"gone"],
26763 &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
26764 &[b"VGETATTR", b"v", b"e1"],
26765 &[b"VRANGE", b"v", b"-", b"+"],
26766 &[b"VLINKS", b"v", b"e1"],
26767 &[b"VINFO", b"v"],
26768 &[b"VREM", b"v", b"e2"],
26769 &[b"VCARD", b"v"],
26770 &[b"SET", b"plain", b"v"],
26772 &[b"G.NGET", b"plain", b"n1"],
26773 &[b"VCARD", b"plain"],
26774 &[b"G.NADD", b"gone2", b"n"],
26775 &[b"VEMB", b"gone3", b"e"],
26776 ];
26777
26778 let mut one = Fixture::new();
26779 let mut many = Fixture::striped(8);
26780 for parts in script {
26781 let a = one.run(parts);
26782 let b = many.run(parts);
26783 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26784 }
26785 }
26786
26787 #[test]
26790 fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
26791 let script: &[&[&[u8]]] = &[
26792 &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
26794 &[b"BF.ADD", b"bf", b"a"],
26795 &[b"BF.ADD", b"bf", b"a"],
26796 &[b"BF.MADD", b"bf", b"b", b"c"],
26797 &[b"BF.EXISTS", b"bf", b"a"],
26798 &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
26799 &[b"BF.CARD", b"bf"],
26800 &[b"BF.INFO", b"bf"],
26801 &[b"BF.INFO", b"bf", b"CAPACITY"],
26802 &[b"BF.DEBUG", b"bf"],
26803 &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
26804 &[b"BF.EXISTS", b"made", b"x"],
26805 &[b"BF.SCANDUMP", b"bf", b"0"],
26806 &[b"CF.RESERVE", b"cf", b"100"],
26808 &[b"CF.ADD", b"cf", b"a"],
26809 &[b"CF.ADDNX", b"cf", b"a"],
26810 &[b"CF.COUNT", b"cf", b"a"],
26811 &[b"CF.EXISTS", b"cf", b"a"],
26812 &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
26813 &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
26814 &[b"CF.DEL", b"cf", b"a"],
26815 &[b"CF.COMPACT", b"cf"],
26816 &[b"CF.INFO", b"cf"],
26817 &[b"CF.DEBUG", b"cf"],
26818 &[b"CF.SCANDUMP", b"cf", b"0"],
26819 &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
26821 &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
26822 &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
26823 &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
26824 &[b"CMS.INFO", b"cms"],
26825 &[b"TOPK.RESERVE", b"tk", b"3"],
26827 &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
26828 &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
26829 &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
26830 &[b"TOPK.COUNT", b"tk", b"a", b"c"],
26831 &[b"TOPK.LIST", b"tk"],
26832 &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
26833 &[b"TOPK.INFO", b"tk"],
26834 &[b"TDIGEST.CREATE", b"td"],
26836 &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
26837 &[b"TDIGEST.MIN", b"td"],
26838 &[b"TDIGEST.MAX", b"td"],
26839 &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
26840 &[b"TDIGEST.CDF", b"td", b"3"],
26841 &[b"TDIGEST.RANK", b"td", b"3"],
26842 &[b"TDIGEST.REVRANK", b"td", b"3"],
26843 &[b"TDIGEST.BYRANK", b"td", b"0"],
26844 &[b"TDIGEST.BYREVRANK", b"td", b"0"],
26845 &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
26846 &[b"TDIGEST.INFO", b"td"],
26847 &[b"TDIGEST.RESET", b"td"],
26848 &[b"TDIGEST.MIN", b"td"],
26849 &[b"SET", b"plain", b"v"],
26851 &[b"BF.ADD", b"plain", b"a"],
26852 &[b"CF.ADD", b"plain", b"a"],
26853 &[b"CMS.QUERY", b"plain", b"a"],
26854 &[b"TOPK.ADD", b"plain", b"a"],
26855 &[b"TDIGEST.ADD", b"plain", b"1"],
26856 &[b"CMS.INFO", b"gone"],
26857 &[b"TOPK.INFO", b"gone"],
26858 &[b"TDIGEST.INFO", b"gone"],
26859 ];
26860
26861 let mut one = Fixture::new();
26862 let mut many = Fixture::striped(8);
26863 for parts in script {
26864 let a = one.run(parts);
26865 let b = many.run(parts);
26866 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26867 }
26868 }
26869
26870 #[test]
26877 fn a_sketch_merge_across_stripes_reads_every_source() {
26878 let mut many = Fixture::striped(8);
26879 let other = apart(&mut many, "s1");
26880 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
26881 let mut one = Fixture::new();
26882 let mut both = |parts: &[&[u8]]| {
26883 let a = one.run(parts);
26884 let b = many.run(parts);
26885 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26886 a
26887 };
26888
26889 for key in [b"cd".as_slice(), s1, s2] {
26893 both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
26894 }
26895 both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
26896 both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
26897 assert_eq!(
26898 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
26899 "+OK\r\n",
26900 "the merge took both sources"
26901 );
26902 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
26903 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
26905 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
26906 both(&[b"SET", b"plain", b"v"]);
26908 assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
26909 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
26910
26911 both(&[b"FLUSHALL"]);
26914 both(&[b"TDIGEST.CREATE", b"td"]);
26915 both(&[b"TDIGEST.CREATE", s1]);
26916 both(&[b"TDIGEST.CREATE", s2]);
26917 both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
26918 both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
26919 assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
26920 assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
26921 assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
26922 }
26923
26924 #[test]
26931 fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
26932 let script: &[&[&[u8]]] = &[
26933 &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
26934 &[b"SORT", b"l"],
26935 &[b"SORT", b"l", b"DESC"],
26936 &[b"SORT", b"l", b"ALPHA"],
26937 &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
26938 &[b"SORT_RO", b"l"],
26939 &[
26942 b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
26943 ],
26944 &[b"SORT", b"l", b"BY", b"w_*"],
26945 &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
26946 &[b"DEL", b"w_2"],
26947 &[b"SORT", b"l", b"BY", b"w_*"],
26948 &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
26951 &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
26952 &[b"HSET", b"h_1", b"f", b"9"],
26954 &[b"HSET", b"h_2", b"f", b"8"],
26955 &[b"HSET", b"h_3", b"f", b"7"],
26956 &[b"HSET", b"h_10", b"f", b"6"],
26957 &[b"SORT", b"l", b"BY", b"h_*->f"],
26958 &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
26959 &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
26961 &[b"LRANGE", b"out", b"0", b"-1"],
26962 &[b"SORT", b"l", b"STORE", b"l"],
26963 &[b"LRANGE", b"l", b"0", b"-1"],
26964 &[b"SORT", b"missing", b"STORE", b"out"],
26967 &[b"EXISTS", b"out"],
26968 &[b"SADD", b"s", b"c", b"a", b"b"],
26971 &[b"SORT", b"s", b"ALPHA"],
26972 &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
26973 &[b"LRANGE", b"out", b"0", b"-1"],
26974 &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
26975 &[b"SORT", b"z", b"BY", b"nosort"],
26976 &[b"SORT", b"z", b"ALPHA", b"DESC"],
26977 &[b"SET", b"str", b"v"],
26980 &[b"SORT", b"str"],
26981 &[b"RPUSH", b"words", b"one", b"two"],
26982 &[b"SORT", b"words"],
26983 &[b"SORT_RO", b"l", b"STORE", b"out"],
26984 ];
26985
26986 let mut one = Fixture::new();
26987 let mut many = Fixture::striped(8);
26988 for parts in script {
26989 let a = one.run(parts);
26990 let b = many.run(parts);
26991 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26992 }
26993 }
26994
26995 #[test]
27002 fn a_sort_across_stripes_reads_every_pattern_key() {
27003 let mut f = Fixture::striped(8);
27004 let out = apart(&mut f, "l");
27005 let (list, dest) = (b"l".as_slice(), out.as_bytes());
27006
27007 f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
27008 f.run(&[
27009 b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
27010 ]);
27011 f.run(&[
27012 b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
27013 ]);
27014
27015 let db = f.server.striped(0);
27018 let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
27019 .iter()
27020 .map(|k| db.stripe_of(k.as_slice()))
27021 .collect();
27022 assert!(
27023 weights.iter().any(|s| *s != weights[0]),
27024 "the four weight keys all landed on one stripe, so this proves nothing"
27025 );
27026
27027 assert_eq!(
27028 f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
27029 "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
27030 "the order came off the weights and the answer off the data keys"
27031 );
27032 assert_eq!(
27033 f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
27034 ":4\r\n"
27035 );
27036 assert_eq!(
27037 f.run(&[b"LRANGE", dest, b"0", b"-1"]),
27038 "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
27039 "the destination is on a stripe of its own and got the whole answer"
27040 );
27041 }
27042
27043 #[test]
27051 fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
27052 let mut f = Fixture::striped(8);
27053 let other = apart(&mut f, "h");
27054 let (first, second) = (b"h".as_slice(), other.as_bytes());
27055
27056 assert_eq!(
27057 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
27058 "+OK\r\n"
27059 );
27060 assert_eq!(
27061 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
27062 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
27063 "the read comes off one stripe and has to answer for all of them"
27064 );
27065 for key in [first, second] {
27066 f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
27067 assert_eq!(
27068 f.run(&[b"OBJECT", b"ENCODING", key]),
27069 "$8\r\nlistpack\r\n",
27070 "two fields is still under the ladder"
27071 );
27072 f.run(&[b"HSET", key, b"c", b"3"]);
27073 assert_eq!(
27074 f.run(&[b"OBJECT", b"ENCODING", key]),
27075 "$9\r\nhashtable\r\n",
27076 "three fields is over it, on whichever stripe the key is on"
27077 );
27078 }
27079
27080 assert_eq!(
27083 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
27084 "+OK\r\n"
27085 );
27086 let db = f.server.striped(0);
27087 assert!(
27088 (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
27089 "a stripe kept the old policy"
27090 );
27091 }
27092
27093 fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
27099 let search = f.server.search.lock();
27100 let index = search.named(name).expect("the index is there");
27101 (index.held.docs.len(), index.held.docs.last())
27102 }
27103
27104 #[test]
27107 fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
27108 let mut f = Fixture::new();
27109 f.run(&[
27110 b"FT.CREATE",
27111 b"ix",
27112 b"PREFIX",
27113 b"1",
27114 b"p:",
27115 b"SCHEMA",
27116 b"t",
27117 b"TEXT",
27118 ]);
27119 f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
27120 assert_eq!(held(&f, b"ix"), (1, 1));
27121 f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
27122 assert_eq!(held(&f, b"ix"), (1, 1));
27123
27124 f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
27127 f.run(&[b"HDEL", b"p:1", b"u"]);
27128 assert_eq!(held(&f, b"ix"), (1, 3));
27129 let search = f.server.search.lock();
27130 let index = search.named(b"ix").expect("there");
27131 assert_eq!(index.held.docs.id(b"p:1"), Some(3));
27132 }
27133
27134 #[test]
27137 fn a_fresh_index_reads_the_keys_that_were_already_there() {
27138 let mut f = Fixture::new();
27139 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27140 f.run(&[b"SET", b"p:str", b"not a hash"]);
27141 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
27142 f.run(&[
27143 b"FT.CREATE",
27144 b"ix",
27145 b"PREFIX",
27146 b"1",
27147 b"p:",
27148 b"SCHEMA",
27149 b"t",
27150 b"TEXT",
27151 ]);
27152
27153 assert_eq!(held(&f, b"ix"), (1, 1));
27154 let search = f.server.search.lock();
27155 let index = search.named(b"ix").expect("there");
27156 assert_eq!(index.trouble.whole().failures(), 0);
27157 }
27158
27159 #[test]
27162 fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
27163 let mut f = Fixture::new();
27164 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27165 f.run(&[
27166 b"FT.CREATE",
27167 b"ix",
27168 b"PREFIX",
27169 b"1",
27170 b"p:",
27171 b"SKIPINITIALSCAN",
27172 b"SCHEMA",
27173 b"t",
27174 b"TEXT",
27175 ]);
27176 assert_eq!(held(&f, b"ix"), (0, 0));
27177 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27178 assert_eq!(held(&f, b"ix"), (1, 1));
27179 }
27180
27181 #[test]
27189 fn only_a_real_change_gives_the_document_a_new_number() {
27190 let mut f = Fixture::new();
27191 f.run(&[
27192 b"FT.CREATE",
27193 b"ix",
27194 b"PREFIX",
27195 b"1",
27196 b"p:",
27197 b"SCHEMA",
27198 b"t",
27199 b"TEXT",
27200 ]);
27201 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27202 assert_eq!(held(&f, b"ix"), (1, 1));
27203
27204 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27205 assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
27206
27207 for quiet in [
27208 vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
27209 vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
27210 vec![b"HGET".as_slice(), b"p:1", b"t"],
27211 vec![b"HGETALL".as_slice(), b"p:1"],
27212 vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
27213 vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
27214 vec![
27215 b"HGETEX".as_slice(),
27216 b"p:1",
27217 b"EX",
27218 b"100",
27219 b"FIELDS",
27220 b"1",
27221 b"t",
27222 ],
27223 vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
27224 ] {
27225 f.run(&quiet);
27226 assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
27227 }
27228
27229 f.run(&[b"HSET", b"p:2", b"n", b"1"]);
27231 f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
27232 assert_eq!(held(&f, b"ix"), (2, 4));
27233 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
27239 assert_eq!(held(&f, b"ix"), (1, 5));
27240 }
27241
27242 #[test]
27248 fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
27249 fn refused(f: &Fixture, name: &[u8]) -> u64 {
27251 let search = f.server.search.lock();
27252 let index = search.named(name).expect("the index is there");
27253 index.trouble.whole().failures()
27254 }
27255
27256 let mut f = Fixture::new();
27257 f.run(&[
27258 b"FT.CREATE",
27259 b"ix",
27260 b"PREFIX",
27261 b"1",
27262 b"p:",
27263 b"SCHEMA",
27264 b"t",
27265 b"TEXT",
27266 ]);
27267 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27268 assert_eq!(held(&f, b"ix"), (1, 1));
27269 f.run(&[b"HDEL", b"p:1", b"t"]);
27270 assert_eq!(
27271 held(&f, b"ix"),
27272 (0, 1),
27273 "HDEL of the last field spends none"
27274 );
27275 assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
27276
27277 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
27278 assert_eq!(held(&f, b"ix"), (1, 2));
27279 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
27280 assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
27281 assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
27282
27283 f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
27284 assert_eq!(held(&f, b"ix"), (1, 4));
27285 f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
27286 assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
27287
27288 f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
27291 assert_eq!(held(&f, b"ix"), (1, 6));
27292 f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
27293 assert_eq!(held(&f, b"ix"), (0, 7));
27294 assert_eq!(refused(&f, b"ix"), 1);
27295 }
27296
27297 #[test]
27301 fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
27302 let mut f = Fixture::new();
27303 f.run(&[
27304 b"FT.CREATE",
27305 b"ix",
27306 b"PREFIX",
27307 b"1",
27308 b"p:",
27309 b"SCHEMA",
27310 b"t",
27311 b"TEXT",
27312 b"u",
27313 b"TEXT",
27314 ]);
27315 f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
27316 assert_eq!(held(&f, b"ix"), (1, 1));
27317 f.run(&[
27318 b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
27319 ]);
27320 assert_eq!(
27321 held(&f, b"ix"),
27322 (1, 3),
27323 "the key lived and the field did not"
27324 );
27325
27326 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
27328 assert_eq!(held(&f, b"ix"), (2, 4));
27329 f.run(&[
27330 b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
27331 ]);
27332 assert_eq!(held(&f, b"ix"), (1, 6));
27333 }
27334
27335 fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
27338 let search = f.server.search.lock();
27339 let index = search.named(name).expect("the index is there");
27340 index.held.docs.id(key)
27341 }
27342
27343 fn indexed() -> Fixture {
27346 let mut f = Fixture::new();
27347 f.run(&[
27348 b"FT.CREATE",
27349 b"ix",
27350 b"PREFIX",
27351 b"1",
27352 b"p:",
27353 b"SCHEMA",
27354 b"t",
27355 b"TEXT",
27356 ]);
27357 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27358 f
27359 }
27360
27361 #[test]
27364 fn a_key_a_keyspace_command_takes_away_loses_its_document() {
27365 for take in [
27366 vec![b"DEL".as_slice(), b"p:1"],
27367 vec![b"UNLINK".as_slice(), b"p:1"],
27368 vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
27369 vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
27370 ] {
27371 let mut f = indexed();
27372 assert_eq!(held(&f, b"ix"), (1, 1));
27373 f.run(&take);
27374 assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
27375 let search = f.server.search.lock();
27376 let index = search.named(b"ix").expect("the index is there");
27377 assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
27378 }
27379
27380 let mut f = indexed();
27382 f.run(&[b"EXPIRE", b"p:1", b"1000"]);
27383 assert_eq!(held(&f, b"ix"), (1, 1));
27384 f.run(&[b"PERSIST", b"p:1"]);
27385 assert_eq!(held(&f, b"ix"), (1, 1));
27386 }
27387
27388 #[test]
27392 fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
27393 let mut f = indexed();
27394 f.run(&[b"RENAME", b"p:1", b"p:2"]);
27395 assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
27396 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
27397 assert_eq!(number(&f, b"ix", b"p:1"), None);
27398
27399 f.run(&[b"RENAME", b"p:2", b"q:1"]);
27400 assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
27401
27402 f.run(&[b"RENAME", b"q:1", b"p:3"]);
27403 assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
27404 assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
27405
27406 f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
27409 assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
27410 assert_eq!(held(&f, b"ix"), (2, 3));
27411 f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
27412 assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
27413 }
27414
27415 #[test]
27418 fn a_rename_over_a_document_leaves_one_of_them() {
27419 let mut f = indexed();
27420 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
27421 assert_eq!(held(&f, b"ix"), (2, 2));
27422 f.run(&[b"RENAME", b"p:1", b"p:2"]);
27423 assert_eq!(held(&f, b"ix"), (1, 2));
27424 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
27425 }
27426
27427 #[test]
27431 fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
27432 let mut f = indexed();
27433 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
27434 f.run(&[b"COPY", b"q:1", b"p:2"]);
27435 assert_eq!(held(&f, b"ix"), (2, 2));
27436 assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
27437
27438 f.run(&[b"COPY", b"p:1", b"q:2"]);
27440 assert_eq!(held(&f, b"ix"), (2, 2));
27441
27442 f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
27444 assert_eq!(held(&f, b"ix"), (2, 3));
27445 assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
27446
27447 f.run(&[b"SET", b"s:1", b"plain"]);
27450 f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
27451 assert_eq!(held(&f, b"ix"), (1, 3));
27452 let dump = f.run(&[b"DUMP", b"q:1"]);
27453 assert!(dump.starts_with('$'), "{dump}");
27454 }
27455
27456 #[test]
27461 fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
27462 let mut f = indexed();
27463 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
27464 assert_eq!(held(&f, b"ix"), (2, 2));
27465 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
27468 assert_eq!(held(&f, b"ix"), (2, 3));
27469 f.run(&[b"DEL", b"p:2"]);
27472 assert_eq!(held(&f, b"ix"), (1, 3));
27473 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
27474 assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
27475
27476 let dump = f.run(&[b"DUMP", b"p:1"]);
27478 assert!(dump.starts_with('$'), "{dump}");
27479 f.run(&[b"SELECT", b"1"]);
27480 f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
27481 f.run(&[b"RENAME", b"q:1", b"p:3"]);
27482 assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
27483 }
27484
27485 #[test]
27488 fn a_move_leaves_the_document_where_it_is() {
27489 let mut f = indexed();
27490 f.run(&[b"MOVE", b"p:1", b"1"]);
27491 assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
27492 assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
27493
27494 f.run(&[b"SELECT", b"1"]);
27495 f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
27496 assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
27497 f.run(&[b"DEL", b"p:1"]);
27498 assert_eq!(held(&f, b"ix"), (0, 2));
27499 }
27500
27501 #[test]
27503 fn a_flush_drops_the_indexes() {
27504 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
27505 let mut f = indexed();
27506 f.run(&[flush]);
27507 assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
27508 assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
27509 }
27510
27511 let mut f = indexed();
27514 f.run(&[b"SELECT", b"9"]);
27515 f.run(&[b"FLUSHDB"]);
27516 assert!(f.server.search.lock().is_empty());
27517 }
27518
27519 fn tagged() -> Fixture {
27522 let mut f = Fixture::new();
27523 f.run(&[
27524 b"FT.CREATE",
27525 b"tv",
27526 b"PREFIX",
27527 b"1",
27528 b"tv:",
27529 b"SCHEMA",
27530 b"g",
27531 b"AS",
27532 b"gg",
27533 b"TAG",
27534 b"h",
27535 b"TAG",
27536 b"SEPARATOR",
27537 b"|",
27538 b"CASESENSITIVE",
27539 b"n",
27540 b"NUMERIC",
27541 ]);
27542 f.run(&[
27543 b"HSET",
27544 b"tv:1",
27545 b"g",
27546 b"Red, BLUE ",
27547 b"h",
27548 b"Aa|bB",
27549 b"n",
27550 b"1",
27551 ]);
27552 f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
27553 f
27554 }
27555
27556 #[test]
27560 fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
27561 let mut f = tagged();
27562 assert_eq!(
27563 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
27564 "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
27565 );
27566 assert_eq!(
27567 f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
27568 "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
27569 );
27570 }
27571
27572 #[test]
27575 fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
27576 let mut f = tagged();
27577 for (name, want) in [
27578 (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
27579 (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
27580 (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
27581 ] {
27582 assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
27583 }
27584 assert_eq!(
27585 f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
27586 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
27587 );
27588 }
27589
27590 #[test]
27593 fn asking_for_tag_values_counts_a_use_of_the_index() {
27594 let mut f = tagged();
27595 let uses = |f: &mut Fixture| {
27596 let reply = f.run(&[b"FT.INFO", b"tv"]);
27597 let at = reply.find("number_of_uses").expect("the field is reported");
27598 let value = reply[at..].split("\r\n").nth(1).unwrap();
27599 value.trim_start_matches(':').parse::<i64>().unwrap()
27600 };
27601 let before = uses(&mut f);
27602 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
27603 f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
27604 assert_eq!(uses(&mut f), before + 3);
27606 }
27607
27608 #[test]
27611 fn a_tag_field_with_nothing_in_it_answers_empty() {
27612 let mut f = Fixture::new();
27613 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
27614 assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
27615 }
27616
27617 #[test]
27620 fn a_dictionary_is_not_a_key() {
27621 let mut f = Fixture::new();
27622 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
27623 assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
27624 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
27625 assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
27626 }
27627
27628 #[test]
27631 fn a_dictionary_counts_the_terms_it_had_not_seen() {
27632 let mut f = Fixture::new();
27633 assert_eq!(
27634 f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
27635 ":3\r\n"
27636 );
27637 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
27638 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
27639 assert_eq!(
27640 f.run(&[b"FT.DICTDUMP", b"d"]),
27641 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
27642 );
27643 assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
27644 }
27645
27646 #[test]
27650 fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
27651 let mut f = Fixture::new();
27652 assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
27653 assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
27654 }
27655
27656 #[test]
27658 fn a_flush_drops_the_dictionaries() {
27659 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
27660 let mut f = Fixture::new();
27661 f.run(&[b"FT.DICTADD", b"d", b"a"]);
27662 f.run(&[flush]);
27663 assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
27664 }
27665 }
27666
27667 fn profiling() -> Fixture {
27672 let mut f = Fixture::new();
27673 f.run(&[
27674 b"FT.CREATE",
27675 b"ix",
27676 b"PREFIX",
27677 b"1",
27678 b"p:",
27679 b"SCHEMA",
27680 b"t",
27681 b"TEXT",
27682 b"n",
27683 b"NUMERIC",
27684 ]);
27685 f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
27686 f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
27687 f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
27688 f
27689 }
27690
27691 fn timeless(reply: &str) -> String {
27694 const KEYS: &[&str] = &[
27695 "+Total profile time",
27696 "+Parsing time",
27697 "+Workers queue time",
27698 "+Pipeline creation time",
27699 "+Time",
27700 ];
27701 let mut out = String::new();
27702 let mut parts = reply.split("\r\n").peekable();
27703 while let Some(part) = parts.next() {
27704 out.push_str(part);
27705 out.push_str("\r\n");
27706 if !KEYS.contains(&part) {
27707 continue;
27708 }
27709 match parts.next() {
27712 Some(head) if head.starts_with('$') => {
27713 parts.next();
27714 }
27715 _ => {}
27716 }
27717 out.push_str("<t>\r\n");
27718 }
27719 out.truncate(out.len() - 2);
27721 out
27722 }
27723
27724 #[test]
27727 fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
27728 let mut f = profiling();
27729 assert_eq!(
27730 timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
27731 "*2\r\n\
27732 *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\
27733 $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\
27734 *4\r\n+Shards\r\n*1\r\n*14\r\n\
27735 +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
27736 +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
27737 +Warning\r\n*1\r\n+None\r\n\
27738 +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
27739 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
27740 +Estimated number of matches\r\n:2\r\n\
27741 +Result processors profile\r\n*4\r\n\
27742 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
27743 *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
27744 *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
27745 *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
27746 +Coordinator\r\n*0\r\n"
27747 );
27748 let mut g = profiling();
27749 g.run(&[b"HELLO", b"3"]);
27750 let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
27751 assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
27752 assert!(
27753 three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
27754 "{three}"
27755 );
27756 assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
27757 assert!(
27758 three.contains(
27759 "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
27760 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
27761 +Estimated number of matches\r\n:2\r\n"
27762 ),
27763 "{three}"
27764 );
27765 }
27766
27767 #[test]
27770 fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
27771 let mut f = profiling();
27772 let tree = |f: &mut Fixture, query: &[u8]| {
27773 let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
27774 let at = reply.find("+Iterators profile").expect("a tree");
27775 let end = reply.find("+Result processors").expect("a list of steps");
27776 reply[at..end].to_string()
27777 };
27778 assert_eq!(
27779 tree(&mut f, b"alpha beta"),
27780 "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
27781 +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
27782 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
27783 +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
27784 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
27785 +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
27786 );
27787 assert!(tree(&mut f, b"alpha|beta").starts_with(
27788 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
27789 +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
27790 ));
27791 assert!(tree(&mut f, b"-alpha").starts_with(
27794 "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
27795 +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
27796 ));
27797 assert!(tree(&mut f, b"~alpha").starts_with(
27798 "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
27799 +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
27800 ));
27801 assert_eq!(
27803 tree(&mut f, b"*"),
27804 "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
27805 +Number of reading operations\r\n:3\r\n"
27806 );
27807 assert!(tree(&mut f, b"@n:[1 2]").starts_with(
27808 "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
27809 $19\r\n1.000000 - 2.000000\r\n"
27810 ));
27811 }
27812
27813 #[test]
27816 fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
27817 let mut f = profiling();
27818 f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
27819 let tree = |f: &mut Fixture, words: &[&[u8]]| {
27820 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
27821 argv.extend_from_slice(words);
27822 let reply = timeless(&f.run(&argv));
27823 let at = reply.find("+Iterators profile").expect("a tree");
27824 let end = reply.find("+Result processors").expect("a list of steps");
27825 reply[at..end].to_string()
27826 };
27827 assert_eq!(
27828 tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
27829 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
27830 +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
27831 +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
27832 +The number of iterators in the union is 2\r\n"
27833 );
27834 assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
27835 assert!(
27836 tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
27837 .contains("+Child iterators\r\n*2\r\n")
27838 );
27839 assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
27843 assert!(
27844 tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
27845 );
27846 }
27847
27848 #[test]
27851 fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
27852 let mut f = profiling();
27853 let steps = |f: &mut Fixture, words: &[&[u8]]| {
27854 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
27855 argv.extend_from_slice(words);
27856 let reply = timeless(&f.run(&argv));
27857 let at = reply.find("+Result processors").expect("a list of steps");
27858 let end = reply.find("+Coordinator").expect("an end");
27859 let mut out = Vec::new();
27860 let mut parts = reply[at..end].split("\r\n").peekable();
27861 while let Some(part) = parts.next() {
27862 if part == "+Type" {
27863 out.push(parts.next().unwrap_or_default().to_string());
27864 }
27865 }
27866 out
27867 };
27868 assert_eq!(
27869 steps(&mut f, &[]),
27870 ["+Index", "+Scorer", "+Sorter", "+Loader"]
27871 );
27872 assert_eq!(
27873 steps(&mut f, &[b"NOCONTENT"]),
27874 ["+Index", "+Scorer", "+Sorter"]
27875 );
27876 assert_eq!(
27879 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
27880 ["+Index", "+Counter"]
27881 );
27882 assert_eq!(
27885 steps(&mut f, &[b"SORTBY", b"n"]),
27886 ["+Index", "+Sorter", "+Loader"]
27887 );
27888 assert_eq!(
27889 steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
27890 ["+Index", "+Scorer", "+Sorter", "+Loader"]
27891 );
27892 assert_eq!(
27893 steps(&mut f, &[b"HIGHLIGHT"]),
27894 ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
27895 );
27896 assert_eq!(
27897 steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
27898 ["+Index", "+Scorer", "+Sorter"]
27899 );
27900 }
27901
27902 #[test]
27905 fn a_pipeline_names_every_step_after_what_it_runs() {
27906 let mut f = profiling();
27907 let steps = |f: &mut Fixture, words: &[&[u8]]| {
27908 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
27909 argv.extend_from_slice(words);
27910 let reply = timeless(&f.run(&argv));
27911 let at = reply.find("+Result processors").expect("a list of steps");
27912 let end = reply.find("+Coordinator").expect("an end");
27913 let mut out = Vec::new();
27914 let mut parts = reply[at..end].split("\r\n").peekable();
27915 while let Some(part) = parts.next() {
27916 if part == "+Type" {
27917 out.push(parts.next().unwrap_or_default().to_string());
27918 }
27919 }
27920 out
27921 };
27922 assert_eq!(steps(&mut f, &[]), ["+Index"]);
27923 assert_eq!(
27924 steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
27925 ["+Index", "+Projector - Literal 1"]
27926 );
27927 assert_eq!(
27928 steps(
27929 &mut f,
27930 &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
27931 ),
27932 ["+Index", "+Loader", "+Projector - Operator *"]
27933 );
27934 assert_eq!(
27935 steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
27936 ["+Index", "+Loader", "+Filter - Predicate >"]
27937 );
27938 assert_eq!(
27939 steps(
27940 &mut f,
27941 &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
27942 ),
27943 ["+Index", "+Loader", "+Grouper"]
27944 );
27945 assert_eq!(
27946 steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
27947 ["+Index", "+Loader", "+Sorter"]
27948 );
27949 assert_eq!(
27950 steps(&mut f, &[b"LIMIT", b"0", b"2"]),
27951 ["+Index", "+Pager/Limiter"]
27952 );
27953 assert_eq!(
27956 steps(
27957 &mut f,
27958 &[
27959 b"ADDSCORES",
27960 b"LOAD",
27961 b"1",
27962 b"@n",
27963 b"APPLY",
27964 b"@__score",
27965 b"AS",
27966 b"s"
27967 ]
27968 ),
27969 [
27970 "+Index",
27971 "+Scorer",
27972 "+Loader",
27973 "+Projector - Property __score"
27974 ]
27975 );
27976 }
27977
27978 #[test]
27986 fn a_sortable_field_is_read_without_the_key_being_opened() {
27987 let mut f = Fixture::new();
27988 f.run(&[
27989 b"FT.CREATE",
27990 b"sx",
27991 b"PREFIX",
27992 b"1",
27993 b"s:",
27994 b"SCHEMA",
27995 b"n",
27996 b"NUMERIC",
27997 b"SORTABLE",
27998 b"g",
27999 b"TAG",
28000 ]);
28001 f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
28002 f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
28003 let loads = |f: &mut Fixture, words: &[&[u8]]| {
28004 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
28005 argv.extend_from_slice(words);
28006 f.run(&argv).contains("+Loader")
28007 };
28008 assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
28009 assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
28010 assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
28011 assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
28012 assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
28013 assert!(loads(
28014 &mut f,
28015 &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
28016 ));
28017 assert!(loads(&mut f, &[b"LOAD", b"*"]));
28018 }
28019
28020 #[test]
28023 fn a_profile_checks_its_own_words_before_it_runs_anything() {
28024 let mut f = profiling();
28025 assert_eq!(
28026 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
28027 "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
28028 );
28029 assert_eq!(
28030 f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
28031 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
28032 );
28033 assert_eq!(
28036 f.run(&[
28037 b"FT.PROFILE",
28038 b"ix",
28039 b"LIMITED",
28040 b"SEARCH",
28041 b"QUERY",
28042 b"alpha"
28043 ]),
28044 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
28045 );
28046 assert_eq!(
28047 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
28048 "-The QUERY keyword is expected\r\n"
28049 );
28050 assert_eq!(
28051 f.run(&[
28052 b"FT.PROFILE",
28053 b"ix",
28054 b"AGGREGATE",
28055 b"QUERY",
28056 b"alpha",
28057 b"WITHCURSOR"
28058 ]),
28059 "-FT.PROFILE does not support cursor\r\n"
28060 );
28061 assert_eq!(
28064 f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
28065 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
28066 );
28067 assert_eq!(
28068 f.run(&[
28069 b"FT.PROFILE",
28070 b"ix",
28071 b"SEARCH",
28072 b"QUERY",
28073 b"alpha",
28074 b"extra"
28075 ]),
28076 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
28077 );
28078 }
28079
28080 #[test]
28082 fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
28083 let mut f = profiling();
28084 let one = f.run(&[
28085 b"FT.PROFILE",
28086 b"ix",
28087 b"search",
28088 b"limited",
28089 b"query",
28090 b"alpha",
28091 ]);
28092 let two = f.run(&[
28093 b"FT.PROFILE",
28094 b"ix",
28095 b"SEARCH",
28096 b"LIMITED",
28097 b"QUERY",
28098 b"alpha",
28099 ]);
28100 assert_eq!(timeless(&one), timeless(&two));
28101 }
28102
28103 #[test]
28108 fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
28109 let mut f = profiling();
28110 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
28111 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28112
28113 let mut f = profiling();
28114 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28115 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28116
28117 let mut f = profiling();
28118 assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
28119 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28120
28121 let mut f = profiling();
28122 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
28123 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28124 }
28125
28126 #[test]
28129 fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
28130 let mut f = profiling();
28131 let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
28132 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
28133 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
28134 assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
28136 }
28137
28138 #[test]
28141 fn dropping_the_documents_leaves_a_key_the_index_never_read() {
28142 let mut f = profiling();
28143 f.run(&[b"SET", b"p:4", b"alpha"]);
28144 f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
28145 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28146 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28147 assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
28148 }
28149
28150 #[test]
28153 fn another_index_over_the_same_keys_loses_the_documents_too() {
28154 let mut f = profiling();
28155 f.run(&[
28156 b"FT.CREATE",
28157 b"other",
28158 b"PREFIX",
28159 b"1",
28160 b"p:",
28161 b"SCHEMA",
28162 b"t",
28163 b"TEXT",
28164 ]);
28165 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28166 assert_eq!(
28167 f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
28168 "*1\r\n:0\r\n"
28169 );
28170 }
28171
28172 #[test]
28175 fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
28176 let mut f = profiling();
28177 assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
28178 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28179 assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
28180 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28181 }
28182
28183 #[test]
28188 fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
28189 let mut f = Fixture::new();
28190 assert_eq!(
28191 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28192 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28193 );
28194 assert_eq!(
28195 f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
28196 "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
28197 );
28198 let mut g = Fixture::new();
28199 g.run(&[b"HELLO", b"3"]);
28200 assert_eq!(
28201 g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28202 "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28203 );
28204 assert_eq!(
28205 g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
28206 "%1\r\n+EXTLOAD\r\n_\r\n"
28207 );
28208 }
28209
28210 #[test]
28213 fn a_help_row_carries_the_description_and_the_value_together() {
28214 let mut f = Fixture::new();
28215 assert_eq!(
28216 f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
28217 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
28218 +Value\r\n$3\r\n500\r\n"
28219 );
28220 let mut g = Fixture::new();
28221 g.run(&[b"HELLO", b"3"]);
28222 assert_eq!(
28223 g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
28224 "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
28225 +Value\r\n$3\r\n500\r\n"
28226 );
28227 }
28228
28229 #[test]
28232 fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
28233 let mut f = Fixture::new();
28234 assert_eq!(
28235 f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
28236 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28237 );
28238 for name in [
28239 b"TIMEOUT*".as_slice(),
28240 b"?IMEOUT",
28241 b"*TIMEOUT*",
28242 b"TIME",
28243 b"NOSUCH",
28244 b"",
28245 ] {
28246 assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
28247 }
28248 assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
28249 assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
28250 }
28251
28252 #[test]
28255 fn a_read_ignores_whatever_follows_the_name() {
28256 let mut f = Fixture::new();
28257 assert_eq!(
28258 f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
28259 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28260 );
28261 assert_eq!(
28262 f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
28263 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
28264 +Value\r\n$3\r\n500\r\n"
28265 );
28266 }
28267
28268 #[test]
28271 fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
28272 let mut f = Fixture::new();
28273 assert_eq!(
28274 f.run(&[b"FT.CONFIG"]),
28275 "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
28276 );
28277 for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
28278 let want = format!(
28279 "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
28280 String::from_utf8_lossy(sub)
28281 );
28282 assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
28283 }
28284 assert_eq!(
28285 f.run(&[b"ft.config", b"get"]),
28286 "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
28287 );
28288 assert_eq!(
28289 f.run(&[b"FT.CONFIG", b"bogus"]),
28290 "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
28291 );
28292 }
28293
28294 #[test]
28297 fn a_write_checks_the_name_then_the_setting_then_the_value() {
28298 let mut f = Fixture::new();
28299 for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
28300 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
28301 cmd.extend(tail);
28302 assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
28303 }
28304 for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
28305 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
28306 cmd.extend(tail);
28307 assert_eq!(
28308 f.run(&cmd),
28309 "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
28310 );
28311 }
28312 assert_eq!(
28313 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
28314 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
28315 );
28316 }
28317
28318 #[test]
28321 fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
28322 let mut f = Fixture::new();
28323 assert_eq!(
28324 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
28325 "+OK\r\n"
28326 );
28327 assert_eq!(
28328 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
28329 "+EXCESSARGS\r\n"
28330 );
28331 assert_eq!(
28332 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28333 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
28334 );
28335 }
28336
28337 #[test]
28340 fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
28341 let mut f = Fixture::new();
28342 for (given, want) in [
28343 (b"0x10".as_slice(), "16"),
28344 (b"0X1f", "31"),
28345 (b"+0x10", "16"),
28346 (b"+5", "5"),
28347 (b"010", "10"),
28348 (b"08", "8"),
28349 (b"0777", "777"),
28350 (b"1e3", "1000"),
28351 (b"0.0", "0"),
28352 (b"-0.0", "0"),
28353 ] {
28354 assert_eq!(
28355 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28356 "+OK\r\n",
28357 "{given:?}"
28358 );
28359 let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
28360 assert_eq!(
28361 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28362 want,
28363 "{given:?}"
28364 );
28365 }
28366 for given in [
28367 b" 5".as_slice(),
28368 b"5 ",
28369 b"1.5",
28370 b"1e-3",
28371 b"x",
28372 b"",
28373 b"0b11",
28374 b"0xg",
28375 b"nan",
28376 b"inf",
28377 b"1e100",
28378 b"99999999999999999999",
28379 ] {
28380 assert_eq!(
28381 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28382 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
28383 "{given:?}"
28384 );
28385 }
28386 }
28387
28388 #[test]
28391 fn a_negative_is_answered_by_whichever_reader_found_it() {
28392 let mut f = Fixture::new();
28393 for given in [b"-1".as_slice(), b"-16"] {
28394 assert_eq!(
28395 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28396 "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
28397 "{given:?}"
28398 );
28399 }
28400 for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
28401 assert_eq!(
28402 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28403 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
28404 "{given:?}"
28405 );
28406 }
28407 let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
28408 for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
28409 assert_eq!(
28410 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
28411 "+OK\r\n",
28412 "{given:?}"
28413 );
28414 assert_eq!(
28415 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
28416 unlimited,
28417 "{given:?}"
28418 );
28419 }
28420 }
28421
28422 #[test]
28425 fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
28426 let mut f = Fixture::new();
28427 for (given, want) in [
28428 (b"2147483647".as_slice(), "2147483647"),
28429 (b"2147483648", "unlimited"),
28430 (b"4294967295", "unlimited"),
28431 (b"9223372036854775806", "unlimited"),
28432 (b"0", "0"),
28433 ] {
28434 assert_eq!(
28435 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
28436 "+OK\r\n",
28437 "{given:?}"
28438 );
28439 let want = format!(
28440 "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
28441 want.len()
28442 );
28443 assert_eq!(
28444 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
28445 want,
28446 "{given:?}"
28447 );
28448 }
28449 }
28450
28451 #[test]
28454 fn a_number_out_of_range_names_the_limit_it_crossed() {
28455 let mut f = Fixture::new();
28456 let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
28457 for (name, given) in [
28458 (b"MINPREFIX".as_slice(), b"0".as_slice()),
28459 (b"MAX_AGGREGATE_GROUPS", b"0"),
28460 (b"BM25STD_TANH_FACTOR", b"0"),
28461 (b"DEFAULT_DIALECT", b"0"),
28462 (b"MINSTEMLEN", b"4294967296"),
28463 (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
28464 (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
28465 (b"CONNECT_TIMEOUT", b"2147483648"),
28466 ] {
28467 assert_eq!(
28468 f.run(&[b"FT.CONFIG", b"SET", name, given]),
28469 bounds,
28470 "{name:?}"
28471 );
28472 }
28473 for (name, given, want) in [
28474 (
28475 b"MINSTEMLEN".as_slice(),
28476 b"1".as_slice(),
28477 "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
28478 ),
28479 (
28480 b"MAX_AGGREGATE_GROUPS",
28481 b"67108865",
28482 "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
28483 ),
28484 (
28485 b"WORKERS",
28486 b"17",
28487 "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
28488 ),
28489 (
28490 b"_NUMERIC_RANGES_PARENTS",
28491 b"3",
28492 "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
28493 depth for balance\r\n",
28494 ),
28495 (
28496 b"DEFAULT_DIALECT",
28497 b"5",
28498 "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
28499 ),
28500 (
28501 b"_BG_INDEX_MEM_PCT_THR",
28502 b"101",
28503 "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
28504 100%\r\n",
28505 ),
28506 (
28507 b"BM25STD_TANH_FACTOR",
28508 b"10001",
28509 "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
28510 inclusive\r\n",
28511 ),
28512 (
28513 b"BG_INDEX_SLEEP_DURATION_US",
28514 b"1000000",
28515 "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
28516 999999 (usleep POSIX limit)\r\n",
28517 ),
28518 ] {
28519 assert_eq!(
28520 f.run(&[b"FT.CONFIG", b"SET", name, given]),
28521 want,
28522 "{name:?}"
28523 );
28524 }
28525 }
28526
28527 #[test]
28530 fn the_trimming_delays_are_checked_against_one_another() {
28531 let mut f = Fixture::new();
28532 assert_eq!(
28533 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
28534 "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
28535 _MAX_TRIM_DELAY_MS (5000)\r\n"
28536 );
28537 assert_eq!(
28538 f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
28539 "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
28540 _MIN_TRIM_DELAY_MS (2000)\r\n"
28541 );
28542 assert_eq!(
28543 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
28544 "+OK\r\n"
28545 );
28546 }
28547
28548 #[test]
28551 fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
28552 let mut f = Fixture::new();
28553 assert_eq!(
28554 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
28555 "+OK\r\n"
28556 );
28557 assert_eq!(
28558 f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
28559 "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
28560 );
28561 assert_eq!(
28562 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
28563 "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
28564 );
28565 assert_eq!(
28566 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
28567 "+OK\r\n"
28568 );
28569 assert_eq!(
28570 f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
28571 "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
28572 );
28573 assert_eq!(
28574 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
28575 "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
28576 );
28577 let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
28578 for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
28579 assert_eq!(
28580 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
28581 bad,
28582 "{given:?}"
28583 );
28584 }
28585 assert_eq!(
28586 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
28587 "+OK\r\n"
28588 );
28589 }
28590
28591 #[test]
28594 fn a_yes_or_no_setting_takes_those_two_words_only() {
28595 let mut f = Fixture::new();
28596 assert_eq!(
28597 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
28598 "+OK\r\n"
28599 );
28600 assert_eq!(
28601 f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
28602 "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
28603 );
28604 for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
28605 assert_eq!(
28606 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
28607 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
28608 "{given:?}"
28609 );
28610 }
28611 }
28612
28613 #[test]
28616 fn two_names_for_one_setting_move_together() {
28617 let mut f = Fixture::new();
28618 f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
28619 assert_eq!(
28620 f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
28621 "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
28622 );
28623 f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
28624 assert_eq!(
28625 f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
28626 "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
28627 );
28628 let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
28629 let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
28630 f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
28631 assert_eq!(
28632 f.run(&[b"FT.CONFIG", b"GET", short]),
28633 "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
28634 );
28635 assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
28636 assert_eq!(
28637 f.run(&[b"FT.CONFIG", b"GET", long]),
28638 "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
28639 );
28640 }
28641
28642 #[test]
28644 fn a_password_reads_back_as_stars_whatever_was_written() {
28645 let mut f = Fixture::new();
28646 assert_eq!(
28647 f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
28648 "+OK\r\n"
28649 );
28650 assert_eq!(
28651 f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
28652 "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
28653 );
28654 }
28655
28656 #[test]
28659 fn a_flush_leaves_the_settings_alone() {
28660 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
28661 let mut f = Fixture::new();
28662 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
28663 f.run(&[flush]);
28664 assert_eq!(
28665 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28666 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
28667 "{flush:?}"
28668 );
28669 }
28670 }
28671
28672 fn debugging() -> Fixture {
28677 let mut f = Fixture::new();
28678 f.run(&[
28679 b"FT.CREATE",
28680 b"dx",
28681 b"PREFIX",
28682 b"1",
28683 b"d:",
28684 b"SCHEMA",
28685 b"t",
28686 b"TEXT",
28687 b"g",
28688 b"TAG",
28689 b"n",
28690 b"NUMERIC",
28691 b"s",
28692 b"TEXT",
28693 b"SORTABLE",
28694 ]);
28695 f.run(&[
28696 b"HSET",
28697 b"d:1",
28698 b"t",
28699 b"running dogs",
28700 b"g",
28701 b"red,blue",
28702 b"n",
28703 b"1",
28704 b"s",
28705 b"Alpha",
28706 ]);
28707 f.run(&[
28708 b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
28709 ]);
28710 f.run(&[
28711 b"HSET",
28712 b"d:3",
28713 b"t",
28714 b"dogs alpha",
28715 b"g",
28716 b"green",
28717 b"n",
28718 b"3",
28719 ]);
28720 f
28721 }
28722
28723 #[test]
28726 fn a_term_dump_lists_the_stems_beside_the_words() {
28727 let mut f = debugging();
28728 assert_eq!(
28729 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
28730 "*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\
28731 $4\r\ndogs\r\n$7\r\nrunning\r\n"
28732 );
28733 }
28734
28735 #[test]
28738 fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
28739 let mut f = debugging();
28740 assert_eq!(
28741 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
28742 "*2\r\n:1\r\n:2\r\n"
28743 );
28744 assert_eq!(
28745 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
28746 "*2\r\n:1\r\n:2\r\n"
28747 );
28748 for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
28749 assert_eq!(
28750 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
28751 "-Can not find the inverted index\r\n",
28752 "{term:?}"
28753 );
28754 }
28755 }
28756
28757 #[test]
28760 fn a_tag_dump_pairs_every_value_with_its_documents() {
28761 let mut f = debugging();
28762 assert_eq!(
28763 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
28764 "*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\
28765 *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
28766 );
28767 }
28768
28769 #[test]
28772 fn a_number_dump_answers_a_single_range() {
28773 let mut f = debugging();
28774 assert_eq!(
28775 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
28776 "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
28777 );
28778 }
28779
28780 #[test]
28783 fn a_geo_field_is_dumped_as_a_numeric_one() {
28784 let mut f = Fixture::new();
28785 f.run(&[
28786 b"FT.CREATE",
28787 b"gx",
28788 b"PREFIX",
28789 b"1",
28790 b"q:",
28791 b"SCHEMA",
28792 b"loc",
28793 b"GEO",
28794 b"gg",
28795 b"AS",
28796 b"tag",
28797 b"TAG",
28798 ]);
28799 f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
28800 f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
28801 assert_eq!(
28802 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
28803 "*1\r\n*2\r\n:1\r\n:2\r\n"
28804 );
28805 assert_eq!(
28806 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
28807 "-Could not find given field in index spec\r\n"
28808 );
28809 }
28810
28811 #[test]
28814 fn a_dump_takes_the_attribute_and_not_the_identifier() {
28815 let mut f = Fixture::new();
28816 f.run(&[
28817 b"FT.CREATE",
28818 b"zx",
28819 b"PREFIX",
28820 b"1",
28821 b"z:",
28822 b"SCHEMA",
28823 b"gg",
28824 b"AS",
28825 b"tag",
28826 b"TAG",
28827 ]);
28828 f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
28829 assert_eq!(
28830 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
28831 "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
28832 );
28833 assert_eq!(
28834 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
28835 "-Could not find given field in index spec\r\n"
28836 );
28837 }
28838
28839 #[test]
28842 fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
28843 let mut f = debugging();
28844 assert_eq!(
28845 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
28846 "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
28847 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
28848 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
28849 +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\
28850 $5\r\nvalue\r\n$5\r\nalpha\r\n"
28851 );
28852 let mut g = debugging();
28853 g.run(&[b"HELLO", b"3"]);
28854 assert_eq!(
28855 g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
28856 "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
28857 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
28858 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
28859 +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\
28860 $5\r\nvalue\r\n$5\r\nalpha\r\n"
28861 );
28862 }
28863
28864 #[test]
28867 fn a_document_with_no_sortable_value_drops_the_key() {
28868 let mut f = debugging();
28869 assert_eq!(
28870 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
28871 "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
28872 +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"
28873 );
28874 }
28875
28876 #[test]
28879 fn the_flag_word_spells_out_the_bits_it_carries() {
28880 let mut f = Fixture::new();
28881 f.run(&[
28882 b"FT.CREATE",
28883 b"nx",
28884 b"NOOFFSETS",
28885 b"PREFIX",
28886 b"1",
28887 b"o:",
28888 b"SCHEMA",
28889 b"t",
28890 b"TEXT",
28891 ]);
28892 f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
28893 assert!(
28894 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
28895 .contains("$6\r\n(0x0):\r\n")
28896 );
28897 }
28898
28899 #[test]
28902 fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
28903 let mut f = debugging();
28904 assert!(
28905 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
28906 .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
28907 );
28908 }
28909
28910 #[test]
28913 fn a_document_row_reads_its_keyword_at_a_fixed_place() {
28914 let mut f = debugging();
28915 let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
28916 assert_eq!(
28917 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
28918 want
28919 );
28920 assert_eq!(
28921 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
28922 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
28923 );
28924 assert_eq!(
28925 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
28926 "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
28927 );
28928 }
28929
28930 #[test]
28933 fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
28934 let mut f = debugging();
28935 assert_eq!(
28936 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
28937 "-Document not found in index\r\n"
28938 );
28939 assert_eq!(
28940 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
28941 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
28942 );
28943 }
28944
28945 #[test]
28948 fn a_document_number_goes_both_ways() {
28949 let mut f = debugging();
28950 assert_eq!(
28951 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
28952 "$3\r\nd:2\r\n"
28953 );
28954 assert_eq!(
28955 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
28956 ":2\r\n"
28957 );
28958 assert_eq!(
28959 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
28960 ":0\r\n"
28961 );
28962 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
28963 for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
28964 assert_eq!(
28965 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
28966 "-document was removed\r\n",
28967 "{id:?}"
28968 );
28969 }
28970 }
28971
28972 #[test]
28975 fn a_document_number_is_read_the_strict_way() {
28976 let mut f = debugging();
28977 for id in [
28978 b"x".as_slice(),
28979 b"1.5",
28980 b" 1",
28981 b"+1",
28982 b"01",
28983 b"0x1",
28984 b"",
28985 b"9223372036854775808",
28986 b"18446744073709551615",
28987 ] {
28988 assert_eq!(
28989 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
28990 "-bad id given\r\n",
28991 "{id:?}"
28992 );
28993 }
28994 }
28995
28996 #[test]
28999 fn a_dump_keeps_a_number_the_table_has_given_up() {
29000 let mut f = debugging();
29001 f.run(&[b"DEL", b"d:2"]);
29002 assert_eq!(
29003 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29004 "*2\r\n:1\r\n:2\r\n"
29005 );
29006 assert_eq!(
29007 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
29008 "-document was removed\r\n"
29009 );
29010 assert_eq!(
29011 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
29012 ":0\r\n"
29013 );
29014 }
29015
29016 #[test]
29019 fn a_rewrite_takes_a_number_of_its_own() {
29020 let mut f = debugging();
29021 f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
29022 assert_eq!(
29023 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
29024 ":4\r\n"
29025 );
29026 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
29027 assert_eq!(
29028 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
29029 "-document was removed\r\n"
29030 );
29031 assert_eq!(
29032 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29033 "*2\r\n:1\r\n:2\r\n"
29034 );
29035 }
29036
29037 #[test]
29039 fn a_dump_follows_an_alias() {
29040 let mut f = debugging();
29041 f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
29042 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
29043 assert_eq!(
29044 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
29045 "$3\r\nd:1\r\n"
29046 );
29047 }
29048
29049 #[test]
29052 fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
29053 let mut f = debugging();
29054 assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
29055 assert_eq!(
29056 f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
29057 "-Can not create a search ctx\r\n"
29058 );
29059 assert_eq!(
29060 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
29061 "-Can not create a search ctx\r\n"
29062 );
29063 }
29064
29065 #[test]
29068 fn an_empty_field_dumps_as_nothing_at_all() {
29069 let mut f = Fixture::new();
29070 f.run(&[
29071 b"FT.CREATE",
29072 b"ex",
29073 b"PREFIX",
29074 b"1",
29075 b"e:",
29076 b"SCHEMA",
29077 b"t",
29078 b"TEXT",
29079 b"g",
29080 b"TAG",
29081 b"n",
29082 b"NUMERIC",
29083 ]);
29084 assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
29085 assert_eq!(
29086 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
29087 "*0\r\n"
29088 );
29089 assert_eq!(
29090 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
29091 "*0\r\n"
29092 );
29093 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
29094 }
29095
29096 #[test]
29099 fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
29100 let mut f = debugging();
29101 for (sub, extra) in [
29102 (b"DUMP_TERMS".as_slice(), 1),
29103 (b"GET_MAX_DOC_ID", 1),
29104 (b"DUMP_INVIDX", 2),
29105 (b"DUMP_TAGIDX", 2),
29106 (b"DUMP_NUMIDX", 2),
29107 (b"IDTODOCID", 2),
29108 (b"DOCIDTOID", 2),
29109 ] {
29110 let want = format!(
29111 "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
29112 str::from_utf8(sub).unwrap()
29113 );
29114 for given in [extra - 1, extra + 1] {
29115 let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
29116 cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
29117 assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
29118 }
29119 let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
29120 right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
29121 assert_ne!(f.run(&right), want, "{sub:?}");
29122 }
29123 assert_eq!(
29124 f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
29125 "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
29126 );
29127 }
29128
29129 #[test]
29132 fn the_help_names_the_subcommands_that_answer() {
29133 let mut f = Fixture::new();
29134 let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
29135 $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
29136 $14\r\nGET_MAX_DOC_ID\r\n";
29137 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
29138 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
29139 }
29140
29141 #[test]
29146 fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
29147 let mut f = Fixture::new();
29148 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29149 assert_eq!(
29150 f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
29151 "+OK\r\n"
29152 );
29153 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
29154 assert_eq!(
29155 f.run(&[b"FT.SYNDUMP", b"e"]),
29156 "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
29157 $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
29158 );
29159 }
29160
29161 #[test]
29165 fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
29166 let mut f = Fixture::new();
29167 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29168 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
29169 assert_eq!(
29170 f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
29171 "$69\r\nUNION {\n jogging\n ~gr(expanded)\n +jog(expanded)\n jog(expanded)\n}\n\r\n"
29172 );
29173 }
29174
29175 #[test]
29184 fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
29185 let mut f = Fixture::new();
29186 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29187 f.run(&[b"HSET", b"d1", b"t", b"boy"]);
29188 f.run(&[b"HSET", b"d2", b"t", b"runs"]);
29189 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
29190 assert_eq!(
29191 f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
29192 "*2\r\n:1\r\n$2\r\nd1\r\n"
29193 );
29194 assert_eq!(
29195 f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
29196 "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
29197 );
29198 }
29199
29200 #[test]
29203 fn a_synonym_command_on_a_name_that_is_not_there_fails() {
29204 let mut f = Fixture::new();
29205 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
29206 assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
29207 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
29208 }
29209
29210 #[test]
29214 fn params_counts_the_words_before_it_pairs_them_up() {
29215 let mut f = Fixture::new();
29216 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29217 let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
29218 Expected an argument, but none provided\r\n";
29219 let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
29220 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
29221 assert_eq!(
29222 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
29223 none
29224 );
29225 assert_eq!(
29226 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
29227 odd
29228 );
29229 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
29230 assert_eq!(
29231 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
29232 "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
29233 );
29234 }
29235
29236 fn vectored(f: &mut Fixture) {
29246 f.run(&[
29247 b"FT.CREATE",
29248 b"h",
29249 b"SCHEMA",
29250 b"t",
29251 b"TEXT",
29252 b"v",
29253 b"VECTOR",
29254 b"FLAT",
29255 b"6",
29256 b"TYPE",
29257 b"FLOAT32",
29258 b"DIM",
29259 b"2",
29260 b"DISTANCE_METRIC",
29261 b"L2",
29262 ]);
29263 let at: [&[u8]; 5] = [
29264 b"\x00\x00\x80\x40\x00\x00\x00\x00",
29265 b"\x00\x00\x40\x40\x00\x00\x00\x00",
29266 b"\x00\x00\x00\x40\x00\x00\x00\x00",
29267 b"\x00\x00\x80\x3f\x00\x00\x00\x00",
29268 b"\x00\x00\x00\x00\x00\x00\x00\x00",
29269 ];
29270 for (n, point) in at.iter().enumerate() {
29271 let key = format!("d{}", n + 1);
29272 let word: &[u8] = match n {
29273 0 | 1 | 3 => b"alpha",
29274 _ => b"beta",
29275 };
29276 f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
29277 }
29278 }
29279
29280 const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
29282
29283 #[test]
29287 fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
29288 let mut f = Fixture::new();
29289 vectored(&mut f);
29290 assert_eq!(
29291 f.run(&[
29292 b"FT.SEARCH",
29293 b"h",
29294 b"*=>[KNN 5 @v $vec]",
29295 b"PARAMS",
29296 b"2",
29297 b"vec",
29298 ORIGIN,
29299 b"DIALECT",
29300 b"2",
29301 b"NOCONTENT",
29302 ]),
29303 "*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"
29304 );
29305 assert_eq!(
29306 f.run(&[
29307 b"FT.SEARCH",
29308 b"h",
29309 b"*=>[KNN 3 @v $vec]",
29310 b"PARAMS",
29311 b"2",
29312 b"vec",
29313 ORIGIN,
29314 b"DIALECT",
29315 b"2",
29316 b"NOCONTENT",
29317 ]),
29318 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
29319 );
29320 }
29321
29322 #[test]
29325 fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
29326 let mut f = Fixture::new();
29327 vectored(&mut f);
29328 for (radius, want) in [
29329 ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
29330 ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
29331 (
29332 "9",
29333 "*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",
29334 ),
29335 ] {
29336 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
29337 assert_eq!(
29338 f.run(&[
29339 b"FT.SEARCH",
29340 b"h",
29341 query.as_bytes(),
29342 b"PARAMS",
29343 b"2",
29344 b"vec",
29345 ORIGIN,
29346 b"DIALECT",
29347 b"2",
29348 b"NOCONTENT",
29349 ]),
29350 want,
29351 "radius {radius}"
29352 );
29353 }
29354 }
29355
29356 #[test]
29360 fn a_knn_measures_what_the_query_in_front_of_it_matched() {
29361 let mut f = Fixture::new();
29362 vectored(&mut f);
29363 assert_eq!(
29364 f.run(&[
29365 b"FT.SEARCH",
29366 b"h",
29367 b"alpha=>[KNN 2 @v $vec]",
29368 b"PARAMS",
29369 b"2",
29370 b"vec",
29371 ORIGIN,
29372 b"DIALECT",
29373 b"2",
29374 b"NOCONTENT",
29375 ]),
29376 "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
29377 );
29378 }
29379
29380 #[test]
29389 fn a_count_and_a_radius_are_refused_in_their_own_words() {
29390 let mut f = Fixture::new();
29391 vectored(&mut f);
29392 let ask = |f: &mut Fixture, query: &str| {
29393 f.run(&[
29394 b"FT.SEARCH",
29395 b"h",
29396 query.as_bytes(),
29397 b"PARAMS",
29398 b"2",
29399 b"vec",
29400 ORIGIN,
29401 b"DIALECT",
29402 b"2",
29403 b"NOCONTENT",
29404 ])
29405 };
29406 for (query, at, near) in [
29407 ("*=>[KNN -1 @v $vec]", 8, "-1"),
29408 ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
29409 ("*=>[KNN +3 @v $vec]", 8, "+3"),
29410 ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
29411 ("*=>[KNN abc @v $vec]", 8, "abc"),
29412 ("*=>[KNN 3 $vec]", 10, "vec"),
29413 ("*=>[KNN 3 @v vec]", 13, "vec"),
29414 ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
29415 ] {
29416 assert_eq!(
29417 ask(&mut f, query),
29418 format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
29419 "{query}"
29420 );
29421 }
29422
29423 let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29426 query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
29427 assert_eq!(
29428 ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
29429 "*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"
29430 );
29431 assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
29432 assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
29433
29434 for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
29435 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
29436 assert_eq!(
29437 ask(&mut f, &query),
29438 format!(
29439 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29440 negative radius ({printed}) given in a range query\r\n"
29441 ),
29442 "{query}"
29443 );
29444 }
29445 assert_eq!(
29447 ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
29448 "*2\r\n:1\r\n$2\r\nd5\r\n"
29449 );
29450 }
29451
29452 #[test]
29455 fn a_count_that_came_from_params_is_read_by_its_own_rules() {
29456 let mut f = Fixture::new();
29457 vectored(&mut f);
29458 let ask = |f: &mut Fixture, count: &[u8]| {
29459 f.run(&[
29460 b"FT.SEARCH",
29461 b"h",
29462 b"*=>[KNN $k @v $vec]",
29463 b"PARAMS",
29464 b"4",
29465 b"vec",
29466 ORIGIN,
29467 b"k",
29468 count,
29469 b"DIALECT",
29470 b"2",
29471 b"NOCONTENT",
29472 ])
29473 };
29474 let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
29475 assert_eq!(ask(&mut f, b"3"), three);
29476 assert_eq!(ask(&mut f, b" 3"), three);
29477 assert_eq!(ask(&mut f, b"+3"), three);
29478 for bad in [
29479 &b"3.0"[..],
29480 b"0x3",
29481 b"-1",
29482 b"abc",
29483 b"",
29484 b"99999999999999999999",
29485 ] {
29486 let value = String::from_utf8_lossy(bad).into_owned();
29487 assert_eq!(
29488 ask(&mut f, bad),
29489 format!(
29490 "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
29491 for parameter `k`\r\n"
29492 ),
29493 "{value}"
29494 );
29495 }
29496 assert_eq!(
29497 ask(&mut f, b"288230376151711777"),
29498 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29499 query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
29500 );
29501 }
29502
29503 #[test]
29506 fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
29507 let mut f = Fixture::new();
29508 vectored(&mut f);
29509 assert_eq!(
29510 f.run(&[
29511 b"FT.SEARCH",
29512 b"h",
29513 b"*=>[KNN 5 @v $vec]",
29514 b"PARAMS",
29515 b"2",
29516 b"vec",
29517 b"abc",
29518 b"DIALECT",
29519 b"2",
29520 b"NOCONTENT",
29521 ]),
29522 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29523 query vector blob size (3) does not match index's expected size (8).\r\n"
29524 );
29525 }
29526
29527 #[test]
29532 fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
29533 let mut f = Fixture::new();
29534 vectored(&mut f);
29535 let ask = |f: &mut Fixture, query: &str| {
29536 f.run(&[
29537 b"FT.SEARCH",
29538 b"h",
29539 query.as_bytes(),
29540 b"PARAMS",
29541 b"2",
29542 b"vec",
29543 ORIGIN,
29544 b"DIALECT",
29545 b"2",
29546 b"LIMIT",
29547 b"0",
29548 b"1",
29549 ])
29550 };
29551 assert_eq!(
29552 ask(&mut f, "*=>[KNN 3 @v $vec]"),
29553 "*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"
29554 );
29555 assert_eq!(
29556 ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
29557 "*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"
29558 );
29559 assert_eq!(
29560 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
29561 "*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"
29562 );
29563 assert_eq!(
29564 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
29565 "*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"
29566 );
29567 }
29568
29569 #[test]
29576 fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
29577 let mut f = Fixture::new();
29578 vectored(&mut f);
29579 let ask = |f: &mut Fixture, ret: &[&[u8]]| {
29580 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
29581 args.extend_from_slice(ret);
29582 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
29583 f.run(&args)
29584 };
29585 assert_eq!(
29586 ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
29587 "*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"
29588 );
29589 assert_eq!(
29590 ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
29591 "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
29592 );
29593 assert_eq!(
29594 ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
29595 "*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"
29596 );
29597 assert_eq!(
29598 ask(&mut f, &[b"RETURN", b"1", b"t"]),
29599 "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
29600 );
29601 assert_eq!(
29604 ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
29605 "*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"
29606 );
29607 assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
29608 }
29609
29610 #[test]
29614 fn a_sortby_can_name_a_distance_the_query_yielded() {
29615 let mut f = Fixture::new();
29616 vectored(&mut f);
29617 let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
29618 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
29619 if desc {
29620 args.push(b"DESC");
29621 }
29622 args.extend_from_slice(&[
29623 b"PARAMS",
29624 b"2",
29625 b"vec",
29626 ORIGIN,
29627 b"DIALECT",
29628 b"2",
29629 b"NOCONTENT",
29630 ]);
29631 f.run(&args)
29632 };
29633 assert_eq!(
29634 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
29635 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
29636 );
29637 assert_eq!(
29638 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
29639 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
29640 );
29641 assert_eq!(
29642 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
29643 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
29644 );
29645 let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
29648 not loaded nor in schema\r\n";
29649 assert_eq!(
29650 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
29651 missing
29652 );
29653 assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
29654 assert_eq!(
29657 ask(&mut f, "foo(", b"zz", false),
29658 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
29659 );
29660 }
29661
29662 #[test]
29664 fn two_vector_clauses_answer_two_distances() {
29665 let mut f = Fixture::new();
29666 vectored(&mut f);
29667 assert_eq!(
29668 f.run(&[
29669 b"FT.SEARCH",
29670 b"h",
29671 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
29672 b"RETURN",
29673 b"2",
29674 b"rr",
29675 b"__v_score",
29676 b"PARAMS",
29677 b"2",
29678 b"vec",
29679 ORIGIN,
29680 b"DIALECT",
29681 b"2",
29682 ]),
29683 "*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"
29684 );
29685 }
29686
29687 #[test]
29691 fn an_aggregation_answers_a_distance_nothing_asked_for() {
29692 let mut f = Fixture::new();
29693 vectored(&mut f);
29694 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
29695 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
29696 args.extend_from_slice(rest);
29697 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
29698 f.run(&args)
29699 };
29700 assert_eq!(
29701 ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
29702 "*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"
29703 );
29704 assert_eq!(
29705 ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
29706 "*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"
29707 );
29708 assert_eq!(
29709 ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
29710 "*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"
29711 );
29712 assert_eq!(
29714 ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
29715 "*3\r\n:1\r\n*0\r\n*0\r\n"
29716 );
29717 assert_eq!(
29718 ask(
29719 &mut f,
29720 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
29721 &[]
29722 ),
29723 "*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"
29724 );
29725 }
29726
29727 #[test]
29731 fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
29732 let mut f = Fixture::new();
29733 vectored(&mut f);
29734 const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
29736 let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
29737 f.run(&[
29738 b"FT.AGGREGATE",
29739 b"h",
29740 query.as_bytes(),
29741 b"LOAD",
29742 b"1",
29743 b"@t",
29744 b"PARAMS",
29745 b"2",
29746 b"vec",
29747 vec,
29748 b"DIALECT",
29749 b"2",
29750 ])
29751 };
29752 assert_eq!(
29753 ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
29754 "*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"
29755 );
29756 assert_eq!(
29758 ask(
29759 &mut f,
29760 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
29761 MIDDLE
29762 ),
29763 "*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"
29764 );
29765 }
29766
29767 #[test]
29771 fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
29772 let mut f = Fixture::new();
29773 vectored(&mut f);
29774 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
29775 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
29776 args.extend_from_slice(rest);
29777 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
29778 f.run(&args)
29779 };
29780 let knn = "*=>[KNN 2 @v $vec]";
29781 assert_eq!(
29782 ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
29783 "*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"
29784 );
29785 assert_eq!(
29786 ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
29787 "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
29788 );
29789 assert_eq!(
29790 ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
29791 "*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"
29792 );
29793 assert_eq!(
29794 ask(
29795 &mut f,
29796 knn,
29797 &[
29798 b"GROUPBY",
29799 b"1",
29800 b"@t",
29801 b"REDUCE",
29802 b"MAX",
29803 b"1",
29804 b"@__v_score",
29805 b"AS",
29806 b"m"
29807 ]
29808 ),
29809 "*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"
29810 );
29811 assert_eq!(
29812 ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
29813 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
29814 `__v_score`\r\n"
29815 );
29816 assert_eq!(
29817 ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
29818 "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
29819 );
29820 assert_eq!(
29821 ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
29822 "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
29823 schema\r\n"
29824 );
29825 }
29826
29827 #[test]
29835 fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
29836 let mut f = Fixture::new();
29837 vectored(&mut f);
29838 let ask = |f: &mut Fixture, rest: &[&[u8]]| {
29839 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
29840 args.extend_from_slice(rest);
29841 f.run(&args)
29842 };
29843 assert_eq!(
29844 ask(
29845 &mut f,
29846 &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
29847 ),
29848 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
29849 );
29850 assert_eq!(
29851 ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
29852 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
29853 );
29854 assert_eq!(
29855 ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
29856 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
29857 );
29858 assert_eq!(
29861 ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
29862 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
29863 );
29864 assert_eq!(
29865 ask(
29866 &mut f,
29867 &[
29868 b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
29869 ]
29870 ),
29871 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
29872 );
29873 assert_eq!(
29874 ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
29875 "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
29876 );
29877 }
29878
29879 #[test]
29882 fn a_vector_step_says_which_way_it_was_answered() {
29883 let mut f = Fixture::new();
29884 vectored(&mut f);
29885 let tree = |f: &mut Fixture, query: &[u8]| {
29886 let reply = timeless(&f.run(&[
29887 b"FT.PROFILE",
29888 b"h",
29889 b"AGGREGATE",
29890 b"QUERY",
29891 query,
29892 b"PARAMS",
29893 b"2",
29894 b"vec",
29895 ORIGIN,
29896 b"DIALECT",
29897 b"2",
29898 ]));
29899 let at = reply.find("+Iterators profile").expect("a tree");
29900 let end = reply.find("+Result processors").expect("a list of steps");
29901 reply[at..end].to_string()
29902 };
29903 assert_eq!(
29904 tree(&mut f, b"*=>[KNN 3 @v $vec]"),
29905 "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
29906 +Number of reading operations\r\n:3\r\n\
29907 +Vector search mode\r\n+STANDARD_KNN\r\n"
29908 );
29909 assert_eq!(
29911 tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
29912 tree(&mut f, b"*=>[KNN 3 @v $vec]")
29913 );
29914 assert_eq!(
29917 tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
29918 "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
29919 +Number of reading operations\r\n:4\r\n"
29920 );
29921 assert_eq!(
29922 tree(
29923 &mut f,
29924 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
29925 ),
29926 "+Iterators profile\r\n*8\r\n\
29927 +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
29928 +Number of reading operations\r\n:4\r\n\
29929 +Vector search mode\r\n+RANGE_QUERY\r\n"
29930 );
29931 }
29932
29933 #[test]
29937 fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
29938 let mut f = Fixture::new();
29939 vectored(&mut f);
29940 let ask = |f: &mut Fixture, query: &[u8]| {
29941 timeless(&f.run(&[
29942 b"FT.PROFILE",
29943 b"h",
29944 b"AGGREGATE",
29945 b"QUERY",
29946 query,
29947 b"PARAMS",
29948 b"2",
29949 b"vec",
29950 ORIGIN,
29951 b"DIALECT",
29952 b"2",
29953 ]))
29954 };
29955 let cut = |reply: &str| {
29956 let at = reply.find("+Iterators profile").expect("a tree");
29957 reply[at..].to_string()
29958 };
29959 assert_eq!(
29960 cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
29961 "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
29962 +Number of reading operations\r\n:3\r\n\
29963 +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
29964 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
29965 +Number of reading operations\r\n:3\r\n\
29966 +Estimated number of matches\r\n:3\r\n\
29967 +Result processors profile\r\n*2\r\n\
29968 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
29969 *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
29970 +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
29971 );
29972 assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
29975 "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
29976 +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
29977 ));
29978 assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
29981 }
29982
29983 #[test]
29987 fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
29988 let mut f = profiling();
29989 let steps = |f: &mut Fixture, words: &[&[u8]]| {
29990 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
29991 argv.extend_from_slice(words);
29992 let reply = timeless(&f.run(&argv));
29993 let at = reply.find("+Result processors").expect("a list of steps");
29994 reply[at..].to_string()
29995 };
29996 assert_eq!(
29997 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
29998 "+Result processors profile\r\n*2\r\n\
29999 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
30000 *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
30001 +Coordinator\r\n*0\r\n"
30002 );
30003 assert!(
30004 steps(
30005 &mut f,
30006 &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
30007 )
30008 .contains("+Type\r\n+Counter\r\n")
30009 );
30010 assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
30012 "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
30013 +Results processed\r\n:2\r\n"
30014 ));
30015 }
30016
30017 #[test]
30023 fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
30024 let mut f = Fixture::new();
30025 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30026 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30027 f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
30028 assert_eq!(
30029 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
30030 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
30031 *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"
30032 );
30033 }
30034
30035 #[test]
30039 fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
30040 let mut f = Fixture::new();
30041 f.run(&[b"HELLO", b"3"]);
30042 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30043 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30044 assert_eq!(
30045 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
30046 "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
30047 *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
30048 );
30049 }
30050
30051 #[test]
30055 fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
30056 let mut f = Fixture::new();
30057 f.run(&[
30058 b"FT.CREATE",
30059 b"e",
30060 b"SCHEMA",
30061 b"a",
30062 b"TEXT",
30063 b"NOSTEM",
30064 b"b",
30065 b"TEXT",
30066 b"NOSTEM",
30067 ]);
30068 f.run(&[b"HSET", b"d1", b"b", b"world"]);
30069 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
30070 assert_eq!(
30071 f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
30072 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
30073 );
30074 }
30075
30076 #[test]
30080 fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
30081 let mut f = Fixture::new();
30082 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30083 f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
30084 assert_eq!(
30085 f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
30086 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
30087 *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"
30088 );
30089 assert_eq!(
30090 f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
30091 "*0\r\n"
30092 );
30093 assert_eq!(
30094 f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
30095 "-Dict does not exist: nope\r\n"
30096 );
30097 }
30098
30099 #[test]
30103 fn a_spellcheck_reads_its_arguments_leniently() {
30104 let mut f = Fixture::new();
30105 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30106 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30107 let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
30108 *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
30109 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
30110 let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
30111 let args: &[&[u8]] = &[
30112 b"FT.SPELLCHECK",
30113 b"e",
30114 b"helqp",
30115 b"DISTANCE",
30116 b"1",
30117 b"DISTANCE",
30118 b"4",
30119 ];
30120 assert_eq!(f.run(args), none);
30121 assert_eq!(
30122 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
30123 "-bad distance given, distance must be a natural number between 1 to 4\r\n"
30124 );
30125 assert_eq!(
30126 f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
30127 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
30128 );
30129 }
30130
30131 #[test]
30136 fn an_add_answers_how_many_suggestions_are_in_there_now() {
30137 let mut f = Fixture::new();
30138 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
30139 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
30140 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
30141 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
30142 assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
30143 }
30144
30145 #[test]
30148 fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
30149 let mut f = Fixture::new();
30150 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30151 assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
30152 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
30153 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
30154 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
30155 assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
30156 assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
30157 assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
30158 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
30159 }
30160
30161 #[test]
30164 fn deleting_the_last_suggestion_deletes_the_key() {
30165 let mut f = Fixture::new();
30166 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30167 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
30168 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
30169 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
30170 assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
30171 }
30172
30173 #[test]
30176 fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
30177 let mut f = Fixture::new();
30178 f.run(&[b"SET", b"s", b"x"]);
30179 for cmd in [
30180 vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
30181 vec![&b"FT.SUGGET"[..], b"s", b"t"],
30182 vec![&b"FT.SUGDEL"[..], b"s", b"t"],
30183 vec![&b"FT.SUGLEN"[..], b"s"],
30184 ] {
30185 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
30186 }
30187 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
30188 }
30189
30190 #[test]
30193 fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
30194 let mut f = Fixture::new();
30195 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30196 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
30197 f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
30198 assert_eq!(
30199 f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
30200 "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
30201 $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
30202 $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
30203 );
30204 assert_eq!(
30205 f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
30206 "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
30207 );
30208 assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
30209 }
30210
30211 #[test]
30213 fn fuzzy_allows_one_edit_and_nothing_allows_two() {
30214 let mut f = Fixture::new();
30215 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
30216 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
30217 assert_eq!(
30218 f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
30219 "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
30220 );
30221 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
30222 }
30223
30224 #[test]
30226 fn a_lookup_answers_five_unless_it_is_told_otherwise() {
30227 let mut f = Fixture::new();
30228 for (term, score) in [
30229 (&b"a1"[..], &b"1"[..]),
30230 (b"a2", b"2"),
30231 (b"a3", b"3"),
30232 (b"a4", b"4"),
30233 (b"a5", b"5"),
30234 (b"a6", b"6"),
30235 ] {
30236 f.run(&[b"FT.SUGADD", b"s", term, score]);
30237 }
30238 assert_eq!(
30239 f.run(&[b"FT.SUGGET", b"s", b"a"]),
30240 "*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"
30241 );
30242 assert_eq!(
30243 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
30244 "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
30245 );
30246 assert!(
30248 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
30249 .starts_with("*6\r\n")
30250 );
30251 }
30252
30253 #[test]
30256 fn a_payload_comes_back_beside_the_term_or_a_null_does() {
30257 let mut f = Fixture::new();
30258 f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
30259 assert_eq!(
30260 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30261 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30262 );
30263 f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
30264 assert_eq!(
30265 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30266 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30267 );
30268 f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
30271 assert_eq!(
30272 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30273 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30274 );
30275 f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
30277 assert_eq!(
30278 f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
30279 "*2\r\n$5\r\nother\r\n$-1\r\n"
30280 );
30281 }
30282
30283 #[test]
30287 fn incr_adds_to_the_score_that_is_already_there() {
30288 let mut f = Fixture::new();
30289 for _ in 0..3 {
30290 f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
30291 }
30292 assert_eq!(
30293 f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
30294 "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
30295 );
30296 }
30297
30298 #[test]
30300 fn the_suggestion_errors_are_the_lines_the_module_sends() {
30301 let mut f = Fixture::new();
30302 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30303 assert_eq!(
30304 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
30305 "-ERR invalid score\r\n"
30306 );
30307 assert_eq!(
30309 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
30310 "-Unknown argument `NOPE`\r\n"
30311 );
30312 assert_eq!(
30313 f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
30314 "-Invalid payload: Expected an argument, but none provided\r\n"
30315 );
30316 assert!(
30318 f.run(&[
30319 b"FT.SUGADD",
30320 b"s",
30321 b"t",
30322 b"1",
30323 b"PAYLOAD",
30324 b"a",
30325 b"PAYLOAD",
30326 b"b"
30327 ])
30328 .contains("wrong number of arguments")
30329 );
30330 assert_eq!(
30331 f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
30332 "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
30333 );
30334 for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
30338 assert_eq!(
30339 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30340 "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
30341 "{}",
30342 String::from_utf8_lossy(max)
30343 );
30344 }
30345 for max in [
30346 &b"abc"[..],
30347 b"0.0",
30348 b"00",
30349 b"-0",
30350 b"+0",
30351 b"0.5",
30352 b"-1.5",
30353 b"1e400",
30354 ] {
30355 assert_eq!(
30356 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30357 "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
30358 "{}",
30359 String::from_utf8_lossy(max)
30360 );
30361 }
30362 for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
30363 assert_eq!(
30364 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30365 "*1\r\n$3\r\none\r\n",
30366 "{}",
30367 String::from_utf8_lossy(max)
30368 );
30369 }
30370 assert_eq!(
30371 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
30372 "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
30373 );
30374 assert_eq!(
30377 f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
30378 "-ERR invalid score\r\n"
30379 );
30380 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
30381 }
30382
30383 #[test]
30387 fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
30388 let mut f = Fixture::new();
30389 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30390 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
30391 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
30392 assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
30393 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
30394 assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
30395 assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
30396 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
30397 }
30398
30399 #[test]
30402 fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
30403 let mut f = Fixture::new();
30404 f.run(&[
30405 b"FT.CREATE",
30406 b"ix",
30407 b"PREFIX",
30408 b"1",
30409 b"p:",
30410 b"SCHEMA",
30411 b"n",
30412 b"NUMERIC",
30413 ]);
30414 f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
30415 assert_eq!(held(&f, b"ix"), (0, 0));
30416
30417 let reply = f.run(&[b"FT.INFO", b"ix"]);
30418 assert!(
30419 reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
30420 "{reply}"
30421 );
30422 assert!(reply.contains("hash_indexing_failures"), "{reply}");
30423 }
30424
30425 #[test]
30428 fn an_index_can_only_be_made_on_database_zero() {
30429 let mut f = Fixture::new();
30430 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
30431 f.run(&[b"SELECT", b"1"]);
30432 let refused = "-Cannot create index on db != 0\r\n";
30433 assert_eq!(
30434 f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
30435 refused
30436 );
30437 assert_eq!(
30439 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
30440 refused
30441 );
30442 assert_eq!(
30444 f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
30445 refused
30446 );
30447 assert_eq!(
30449 f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
30450 "+OK\r\n"
30451 );
30452 assert_eq!(f.server.search.lock().len(), 1);
30453 }
30454
30455 #[test]
30461 fn the_scan_is_one_database_and_the_following_is_all_of_them() {
30462 let mut f = Fixture::new();
30463 f.run(&[b"SELECT", b"1"]);
30464 f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
30465 f.run(&[b"SELECT", b"0"]);
30466 f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
30467 f.run(&[
30468 b"FT.CREATE",
30469 b"ix",
30470 b"PREFIX",
30471 b"1",
30472 b"p:",
30473 b"SCHEMA",
30474 b"t",
30475 b"TEXT",
30476 ]);
30477 assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
30478
30479 f.run(&[b"SELECT", b"1"]);
30480 f.run(&[b"HSET", b"p:8", b"t", b"later"]);
30481 assert_eq!(
30482 held(&f, b"ix"),
30483 (2, 2),
30484 "and then it follows every database"
30485 );
30486 }
30487
30488 fn corpus(f: &mut Fixture) {
30491 f.run(&[
30492 b"FT.CREATE",
30493 b"sx",
30494 b"PREFIX",
30495 b"1",
30496 b"d:",
30497 b"SCHEMA",
30498 b"t",
30499 b"TEXT",
30500 b"g",
30501 b"TAG",
30502 b"n",
30503 b"NUMERIC",
30504 ]);
30505 for (key, text, tag, number) in [
30506 (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
30507 (b"d:2", "alpha gamma", "bb", "2"),
30508 (b"d:3", "delta", "cc", "3"),
30509 (b"d:4", "alpha beta gamma", "aa,cc", "4"),
30510 ] {
30511 f.run(&[
30512 b"HSET",
30513 key,
30514 b"t",
30515 text.as_bytes(),
30516 b"g",
30517 tag.as_bytes(),
30518 b"n",
30519 number.as_bytes(),
30520 ]);
30521 }
30522 }
30523
30524 fn sortable(f: &mut Fixture) {
30528 f.run(&[
30529 b"FT.CREATE",
30530 b"sy",
30531 b"PREFIX",
30532 b"1",
30533 b"s:",
30534 b"SCHEMA",
30535 b"t",
30536 b"TEXT",
30537 b"SORTABLE",
30538 b"n",
30539 b"NUMERIC",
30540 b"SORTABLE",
30541 b"body",
30542 b"AS",
30543 b"b",
30544 b"TEXT",
30545 b"SORTABLE",
30546 b"p",
30547 b"TEXT",
30548 ]);
30549 for (key, text, number) in [
30550 (b"s:1".as_slice(), "Banana Split", "2"),
30551 (b"s:2", "apple", "10"),
30552 ] {
30553 f.run(&[
30554 b"HSET",
30555 key,
30556 b"t",
30557 text.as_bytes(),
30558 b"n",
30559 number.as_bytes(),
30560 b"body",
30561 text.as_bytes(),
30562 b"p",
30563 b"alpha",
30564 ]);
30565 }
30566 f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
30569 }
30570
30571 #[test]
30574 fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
30575 let mut f = Fixture::new();
30576 sortable(&mut f);
30577 assert_eq!(
30578 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
30579 "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
30580 );
30581 assert_eq!(
30582 f.run(&[
30583 b"FT.SEARCH",
30584 b"sy",
30585 b"alpha",
30586 b"SORTBY",
30587 b"n",
30588 b"DESC",
30589 b"NOCONTENT"
30590 ]),
30591 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
30592 );
30593 assert_eq!(
30596 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
30597 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
30598 );
30599 }
30600
30601 #[test]
30604 fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
30605 let mut f = Fixture::new();
30606 sortable(&mut f);
30607 f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
30608 assert_eq!(
30609 f.run(&[
30610 b"FT.SEARCH",
30611 b"sy",
30612 b"alpha",
30613 b"SORTBY",
30614 b"p",
30615 b"NOCONTENT",
30616 b"LIMIT",
30617 b"0",
30618 b"2"
30619 ]),
30620 "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
30621 );
30622 assert_eq!(
30625 f.run(&[
30626 b"FT.SEARCH",
30627 b"sy",
30628 b"alpha",
30629 b"SORTBY",
30630 b"p",
30631 b"WITHSORTKEYS",
30632 b"NOCONTENT",
30633 b"LIMIT",
30634 b"2",
30635 b"1"
30636 ]),
30637 "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
30638 );
30639 }
30640
30641 #[test]
30644 fn a_search_can_send_the_value_it_sorted_by_back() {
30645 let mut f = Fixture::new();
30646 sortable(&mut f);
30647 assert_eq!(
30648 f.run(&[
30649 b"FT.SEARCH",
30650 b"sy",
30651 b"alpha",
30652 b"SORTBY",
30653 b"n",
30654 b"WITHSORTKEYS",
30655 b"NOCONTENT"
30656 ]),
30657 concat!(
30658 "*7\r\n:3\r\n",
30659 "$3\r\ns:1\r\n$2\r\n#2\r\n",
30660 "$3\r\ns:2\r\n$3\r\n#10\r\n",
30661 "$3\r\ns:3\r\n$-1\r\n"
30662 )
30663 );
30664 assert_eq!(
30665 f.run(&[
30666 b"FT.SEARCH",
30667 b"sy",
30668 b"alpha",
30669 b"SORTBY",
30670 b"t",
30671 b"WITHSORTKEYS",
30672 b"NOCONTENT"
30673 ]),
30674 concat!(
30675 "*7\r\n:3\r\n",
30676 "$3\r\ns:2\r\n$6\r\n$apple\r\n",
30677 "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
30678 "$3\r\ns:3\r\n$-1\r\n"
30679 )
30680 );
30681 assert_eq!(
30684 f.run(&[
30685 b"FT.SEARCH",
30686 b"sy",
30687 b"banana",
30688 b"WITHSORTKEYS",
30689 b"NOCONTENT"
30690 ]),
30691 "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
30692 );
30693 }
30694
30695 #[test]
30698 fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
30699 let mut f = Fixture::new();
30700 sortable(&mut f);
30701 assert_eq!(
30705 f.run(&[
30706 b"FT.SEARCH",
30707 b"sy",
30708 b"alpha",
30709 b"SORTBY",
30710 b"b",
30711 b"LIMIT",
30712 b"0",
30713 b"1"
30714 ]),
30715 concat!(
30716 "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
30717 "$1\r\nb\r\n$5\r\napple\r\n",
30718 "$1\r\nt\r\n$5\r\napple\r\n",
30719 "$1\r\nn\r\n$2\r\n10\r\n",
30720 "$4\r\nbody\r\n$5\r\napple\r\n",
30721 "$1\r\np\r\n$5\r\nalpha\r\n"
30722 )
30723 );
30724 assert_eq!(
30727 f.run(&[
30728 b"FT.SEARCH",
30729 b"sy",
30730 b"alpha",
30731 b"SORTBY",
30732 b"b",
30733 b"RETURN",
30734 b"2",
30735 b"p",
30736 b"b",
30737 b"LIMIT",
30738 b"0",
30739 b"1"
30740 ]),
30741 concat!(
30742 "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
30743 "$1\r\nb\r\n$5\r\napple\r\n",
30744 "$1\r\np\r\n$5\r\nalpha\r\n"
30745 )
30746 );
30747 }
30748
30749 #[test]
30751 fn a_search_refuses_the_sorts_it_cannot_run() {
30752 let mut f = Fixture::new();
30753 sortable(&mut f);
30754 assert_eq!(
30755 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
30756 "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
30757 );
30758 assert_eq!(
30759 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
30760 "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
30761 );
30762 assert_eq!(
30763 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
30764 "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
30765 );
30766 assert_eq!(
30767 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
30768 "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
30769 );
30770 assert_eq!(
30773 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
30774 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
30775 );
30776 }
30777
30778 fn marking(f: &mut Fixture) {
30782 f.run(&[
30783 b"FT.CREATE",
30784 b"mk",
30785 b"ON",
30786 b"HASH",
30787 b"PREFIX",
30788 b"1",
30789 b"m:",
30790 b"SCHEMA",
30791 b"a",
30792 b"TEXT",
30793 b"b",
30794 b"TEXT",
30795 b"n",
30796 b"NUMERIC",
30797 b"g",
30798 b"TAG",
30799 ]);
30800 f.run(&[
30801 b"HSET",
30802 b"m:1",
30803 b"a",
30804 b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
30805 b"b",
30806 b"t1 t2 t3 t4 t5 t6 t7 t8",
30807 b"n",
30808 b"1",
30809 b"g",
30810 b"red",
30811 ]);
30812 }
30813
30814 #[test]
30817 fn a_summarize_cuts_a_field_down_to_what_matched() {
30818 let mut f = Fixture::new();
30819 marking(&mut f);
30820 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
30821 assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
30822 assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
30824 assert!(got.contains("$1\r\nr\r\n"), "{got}");
30826 }
30827
30828 #[test]
30832 fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
30833 let mut f = Fixture::new();
30834 marking(&mut f);
30835 let got = f.run(&[
30836 b"FT.SEARCH",
30837 b"mk",
30838 b"fox",
30839 b"SUMMARIZE",
30840 b"FRAGS",
30841 b"1",
30842 b"LEN",
30843 b"20",
30844 ]);
30845 assert!(
30846 got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
30847 "{got}"
30848 );
30849 let two = f.run(&[
30852 b"FT.SEARCH",
30853 b"mk",
30854 b"fox",
30855 b"SUMMARIZE",
30856 b"FRAGS",
30857 b"2",
30858 b"LEN",
30859 b"20",
30860 ]);
30861 assert!(
30862 two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
30863 "{two}"
30864 );
30865 }
30866
30867 #[test]
30870 fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
30871 let mut f = Fixture::new();
30872 marking(&mut f);
30873 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
30874 assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
30875 let both = f.run(&[
30876 b"FT.SEARCH",
30877 b"mk",
30878 b"fox",
30879 b"SUMMARIZE",
30880 b"LEN",
30881 b"2",
30882 b"HIGHLIGHT",
30883 ]);
30884 assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
30885 assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
30887 assert!(both.contains("$3\r\nred\r\n"), "{both}");
30888 let split = f.run(&[
30890 b"FT.SEARCH",
30891 b"mk",
30892 b"fox",
30893 b"SUMMARIZE",
30894 b"FIELDS",
30895 b"1",
30896 b"b",
30897 b"LEN",
30898 b"2",
30899 b"HIGHLIGHT",
30900 b"FIELDS",
30901 b"1",
30902 b"a",
30903 ]);
30904 assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
30905 }
30906
30907 #[test]
30909 fn a_highlight_does_not_mark_a_tag() {
30910 let mut f = Fixture::new();
30911 marking(&mut f);
30912 f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
30913 let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
30914 assert!(!got.contains("<b>"), "{got}");
30915 assert!(got.contains("red and blue"), "{got}");
30916 }
30917
30918 #[test]
30921 fn a_search_answers_a_total_and_then_the_rows() {
30922 let mut f = Fixture::new();
30923 corpus(&mut f);
30924 assert_eq!(
30925 f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
30926 "*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"
30927 );
30928 f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
30931 assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
30932 assert_eq!(
30935 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
30936 "*2\r\n:1\r\n$3\r\nd:3\r\n"
30937 );
30938 assert_eq!(
30939 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
30940 "*1\r\n:3\r\n"
30941 );
30942 }
30943
30944 #[test]
30947 fn the_window_is_ten_rows_and_a_million_wide_at_most() {
30948 let mut f = Fixture::new();
30949 corpus(&mut f);
30950 assert_eq!(
30951 f.run(&[
30952 b"FT.SEARCH",
30953 b"sx",
30954 b"alpha",
30955 b"NOCONTENT",
30956 b"LIMIT",
30957 b"1",
30958 b"1"
30959 ]),
30960 "*2\r\n:3\r\n$3\r\nd:2\r\n"
30961 );
30962 assert_eq!(
30963 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
30964 "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
30965 );
30966 assert_eq!(
30967 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
30968 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
30969 );
30970 assert_eq!(
30971 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
30972 "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
30973 );
30974 assert_eq!(
30975 f.run(&[
30976 b"FT.SEARCH",
30977 b"sx",
30978 b"alpha",
30979 b"NOCONTENT",
30980 b"LIMIT",
30981 b"999999",
30982 b"1000000"
30983 ]),
30984 "*1\r\n:3\r\n"
30985 );
30986 }
30987
30988 #[test]
30992 fn a_return_of_nothing_is_not_the_same_as_nocontent() {
30993 let mut f = Fixture::new();
30994 corpus(&mut f);
30995 let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
30996 assert_eq!(
30997 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
30998 bare
30999 );
31000 assert_eq!(
31001 f.run(&[
31002 b"FT.SEARCH",
31003 b"sx",
31004 b"delta",
31005 b"NOCONTENT",
31006 b"RETURN",
31007 b"1",
31008 b"t"
31009 ]),
31010 bare
31011 );
31012 assert_eq!(
31013 f.run(&[
31014 b"FT.SEARCH",
31015 b"sx",
31016 b"delta",
31017 b"RETURN",
31018 b"0",
31019 b"RETURN",
31020 b"1",
31021 b"t"
31022 ]),
31023 "*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"
31024 );
31025 }
31026
31027 #[test]
31030 fn the_count_after_return_counts_words() {
31031 let mut f = Fixture::new();
31032 corpus(&mut f);
31033 assert_eq!(
31036 f.run(&[
31037 b"FT.SEARCH",
31038 b"sx",
31039 b"delta",
31040 b"RETURN",
31041 b"3",
31042 b"t",
31043 b"AS",
31044 b"x"
31045 ]),
31046 "*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"
31047 );
31048 assert_eq!(
31051 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
31052 "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
31053 );
31054 assert_eq!(
31055 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
31056 "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
31057 );
31058 assert_eq!(
31062 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
31063 "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
31064 );
31065 }
31066
31067 #[test]
31070 fn a_filter_is_a_range_written_outside_the_query() {
31071 let mut f = Fixture::new();
31072 corpus(&mut f);
31073 assert_eq!(
31074 f.run(&[
31075 b"FT.SEARCH",
31076 b"sx",
31077 b"alpha",
31078 b"NOCONTENT",
31079 b"FILTER",
31080 b"n",
31081 b"2",
31082 b"4"
31083 ]),
31084 "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
31085 );
31086 assert_eq!(
31087 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
31088 "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
31089 );
31090 assert_eq!(
31091 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
31092 "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
31093 );
31094 assert_eq!(
31095 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
31096 "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
31097 );
31098 for field in [b"g".as_slice(), b"nope"] {
31101 assert_eq!(
31102 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
31103 "*1\r\n:0\r\n"
31104 );
31105 }
31106 }
31107
31108 #[test]
31111 fn the_index_is_found_before_the_arguments_are_read() {
31112 let mut f = Fixture::new();
31113 corpus(&mut f);
31114 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
31115 assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
31116 assert_eq!(
31117 f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
31118 missing
31119 );
31120 assert_eq!(
31123 f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
31124 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
31125 );
31126 }
31127
31128 #[test]
31131 fn inkeys_comes_off_the_total() {
31132 let mut f = Fixture::new();
31133 corpus(&mut f);
31134 assert_eq!(
31135 f.run(&[
31136 b"FT.SEARCH",
31137 b"sx",
31138 b"alpha",
31139 b"NOCONTENT",
31140 b"INKEYS",
31141 b"1",
31142 b"d:1"
31143 ]),
31144 "*2\r\n:1\r\n$3\r\nd:1\r\n"
31145 );
31146 assert_eq!(
31147 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
31148 "*1\r\n:0\r\n"
31149 );
31150 }
31151
31152 #[test]
31159 fn the_fields_are_read_from_the_session_database() {
31160 let mut f = Fixture::new();
31161 corpus(&mut f);
31162 f.run(&[b"SELECT", b"1"]);
31163 f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
31164 assert_eq!(
31167 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
31168 "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
31169 );
31170 assert_eq!(
31171 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
31172 "*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"
31173 );
31174 }
31175
31176 #[test]
31179 fn the_third_protocol_answers_a_map_of_five() {
31180 let mut f = Fixture::new();
31181 corpus(&mut f);
31182 f.out = Out::new(Proto::Resp3);
31183 assert_eq!(
31184 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
31185 concat!(
31186 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
31187 "%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",
31188 "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
31189 )
31190 );
31191 }
31192
31193 #[test]
31197 fn a_window_of_nothing_has_to_start_at_the_top() {
31198 let mut f = Fixture::new();
31199 corpus(&mut f);
31200 let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
31201 assert_eq!(
31202 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31203 refused
31204 );
31205 assert_eq!(
31206 f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31207 refused
31208 );
31209 assert_eq!(
31210 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31211 refused
31212 );
31213 assert_eq!(
31214 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
31215 "*1\r\n:3\r\n"
31216 );
31217 }
31218
31219 #[test]
31222 fn an_aggregation_answers_a_count_and_then_the_properties() {
31223 let mut f = Fixture::new();
31224 corpus(&mut f);
31225 assert_eq!(
31226 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
31227 "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
31228 );
31229 assert_eq!(
31233 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
31234 concat!(
31235 "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
31236 "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
31237 "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
31238 )
31239 );
31240 assert_eq!(
31244 f.run(&[
31245 b"FT.AGGREGATE",
31246 b"sx",
31247 b"alpha",
31248 b"LOAD",
31249 b"1",
31250 b"@n",
31251 b"LIMIT",
31252 b"1",
31253 b"2"
31254 ]),
31255 "*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"
31256 );
31257 assert_eq!(
31259 f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
31260 "*1\r\n:0\r\n"
31261 );
31262 }
31263
31264 #[test]
31268 fn a_load_counts_words_and_can_rename_what_it_reads() {
31269 let mut f = Fixture::new();
31270 corpus(&mut f);
31271 assert_eq!(
31273 f.run(&[
31274 b"FT.AGGREGATE",
31275 b"sx",
31276 b"alpha",
31277 b"LOAD",
31278 b"3",
31279 b"@t",
31280 b"AS",
31281 b"text"
31282 ]),
31283 concat!(
31284 "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
31285 "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
31286 "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
31287 )
31288 );
31289 assert_eq!(
31290 f.run(&[
31291 b"FT.AGGREGATE",
31292 b"sx",
31293 b"alpha",
31294 b"LOAD",
31295 b"*",
31296 b"LIMIT",
31297 b"0",
31298 b"1"
31299 ]),
31300 concat!(
31301 "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
31302 "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
31303 )
31304 );
31305 assert_eq!(
31307 f.run(&[
31308 b"FT.AGGREGATE",
31309 b"sx",
31310 b"alpha",
31311 b"LOAD",
31312 b"2",
31313 b"@n",
31314 b"@nope",
31315 b"LIMIT",
31316 b"0",
31317 b"2"
31318 ]),
31319 "*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"
31320 );
31321 }
31322
31323 #[test]
31326 fn a_load_refuses_a_count_it_cannot_use() {
31327 let mut f = Fixture::new();
31328 corpus(&mut f);
31329 let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
31330 assert_eq!(
31331 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
31332 format!("{head}Expected number of fields or `*`\r\n")
31333 );
31334 assert_eq!(
31335 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
31336 format!("{head}Value is outside acceptable bounds\r\n")
31337 );
31338 assert_eq!(
31339 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
31340 format!("{head}Expected an argument, but none provided\r\n")
31341 );
31342 assert_eq!(
31343 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
31344 format!("{head}Expected an argument, but none provided\r\n")
31345 );
31346 assert_eq!(
31350 f.run(&[
31351 b"FT.AGGREGATE",
31352 b"sx",
31353 b"alpha",
31354 b"LOAD",
31355 b"2",
31356 b"@t",
31357 b"AS"
31358 ]),
31359 "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
31360 );
31361 assert_eq!(
31364 f.run(&[
31365 b"FT.AGGREGATE",
31366 b"sx",
31367 b"alpha",
31368 b"LOAD",
31369 b"2",
31370 b"@t",
31371 b"AS",
31372 b"VERBATIM"
31373 ]),
31374 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
31375 );
31376 assert_eq!(
31380 f.run(&[
31381 b"FT.AGGREGATE",
31382 b"sx",
31383 b"alpha",
31384 b"LOAD",
31385 b"0",
31386 b"LIMIT",
31387 b"0",
31388 b"1"
31389 ]),
31390 "*2\r\n:1\r\n*0\r\n"
31391 );
31392 }
31393
31394 #[test]
31397 fn a_pipeline_step_closes_the_door_on_the_search_words() {
31398 let mut f = Fixture::new();
31399 corpus(&mut f);
31400 assert_eq!(
31401 f.run(&[
31402 b"FT.AGGREGATE",
31403 b"sx",
31404 b"alpha",
31405 b"LOAD",
31406 b"1",
31407 b"@t",
31408 b"VERBATIM"
31409 ]),
31410 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
31411 );
31412 assert_eq!(
31413 f.run(&[
31414 b"FT.AGGREGATE",
31415 b"sx",
31416 b"alpha",
31417 b"LIMIT",
31418 b"0",
31419 b"1",
31420 b"VERBATIM"
31421 ]),
31422 "*2\r\n:1\r\n*0\r\n"
31423 );
31424 for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
31427 let name = core::str::from_utf8(word).expect("the three words are text");
31428 assert_eq!(
31429 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
31430 format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
31431 );
31432 }
31433 }
31434
31435 #[test]
31438 fn addscores_writes_a_shorter_score_than_withscores() {
31439 let mut f = Fixture::new();
31440 corpus(&mut f);
31441 assert_eq!(
31442 f.run(&[
31443 b"FT.AGGREGATE",
31444 b"sx",
31445 b"alpha",
31446 b"ADDSCORES",
31447 b"LOAD",
31448 b"1",
31449 b"@n",
31450 b"LIMIT",
31451 b"0",
31452 b"2"
31453 ]),
31454 concat!(
31455 "*3\r\n:1\r\n",
31456 "*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",
31457 "*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"
31458 )
31459 );
31460 assert_eq!(
31464 f.run(&[
31465 b"FT.AGGREGATE",
31466 b"sx",
31467 b"alpha",
31468 b"NOCONTENT",
31469 b"WITHSCORES",
31470 b"LIMIT",
31471 b"0",
31472 b"2"
31473 ]),
31474 "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
31475 );
31476 assert_eq!(
31477 f.run(&[
31478 b"FT.AGGREGATE",
31479 b"sx",
31480 b"alpha",
31481 b"WITHSORTKEYS",
31482 b"LOAD",
31483 b"1",
31484 b"@n",
31485 b"LIMIT",
31486 b"0",
31487 b"1"
31488 ]),
31489 "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
31490 );
31491 }
31492
31493 #[test]
31496 fn a_normalising_scorer_answers_the_rows_backwards() {
31497 let mut f = Fixture::new();
31498 corpus(&mut f);
31499 assert_eq!(
31500 f.run(&[
31501 b"FT.AGGREGATE",
31502 b"sx",
31503 b"alpha",
31504 b"SCORER",
31505 b"BM25STD.NORM",
31506 b"ADDSCORES",
31507 b"LOAD",
31508 b"1",
31509 b"@n",
31510 b"LIMIT",
31511 b"1",
31512 b"2"
31513 ]),
31514 concat!(
31515 "*3\r\n:3\r\n",
31516 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
31517 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
31518 )
31519 );
31520 assert_eq!(
31523 f.run(&[
31524 b"FT.AGGREGATE",
31525 b"sx",
31526 b"alpha",
31527 b"SCORER",
31528 b"BM25STD.NORM",
31529 b"LOAD",
31530 b"1",
31531 b"@n",
31532 b"LIMIT",
31533 b"1",
31534 b"2"
31535 ]),
31536 "*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"
31537 );
31538 }
31539
31540 #[test]
31543 fn an_aggregation_answers_a_map_of_five_as_well() {
31544 let mut f = Fixture::new();
31545 corpus(&mut f);
31546 f.out = Out::new(Proto::Resp3);
31547 assert_eq!(
31548 f.run(&[
31549 b"FT.AGGREGATE",
31550 b"sx",
31551 b"alpha",
31552 b"ADDSCORES",
31553 b"WITHSCORES",
31554 b"WITHSORTKEYS",
31555 b"LOAD",
31556 b"1",
31557 b"@n",
31558 b"LIMIT",
31559 b"0",
31560 b"1"
31561 ]),
31562 concat!(
31563 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
31564 "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
31565 "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
31566 "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
31567 "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
31568 )
31569 );
31570 assert_eq!(
31573 f.run(&[
31574 b"FT.AGGREGATE",
31575 b"sx",
31576 b"alpha",
31577 b"NOCONTENT",
31578 b"LIMIT",
31579 b"0",
31580 b"1"
31581 ]),
31582 concat!(
31583 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
31584 "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
31585 )
31586 );
31587 }
31588 const INFO_FIELDS: &[&str] = &[
31596 "id",
31597 "addr",
31598 "laddr",
31599 "fd",
31600 "name",
31601 "age",
31602 "idle",
31603 "flags",
31604 "db",
31605 "sub",
31606 "psub",
31607 "ssub",
31608 "multi",
31609 "watch",
31610 "qbuf",
31611 "qbuf-free",
31612 "argv-mem",
31613 "multi-mem",
31614 "rbs",
31615 "rbp",
31616 "obl",
31617 "oll",
31618 "omem",
31619 "omem-shared",
31620 "omem-unshared",
31621 "tot-mem",
31622 "events",
31623 "cmd",
31624 "user",
31625 "redir",
31626 "resp",
31627 "lib-name",
31628 "lib-ver",
31629 "io-thread",
31630 "tot-net-in",
31631 "tot-net-out",
31632 "tot-cmds",
31633 "read-events",
31634 "avg-pipeline-len-sum",
31635 "avg-pipeline-len-cnt",
31636 ];
31637
31638 fn client_info(f: &mut Fixture) -> Vec<(String, String)> {
31641 let reply = f.run(&[b"CLIENT", b"INFO"]);
31642 let body = reply.split_once("\r\n").expect("a bulk header").1;
31643 let line = body.trim_end_matches("\r\n").trim_start_matches("txt:");
31646 assert!(
31647 line.ends_with('\n'),
31648 "the report ends in a newline: {line:?}"
31649 );
31650 line.trim_end()
31651 .split(' ')
31652 .map(|pair| {
31653 let (name, value) = pair.split_once('=').expect("name=value");
31654 (name.to_string(), value.to_string())
31655 })
31656 .collect()
31657 }
31658
31659 fn client_field(f: &mut Fixture, name: &str) -> String {
31661 client_info(f)
31662 .into_iter()
31663 .find(|(n, _)| n == name)
31664 .map(|(_, v)| v)
31665 .unwrap_or_else(|| panic!("no {name} field"))
31666 }
31667
31668 #[test]
31669 fn client_info_names_every_field_a_real_server_names() {
31670 let mut f = Fixture::new();
31671 let got: Vec<String> = client_info(&mut f).into_iter().map(|(n, _)| n).collect();
31672 assert_eq!(got, INFO_FIELDS);
31673 }
31674
31675 #[test]
31678 fn a_connection_with_no_socket_reports_no_address_and_no_descriptor() {
31679 let mut f = Fixture::new();
31680 assert_eq!(client_field(&mut f, "addr"), "");
31681 assert_eq!(client_field(&mut f, "laddr"), "");
31682 assert_eq!(client_field(&mut f, "fd"), "-1");
31683 assert_eq!(client_field(&mut f, "id"), "7");
31684 }
31685
31686 #[test]
31687 fn client_setname_takes_a_name_back_and_refuses_one_with_a_space_in_it() {
31688 let mut f = Fixture::new();
31689 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$-1\r\n");
31690 assert_eq!(f.run(&[b"CLIENT", b"SETNAME", b"worker"]), "+OK\r\n");
31691 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
31692 assert_eq!(client_field(&mut f, "name"), "worker");
31693 assert_eq!(
31694 f.run(&[b"CLIENT", b"SETNAME", b"two words"]),
31695 "-ERR Client names cannot contain spaces, newlines or special characters.\r\n"
31696 );
31697 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
31699 }
31700
31701 #[test]
31704 fn reset_clears_the_name_and_the_switches_and_keeps_the_library() {
31705 let mut f = Fixture::new();
31706 f.run(&[b"CLIENT", b"SETNAME", b"worker"]);
31707 f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME", b"yo-py"]);
31708 f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"1.2.3"]);
31709 f.run(&[b"CLIENT", b"NO-EVICT", b"on"]);
31710 f.run(&[b"CLIENT", b"NO-TOUCH", b"on"]);
31711 assert_eq!(client_field(&mut f, "flags"), "eT");
31712
31713 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
31714 assert_eq!(client_field(&mut f, "name"), "");
31715 assert_eq!(client_field(&mut f, "flags"), "N");
31716 assert_eq!(client_field(&mut f, "lib-name"), "yo-py");
31717 assert_eq!(client_field(&mut f, "lib-ver"), "1.2.3");
31718 }
31719
31720 #[test]
31721 fn client_setinfo_complains_the_way_a_real_server_does() {
31722 let mut f = Fixture::new();
31723 assert_eq!(
31724 f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME"]),
31725 "-ERR wrong number of arguments for 'client|setinfo' command\r\n"
31726 );
31727 assert_eq!(
31728 f.run(&[b"CLIENT", b"SETINFO", b"NOPE", b"x"]),
31729 "-ERR Unrecognized option 'NOPE'\r\n"
31730 );
31731 assert_eq!(
31732 f.run(&[b"CLIENT", b"SETINFO", b"lib-name", b"ok x"]),
31733 "-ERR lib-name cannot contain spaces, newlines or special characters.\r\n"
31734 );
31735 assert_eq!(
31736 f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"has space"]),
31737 "-ERR lib-ver cannot contain spaces, newlines or special characters.\r\n"
31738 );
31739 }
31740
31741 #[test]
31742 fn client_refuses_a_subcommand_it_does_not_have_and_arguments_it_did_not_ask_for() {
31743 let mut f = Fixture::new();
31744 assert_eq!(
31745 f.run(&[b"CLIENT", b"NOPE"]),
31746 "-ERR unknown subcommand 'NOPE'. Try CLIENT HELP.\r\n"
31747 );
31748 assert_eq!(
31749 f.run(&[b"CLIENT", b"GETNAME", b"extra"]),
31750 "-ERR wrong number of arguments for 'client|getname' command\r\n"
31751 );
31752 assert_eq!(
31753 f.run(&[b"CLIENT", b"NO-EVICT", b"maybe"]),
31754 "-ERR syntax error\r\n"
31755 );
31756 assert_eq!(
31757 f.run(&[b"CLIENT", b"REPLY", b"BAD"]),
31758 "-ERR syntax error\r\n"
31759 );
31760 }
31761
31762 #[test]
31766 fn client_info_counts_the_three_subscribe_namespaces_apart() {
31767 let mut f = Fixture::new();
31768 f.run(&[b"HELLO", b"3"]);
31771 f.run(&[b"SUBSCRIBE", b"a", b"b"]);
31772 f.run(&[b"PSUBSCRIBE", b"p*"]);
31773 f.run(&[b"SSUBSCRIBE", b"s"]);
31774 let info = client_info(&mut f);
31775 let at = |name: &str| {
31776 info.iter()
31777 .find(|(n, _)| n == name)
31778 .map(|(_, v)| v.clone())
31779 .unwrap()
31780 };
31781 assert_eq!(at("sub"), "2");
31782 assert_eq!(at("psub"), "1");
31783 assert_eq!(at("ssub"), "1");
31784 assert_eq!(at("flags"), "P");
31785 forget_session(&f.server, &mut f.session);
31786 }
31787
31788 #[test]
31791 fn client_info_reports_itself_as_the_command_running() {
31792 let mut f = Fixture::new();
31793 assert_eq!(client_field(&mut f, "cmd"), "client|info");
31794 f.run(&[b"GET", b"nothing"]);
31795 assert_eq!(client_field(&mut f, "cmd"), "client|info");
31798 }
31799
31800 #[test]
31802 fn the_command_field_is_lower_case_however_the_client_spelled_it() {
31803 let mut f = Fixture::new();
31804 let reply = f.run(&[b"CLIENT", b"Info"]);
31805 assert!(reply.contains("cmd=client|info"), "{reply}");
31806 }
31807
31808 #[test]
31809 fn client_help_lists_the_subcommands_that_are_here() {
31810 let mut f = Fixture::new();
31811 let reply = f.run(&[b"CLIENT", b"HELP"]);
31812 for sub in ["ID", "GETNAME", "SETNAME", "SETINFO", "INFO", "REPLY"] {
31813 assert!(reply.contains(sub), "no {sub} in {reply}");
31814 }
31815 assert!(!reply.contains("TRACKING"), "{reply}");
31818 }
31819
31820 fn commands(stream: &str) -> Vec<Vec<String>> {
31828 let mut out = Vec::new();
31829 let mut rest = stream;
31830 while let Some(tail) = rest.strip_prefix('*') {
31831 let (n, tail) = tail.split_once("\r\n").expect("a header ends");
31832 let mut one = Vec::new();
31833 let mut tail = tail;
31834 for _ in 0..n.parse::<usize>().expect("a count") {
31835 let body = tail.strip_prefix('$').expect("a bulk string");
31836 let (len, body) = body.split_once("\r\n").expect("a length ends");
31837 let len: usize = len.parse().expect("a length");
31838 one.push(body[..len].to_string());
31839 tail = &body[len + 2..];
31840 }
31841 out.push(one);
31842 rest = tail;
31843 }
31844 assert!(rest.is_empty(), "left over: {rest:?}");
31845 out
31846 }
31847
31848 fn only(stream: &str) -> Vec<String> {
31850 let mut each = commands(stream);
31851 assert_eq!(each.len(), 1, "expected one command: {stream:?}");
31852 each.pop().expect("one command")
31853 }
31854
31855 #[test]
31856 fn the_stream_opens_with_a_select_and_says_it_once() {
31857 let mut f = Fixture::replicated();
31858 assert_eq!(
31859 commands(&f.crossed(&[b"SET", b"k", b"v"])),
31860 vec![
31861 vec!["SELECT".to_string(), "0".to_string()],
31862 vec!["SET".to_string(), "k".to_string(), "v".to_string()],
31863 ]
31864 );
31865 assert_eq!(only(&f.crossed(&[b"SET", b"k2", b"v"])), ["SET", "k2", "v"]);
31867 f.run(&[b"SELECT", b"3"]);
31870 assert_eq!(
31871 commands(&f.crossed(&[b"SET", b"k3", b"v"])),
31872 vec![
31873 vec!["SELECT".to_string(), "3".to_string()],
31874 vec!["SET".to_string(), "k3".to_string(), "v".to_string()],
31875 ]
31876 );
31877 }
31878
31879 #[test]
31884 fn a_relative_deadline_crosses_as_the_instant_it_resolved_to() {
31885 let mut f = Fixture::replicated();
31886 f.crossed(&[b"SET", b"seed", b"1"]);
31887 for parts in [
31888 &[b"SET".as_slice(), b"k", b"v", b"EX", b"100"][..],
31889 &[b"SETEX".as_slice(), b"k", b"100", b"v"][..],
31890 ] {
31891 let words = only(&f.crossed(parts));
31892 assert_eq!(&words[..3], ["SET", "k", "v"], "{words:?}");
31893 assert_eq!(words[3], "PXAT", "{words:?}");
31894 let at: i64 = words[4].parse().expect("an instant");
31895 assert!(at > f.server.clock.now_ms() as i64, "{words:?}");
31896 }
31897 for parts in [
31898 &[b"EXPIRE".as_slice(), b"k", b"50"][..],
31899 &[b"PEXPIRE".as_slice(), b"k", b"50000"][..],
31900 &[b"EXPIREAT".as_slice(), b"k", b"99999999999"][..],
31901 &[b"GETEX".as_slice(), b"k", b"EX", b"100"][..],
31902 ] {
31903 let words = only(&f.crossed(parts));
31904 assert_eq!(&words[..2], ["PEXPIREAT", "k"], "{words:?}");
31905 }
31906 assert_eq!(
31907 only(&f.crossed(&[b"GETEX", b"k", b"PERSIST"])),
31908 ["PERSIST", "k"]
31909 );
31910 }
31911
31912 #[test]
31914 fn nothing_crosses_for_a_read_or_for_a_failure() {
31915 let mut f = Fixture::replicated();
31916 f.crossed(&[b"SET", b"k", b"v"]);
31917 for parts in [
31918 &[b"GET".as_slice(), b"k"][..],
31919 &[b"TYPE".as_slice(), b"k"][..],
31920 &[b"STRLEN".as_slice(), b"k"][..],
31921 &[b"EXISTS".as_slice(), b"k"][..],
31922 &[b"PING".as_slice()][..],
31923 &[b"LPUSH".as_slice(), b"k", b"a"][..],
31926 &[b"INCR".as_slice(), b"k"][..],
31927 ] {
31928 assert_eq!(f.crossed(parts), "", "{parts:?}");
31929 }
31930 }
31931
31932 #[test]
31940 fn a_write_that_changed_nothing_still_crosses() {
31941 let mut f = Fixture::replicated();
31942 f.crossed(&[b"SET", b"k", b"v"]);
31943 assert_eq!(
31944 only(&f.crossed(&[b"DEL", b"nosuchkey"])),
31945 ["DEL", "nosuchkey"]
31946 );
31947 assert_eq!(only(&f.crossed(&[b"SET", b"k", b"v"])), ["SET", "k", "v"]);
31948 for parts in [
31950 &[b"SPOP".as_slice(), b"nosuchset"][..],
31951 &[b"EXPIRE".as_slice(), b"nosuchkey", b"100"][..],
31952 &[
31953 b"XADD".as_slice(),
31954 b"nosuchstream",
31955 b"NOMKSTREAM",
31956 b"*",
31957 b"f",
31958 b"v",
31959 ][..],
31960 ] {
31961 assert_eq!(f.crossed(parts), "", "{parts:?}");
31962 }
31963 }
31964
31965 #[test]
31968 fn a_condition_that_held_crosses_without_it() {
31969 let mut f = Fixture::replicated();
31970 f.crossed(&[b"SET", b"seed", b"1"]);
31971 assert_eq!(only(&f.crossed(&[b"SETNX", b"k", b"v"])), ["SET", "k", "v"]);
31972 assert_eq!(
31973 only(&f.crossed(&[b"SET", b"k", b"w", b"XX"])),
31974 ["SET", "k", "w"]
31975 );
31976 }
31977
31978 #[test]
31980 fn a_random_or_derived_write_crosses_as_what_it_did() {
31981 let mut f = Fixture::replicated();
31982 f.crossed(&[b"SET", b"seed", b"1"]);
31983 f.crossed(&[b"SADD", b"s", b"one", b"two"]);
31984 let words = only(&f.crossed(&[b"SPOP", b"s"]));
31985 assert_eq!(&words[..2], ["SREM", "s"], "{words:?}");
31986 assert!(words[2] == "one" || words[2] == "two", "{words:?}");
31987 assert_eq!(only(&f.crossed(&[b"SPOP", b"s"])), ["DEL", "s"]);
31990 f.crossed(&[b"SET", b"n", b"1"]);
31991 assert_eq!(
31992 only(&f.crossed(&[b"INCRBYFLOAT", b"n", b"1.5"])),
31993 ["SET", "n", "2.5", "KEEPTTL"]
31994 );
31995 assert_eq!(only(&f.crossed(&[b"GETDEL", b"n"])), ["DEL", "n"]);
31996 let words = only(&f.crossed(&[b"XADD", b"st", b"*", b"f", b"v"]));
31997 assert_eq!(&words[..2], ["XADD", "st"], "{words:?}");
31998 assert_ne!(words[2], "*", "an auto id has to be resolved: {words:?}");
31999 assert_eq!(&words[3..], ["f", "v"], "{words:?}");
32000 }
32001
32002 #[test]
32009 fn an_expiry_a_read_noticed_crosses_as_a_deletion() {
32010 let mut f = Fixture::replicated();
32011 f.crossed(&[b"SET", b"k", b"v", b"PX", b"50"]);
32012 f.advance(100);
32013 assert_eq!(only(&f.crossed(&[b"GET", b"k"])), ["DEL", "k"]);
32014 f.run(&[b"SET", b"k2", b"v", b"PX", b"50"]);
32016 f.crossed(&[b"PING"]);
32017 f.advance(100);
32018 assert_eq!(
32019 commands(&f.crossed(&[b"LPUSH", b"k2", b"a"])),
32020 vec![
32021 vec!["DEL".to_string(), "k2".to_string()],
32022 vec!["LPUSH".to_string(), "k2".to_string(), "a".to_string()],
32023 ]
32024 );
32025 }
32026
32027 #[test]
32032 fn a_blocking_command_that_parked_crosses_nothing() {
32033 let mut f = Fixture::replicated();
32034 f.crossed(&[b"SET", b"seed", b"1"]);
32035 assert_eq!(f.flow(&[b"BLPOP", b"gone", b"0"]).0, Flow::Block);
32036 assert_eq!(f.server.stream_since(f.mark).0, "");
32037 f.run(&[b"XADD", b"st", b"1-1", b"f", b"v"]);
32038 f.run(&[b"XGROUP", b"CREATE", b"st", b"g", b"$"]);
32039 f.crossed(&[b"PING"]);
32040 f.run(&[
32043 b"XREADGROUP",
32044 b"GROUP",
32045 b"g",
32046 b"c",
32047 b"COUNT",
32048 b"1",
32049 b"STREAMS",
32050 b"st",
32051 b">",
32052 ]);
32053 assert_eq!(
32054 only(&f.crossed(&[b"PING"])),
32055 ["XGROUP", "CREATECONSUMER", "st", "g", "c"]
32056 );
32057 }
32058
32059 #[test]
32062 fn every_xgroup_subcommand_that_changed_something_crosses() {
32063 let mut f = Fixture::replicated();
32064 f.crossed(&[b"XADD", b"st", b"1-1", b"f", b"v"]);
32065 assert_eq!(
32068 only(&f.crossed(&[b"XGROUP", b"CREATE", b"st", b"g", b"$"])),
32069 ["XGROUP", "CREATE", "st", "g", "1-1"]
32070 );
32071 assert_eq!(
32072 only(&f.crossed(&[b"XGROUP", b"CREATECONSUMER", b"st", b"g", b"c"])),
32073 ["XGROUP", "CREATECONSUMER", "st", "g", "c"]
32074 );
32075 assert_eq!(
32076 only(&f.crossed(&[b"XGROUP", b"SETID", b"st", b"g", b"0"])),
32077 ["XGROUP", "SETID", "st", "g", "0-0"]
32078 );
32079 assert_eq!(
32080 only(&f.crossed(&[b"XGROUP", b"DELCONSUMER", b"st", b"g", b"c"])),
32081 ["XGROUP", "DELCONSUMER", "st", "g", "c"]
32082 );
32083 assert_eq!(
32084 only(&f.crossed(&[b"XGROUP", b"DESTROY", b"st", b"g"])),
32085 ["XGROUP", "DESTROY", "st", "g"]
32086 );
32087 assert_eq!(f.crossed(&[b"XGROUP", b"DESTROY", b"st", b"g"]), "");
32089 }
32090
32091 #[test]
32096 fn a_publish_crosses_with_nobody_listening() {
32097 let mut f = Fixture::replicated();
32098 f.crossed(&[b"SET", b"seed", b"1"]);
32099 assert_eq!(
32100 only(&f.crossed(&[b"PUBLISH", b"news", b"hello"])),
32101 ["PUBLISH", "news", "hello"]
32102 );
32103 assert_eq!(
32104 only(&f.crossed(&[b"SPUBLISH", b"news", b"hello"])),
32105 ["SPUBLISH", "news", "hello"]
32106 );
32107 }
32108
32109 #[test]
32117 fn a_replica_asking_to_carry_on_is_caught_up_from_the_backlog() {
32118 let mut f = Fixture::replicated();
32119 f.crossed(&[b"SET", b"k", b"v"]);
32120 let id = f.server.repl_id();
32121 let had = f.mark;
32122 f.crossed(&[b"SET", b"k2", b"later"]);
32123 let asked = (had + 1).to_string();
32124 let reply = f.run(&[b"PSYNC", &id, asked.as_bytes()]);
32125 assert!(reply.starts_with("+CONTINUE "), "{reply}");
32126 assert!(reply.contains("later"), "{reply}");
32127 assert_eq!(reply.matches("k2").count(), 1, "{reply}");
32129 }
32130
32131 #[test]
32133 fn a_replica_with_no_history_is_sent_a_snapshot() {
32134 let mut f = Fixture::replicated();
32135 f.crossed(&[b"SET", b"k", b"v"]);
32136 let reply = f.run(&[b"PSYNC", b"?", b"-1"]);
32137 assert!(reply.starts_with("+FULLRESYNC "), "{reply}");
32138 let body = reply.split_once("\r\n").expect("a header ends").1;
32140 assert!(body.starts_with('$'), "{body:?}");
32141 assert!(!body.ends_with("\r\n"), "{body:?}");
32142 }
32143
32144 #[test]
32148 fn a_read_only_replica_refuses_a_write_and_answers_a_read() {
32149 let mut f = Fixture::new();
32150 f.run(&[b"SET", b"k", b"v"]);
32151 f.server.pretend_following("127.0.0.1", 6379, true);
32152 assert_eq!(
32153 f.run(&[b"SET", b"k", b"other"]),
32154 "-READONLY You can't write against a read only replica.\r\n"
32155 );
32156 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
32157 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
32159 }
32160
32161 #[test]
32164 fn a_master_takes_writes_however_the_read_only_setting_is_left() {
32165 let mut f = Fixture::new();
32166 f.server.set_replica_read_only(true);
32167 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32168 f.server.pretend_following("127.0.0.1", 6379, true);
32169 assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32170 f.server.set_replica_read_only(false);
32172 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32173 f.server.set_replica_read_only(true);
32174 f.server.pretend_master();
32177 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32178 }
32179
32180 #[test]
32182 fn the_link_to_the_master_writes_through_the_read_only_refusal() {
32183 let mut f = Fixture::new();
32184 f.server.pretend_following("127.0.0.1", 6379, true);
32185 assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32186 f.session.serve_master(true);
32187 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32188 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
32189 }
32190
32191 #[test]
32194 fn a_transaction_on_a_read_only_replica_is_refused_whole() {
32195 let mut f = Fixture::new();
32196 f.server.pretend_following("127.0.0.1", 6379, true);
32197 f.run(&[b"MULTI"]);
32198 assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32199 assert!(f.run(&[b"EXEC"]).starts_with("-EXECABORT"));
32200 }
32201
32202 #[test]
32204 fn a_replica_says_who_it_follows_in_info_and_in_role() {
32205 let mut f = Fixture::new();
32206 assert!(f.run(&[b"INFO", b"replication"]).contains("role:master"));
32207 f.server.pretend_following("10.0.0.4", 7000, true);
32208 let info = f.run(&[b"INFO", b"replication"]);
32209 assert!(info.contains("role:slave"), "{info}");
32210 assert!(info.contains("master_host:10.0.0.4"), "{info}");
32211 assert!(info.contains("master_port:7000"), "{info}");
32212 assert!(info.contains("master_link_status:up"), "{info}");
32213 assert!(info.contains("slave_read_only:1"), "{info}");
32214 let role = f.run(&[b"ROLE"]);
32217 assert!(role.starts_with("*5\r\n$5\r\nslave\r\n"), "{role}");
32218 assert!(role.contains("10.0.0.4"), "{role}");
32219 assert!(role.contains("connected"), "{role}");
32220 f.server.pretend_following("10.0.0.4", 7000, false);
32222 assert!(
32223 f.run(&[b"INFO", b"replication"])
32224 .contains("master_link_status:down"),
32225 "a link that is not up is down"
32226 );
32227 assert!(f.run(&[b"ROLE"]).contains("connect"));
32228 }
32229
32230 #[test]
32233 fn replicaof_on_an_embedded_server_says_it_is_not_available() {
32234 let mut f = Fixture::new();
32235 let said = f.run(&[b"REPLICAOF", b"127.0.0.1", b"6379"]);
32236 assert!(
32237 said.contains("not available on an embedded server"),
32238 "{said}"
32239 );
32240 assert!(
32243 f.run(&[b"REPLICAOF", b"127.0.0.1"])
32244 .starts_with("-ERR wrong number")
32245 );
32246 assert_eq!(
32247 f.run(&[b"SLAVEOF", b"127.0.0.1", b"abc"]),
32248 "-ERR Invalid master port\r\n"
32249 );
32250 assert_eq!(
32251 f.run(&[b"REPLICAOF", b"127.0.0.1", b"99999"]),
32252 "-ERR Invalid master port\r\n"
32253 );
32254 }
32255
32256 #[test]
32259 fn a_promotion_keeps_the_old_history_as_the_second_id() {
32260 let f = Fixture::new();
32261 let was = f.server.repl_id();
32262 f.server.promote();
32263 assert_ne!(f.server.repl_id(), was);
32264 let info = {
32265 let mut f = f;
32266 f.run(&[b"INFO", b"replication"])
32267 };
32268 let was = String::from_utf8_lossy(&was).into_owned();
32269 assert!(info.contains(&format!("master_replid2:{was}")), "{info}");
32270 }
32271
32272 #[test]
32275 fn change_repl_id_takes_a_new_id_and_forgets_the_old_one() {
32276 let mut f = Fixture::new();
32277 let was = f.server.repl_id();
32278 f.server.promote();
32279 assert_eq!(f.run(&[b"DEBUG", b"CHANGE-REPL-ID"]), "+OK\r\n");
32280 assert_ne!(f.server.repl_id(), was);
32281 let info = f.run(&[b"INFO", b"replication"]);
32282 assert!(
32283 info.contains(&format!("master_replid2:{}", "0".repeat(40))),
32284 "{info}"
32285 );
32286 }
32287
32288 #[test]
32292 fn failover_refuses_in_the_order_the_reference_refuses() {
32293 let mut f = Fixture::new();
32294 assert_eq!(
32296 f.run(&[b"FAILOVER", b"ABORT"]),
32297 "-ERR No failover in progress.\r\n"
32298 );
32299 assert_eq!(
32303 f.run(&[b"FAILOVER", b"TIMEOUT", b"0"]),
32304 "-ERR FAILOVER timeout must be greater than 0\r\n"
32305 );
32306 assert_eq!(
32307 f.run(&[b"FAILOVER", b"TIMEOUT", b"-1"]),
32308 "-ERR FAILOVER timeout must be greater than 0\r\n"
32309 );
32310 assert!(
32311 f.run(&[b"FAILOVER", b"TIMEOUT", b"abc"])
32312 .starts_with("-ERR value is not an integer")
32313 );
32314 assert_eq!(f.run(&[b"FAILOVER", b"bogus"]), "-ERR syntax error\r\n");
32317 assert_eq!(
32318 f.run(&[b"FAILOVER", b"TIMEOUT", b"1", b"TIMEOUT", b"2"]),
32319 "-ERR syntax error\r\n"
32320 );
32321 assert_eq!(
32322 f.run(&[b"FAILOVER", b"FORCE", b"FORCE"]),
32323 "-ERR syntax error\r\n"
32324 );
32325 assert_eq!(f.run(&[b"FAILOVER", b"TO", b"h"]), "-ERR syntax error\r\n");
32328 assert_eq!(
32330 f.run(&[b"FAILOVER", b"ABORT", b"TIMEOUT", b"1"]),
32331 "-ERR syntax error\r\n"
32332 );
32333 assert_eq!(
32336 f.run(&[b"FAILOVER"]),
32337 "-ERR FAILOVER requires connected replicas.\r\n"
32338 );
32339 assert_eq!(
32340 f.run(&[b"FAILOVER", b"FORCE"]),
32341 "-ERR FAILOVER requires connected replicas.\r\n"
32342 );
32343 f.server.pretend_following("10.0.0.4", 7000, true);
32345 assert_eq!(
32346 f.run(&[b"FAILOVER"]),
32347 "-ERR FAILOVER is not valid when server is a replica.\r\n"
32348 );
32349 }
32350
32351 #[test]
32354 fn a_server_that_is_not_failing_over_says_no_failover() {
32355 let mut f = Fixture::new();
32356 assert!(
32357 f.run(&[b"INFO", b"replication"])
32358 .contains("master_failover_state:no-failover"),
32359 "the field is there and says nothing is going on"
32360 );
32361 }
32362
32363 #[test]
32366 fn a_transaction_crosses_as_its_commands() {
32367 let mut f = Fixture::replicated();
32368 f.crossed(&[b"SET", b"seed", b"1"]);
32369 f.run(&[b"MULTI"]);
32370 f.run(&[b"SET", b"a", b"1"]);
32371 f.run(&[b"INCR", b"a"]);
32372 assert_eq!(
32373 commands(&f.crossed(&[b"EXEC"])),
32374 vec![
32375 vec!["SET".to_string(), "a".to_string(), "1".to_string()],
32376 vec!["INCR".to_string(), "a".to_string()],
32377 ]
32378 );
32379 }
32380
32381 const FOO: u16 = 12182;
32387
32388 const BAR: u16 = 5061;
32390
32391 fn clustered() -> Fixture {
32392 let mut server = Server::new();
32393 server.enable_cluster("", 7000);
32394 server.cluster_own_everything();
32395 let other = server.cluster_pretend_node(
32396 "5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f",
32397 "10.0.0.9",
32398 7002,
32399 );
32400 assert_eq!(other, 1, "the made up node is the second one in the table");
32401 Fixture::on(server)
32402 }
32403
32404 #[test]
32408 fn cluster_slots_comes_out_in_slot_order() {
32409 let mut f = clustered();
32410 for slot in 0..100u16 {
32411 f.server.cluster_hand_over(slot, 1);
32412 }
32413 let reply = f.run(&[b"CLUSTER", b"SLOTS"]);
32414 assert!(
32415 reply.starts_with("*2\r\n*3\r\n:0\r\n:99\r\n*4\r\n$8\r\n10.0.0.9\r\n:7002\r\n"),
32416 "the other node's run is first because it starts at slot 0: {reply}"
32417 );
32418 assert!(
32419 reply.contains("*3\r\n:100\r\n:16383\r\n"),
32420 "and this node's run is the rest of them: {reply}"
32421 );
32422 }
32423
32424 #[test]
32426 fn a_key_on_another_node_is_moved_there() {
32427 let mut f = clustered();
32428 f.server.cluster_hand_over(BAR, 1);
32429 assert_eq!(
32430 f.run(&[b"GET", b"bar"]),
32431 format!("-MOVED {BAR} 10.0.0.9:7002\r\n")
32432 );
32433 assert_eq!(f.run(&[b"GET", b"foo"]), "$-1\r\n");
32435 }
32436
32437 #[test]
32440 fn a_command_with_no_keys_never_redirects() {
32441 let mut f = clustered();
32442 for slot in 0..16384u16 {
32443 f.server.cluster_hand_over(slot, 1);
32444 }
32445 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
32446 assert_eq!(f.run(&[b"ECHO", b"hi"]), "$2\r\nhi\r\n");
32447 }
32448
32449 #[test]
32452 fn two_slots_in_one_command_is_a_cross_slot() {
32453 let mut f = clustered();
32454 assert_eq!(
32455 f.run(&[b"MGET", b"foo", b"bar"]),
32456 "-CROSSSLOT Keys in request don't hash to the same slot\r\n"
32457 );
32458 assert_eq!(
32460 f.run(&[b"MGET", b"{t}foo", b"{t}bar"]),
32461 "*2\r\n$-1\r\n$-1\r\n"
32462 );
32463 }
32464
32465 #[test]
32468 fn a_hole_is_reported_before_the_cross_slot() {
32469 let mut server = Server::new();
32470 server.enable_cluster("", 7000);
32471 let mut f = Fixture::on(server);
32472 assert_eq!(
32473 f.run(&[b"MGET", b"foo", b"bar"]),
32474 "-CLUSTERDOWN Hash slot not served\r\n"
32475 );
32476 f.server.cluster_own_everything();
32478 assert_eq!(
32479 f.run(&[b"MGET", b"foo", b"bar"]),
32480 "-CROSSSLOT Keys in request don't hash to the same slot\r\n"
32481 );
32482 }
32483
32484 #[test]
32488 fn a_migrating_slot_asks_for_the_keys_that_have_gone() {
32489 let mut f = clustered();
32490 f.run(&[b"SET", b"foo", b"1"]);
32491 f.server.cluster_moving(FOO, Some(1), None);
32492 assert_eq!(f.run(&[b"GET", b"foo"]), "$1\r\n1\r\n");
32494 assert_eq!(
32496 f.run(&[b"GET", b"{foo}gone"]),
32497 format!("-ASK {FOO} 10.0.0.9:7002\r\n")
32498 );
32499 }
32500
32501 #[test]
32504 fn a_half_moved_slot_is_a_try_again() {
32505 let mut f = clustered();
32506 f.run(&[b"SET", b"{t}here", b"1"]);
32507 let slot = cluster::key_slot(b"{t}here");
32508 f.server.cluster_moving(slot, Some(1), None);
32509 assert_eq!(
32510 f.run(&[b"MGET", b"{t}here", b"{t}gone"]),
32511 "-TRYAGAIN Multiple keys request during rehashing of slot\r\n"
32512 );
32513 }
32514
32515 #[test]
32518 fn asking_lets_one_command_into_an_importing_slot() {
32519 let mut f = clustered();
32520 f.server.cluster_hand_over(FOO, 1);
32521 f.server.cluster_moving(FOO, None, Some(1));
32522 let moved = format!("-MOVED {FOO} 10.0.0.9:7002\r\n");
32523 assert_eq!(f.run(&[b"GET", b"foo"]), moved);
32524 assert_eq!(f.run(&[b"ASKING"]), "+OK\r\n");
32525 assert_eq!(f.run(&[b"SET", b"foo", b"1"]), "+OK\r\n");
32526 assert_eq!(f.run(&[b"GET", b"foo"]), moved);
32528 }
32529
32530 #[test]
32535 fn restore_asking_gets_into_an_importing_slot_on_its_own() {
32536 let mut f = clustered();
32537 f.server.cluster_hand_over(FOO, 1);
32538 f.server.cluster_moving(FOO, None, Some(1));
32539 f.run(&[b"SET", b"scratch", b"1"]);
32542 let dumped = f.raw(&[b"DUMP", b"scratch"]);
32543 let payload =
32544 &dumped[dumped.iter().position(|b| *b == b'\n').unwrap() + 1..dumped.len() - 2];
32545 let payload = payload.to_vec();
32546 assert_eq!(
32548 f.run(&[b"RESTORE", b"foo", b"0", &payload]),
32549 format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
32550 );
32551 assert_eq!(
32553 f.run(&[b"RESTORE-ASKING", b"foo", b"0", &payload]),
32554 "+OK\r\n"
32555 );
32556 assert_eq!(
32559 f.run(&[b"GET", b"foo"]),
32560 format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
32561 );
32562 }
32563
32564 #[test]
32568 fn closing_an_import_takes_a_higher_epoch() {
32569 let mut f = clustered();
32570 f.server.cluster_hand_over(FOO, 1);
32571 f.server.cluster_moving(FOO, None, Some(1));
32572 let me = f.run(&[b"CLUSTER", b"MYID"]);
32573 let me = me[me.find("\r\n").unwrap() + 2..me.len() - 2].to_owned();
32574 assert_eq!(
32575 f.run(&[
32576 b"CLUSTER",
32577 b"SETSLOT",
32578 FOO.to_string().as_bytes(),
32579 b"NODE",
32580 me.as_bytes()
32581 ]),
32582 "+OK\r\n"
32583 );
32584 assert_eq!(f.run(&[b"CLUSTER", b"BUMPEPOCH"]), "+STILL 1\r\n");
32587 assert_eq!(f.run(&[b"GET", b"foo"]), "$-1\r\n");
32589 }
32590
32591 #[test]
32594 fn a_plain_hand_over_does_not_touch_the_epoch() {
32595 let mut f = clustered();
32596 let me = f.run(&[b"CLUSTER", b"MYID"]);
32597 let me = me[me.find("\r\n").unwrap() + 2..me.len() - 2].to_owned();
32598 assert_eq!(
32599 f.run(&[
32600 b"CLUSTER",
32601 b"SETSLOT",
32602 FOO.to_string().as_bytes(),
32603 b"NODE",
32604 me.as_bytes()
32605 ]),
32606 "+OK\r\n"
32607 );
32608 assert_eq!(
32609 f.run(&[b"CLUSTER", b"BUMPEPOCH"]),
32610 "+BUMPED 1\r\n",
32611 "the epoch was still zero, so this is the first thing to move it"
32612 );
32613 }
32614
32615 #[test]
32618 fn a_slot_with_keys_in_it_is_not_handed_over() {
32619 let mut f = clustered();
32620 f.run(&[b"SET", b"foo", b"1"]);
32621 let them = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
32622 assert_eq!(
32623 f.run(&[
32624 b"CLUSTER",
32625 b"SETSLOT",
32626 FOO.to_string().as_bytes(),
32627 b"NODE",
32628 them
32629 ]),
32630 format!(
32631 "-ERR Can't assign hashslot {FOO} to a different node while I still hold keys for this hash slot.\r\n"
32632 )
32633 );
32634 f.run(&[b"DEL", b"foo"]);
32636 assert_eq!(
32637 f.run(&[
32638 b"CLUSTER",
32639 b"SETSLOT",
32640 FOO.to_string().as_bytes(),
32641 b"NODE",
32642 them
32643 ]),
32644 "+OK\r\n"
32645 );
32646 assert_eq!(
32647 f.run(&[b"GET", b"foo"]),
32648 format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
32649 );
32650 }
32651
32652 #[test]
32661 fn the_slot_migration_protocol_is_shut_to_a_client() {
32662 let mut f = clustered();
32663 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS"]);
32666 assert_eq!(
32667 reply,
32668 "-ERR wrong number of arguments for 'cluster|syncslots' command\r\n"
32669 );
32670 assert_eq!(flow, Flow::Continue);
32671 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
32672 assert_eq!(
32673 reply,
32674 "-ERR CLUSTER SYNCSLOTS subcommands are only allowed for internal clients\r\n"
32675 );
32676 assert_eq!(flow, Flow::Close, "and the socket goes with it");
32677 }
32678
32679 #[test]
32682 fn the_internal_login_wants_the_cluster_secret() {
32683 let mut f = Fixture::new();
32684 assert_eq!(
32685 f.run(&[b"AUTH", b"internal connection", b"x"]),
32686 "-ERR Cannot authenticate as an internal connection on non-cluster instances\r\n"
32687 );
32688 assert_eq!(
32689 f.run(&[b"DEBUG", b"INTERNAL_SECRET"]),
32690 "-ERR Internal secret is missing\r\n"
32691 );
32692 let mut f = clustered();
32693 assert_eq!(
32694 f.run(&[b"AUTH", b"internal connection", b"x"]),
32695 "-WRONGPASS invalid internal password\r\n"
32696 );
32697 assert_eq!(
32700 f.run(&[b"AUTH", b"INTERNAL CONNECTION", b"x"]),
32701 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
32702 );
32703 let secret = f.server.cluster_secret();
32704 assert_eq!(secret.len(), 40, "forty characters, like a node id");
32705 assert_eq!(
32706 f.run(&[b"DEBUG", b"INTERNAL_SECRET"]),
32707 format!(":{}\r\n", yo_common::crc::crc16(secret.as_bytes())),
32708 "what comes back is a checksum, so a test can see two nodes agree \
32709 and nobody can log in with what they read"
32710 );
32711 assert_eq!(
32712 f.run(&[b"AUTH", b"internal connection", secret.as_bytes()]),
32713 "+OK\r\n"
32714 );
32715 assert_eq!(
32716 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]),
32717 "+OK\r\n"
32718 );
32719 }
32720
32721 #[test]
32724 fn conf_says_ok_after_an_option_it_did_not_know() {
32725 let mut f = clustered();
32726 assert_eq!(f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]), "+OK\r\n");
32727 assert_eq!(
32728 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"zzz", b"1"]),
32729 "-ERR Unknown option zzz\r\n+OK\r\n"
32730 );
32731 assert_eq!(
32734 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"quantum"]),
32735 "+OK\r\n"
32736 );
32737 assert_eq!(
32739 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"node-id", b"abc"]),
32740 "-ERR Invalid node id length 3\r\n"
32741 );
32742 let unknown = b"1111111111111111111111111111111111111111";
32743 assert_eq!(
32744 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"node-id", unknown]),
32745 "-ERR Node 1111111111111111111111111111111111111111 not found in cluster\r\n"
32746 );
32747 assert_eq!(
32748 f.run(&[
32749 b"CLUSTER",
32750 b"SYNCSLOTS",
32751 b"CONF",
32752 b"node-id",
32753 b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f"
32754 ]),
32755 "+OK\r\n"
32756 );
32757 assert_eq!(
32759 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"slot-info", b"5:10:2"]),
32760 "+OK\r\n"
32761 );
32762 for bad in [b"zz".as_slice(), b"16384:0:0", b"5:10:2:3", b"5:-1:0"] {
32763 assert_eq!(
32764 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"slot-info", bad]),
32765 format!(
32766 "-ERR Invalid slot info: {}\r\n",
32767 String::from_utf8_lossy(bad)
32768 )
32769 );
32770 }
32771 assert_eq!(
32774 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"asm-task", b"x"]),
32775 "-ERR CLUSTER SYNCSLOTS CONF ASM-TASK only allowed on replica\r\n"
32776 );
32777 assert_eq!(
32778 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT", b"UNMARK"]),
32779 "+OK\r\n"
32780 );
32781 let (flow, _) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
32782 assert_eq!(flow, Flow::Close, "and the door shuts again");
32783 }
32784
32785 #[test]
32788 fn the_slot_ranges_of_a_sync_are_checked_in_full() {
32789 let mut f = clustered();
32790 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
32791 let id = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
32792 fn sync<'a>(id: &'a [u8], slots: &[&'a [u8]]) -> Vec<&'a [u8]> {
32793 let mut parts: Vec<&[u8]> = vec![b"CLUSTER", b"SYNCSLOTS", b"SYNC", id];
32794 parts.extend_from_slice(slots);
32795 parts
32796 }
32797 let bar = BAR.to_string();
32798 let bar = bar.as_bytes();
32799 assert_eq!(
32800 f.run(&sync(id, &[b"5", b"4"])),
32801 "-ERR start slot number 5 is greater than end slot number 4\r\n"
32802 );
32803 assert_eq!(
32804 f.run(&sync(id, &[b"99999", b"2"])),
32805 "-ERR Invalid or out of range slot\r\n"
32806 );
32807 assert_eq!(
32810 f.run(&sync(id, &[b"1", b"2", b"2", b"3"])),
32811 "-ERR Slot 2 specified multiple times\r\n"
32812 );
32813 assert_eq!(
32814 f.run(&sync(id, &[b"1", b"2", b"3", b"4"])),
32815 "-ERR CLUSTER SYNCSLOTS SYNC is not implemented yet, move the slot with SETSLOT and MIGRATE\r\n"
32816 );
32817 f.server.cluster_hand_over(BAR, 1);
32819 assert_eq!(
32820 f.run(&sync(id, &[bar])),
32821 "-ERR syntax error\r\n",
32822 "one slot number is not a range, and a shape it does not know is a \
32823 syntax error rather than a count it can complain about"
32824 );
32825 assert_eq!(
32826 f.run(&sync(id, &[bar, bar])),
32827 "-ERR This node is not the owner of the slots\r\n"
32828 );
32829 f.server.cluster_moving(FOO, Some(1), None);
32831 assert_eq!(
32832 f.run(&sync(id, &[b"1", b"2"])),
32833 "-ERR all slot states must be STABLE to start a slot migration task.\r\n"
32834 );
32835 }
32836
32837 #[test]
32840 fn a_replica_only_hears_the_settings_and_only_from_its_master() {
32841 let mut server = Server::new();
32842 server.enable_cluster("", 7000);
32843 let of = server.cluster_pretend_node(
32844 "5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f",
32845 "10.0.0.9",
32846 7002,
32847 );
32848 server.cluster_pretend_follower(of);
32849 let mut f = Fixture::on(server);
32850 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
32851 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
32852 assert_eq!(
32853 reply,
32854 "-ERR CLUSTER SYNCSLOTS subcommands are only allowed for master\r\n"
32855 );
32856 assert_eq!(flow, Flow::Close);
32857 f.session.serve_master(true);
32861 assert_eq!(
32862 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]),
32863 "+OK\r\n"
32864 );
32865 assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"SNAPSHOT-EOF"]), "");
32866 assert_eq!(
32867 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"asm-task", b"x"]),
32868 "-ERR Failed to handle master task: x\r\n+OK\r\n",
32869 "there is no migration for a replica to follow along with yet, and \
32870 this option is one the reference keeps going past as well"
32871 );
32872 }
32873
32874 #[test]
32877 fn the_one_way_arms_of_the_protocol_say_nothing_back() {
32878 let mut f = clustered();
32879 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
32880 assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"ACK", b"x", b"1"]), "");
32881 assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"FAIL", b"boom"]), "");
32882 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"STREAM-EOF"]);
32885 assert_eq!(reply, "");
32886 assert_eq!(flow, Flow::Close);
32887 let mut f = clustered();
32889 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
32890 assert_eq!(
32891 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"RDBCHANNEL", b"abc"]),
32892 "-ERR Invalid task id\r\n"
32893 );
32894 assert_eq!(
32895 f.run(&[
32896 b"CLUSTER",
32897 b"SYNCSLOTS",
32898 b"RDBCHANNEL",
32899 b"0000000000000000000000000000000000000000"
32900 ]),
32901 "-ERR No slot migration task in progress\r\n"
32902 );
32903 assert_eq!(
32904 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"NONSENSE"]),
32905 "-ERR syntax error\r\n"
32906 );
32907 }
32908
32909 #[test]
32912 fn a_transaction_is_refused_when_it_is_queued() {
32913 let mut f = clustered();
32914 f.server.cluster_hand_over(BAR, 1);
32915 assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
32916 assert_eq!(
32917 f.run(&[b"GET", b"bar"]),
32918 format!("-MOVED {BAR} 10.0.0.9:7002\r\n")
32919 );
32920 assert_eq!(
32921 f.run(&[b"EXEC"]),
32922 "-EXECABORT Transaction discarded because of previous errors.\r\n"
32923 );
32924 }
32925
32926 #[test]
32929 fn select_and_swapdb_are_not_cluster_commands() {
32930 let mut f = clustered();
32931 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
32932 assert_eq!(
32933 f.run(&[b"SELECT", b"1"]),
32934 "-ERR SELECT is not allowed in cluster mode\r\n"
32935 );
32936 assert_eq!(
32937 f.run(&[b"SWAPDB", b"0", b"1"]),
32938 "-ERR SWAPDB is not allowed in cluster mode\r\n"
32939 );
32940 }
32941
32942 #[test]
32945 fn a_plain_server_has_no_cluster_in_it() {
32946 let mut f = Fixture::new();
32947 for argv in [
32948 &[b"CLUSTER".as_slice(), b"INFO".as_slice()][..],
32949 &[b"CLUSTER", b"MYID"],
32950 &[b"CLUSTER", b"SLOTS"],
32951 &[b"CLUSTER", b"HELP"],
32952 &[b"ASKING"],
32953 &[b"READONLY"],
32954 &[b"READWRITE"],
32955 ] {
32956 assert_eq!(
32957 f.run(argv),
32958 "-ERR This instance has cluster support disabled\r\n",
32959 "{argv:?}"
32960 );
32961 }
32962 assert_eq!(
32964 f.run(&[b"CLUSTER", b"KEYSLOT"]),
32965 "-ERR wrong number of arguments for 'cluster|keyslot' command\r\n"
32966 );
32967 }
32968}