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 unmeasured: AtomicU64,
424 measure_ms: AtomicU64,
434 measure_db: AtomicUsize,
443}
444
445impl Local {
446 fn at(at: usize) -> Local {
448 Local {
449 stats: Stats::default(),
450 cmdstats: CommandStats::default(),
451 dirty: AtomicU64::new(0),
452 turn: AtomicU64::new(ALL_DATABASES),
453 parked: AtomicUsize::new(0),
454 collect_ms: AtomicU64::new(u64::MAX),
455 compact_db: AtomicUsize::new(at),
456 unmeasured: AtomicU64::new(ALL_DATABASES),
457 measure_ms: AtomicU64::new(u64::MAX),
458 measure_db: AtomicUsize::new(at),
459 }
460 }
461}
462
463impl Default for Local {
464 fn default() -> Local {
465 Local::at(0)
466 }
467}
468
469impl Local {
470 fn mark(&self, dbs: u64) {
472 self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
473 self.unmeasure(dbs);
474 }
475
476 fn unmeasure(&self, dbs: u64) {
485 self.unmeasured
486 .store(self.unmeasured.load(Relaxed) | dbs, Relaxed);
487 }
488
489 fn to_weigh(&self) -> u64 {
496 self.unmeasured.swap(0, Relaxed)
497 }
498
499 fn measure_next(&self) -> usize {
502 let at = self.measure_db.load(Relaxed) % DATABASES;
503 self.measure_db.store((at + 1) % DATABASES, Relaxed);
504 at
505 }
506
507 fn measuring(&self, now: u64) -> bool {
513 if self.measure_ms.load(Relaxed) == now {
514 return false;
515 }
516 self.measure_ms.store(now, Relaxed);
517 true
518 }
519
520 fn note(&self, dbs: u64) {
522 self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
523 }
524
525 fn done(&self, at: usize) {
527 self.turn
528 .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
529 }
530
531 fn wanted(&self, at: usize) -> bool {
533 self.turn.load(Relaxed) & (1u64 << at) != 0
534 }
535
536 fn collecting(&self, now: u64) -> bool {
541 if self.collect_ms.load(Relaxed) == now {
542 return false;
543 }
544 self.collect_ms.store(now, Relaxed);
545 true
546 }
547
548 fn blocked(&self, n: usize) {
550 self.parked
551 .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
552 }
553
554 fn woke(&self, n: usize) {
556 self.parked
557 .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
558 }
559}
560
561fn one_thread() -> Box<[Local]> {
563 slots(1)
564}
565
566fn slots(threads: usize) -> Box<[Local]> {
568 (0..threads.max(1)).map(Local::at).collect()
571}
572
573fn working_dir() -> PathBuf {
580 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
581}
582
583#[derive(Debug, Clone, Copy, Default)]
592pub struct CommandStat {
593 pub calls: u64,
595 pub rejected: u64,
598 pub failed: u64,
600}
601
602impl CommandStat {
603 const fn seen(&self) -> bool {
609 self.calls != 0 || self.rejected != 0 || self.failed != 0
610 }
611}
612
613#[derive(Debug, Default)]
618struct Row {
619 calls: Counter,
621 rejected: Counter,
623 failed: Counter,
625}
626
627#[derive(Debug)]
634struct CommandStats(Box<[Row]>);
635
636impl Default for CommandStats {
637 fn default() -> CommandStats {
638 CommandStats((0..table::count()).map(|_| Row::default()).collect())
639 }
640}
641
642impl CommandStats {
643 fn at(&self, spec: &'static Spec) -> &Row {
645 &self.0[table::index_of(spec)]
646 }
647}
648
649pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
655
656const _: () = {
661 const fn shareable<T: Send + Sync>() {}
662 shareable::<Server>();
663};
664
665pub struct Server {
673 dbs: Vec<Db>,
674 width: usize,
680 clock: Clock,
681 started_ms: u64,
682 next_db: AtomicUsize,
695 conn_bytes: AtomicUsize,
712 maxmemory: AtomicU64,
720 store: Lock<Option<Box<StoreSource>>>,
737 maxstore: AtomicU64,
759 used: AtomicUsize,
776 db_bytes: [AtomicUsize; DATABASES],
787 startup: AtomicUsize,
794 peak: AtomicUsize,
799 evict_db: AtomicUsize,
809 expire_db: AtomicUsize,
816 expire_ms: AtomicU64,
826 waiters: Lock<Waiters>,
834 parked: AtomicUsize,
847 peers: Lock<migrate::Peers>,
859 locals: Box<[Local]>,
865 claimed: AtomicUsize,
867 next_client: AtomicU64,
875 dir: PathBuf,
883 backup: Lock<backup::State>,
893 sealed: AtomicBool,
903 search: Lock<Registry>,
918 cursors: Lock<Cursors>,
927 scripts: Lock<lua::Scripts>,
939 libraries: Lock<lua::library::Libraries>,
946 stopping: AtomicBool,
955 watches: Lock<Watches>,
962 watched: AtomicUsize,
969 pubsub: Lock<pubsub::Registry>,
976 subs: AtomicUsize,
982 mail: Box<[pubsub::Mailbox]>,
988 notify: AtomicU32,
995 clients: Lock<clients::Clients>,
1002 kills: AtomicUsize,
1007 pause: AtomicU64,
1017 monitors: monitor::Monitors,
1023 repl: repl::Replication,
1029 follow: follow::Follower,
1035 failover: failover::Failover,
1041 cluster: cluster::Cluster,
1049 myself: Lock<Weak<Server>>,
1062 persist: persist::Persistence,
1064 acl: acl::Users,
1066 acllog: acl::Log,
1068 aclfile: PathBuf,
1076 plain: acl::Plain,
1078 debug: debug::Knobs,
1080}
1081
1082impl Server {
1083 #[must_use]
1085 pub fn new() -> Server {
1086 let clock = Clock::system();
1087 let server = Server {
1088 dbs: (0..DATABASES)
1089 .map(|_| Db::with_clock(clock.clone(), 1))
1090 .collect(),
1091 width: 1,
1092 started_ms: clock.now_ms(),
1093 clock,
1094 next_db: AtomicUsize::new(0),
1095 conn_bytes: AtomicUsize::new(0),
1096 maxmemory: AtomicU64::new(0),
1097 store: Lock::new(None),
1098 maxstore: AtomicU64::new(NO_MAXSTORE),
1099 used: AtomicUsize::new(0),
1100 db_bytes: [const { AtomicUsize::new(0) }; DATABASES],
1101 startup: AtomicUsize::new(0),
1102 peak: AtomicUsize::new(0),
1103 evict_db: AtomicUsize::new(0),
1104 expire_db: AtomicUsize::new(0),
1105 expire_ms: AtomicU64::new(0),
1106 waiters: Lock::default(),
1107 parked: AtomicUsize::new(0),
1108 peers: Lock::default(),
1109 locals: one_thread(),
1110 claimed: AtomicUsize::new(0),
1111 next_client: AtomicU64::new(1),
1112 dir: working_dir(),
1113 backup: Lock::default(),
1114 sealed: AtomicBool::new(false),
1115 search: Lock::new(Registry::new()),
1116 cursors: Lock::default(),
1117 scripts: Lock::default(),
1118 libraries: Lock::default(),
1119 stopping: AtomicBool::new(false),
1120 watches: Lock::default(),
1121 watched: AtomicUsize::new(0),
1122 pubsub: Lock::default(),
1123 subs: AtomicUsize::new(0),
1124 notify: AtomicU32::new(0),
1125 clients: Lock::default(),
1126 kills: AtomicUsize::new(0),
1127 pause: AtomicU64::new(0),
1128 monitors: monitor::Monitors::default(),
1129 repl: repl::Replication::default(),
1130 follow: follow::Follower::default(),
1131 failover: failover::Failover::default(),
1132 cluster: cluster::Cluster::default(),
1133 myself: Lock::new(Weak::new()),
1134 persist: persist::Persistence::default(),
1135 acl: acl::Users::default(),
1136 acllog: acl::Log::default(),
1137 aclfile: PathBuf::new(),
1138 plain: acl::Plain::default(),
1139 debug: debug::Knobs::default(),
1140 mail: pubsub::boxes(1),
1141 };
1142 server.note_startup();
1143 server
1144 }
1145
1146 #[must_use]
1157 pub fn with_width(width: usize) -> Server {
1158 let mut server = Server::new();
1159 let clock = server.clock.clone();
1163 server.dbs = (0..DATABASES)
1164 .map(|_| Db::with_clock(clock.clone(), width))
1165 .collect();
1166 server.width = server.dbs[0].width();
1167 server.note_startup();
1171 server
1172 }
1173
1174 #[must_use]
1176 pub fn with_clock(clock: Clock) -> Server {
1177 let server = Server {
1178 dbs: (0..DATABASES)
1179 .map(|_| Db::with_clock(clock.clone(), 1))
1180 .collect(),
1181 width: 1,
1182 started_ms: clock.now_ms(),
1183 clock,
1184 next_db: AtomicUsize::new(0),
1185 conn_bytes: AtomicUsize::new(0),
1186 maxmemory: AtomicU64::new(0),
1187 store: Lock::new(None),
1188 maxstore: AtomicU64::new(NO_MAXSTORE),
1189 used: AtomicUsize::new(0),
1190 db_bytes: [const { AtomicUsize::new(0) }; DATABASES],
1191 startup: AtomicUsize::new(0),
1192 peak: AtomicUsize::new(0),
1193 evict_db: AtomicUsize::new(0),
1194 expire_db: AtomicUsize::new(0),
1195 expire_ms: AtomicU64::new(0),
1196 waiters: Lock::default(),
1197 parked: AtomicUsize::new(0),
1198 peers: Lock::default(),
1199 locals: one_thread(),
1200 claimed: AtomicUsize::new(0),
1201 next_client: AtomicU64::new(1),
1202 dir: working_dir(),
1203 backup: Lock::default(),
1204 sealed: AtomicBool::new(false),
1205 search: Lock::new(Registry::new()),
1206 cursors: Lock::default(),
1207 scripts: Lock::default(),
1208 libraries: Lock::default(),
1209 stopping: AtomicBool::new(false),
1210 watches: Lock::default(),
1211 watched: AtomicUsize::new(0),
1212 pubsub: Lock::default(),
1213 subs: AtomicUsize::new(0),
1214 notify: AtomicU32::new(0),
1215 clients: Lock::default(),
1216 kills: AtomicUsize::new(0),
1217 pause: AtomicU64::new(0),
1218 monitors: monitor::Monitors::default(),
1219 repl: repl::Replication::default(),
1220 follow: follow::Follower::default(),
1221 failover: failover::Failover::default(),
1222 cluster: cluster::Cluster::default(),
1223 myself: Lock::new(Weak::new()),
1224 persist: persist::Persistence::default(),
1225 acl: acl::Users::default(),
1226 acllog: acl::Log::default(),
1227 aclfile: PathBuf::new(),
1228 plain: acl::Plain::default(),
1229 debug: debug::Knobs::default(),
1230 mail: pubsub::boxes(1),
1231 };
1232 server.note_startup();
1233 server
1234 }
1235
1236 pub fn striped(&self, i: usize) -> &Db {
1260 self.mine().mark(1u64 << i);
1261 &self.dbs[i]
1262 }
1263
1264 fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
1271 self.dbs
1272 .iter()
1273 .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
1274 }
1275
1276 const fn slots(&self) -> usize {
1282 DATABASES * self.width
1283 }
1284
1285 const fn slot_db(&self, i: usize) -> usize {
1287 i / self.width
1288 }
1289
1290 fn slot(&self, i: usize) -> Held<'_, Keyspace> {
1292 let (db, stripe) = (i / self.width, i % self.width);
1293 self.dbs[db].hold_stripe(stripe)
1294 }
1295
1296 #[must_use]
1298 pub fn dir(&self) -> &Path {
1299 &self.dir
1300 }
1301
1302 pub fn set_dir(&mut self, dir: PathBuf) {
1309 self.dir = dir;
1310 }
1311
1312 #[must_use]
1315 pub fn aclfile(&self) -> Option<&Path> {
1316 Some(self.aclfile.as_path()).filter(|p| !p.as_os_str().is_empty())
1317 }
1318
1319 pub fn set_aclfile(&mut self, path: PathBuf) {
1325 self.aclfile = path;
1326 }
1327
1328 pub fn load_acl(&self) -> std::result::Result<(), String> {
1336 match self.aclfile() {
1337 Some(path) => yo_alloc::allow(|| acl::load_file(self, path)),
1338 None => Ok(()),
1339 }
1340 }
1341
1342 pub fn backup_expire(&self) {
1348 backup::expire(self);
1349 }
1350
1351 pub fn stop(&self) {
1357 self.stopping.store(true, Release);
1358 }
1359
1360 #[must_use]
1366 pub fn stopping(&self) -> bool {
1367 self.stopping.load(Acquire)
1368 }
1369
1370 #[must_use]
1377 pub fn striped_ref(&self, i: usize) -> &Db {
1378 &self.dbs[i]
1379 }
1380
1381 fn settings(&self) -> Held<'_, Keyspace> {
1389 self.dbs[0].hold_stripe(0)
1390 }
1391
1392 pub fn refresh_clock(&self) {
1403 self.clock.refresh();
1404 }
1405
1406 pub fn advance_clock_ms(&self, ms: u64) {
1412 let now = self.clock.now_ms() + ms;
1413 self.set_clock_ms(now);
1414 }
1415
1416 pub fn set_clock_ms(&self, ms: u64) {
1424 self.clock.set(ms);
1425 }
1426
1427 #[must_use]
1429 pub fn uptime_secs(&self) -> u64 {
1430 self.clock.now_ms().saturating_sub(self.started_ms) / 1000
1431 }
1432
1433 #[must_use]
1441 pub fn memory_bytes(&self) -> usize {
1442 self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1443 }
1444
1445 #[must_use]
1449 pub fn startup_bytes(&self) -> usize {
1450 self.startup.load(Relaxed)
1451 }
1452
1453 #[must_use]
1463 pub fn peak_bytes(&self) -> usize {
1464 let now = self.memory_bytes();
1465 self.peak.fetch_max(now, Relaxed).max(now)
1466 }
1467
1468 fn note_startup(&self) {
1470 let now = self.memory_bytes();
1471 self.startup.store(now, Relaxed);
1472 self.peak.store(now, Relaxed);
1473 }
1474
1475 #[must_use]
1481 pub fn dataset_bytes(&self) -> usize {
1482 self.keyspaces()
1483 .map(|db| db.map().arena().live_bytes() as usize)
1484 .sum()
1485 }
1486
1487 #[must_use]
1489 pub fn arena_bytes(&self) -> usize {
1490 self.keyspaces()
1491 .map(|db| db.map().arena().reserved_bytes() as usize)
1492 .sum()
1493 }
1494
1495 #[must_use]
1497 pub fn index_bytes(&self) -> usize {
1498 self.keyspaces()
1499 .map(|db| db.map().index().memory_bytes())
1500 .sum()
1501 }
1502
1503 #[must_use]
1510 pub fn compaction(&self) -> yo_kv::Compaction {
1511 self.keyspaces().map(|db| db.map().compaction()).fold(
1512 yo_kv::Compaction::default(),
1513 |a, b| yo_kv::Compaction {
1514 walked: a.walked + b.walked,
1515 moved: a.moved + b.moved,
1516 bytes: a.bytes + b.bytes,
1517 },
1518 )
1519 }
1520
1521 #[must_use]
1529 pub fn listed_runs(&self) -> usize {
1530 self.keyspaces()
1531 .map(|db| db.map().arena().listed_runs())
1532 .sum()
1533 }
1534
1535 #[must_use]
1537 pub fn segment_count(&self) -> usize {
1538 self.keyspaces()
1539 .map(|db| db.map().arena().resident_segments())
1540 .sum()
1541 }
1542
1543 #[must_use]
1545 pub fn conn_bytes(&self) -> usize {
1546 self.conn_bytes.load(Relaxed)
1547 }
1548
1549 pub fn note_conn_bytes(&self, delta: isize) {
1557 self.conn_bytes
1563 .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1564 }
1565
1566 #[must_use]
1568 pub fn expired_keys(&self) -> u64 {
1569 self.keyspaces().map(|db| db.expired_keys()).sum()
1570 }
1571
1572 #[must_use]
1574 pub fn expired_fields(&self) -> u64 {
1575 self.keyspaces().map(|db| db.expired_fields()).sum()
1576 }
1577
1578 #[must_use]
1580 pub fn expired_fields_active(&self) -> u64 {
1581 self.keyspaces().map(|db| db.expired_fields_active()).sum()
1582 }
1583
1584 #[must_use]
1586 pub fn evicted_keys(&self) -> u64 {
1587 self.keyspaces().map(|db| db.evicted_keys()).sum()
1588 }
1589
1590 #[must_use]
1592 pub fn keyspace_hits(&self) -> u64 {
1593 self.keyspaces().map(|db| db.hits()).sum()
1594 }
1595
1596 #[must_use]
1598 pub fn keyspace_misses(&self) -> u64 {
1599 self.keyspaces().map(|db| db.misses()).sum()
1600 }
1601
1602 pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1608 (0..table::count())
1609 .map(|at| (table::name_at(at), self.command_stat(at)))
1610 .filter(|(_, row)| row.seen())
1611 }
1612
1613 fn command_stat(&self, at: usize) -> CommandStat {
1615 let mut sum = CommandStat::default();
1616 for thread in &self.locals {
1617 let row = &thread.cmdstats.0[at];
1618 sum.calls += row.calls.get();
1619 sum.rejected += row.rejected.get();
1620 sum.failed += row.failed.get();
1621 }
1622 sum
1623 }
1624
1625 pub fn counted(&self) -> &Stats {
1634 &self.mine().stats
1635 }
1636
1637 pub fn next_client(&self) -> u64 {
1642 self.next_client.fetch_add(1, Relaxed)
1643 }
1644
1645 pub fn is_behind(self: &Arc<Server>) {
1653 let mut myself = self.myself.lock();
1654 if myself.strong_count() == 0 {
1655 *myself = Arc::downgrade(self);
1656 }
1657 }
1658
1659 pub fn forget_behind(&self) {
1668 let mut myself = self.myself.lock();
1669 *myself = Weak::new();
1670 }
1671
1672 #[must_use]
1678 pub(crate) fn myself(&self) -> Option<Arc<Server>> {
1679 self.myself.lock().upgrade()
1680 }
1681
1682 pub fn my_slot(&self) -> usize {
1689 self.mine_at()
1690 }
1691
1692 fn mine(&self) -> &Local {
1694 &self.locals[self.mine_at()]
1695 }
1696
1697 fn mine_at(&self) -> usize {
1704 let mut slot = SLOT.get();
1705 if slot == usize::MAX {
1706 slot = self.claimed.fetch_add(1, Relaxed);
1707 SLOT.set(slot);
1708 }
1709 slot % self.locals.len()
1710 }
1711
1712 #[must_use]
1714 pub fn totals(&self) -> Totals {
1715 let mut sum = Totals::default();
1716 for thread in &self.locals {
1717 sum.clients += thread.stats.clients.get();
1718 sum.connections += thread.stats.connections.get();
1719 sum.commands += thread.stats.commands.get();
1720 }
1721 sum
1722 }
1723
1724 #[must_use]
1738 pub fn per_thread(&self) -> Vec<Totals> {
1739 self.locals
1740 .iter()
1741 .map(|thread| Totals {
1742 clients: thread.stats.clients.get(),
1743 connections: thread.stats.connections.get(),
1744 commands: thread.stats.commands.get(),
1745 })
1746 .collect()
1747 }
1748
1749 pub fn reset_stats(&self) {
1756 for thread in &self.locals {
1757 thread.stats.connections.zero();
1758 thread.stats.commands.zero();
1759 }
1760 for mut db in self.keyspaces() {
1765 db.zero_stats();
1766 }
1767 }
1768
1769 pub fn set_threads(&mut self, threads: usize) {
1776 self.locals = slots(threads);
1777 self.mail = pubsub::boxes(threads);
1778 self.claimed = AtomicUsize::new(0);
1779 }
1780
1781 #[must_use]
1790 pub fn io_threads(&self) -> usize {
1791 self.locals.len()
1792 }
1793
1794 #[must_use]
1796 pub fn maxmemory(&self) -> u64 {
1797 self.maxmemory.load(Relaxed)
1798 }
1799
1800 pub fn set_maxmemory(&self, bytes: u64) {
1814 self.maxmemory.store(bytes, Relaxed);
1815 for db in &self.dbs {
1816 db.track_memory(bytes != 0);
1817 }
1818 self.mine().unmeasure(ALL_DATABASES);
1823 self.used.store(self.settled_memory(), Relaxed);
1824 }
1825
1826 pub fn set_store_source(
1836 &mut self,
1837 source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1838 ) {
1839 *self.store.lock() = Some(Box::new(source));
1840 }
1841
1842 #[must_use]
1844 pub fn has_store_source(&self) -> bool {
1845 self.store.lock().is_some()
1846 }
1847
1848 fn attach_store(&self, at: usize) {
1855 if self.slot(at).store_bytes().is_some() {
1856 return;
1857 }
1858 let mut source = self.store.lock();
1862 let Some(source) = source.as_mut() else {
1863 return;
1864 };
1865 if let Some(blocks) = source(at) {
1866 self.slot(at).attach(blocks);
1867 }
1868 }
1869
1870 #[must_use]
1872 pub fn maxstore(&self) -> Option<u64> {
1873 match self.maxstore.load(Relaxed) {
1874 NO_MAXSTORE => None,
1875 bytes => Some(bytes),
1876 }
1877 }
1878
1879 pub fn set_maxstore(&self, bytes: Option<u64>) {
1885 self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1886 }
1887
1888 #[must_use]
1894 pub fn store_bytes(&self) -> u64 {
1895 self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1896 }
1897
1898 #[must_use]
1913 pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1914 let mut total = yo_kv::tier::Stats::default();
1915 for db in self.keyspaces() {
1916 let Some(tier) = db.tier() else { continue };
1917 let s = tier.stats();
1918 total.demoted += s.demoted;
1919 total.promoted += s.promoted;
1920 total.faults += s.faults;
1921 total.served += s.served;
1922 total.bytes_out += s.bytes_out;
1923 total.bytes_in += s.bytes_in;
1924 }
1925 total
1926 }
1927
1928 #[must_use]
1935 pub fn regime(&self) -> &'static str {
1936 if (0..self.slots()).any(|at| self.migrates(at)) {
1937 "migrate"
1938 } else {
1939 "evict"
1940 }
1941 }
1942
1943 fn migrates(&self, at: usize) -> bool {
1955 let cap = self.maxstore();
1956 if cap == Some(0) {
1957 return false;
1958 }
1959 let bytes = self.slot(at).store_bytes();
1963 match bytes {
1964 Some(held) => cap.is_none_or(|cap| held < cap),
1965 None => self.store.lock().is_some(),
1969 }
1970 }
1971
1972 pub fn refresh_memory_slice(&self) {
1991 if self.maxmemory() != 0 && self.mine().measuring(self.clock.now_ms()) {
1992 self.refresh_memory();
1993 }
1994 }
1995
1996 pub fn refresh_memory(&self) {
2001 if self.maxmemory() != 0 {
2002 let used = self.settled_memory();
2003 self.used.store(used, Relaxed);
2004 self.peak.fetch_max(used, Relaxed);
2010 }
2011 }
2012
2013 fn settled_memory(&self) -> usize {
2027 let mine = self.mine();
2028 let marked = mine.to_weigh() | 1u64 << mine.measure_next();
2029 let mut total = self.conn_bytes();
2030 for at in 0..DATABASES {
2031 if marked & (1u64 << at) == 0 {
2032 total += self.db_bytes[at].load(Relaxed);
2033 continue;
2034 }
2035 let db = &self.dbs[at];
2036 let now = (0..db.width())
2037 .map(|i| db.hold_stripe(i).settled_memory_bytes())
2038 .sum::<usize>();
2039 self.db_bytes[at].store(now, Relaxed);
2040 total += now;
2041 }
2042 total
2043 }
2044
2045 pub fn make_room(&self) -> bool {
2079 let limit = self.maxmemory();
2080 if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
2081 return true;
2082 }
2083 let mut used = self.settled_memory();
2088 self.used.store(used, Relaxed);
2089 let mut budget = EVICT_BUDGET;
2090 while used as u64 > limit {
2091 let over = used - limit as usize;
2092 if !self.relieve_step(over) {
2093 return false;
2094 }
2095 self.compact_hard_step();
2096 used = self.settled_memory();
2097 self.used.store(used, Relaxed);
2098 budget -= 1;
2099 if budget == 0 {
2100 break;
2101 }
2102 }
2103 true
2104 }
2105
2106 fn relieve_step(&self, over: usize) -> bool {
2123 let from = self.evict_db.load(Relaxed);
2124 for turn in 0..self.slots() {
2125 let i = (from + turn) % self.slots();
2126 let used = !self.slot(i).is_empty();
2129 let gave = if used && self.migrates(i) {
2130 self.attach_store(i);
2131 self.slot(i)
2136 .relieve(over)
2137 .is_ok_and(yo_kv::tier::Relief::made_room)
2138 } else {
2139 let armed = notify::arm(self, self.slot_db(i));
2145 let gone = self.slot(i).evict_one();
2146 notify::drain(self, armed);
2147 gone
2148 };
2149 if gave {
2150 self.evict_db.store((i + 1) % self.slots(), Relaxed);
2151 self.mine().mark(1u64 << self.slot_db(i));
2152 return true;
2153 }
2154 }
2155 false
2156 }
2157
2158 pub fn expire_slice(&self, budget: usize) -> usize {
2173 if !self.expiring() {
2178 return 0;
2179 }
2180 let now = self.clock.now_ms();
2181 if now == self.expire_ms.load(Relaxed) {
2182 return 0;
2183 }
2184 self.expire_ms.store(now, Relaxed);
2185 self.expire_step(budget)
2186 }
2187
2188 pub fn expire_step(&self, budget: usize) -> usize {
2204 let slots = self.slots();
2205 let mut spent = 0;
2206 let from = self.expire_db.load(Relaxed);
2207 for turn in 0..slots {
2208 if spent >= budget {
2209 break;
2210 }
2211 let i = (from + turn) % slots;
2212 let armed = notify::arm(self, self.slot_db(i));
2217 let mut slot = self.slot(i);
2222 let c = slot.expire_cycle(budget - spent);
2223 let left = (budget - spent).saturating_sub(c.examined);
2229 let fields = slot.field_expire_cycle(left);
2230 drop(slot);
2231 notify::drain(self, armed);
2232 repl::swept(self, self.slot_db(i));
2236 spent += c.examined + fields;
2237 if c.expired > 0 {
2238 self.expire_db.store((i + 1) % slots, Relaxed);
2239 self.mine().note(1u64 << self.slot_db(i));
2240 self.mine().unmeasure(1u64 << self.slot_db(i));
2243 }
2244 }
2245 spent
2246 }
2247
2248 fn compact_hard_step(&self) -> Option<usize> {
2255 let from = self.next_db.load(Relaxed);
2256 for turn in 0..self.slots() {
2257 let i = (from + turn) % self.slots();
2258 if let Some(moved) = self.slot(i).compact_hard() {
2259 self.next_db.store((i + 1) % self.slots(), Relaxed);
2260 self.mine().unmeasure(1u64 << self.slot_db(i));
2264 return Some(moved);
2265 }
2266 }
2267 None
2268 }
2269
2270 fn collect_marks(&self) {
2279 let mut marked = 0;
2280 for thread in &self.locals {
2281 marked |= thread.dirty.swap(0, Relaxed);
2282 }
2283 let mine = self.mine();
2284 mine.note(marked);
2285 mine.unmeasure(marked);
2291 }
2292
2293 pub fn compact_step(&self) -> Option<usize> {
2306 if !self.resizing() {
2311 return None;
2312 }
2313 let slots = self.slots();
2314 let looks = COMPACT_LOOKS.min(slots);
2315 if self.mine().collecting(self.clock.now_ms()) {
2321 self.collect_marks();
2322 }
2323 let mine = self.mine();
2324 let from = mine.compact_db.load(Relaxed);
2329 for turn in 0..looks {
2330 let i = (from + turn) % slots;
2331 let at = self.slot_db(i);
2335 if !mine.wanted(at) {
2336 continue;
2337 }
2338 if let Some(moved) = self.slot(i).compact_step() {
2339 mine.compact_db.store((i + 1) % slots, Relaxed);
2340 mine.unmeasure(1u64 << at);
2343 return Some(moved);
2344 }
2345 if i % self.width == self.width - 1 {
2349 mine.done(at);
2350 }
2351 }
2352 mine.compact_db.store((from + looks) % slots, Relaxed);
2353 None
2354 }
2355}
2356
2357impl Server {
2358 fn watching(&self) -> bool {
2367 self.watched.load(Relaxed) != 0
2368 }
2369
2370 pub(crate) fn notify_flags(&self) -> u32 {
2377 self.notify.load(Relaxed)
2378 }
2379
2380 pub(crate) fn set_notify_flags(&self, flags: u32) {
2382 self.notify.store(flags, Relaxed);
2383 }
2384
2385 fn recount(&self, watches: &Watches) {
2390 self.watched.store(watches.len(), Relaxed);
2391 }
2392}
2393
2394impl Default for Server {
2395 fn default() -> Server {
2396 Server::new()
2397 }
2398}
2399
2400pub struct Session {
2402 db: usize,
2403 id: u64,
2404 conn: u32,
2412 name: Vec<u8>,
2413 sets: himport::Fieldsets,
2419 scripted: bool,
2429 multi: Option<multi::Queue>,
2435 watching: Vec<multi::Watched>,
2442 running: bool,
2449 replay: crate::request::Argv,
2458 subs: Option<Box<pubsub::Subs>>,
2466 lib_name: Vec<u8>,
2474 lib_ver: Vec<u8>,
2475 no_evict: bool,
2482 no_touch: bool,
2485 reply: Reply,
2487 sock: Arc<Client>,
2496 authenticated: bool,
2504 acl: Box<acl::Identity>,
2511 master: bool,
2520 asking: bool,
2533 asking_next: bool,
2534 internal: bool,
2545 closing: bool,
2552 node_id: Vec<u8>,
2560}
2561
2562#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
2569pub enum Reply {
2570 #[default]
2572 On,
2573 Off,
2575 SkipNext,
2577 SkipNow,
2579}
2580
2581impl Session {
2582 #[must_use]
2584 pub fn new(id: u64) -> Session {
2585 Session {
2586 db: 0,
2587 id,
2588 conn: u32::MAX,
2589 name: Vec::new(),
2590 sets: himport::Fieldsets::default(),
2591 scripted: false,
2592 multi: None,
2593 watching: Vec::new(),
2594 running: false,
2595 replay: crate::request::Argv::new(),
2596 subs: None,
2597 lib_name: Vec::new(),
2598 lib_ver: Vec::new(),
2599 no_evict: false,
2600 no_touch: false,
2601 reply: Reply::On,
2602 sock: Arc::new(Client::new(id)),
2603 authenticated: false,
2604 master: false,
2605 asking: false,
2606 asking_next: false,
2607 internal: false,
2608 closing: false,
2609 node_id: Vec::new(),
2610 acl: Box::default(),
2611 }
2612 }
2613
2614 pub fn admit(&mut self, yes: bool) {
2621 self.authenticated = yes;
2622 }
2623
2624 #[must_use]
2626 pub(crate) fn authenticated(&self) -> bool {
2627 self.authenticated
2628 }
2629
2630 pub(crate) fn serve_master(&mut self, yes: bool) {
2636 self.master = yes;
2637 }
2638
2639 #[must_use]
2641 pub(crate) fn serving_master(&self) -> bool {
2642 self.master
2643 }
2644
2645 pub(crate) fn serve_internal(&mut self, yes: bool) {
2651 self.internal = yes;
2652 }
2653
2654 #[must_use]
2660 pub(crate) fn internal(&self) -> bool {
2661 self.internal || self.master
2662 }
2663
2664 pub(crate) fn set_node_id(&mut self, id: &[u8]) {
2666 yo_alloc::allow(|| {
2667 self.node_id.clear();
2668 self.node_id.extend_from_slice(id);
2669 });
2670 }
2671
2672 #[must_use]
2674 pub(crate) fn node_id(&self) -> &[u8] {
2675 &self.node_id
2676 }
2677
2678 pub(crate) fn hang_up(&mut self) {
2680 self.closing = true;
2681 }
2682
2683 #[must_use]
2685 pub(crate) fn hanging_up(&self) -> bool {
2686 self.closing
2687 }
2688
2689 pub(crate) fn ask_next(&mut self) {
2692 self.asking_next = true;
2693 }
2694
2695 #[must_use]
2701 pub fn row(&self) -> &Arc<Client> {
2702 &self.sock
2703 }
2704
2705 pub fn opened(&mut self, now_ms: u64) {
2711 self.sock.since_ms.store(now_ms, Relaxed);
2712 self.sock.last_ms.store(now_ms, Relaxed);
2713 }
2714
2715 pub fn set_socket(&mut self, peer: &str, local: &str, fd: i32, unix: bool) {
2722 yo_alloc::allow(|| {
2723 let mut text = self.sock.text.lock();
2724 text.peer.clear();
2725 text.peer.extend_from_slice(peer.as_bytes());
2726 text.local.clear();
2727 text.local.extend_from_slice(local.as_bytes());
2728 });
2729 self.sock.fd.store(fd, Relaxed);
2730 self.sock.set_flag(clients::UNIX, unix);
2731 }
2732
2733 pub fn read_bytes(&mut self, n: usize) {
2735 let row = &self.sock;
2736 row.net_in
2737 .store(row.net_in.load(Relaxed) + n as u64, Relaxed);
2738 row.reads.store(row.reads.load(Relaxed) + 1, Relaxed);
2739 }
2740
2741 pub fn wrote_bytes(&mut self, n: usize) {
2743 let row = &self.sock;
2744 row.net_out
2745 .store(row.net_out.load(Relaxed) + n as u64, Relaxed);
2746 }
2747
2748 pub fn note_buffers(&mut self, waiting: usize, room: usize, held: usize, reply: usize) {
2756 let row = &self.sock;
2757 row.qbuf.store(waiting as u64, Relaxed);
2758 row.qbuf_free.store(room as u64, Relaxed);
2759 row.obl.store(held as u64, Relaxed);
2760 row.rbs.store(reply as u64, Relaxed);
2761 row.rbp
2762 .store(row.rbp.load(Relaxed).max(reply as u64), Relaxed);
2763 }
2764
2765 pub fn note_proto(&mut self, version: i64) {
2771 self.sock.resp.store(version as u32, Relaxed);
2772 }
2773
2774 pub(crate) fn ran(&mut self, at: usize, sub: Option<&[u8]>, argv: u64, now_ms: u64) {
2781 self.sock.last_ms.store(now_ms, Relaxed);
2782 self.sock.argv_mem.store(argv, Relaxed);
2783 self.sock.note_command(at, sub);
2784 }
2785
2786 pub fn finished(&mut self) {
2801 self.asking = core::mem::take(&mut self.asking_next);
2805 let (sub, psub, ssub) = self.sub_counts();
2806 let (multi, multi_mem) = self.queued();
2807 let subscribed = self.subscribed();
2808 let in_multi = self.in_multi();
2809 let watching = self.watching.len();
2810 let db = self.db;
2811 let row = &self.sock;
2812 row.cmds.store(row.cmds.load(Relaxed) + 1, Relaxed);
2813 row.db.store(db as u32, Relaxed);
2814 row.sub.store(sub as u32, Relaxed);
2815 row.psub.store(psub as u32, Relaxed);
2816 row.ssub.store(ssub as u32, Relaxed);
2817 row.watch.store(watching as u32, Relaxed);
2818 row.multi.store(multi, Relaxed);
2819 row.multi_mem.store(multi_mem, Relaxed);
2820 row.set_flag(clients::SUBSCRIBED, subscribed);
2821 row.set_flag(clients::IN_MULTI, in_multi);
2822 }
2823
2824 #[must_use]
2826 pub const fn reply_mode(&self) -> Reply {
2827 self.reply
2828 }
2829
2830 pub const fn step_reply(&mut self) {
2835 self.reply = match self.reply {
2836 Reply::SkipNext => Reply::SkipNow,
2837 Reply::SkipNow => Reply::On,
2838 other => other,
2839 };
2840 }
2841
2842 pub(crate) const fn scripted(&self) -> bool {
2844 self.scripted
2845 }
2846
2847 pub(crate) const fn running(&self) -> bool {
2849 self.running
2850 }
2851
2852 pub(crate) fn monitoring(&self) -> bool {
2859 self.sock.flag(clients::MONITOR)
2860 }
2861
2862 pub(crate) fn replicating(&self) -> bool {
2869 self.sock.flag(clients::REPLICA)
2870 }
2871
2872 pub(crate) fn set_conn(&mut self, conn: u32) {
2878 self.conn = conn;
2879 self.sock.conn.store(conn, Relaxed);
2880 }
2881
2882 #[must_use]
2884 pub const fn id(&self) -> u64 {
2885 self.id
2886 }
2887
2888 #[must_use]
2890 pub const fn db(&self) -> usize {
2891 self.db
2892 }
2893
2894 #[must_use]
2896 pub fn name(&self) -> &[u8] {
2897 &self.name
2898 }
2899
2900 pub fn reset(&mut self) {
2905 self.db = 0;
2906 self.name.clear();
2907 self.sock.set_text(|text| &mut text.name, b"");
2908 self.sets.clear();
2912 self.reply = Reply::On;
2917 self.set_no_evict(false);
2918 self.set_no_touch(false);
2919 self.forget_user();
2923 }
2924
2925 fn set_name(&mut self, name: &[u8]) {
2927 yo_alloc::allow(|| {
2928 self.name.clear();
2929 self.name.extend_from_slice(name);
2930 });
2931 self.sock.set_text(|text| &mut text.name, name);
2932 }
2933
2934 fn set_lib_name(&mut self, value: &[u8]) {
2936 yo_alloc::allow(|| {
2937 self.lib_name.clear();
2938 self.lib_name.extend_from_slice(value);
2939 });
2940 self.sock.set_text(|text| &mut text.lib_name, value);
2941 }
2942
2943 fn set_lib_ver(&mut self, value: &[u8]) {
2945 yo_alloc::allow(|| {
2946 self.lib_ver.clear();
2947 self.lib_ver.extend_from_slice(value);
2948 });
2949 self.sock.set_text(|text| &mut text.lib_ver, value);
2950 }
2951
2952 fn set_no_evict(&mut self, on: bool) {
2954 self.no_evict = on;
2955 self.sock.set_flag(clients::NO_EVICT, on);
2956 }
2957
2958 fn set_no_touch(&mut self, on: bool) {
2960 self.no_touch = on;
2961 self.sock.set_flag(clients::NO_TOUCH, on);
2962 }
2963}
2964
2965pub fn forget_session(server: &Server, session: &mut Session) {
2975 multi::release(server, session);
2976 pubsub::release(server, session);
2977 if session.monitoring() {
2978 server.watch_no_more(session.row());
2979 }
2980 if session.replicating() {
2981 server.drop_replica(session.row().id);
2982 }
2983 if session.internal() {
2987 server.asm_forget(session.row().id);
2988 }
2989}
2990
2991pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
2996 if args.is_empty() {
2999 return Flow::Continue;
3000 }
3001 let flow = resolved(server, session, lookup(args.name()), args, out);
3002 if flow != Flow::Hold {
3012 session.finished();
3013 }
3014 flow
3015}
3016
3017const CONTAINERS: [&str; 13] = [
3023 "acl", "backup", "client", "cluster", "command", "config", "function", "memory", "object",
3024 "pubsub", "script", "xgroup", "xinfo",
3025];
3026
3027fn container_sub<'a>(spec: &Spec, args: &Args<'a>) -> Option<&'a [u8]> {
3033 (args.len() > 1 && CONTAINERS.contains(&spec.name)).then(|| args.get(1))
3034}
3035
3036const MAY_REPLICATE: [&str; 6] = ["eval", "evalsha", "fcall", "pfcount", "publish", "spublish"];
3042
3043fn may_replicate(spec: &Spec, session: &Session) -> bool {
3050 spec.flags.contains(&"write")
3051 || MAY_REPLICATE.contains(&spec.name)
3052 || (spec.name == "exec" && session.queued_writes())
3053}
3054
3055fn touches_keyspace(spec: &Spec) -> bool {
3063 spec.flags.contains(&"write")
3064 || spec.flags.contains(&"readonly")
3065 || MAY_REPLICATE.contains(&spec.name)
3066}
3067
3068pub fn resolved(
3080 server: &Server,
3081 session: &mut Session,
3082 spec: Option<&'static Spec>,
3083 args: Args<'_>,
3084 out: &mut Out,
3085) -> Flow {
3086 if args.is_empty() {
3087 return Flow::Continue;
3088 }
3089 server.mine().stats.commands.bump();
3090
3091 let Some(spec) = spec else {
3098 multi::refuse(server, session, None, &args::unknown_command(args), out);
3099 return Flow::Continue;
3100 };
3101 if !arity_ok(spec, args.len()) {
3102 server.mine().cmdstats.at(spec).rejected.bump();
3103 multi::refuse(
3104 server,
3105 session,
3106 Some(spec),
3107 &args::wrong_arity(spec.name),
3108 out,
3109 );
3110 return Flow::Continue;
3111 }
3112 let argv = (0..args.len()).map(|i| args.get(i).len() as u64).sum();
3118 session.ran(
3119 table::index_of(spec),
3120 container_sub(spec, &args),
3121 argv,
3122 server.now_ms(),
3123 );
3124
3125 if server.guarded()
3137 && !session.authenticated()
3138 && !session.serving_master()
3139 && !spec.flags.contains(&"no_auth")
3140 {
3141 server.mine().cmdstats.at(spec).rejected.bump();
3142 if spec.name == "exec" {
3143 multi::abort(server, session, auth::NOAUTH, out);
3144 } else {
3145 session.dirty_multi();
3146 out.error(auth::NOAUTH.as_bytes());
3147 }
3148 return Flow::Continue;
3149 }
3150
3151 if session.in_multi()
3152 && let Some(e) = multi::refused_in_multi(spec)
3153 {
3154 server.mine().cmdstats.at(spec).rejected.bump();
3155 multi::refuse(server, session, Some(spec), &e, out);
3156 return Flow::Continue;
3157 }
3158
3159 if server.restricted()
3168 && !session.serving_master()
3169 && let Some(said) = acl::gate(server, session, spec, args, out)
3170 {
3171 server.mine().cmdstats.at(spec).rejected.bump();
3172 if spec.name == "exec" {
3173 multi::abort(server, session, &said, out);
3174 } else {
3175 session.dirty_multi();
3176 out.error(said.as_bytes());
3177 }
3178 return Flow::Continue;
3179 }
3180
3181 if server.cluster_enabled()
3194 && !session.serving_master()
3195 && let Some(said) = cluster::gate(
3196 server,
3197 session.db,
3198 session.asking || cluster::asks(spec),
3199 spec,
3200 args,
3201 )
3202 {
3203 server.mine().cmdstats.at(spec).rejected.bump();
3204 if spec.name == "exec" {
3205 multi::abort(server, session, said.message(), out);
3206 } else {
3207 session.dirty_multi();
3208 out.error(said.message().as_bytes());
3209 }
3210 return Flow::Continue;
3211 }
3212
3213 if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
3223 server.mine().cmdstats.at(spec).rejected.bump();
3224 session.dirty_multi();
3225 out.error_line(b"OOM ", OOM);
3226 return Flow::Continue;
3227 }
3228
3229 if server.read_only_replica() && !session.serving_master() && spec.flags.contains(&"write") {
3240 server.mine().cmdstats.at(spec).rejected.bump();
3241 if spec.name == "exec" {
3242 multi::abort(server, session, follow::READONLY, out);
3243 } else {
3244 session.dirty_multi();
3245 out.error(follow::READONLY.as_bytes());
3246 }
3247 return Flow::Continue;
3248 }
3249
3250 if let Some(e) = pubsub::refused(session, spec, out) {
3258 server.mine().cmdstats.at(spec).rejected.bump();
3259 multi::refuse(server, session, Some(spec), &e, out);
3260 return Flow::Continue;
3261 }
3262
3263 if session.monitoring() && touches_keyspace(spec) {
3274 server.mine().cmdstats.at(spec).rejected.bump();
3275 multi::refuse(server, session, Some(spec), &monitor::replica(), out);
3276 return Flow::Continue;
3277 }
3278
3279 if !session.running
3297 && !session.monitoring()
3298 && !session.serving_master()
3299 && let Some(all) = server.paused(server.now_ms())
3300 && (all || may_replicate(spec, session))
3301 {
3302 return Flow::Hold;
3303 }
3304
3305 if !session.running && server.frozen() && may_replicate(spec, session) {
3313 return Flow::Hold;
3314 }
3315
3316 if session.queues(spec.name) {
3321 return multi::queue(session, spec, args, out);
3322 }
3323
3324 server.mine().mark(match spec.group {
3331 "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
3332 | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
3333 1u64 << session.db
3334 }
3335 _ => ALL_DATABASES,
3336 });
3337
3338 let watched = server.monitored() && !monitor::hidden(spec, args);
3347 if watched && monitor::SCRIPTS.contains(&spec.name) {
3348 monitor::feed(server, session, args);
3349 }
3350
3351 let mark = out.len();
3352 let armed = notify::arm(server, session.db);
3363 let copying = server.propagating();
3372 let verbatim = spec.flags.contains(&"write");
3373 misses::report(&server.dbs[session.db], session.db, spec, args);
3378 let reading = lookups::reading(misses::reading(spec, args));
3383 let done = if spec.flags.contains(&"blocking") {
3384 blocking::execute(server, session, spec, args, out)
3385 } else {
3386 match spec.group {
3387 "string" => {
3388 let db = session.db;
3389 strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3390 }
3391 "bitmap" => {
3395 let db = session.db;
3396 bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3397 }
3398 "hyperloglog" => {
3401 let db = session.db;
3402 hll::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3403 }
3404 "set" => {
3405 let db = session.db;
3406 sets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3407 }
3408 "hash" if spec.name == "himport" => {
3413 let db = session.db;
3414 himport::execute(&server.dbs[db], &mut session.sets, args, out)
3415 .map(|()| Flow::Continue)
3416 }
3417 "hash" => {
3422 let db = session.db;
3423 let changed = hashes::execute(&server.dbs[db], db, spec, args, out);
3424 changed.map(|changed| {
3425 indexing::changed(server, db, args.get(1), changed);
3426 Flow::Continue
3427 })
3428 }
3429 "list" => {
3430 let db = session.db;
3431 lists::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3432 }
3433 "zset" => {
3434 let db = session.db;
3435 zsets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3436 }
3437 "geo" => {
3441 let db = session.db;
3442 geo::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3443 }
3444 "array" => {
3445 let db = session.db;
3446 arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3447 }
3448 "graph" => {
3449 let db = session.db;
3450 graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3451 }
3452 "json" => {
3456 let db = session.db;
3457 json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3458 }
3459 "vector" => {
3460 let db = session.db;
3461 vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3462 }
3463 "bloom" => {
3464 let db = session.db;
3465 bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3466 }
3467 "cuckoo" => {
3468 let db = session.db;
3469 cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3470 }
3471 "cms" => {
3472 let db = session.db;
3473 cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3474 }
3475 "topk" => {
3476 let db = session.db;
3477 topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3478 }
3479 "tdigest" => {
3480 let db = session.db;
3481 tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3482 }
3483 "ts" => {
3484 let db = session.db;
3485 ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3486 }
3487 "stream" => {
3492 let db = session.db;
3493 let now = server.now_ms();
3494 streams::execute(&server.dbs[db], db, spec, args, now, out).map(|()| Flow::Continue)
3495 }
3496 "keyspace" if spec.name == "migrate" => {
3500 migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
3501 }
3502 "keyspace" | "server" if spec.group == "keyspace" || spec.name == "restore-asking" => {
3512 let mut touched = indexing::Touched::new(server);
3513 let done =
3514 keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
3515 done.map(|()| {
3516 indexing::touched(server, &touched);
3517 Flow::Continue
3518 })
3519 }
3520 "search" if spec.name == "FT.SEARCH" => {
3527 search::find(server, session.db, args, out).map(|()| Flow::Continue)
3532 }
3533 "search" if spec.name == "FT.AGGREGATE" => {
3534 search::roll(server, session.db, args, out).map(|()| Flow::Continue)
3535 }
3536 "search" if spec.name == "FT.HYBRID" => {
3537 search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
3538 }
3539 "search" if spec.name == "FT.PROFILE" => {
3540 search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
3543 }
3544 "search" if spec.name.starts_with("FT.SUG") => {
3548 let db = session.db;
3549 suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3550 }
3551 "search"
3555 if matches!(
3556 spec.name,
3557 "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
3558 ) =>
3559 {
3560 let db = session.db;
3561 search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
3562 }
3563 "search" if spec.name == "FT.CURSOR" => {
3564 search::cursor::execute(server, args, out).map(|()| Flow::Continue)
3568 }
3569 "search" => {
3570 let db = session.db;
3571 let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
3572 made.map(|made| {
3573 match made {
3574 Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
3575 Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
3576 None => {}
3577 }
3578 Flow::Continue
3579 })
3580 }
3581 "scripting" => {
3582 scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
3583 }
3584 "transactions" => multi::execute(server, session, spec, args, out),
3585 "pubsub" => pubsub::execute(server, session, spec, args, out),
3589 _ => server::execute(server, session, spec, args, out),
3590 }
3591 };
3592 drop(reading);
3593 if watched && !monitor::SCRIPTS.contains(&spec.name) {
3597 monitor::feed(server, session, args);
3598 }
3599 if let Err(e) = &done {
3607 misses::undo(spec, e);
3608 }
3609 notify::drain(server, armed);
3610
3611 repl::swept(server, session.db);
3627 if copying {
3628 if done.is_ok() {
3629 repl::feed(server, session.db, args, verbatim);
3630 } else {
3631 repl::forget();
3632 }
3633 }
3634
3635 let flow = match done {
3636 Ok(flow) => flow,
3637 Err(e) => {
3638 out.truncate(mark);
3639 write_error(out, &e);
3640 Flow::Continue
3641 }
3642 };
3643
3644 if server.watching() && spec.flags.contains(&"write") {
3649 multi::touched(server, session, spec, args);
3650 }
3651
3652 let row = server.mine().cmdstats.at(spec);
3663 row.calls.bump();
3664 if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
3665 row.failed.bump();
3666 }
3667 if session.hanging_up() {
3672 return Flow::Close;
3673 }
3674 flow
3675}
3676
3677fn write_error(out: &mut Out, e: &Error) {
3687 let prefix: &[u8] = match e.code() {
3688 Code::WrongType => b"WRONGTYPE ",
3689 Code::Corrupt => b"INVALIDOBJ ",
3693 _ => b"ERR ",
3694 };
3695 out.error_line(prefix, e.message().as_bytes());
3696}
3697
3698#[cfg(test)]
3699mod tests {
3700 use super::*;
3701 use crate::proto::{Limits, Proto};
3702 use crate::request::Argv;
3703
3704 pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
3709 let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
3710 for p in parts {
3711 wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
3712 wire.extend_from_slice(p);
3713 wire.extend_from_slice(b"\r\n");
3714 }
3715 wire
3716 }
3717
3718 struct Fixture {
3720 server: Server,
3721 session: Session,
3722 argv: Argv,
3723 out: Out,
3724 mark: u64,
3726 }
3727
3728 fn int_of(reply: &str) -> i64 {
3731 reply
3732 .strip_prefix(':')
3733 .and_then(|s| s.strip_suffix("\r\n"))
3734 .unwrap_or_else(|| panic!("not an integer reply: {reply:?}"))
3735 .parse()
3736 .expect("an integer reply holds an integer")
3737 }
3738
3739 impl Fixture {
3740 fn new() -> Fixture {
3741 Fixture::on(Server::new())
3742 }
3743
3744 fn striped(width: usize) -> Fixture {
3746 Fixture::on(Server::with_width(width))
3747 }
3748
3749 fn on(server: Server) -> Fixture {
3750 Fixture {
3751 server,
3752 session: Session::new(7),
3753 argv: Argv::new(),
3754 out: Out::new(Proto::Resp2),
3755 mark: 0,
3756 }
3757 }
3758
3759 fn replicated() -> Fixture {
3766 let f = Fixture::new();
3767 f.server.pretend_replica();
3768 f
3769 }
3770
3771 fn crossed(&mut self, parts: &[&[u8]]) -> String {
3777 self.run(parts);
3778 let (text, upto) = self.server.stream_since(self.mark);
3779 self.mark = upto;
3780 text
3781 }
3782
3783 fn run(&mut self, parts: &[&[u8]]) -> String {
3785 self.flow(parts).1
3786 }
3787
3788 fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
3794 let wire = encode(parts);
3795 self.argv.decode(&wire, &Limits::default()).unwrap();
3796 self.out.clear();
3797 execute(
3798 &self.server,
3799 &mut self.session,
3800 Args::new(&self.argv, &wire),
3801 &mut self.out,
3802 );
3803 self.out.as_slice().to_vec()
3804 }
3805
3806 fn advance(&mut self, ms: u64) {
3808 self.server.advance_clock_ms(ms);
3809 }
3810
3811 fn other(&mut self, parts: &[&[u8]]) -> String {
3816 self.other_in(self.session.db(), parts)
3817 }
3818
3819 fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
3821 let mut session = Session::new(8);
3822 session.db = db;
3823 let reply = self.by(&mut session, parts);
3824 forget_session(&self.server, &mut session);
3825 reply
3826 }
3827
3828 fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
3830 let wire = encode(parts);
3831 let mut argv = Argv::new();
3832 argv.decode(&wire, &Limits::default()).unwrap();
3833 let mut out = Out::new(Proto::Resp2);
3834 execute(&self.server, session, Args::new(&argv, &wire), &mut out);
3835 String::from_utf8_lossy(out.as_slice()).into_owned()
3836 }
3837
3838 fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
3840 let wire = encode(parts);
3841 self.argv.decode(&wire, &Limits::default()).unwrap();
3842 self.out.clear();
3843 let flow = execute(
3844 &self.server,
3845 &mut self.session,
3846 Args::new(&self.argv, &wire),
3847 &mut self.out,
3848 );
3849 (
3850 flow,
3851 String::from_utf8_lossy(self.out.as_slice()).into_owned(),
3852 )
3853 }
3854 }
3855
3856 #[test]
3857 fn multi_holds_commands_and_exec_runs_them() {
3858 let mut f = Fixture::new();
3859 assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
3860 assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
3861 assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
3862 assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
3864 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
3865 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
3866 }
3867
3868 #[test]
3877 fn the_second_exec_of_a_shape_does_not_grow_the_buffer() {
3878 let mut f = Fixture::new();
3879 for _ in 0..2 {
3880 f.run(&[b"MULTI"]);
3881 f.run(&[b"SET", b"k", b"1"]);
3882 f.run(&[b"INCR", b"k"]);
3883 f.run(&[b"EXEC"]);
3884 }
3885 let room = f.session.replay.room();
3886 assert!(room > 0, "the first transaction should have set the room");
3887 f.run(&[b"MULTI"]);
3888 f.run(&[b"SET", b"k", b"1"]);
3889 f.run(&[b"INCR", b"k"]);
3890 f.run(&[b"EXEC"]);
3891 assert_eq!(f.session.replay.room(), room);
3892 }
3893
3894 #[test]
3895 fn an_empty_transaction_answers_an_empty_array() {
3896 let mut f = Fixture::new();
3897 f.run(&[b"MULTI"]);
3898 assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
3899 }
3900
3901 #[test]
3902 fn exec_and_discard_want_a_transaction_to_be_open() {
3903 let mut f = Fixture::new();
3904 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
3905 assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
3906 assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
3909 }
3910
3911 #[test]
3912 fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
3913 let mut f = Fixture::new();
3914 f.run(&[b"MULTI"]);
3915 assert_eq!(
3916 f.run(&[b"MULTI"]),
3917 "-ERR MULTI calls can not be nested\r\n",
3918 "nested MULTI is raised by the command and not by the funnel"
3919 );
3920 assert_eq!(
3921 f.run(&[b"WATCH", b"k"]),
3922 "-ERR WATCH inside MULTI is not allowed\r\n"
3923 );
3924 f.run(&[b"SET", b"k", b"1"]);
3925 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
3926 }
3927
3928 #[test]
3929 fn an_error_the_funnel_raises_kills_the_transaction() {
3930 for bad in [
3931 &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
3932 &[b"GET".as_slice()],
3933 ] {
3934 let mut f = Fixture::new();
3935 f.run(&[b"MULTI"]);
3936 assert!(f.run(bad).starts_with("-ERR "));
3937 assert_eq!(
3938 f.run(&[b"SET", b"k", b"1"]),
3939 "+QUEUED\r\n",
3940 "a dead transaction still answers QUEUED, which is Redis"
3941 );
3942 assert_eq!(
3943 f.run(&[b"EXEC"]),
3944 "-EXECABORT Transaction discarded because of previous errors.\r\n"
3945 );
3946 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3947 }
3948 }
3949
3950 #[test]
3951 fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
3952 let mut f = Fixture::new();
3953 f.run(&[b"MULTI"]);
3954 f.run(&[b"SET", b"k", b"1"]);
3955 assert_eq!(
3956 f.run(&[b"EXEC", b"x"]),
3957 "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
3958 );
3959 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
3960 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3961 }
3962
3963 #[test]
3964 fn a_command_a_transaction_may_not_hold_kills_it() {
3965 let mut f = Fixture::new();
3966 f.run(&[b"MULTI"]);
3967 assert_eq!(
3968 f.run(&[b"SHUTDOWN", b"NOSAVE"]),
3969 "-ERR Command not allowed inside a transaction\r\n"
3970 );
3971 assert_eq!(
3972 f.run(&[b"EXEC"]),
3973 "-EXECABORT Transaction discarded because of previous errors.\r\n"
3974 );
3975 }
3976
3977 #[test]
3978 fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
3979 let mut f = Fixture::new();
3980 f.run(&[b"RPUSH", b"l", b"v"]);
3981 f.run(&[b"MULTI"]);
3982 f.run(&[b"INCR", b"l"]);
3983 f.run(&[b"SET", b"y", b"2"]);
3984 assert_eq!(
3985 f.run(&[b"EXEC"]),
3986 "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
3987 );
3988 assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
3989 }
3990
3991 #[test]
3992 fn discard_and_reset_both_throw_the_queue_away() {
3993 let mut f = Fixture::new();
3994 f.run(&[b"MULTI"]);
3995 f.run(&[b"SET", b"k", b"1"]);
3996 assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
3997 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
3998 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3999
4000 f.run(&[b"MULTI"]);
4001 f.run(&[b"SET", b"k", b"1"]);
4002 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
4003 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
4004 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4005 }
4006
4007 #[test]
4008 fn select_is_queued_and_applied_when_exec_runs_it() {
4009 let mut f = Fixture::new();
4010 f.run(&[b"MULTI"]);
4011 assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
4012 f.run(&[b"SET", b"k", b"1"]);
4013 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
4014 assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
4015 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
4016 }
4017
4018 #[test]
4019 fn a_write_by_another_connection_fails_the_transaction() {
4020 let mut f = Fixture::new();
4021 f.run(&[b"SET", b"k", b"1"]);
4022 assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
4023 f.other(&[b"SET", b"k", b"2"]);
4024 f.run(&[b"MULTI"]);
4025 f.run(&[b"GET", b"k"]);
4026 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
4027 }
4028
4029 #[test]
4030 fn a_write_that_puts_the_same_value_back_still_fails_it() {
4031 let mut f = Fixture::new();
4032 f.run(&[b"SET", b"k", b"1"]);
4033 f.run(&[b"WATCH", b"k"]);
4034 f.other(&[b"SET", b"k", b"1"]);
4035 f.run(&[b"MULTI"]);
4036 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
4037 }
4038
4039 #[test]
4040 fn a_read_by_another_connection_does_not() {
4041 let mut f = Fixture::new();
4042 f.run(&[b"SET", b"k", b"1"]);
4043 f.run(&[b"WATCH", b"k"]);
4044 f.other(&[b"GET", b"k"]);
4045 f.other(&[b"STRLEN", b"k"]);
4046 f.run(&[b"MULTI"]);
4047 f.run(&[b"GET", b"k"]);
4048 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
4049 }
4050
4051 #[test]
4052 fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
4053 let mut f = Fixture::new();
4054 f.run(&[b"WATCH", b"k"]);
4055 f.other(&[b"DEL", b"k"]);
4056 f.run(&[b"MULTI"]);
4057 f.run(&[b"PING"]);
4058 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
4059 f.run(&[b"WATCH", b"k"]);
4061 f.other(&[b"SET", b"k", b"1"]);
4062 f.run(&[b"MULTI"]);
4063 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
4064 }
4065
4066 #[test]
4067 fn a_watched_key_that_expires_fails_the_transaction() {
4068 let mut f = Fixture::new();
4069 f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
4070 f.run(&[b"WATCH", b"k"]);
4071 f.run(&[b"MULTI"]);
4072 f.advance(100);
4073 assert_eq!(
4074 f.run(&[b"EXEC"]),
4075 "*-1\r\n",
4076 "nothing wrote to the key, so only the liveness check can catch this"
4077 );
4078 }
4079
4080 #[test]
4081 fn every_way_a_transaction_ends_lets_go_of_the_watches() {
4082 for end in [
4083 &[b"EXEC".as_slice()] as &[&[u8]],
4084 &[b"DISCARD".as_slice()],
4085 &[b"UNWATCH".as_slice()],
4086 &[b"RESET".as_slice()],
4087 ] {
4088 let mut f = Fixture::new();
4089 f.run(&[b"SET", b"k", b"1"]);
4090 f.run(&[b"WATCH", b"k"]);
4091 if end[0] != b"UNWATCH" && end[0] != b"RESET" {
4092 f.run(&[b"MULTI"]);
4093 }
4094 f.run(end);
4095 assert!(!f.server.watching(), "{end:?} left a row behind");
4096 f.other(&[b"SET", b"k", b"2"]);
4098 f.run(&[b"MULTI"]);
4099 f.run(&[b"GET", b"k"]);
4100 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
4101 }
4102 }
4103
4104 #[test]
4105 fn a_connection_going_away_lets_go_of_its_watches() {
4106 let mut f = Fixture::new();
4107 f.run(&[b"SET", b"k", b"1"]);
4108 f.run(&[b"WATCH", b"k"]);
4109 assert!(f.server.watching());
4110 forget_session(&f.server, &mut f.session);
4111 assert!(!f.server.watching());
4112 }
4113
4114 #[test]
4115 fn watching_the_same_key_twice_is_one_watch() {
4116 let mut f = Fixture::new();
4117 f.run(&[b"SET", b"k", b"1"]);
4118 f.run(&[b"WATCH", b"k", b"k"]);
4119 f.run(&[b"UNWATCH"]);
4120 assert!(
4121 !f.server.watching(),
4122 "the row counts watchers, so a doubled watch would leave one behind"
4123 );
4124 }
4125
4126 #[test]
4127 fn two_connections_can_watch_the_same_key() {
4128 let mut f = Fixture::new();
4129 f.run(&[b"SET", b"k", b"1"]);
4130 f.run(&[b"WATCH", b"k"]);
4131 let mut second = Session::new(9);
4132 second.db = f.session.db();
4133 assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
4134 forget_session(&f.server, &mut second);
4136 assert!(f.server.watching());
4137 f.other(&[b"SET", b"k", b"2"]);
4138 f.run(&[b"MULTI"]);
4139 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
4140 }
4141
4142 #[test]
4143 fn flushdb_fails_a_watch_on_a_key_that_was_there() {
4144 let mut f = Fixture::new();
4145 f.run(&[b"SET", b"k", b"1"]);
4146 f.run(&[b"WATCH", b"k"]);
4147 f.other(&[b"FLUSHDB"]);
4148 f.run(&[b"MULTI"]);
4149 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
4150 }
4151
4152 #[test]
4153 fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
4154 let mut f = Fixture::new();
4155 f.run(&[b"WATCH", b"k"]);
4156 f.other(&[b"FLUSHDB"]);
4157 f.run(&[b"MULTI"]);
4158 f.run(&[b"PING"]);
4159 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
4160 }
4161
4162 #[test]
4163 fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
4164 let mut f = Fixture::new();
4165 f.run(&[b"SET", b"k", b"1"]);
4166 f.run(&[b"WATCH", b"k"]);
4167 let elsewhere = f.session.db() + 1;
4169 f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
4170 f.run(&[b"MULTI"]);
4171 f.run(&[b"GET", b"k"]);
4172 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
4173 }
4174
4175 #[test]
4176 fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
4177 let mut f = Fixture::new();
4178 f.run(&[b"RPUSH", b"src", b"1"]);
4179 f.run(&[b"WATCH", b"dst"]);
4180 f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
4181 f.run(&[b"MULTI"]);
4182 assert_eq!(
4183 f.run(&[b"EXEC"]),
4184 "*-1\r\n",
4185 "SORT is movablekeys, so every watched key in the database is asked"
4186 );
4187 }
4188
4189 #[test]
4190 fn a_server_nobody_is_watching_says_so() {
4191 let mut f = Fixture::new();
4192 assert!(!f.server.watching());
4193 f.run(&[b"SET", b"k", b"1"]);
4194 assert!(!f.server.watching());
4195 }
4196
4197 #[test]
4203 fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
4204 let mut f = Fixture::new();
4205 assert_eq!(
4206 f.run(&[b"SUBSCRIBE", b"a", b"b"]),
4207 "*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"
4208 );
4209 assert_eq!(
4210 f.run(&[b"PSUBSCRIBE", b"c*"]),
4211 "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
4212 );
4213 assert_eq!(
4214 f.run(&[b"SSUBSCRIBE", b"s"]),
4215 "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
4216 );
4217 assert_eq!(
4220 f.run(&[b"SUBSCRIBE", b"a"]),
4221 "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
4222 );
4223 }
4224
4225 #[test]
4228 fn unsubscribe_answers_for_names_it_was_not_holding_too() {
4229 let mut f = Fixture::new();
4230 f.run(&[b"SUBSCRIBE", b"a"]);
4231
4232 assert_eq!(
4235 f.run(&[b"UNSUBSCRIBE", b"zz"]),
4236 "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
4237 );
4238 f.run(&[b"SUBSCRIBE", b"b"]);
4240 f.run(&[b"PSUBSCRIBE", b"p*"]);
4241 assert_eq!(
4242 f.run(&[b"UNSUBSCRIBE"]),
4243 "*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"
4244 );
4245 assert_eq!(
4248 f.run(&[b"UNSUBSCRIBE"]),
4249 "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
4250 "the pattern is still held, so the count is one"
4251 );
4252 assert_eq!(
4253 f.run(&[b"SUNSUBSCRIBE"]),
4254 "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
4255 "shard channels are counted on their own"
4256 );
4257 }
4258
4259 #[test]
4264 fn the_subscribe_gate_does_not_reach_inside_exec() {
4265 let mut f = Fixture::new();
4266 f.run(&[b"SET", b"k", b"1"]);
4267 f.run(&[b"MULTI"]);
4268 assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
4269 assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
4270 assert_eq!(
4271 f.run(&[b"EXEC"]),
4272 "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
4273 );
4274 assert_eq!(
4277 f.run(&[b"GET", b"k"]),
4278 "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
4279 );
4280 }
4281
4282 #[test]
4285 fn exec_sent_by_a_subscriber_aborts_the_transaction() {
4286 let mut f = Fixture::new();
4287 f.run(&[b"MULTI"]);
4288 f.run(&[b"SET", b"k", b"1"]);
4289 f.run(&[b"SUBSCRIBE", b"z"]);
4290 f.run(&[b"EXEC"]);
4291 f.run(&[b"MULTI"]);
4292 assert_eq!(
4293 f.run(&[b"EXEC"]),
4294 "-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"
4295 );
4296 }
4297
4298 #[test]
4301 fn reset_lets_go_of_every_subscription() {
4302 let mut f = Fixture::new();
4303 f.run(&[b"SUBSCRIBE", b"a"]);
4304 f.run(&[b"PSUBSCRIBE", b"p*"]);
4305 f.run(&[b"SSUBSCRIBE", b"s"]);
4306 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
4307 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
4308 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
4309 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
4310 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4312 }
4313
4314 #[test]
4317 fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
4318 let mut f = Fixture::new();
4319 let mut sub = Session::new(9);
4320 f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
4321 f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
4322 f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
4323
4324 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
4325 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
4326 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
4327 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
4328 assert_eq!(
4329 f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
4330 "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
4331 );
4332 assert_eq!(
4333 f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
4334 "*2\r\n$1\r\na\r\n:1\r\n",
4335 "the shard channel and the channel share a name and not a count"
4336 );
4337 assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
4338
4339 forget_session(&f.server, &mut sub);
4340 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
4341 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
4342 }
4343
4344 #[test]
4347 fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
4348 let mut f = Fixture::new();
4349 assert_eq!(
4350 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4351 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
4352 );
4353 assert_eq!(
4354 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
4355 "+OK\r\n"
4356 );
4357 assert_eq!(
4360 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4361 "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
4362 );
4363 assert_eq!(
4364 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
4365 "+OK\r\n"
4366 );
4367 assert_eq!(
4368 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4369 "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
4370 );
4371 }
4372
4373 #[test]
4374 fn a_letter_the_notification_setting_does_not_know_is_refused() {
4375 let mut f = Fixture::new();
4376 assert_eq!(
4377 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
4378 "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
4379 - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
4380 );
4381 assert_eq!(
4384 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4385 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
4386 );
4387 }
4388
4389 #[test]
4395 fn pubsub_says_no_two_different_ways() {
4396 let mut f = Fixture::new();
4397 assert_eq!(
4398 f.run(&[b"PUBSUB"]),
4399 "-ERR wrong number of arguments for 'pubsub' command\r\n"
4400 );
4401 assert_eq!(
4402 f.run(&[b"PUBSUB", b"NOPE"]),
4403 "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
4404 );
4405 assert_eq!(
4406 f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
4407 "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
4408 );
4409 assert_eq!(
4410 f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
4411 "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
4412 );
4413 assert_eq!(
4414 f.run(&[b"PUBSUB", b"HELP", b"x"]),
4415 "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
4416 );
4417 }
4418
4419 #[test]
4422 fn publishing_to_nobody_answers_zero() {
4423 let mut f = Fixture::new();
4424 assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
4425 assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
4426 assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
4428 }
4429
4430 #[test]
4434 fn a_publish_counts_the_deliveries_and_not_the_clients() {
4435 let mut f = Fixture::new();
4436 let mut sub = Session::new(9);
4437 f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
4438 f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
4439 f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
4440 assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
4441 forget_session(&f.server, &mut sub);
4442 }
4443
4444 #[cfg_attr(miri, ignore = "megabytes through the arena")]
4457 #[test]
4458 fn rewriting_the_same_keys_does_not_grow_the_server() {
4459 let mut f = Fixture::new();
4460 let val = vec![b'v'; 1024];
4461 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
4462
4463 for k in &keys {
4464 f.run(&[b"SET", k, &val]);
4465 }
4466 f.server.compact_step();
4467 let after_first = f.server.memory_bytes();
4468
4469 for _ in 0..500 {
4474 for k in &keys {
4475 f.run(&[b"SET", k, &val]);
4476 }
4477 f.server.compact_step();
4478 }
4479
4480 assert!(
4481 f.server.memory_bytes() <= after_first * 2,
4482 "held {} after five hundred passes against {after_first} after one",
4483 f.server.memory_bytes()
4484 );
4485 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
4486 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
4487 }
4488
4489 #[cfg_attr(miri, ignore = "megabytes through the arena")]
4506 #[test]
4507 fn a_database_nobody_started_on_is_still_collected() {
4508 let mut f = Fixture::new();
4509 assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
4510 let val = vec![b'v'; 1024];
4511 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
4512
4513 for k in &keys {
4514 f.run(&[b"SET", k, &val]);
4515 }
4516 let drain = |f: &Fixture| {
4520 for _ in 0..4 * f.server.slots() {
4521 if f.server.compact_step().is_none() && !f.server.mine().wanted(9) {
4522 return;
4523 }
4524 }
4525 panic!("compaction never got to the end of database nine");
4526 };
4527 drain(&f);
4528 assert!(
4529 !f.server.mine().wanted(9),
4530 "database nine was drained and should not be asked again until it is written to"
4531 );
4532 let after_first = f.server.memory_bytes();
4533
4534 for _ in 0..500 {
4535 for k in &keys {
4536 f.run(&[b"SET", k, &val]);
4537 }
4538 f.server.compact_step();
4539 }
4540
4541 assert!(
4542 f.server.memory_bytes() <= after_first * 2,
4543 "held {} after five hundred passes against {after_first} after one",
4544 f.server.memory_bytes()
4545 );
4546 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
4547 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
4548 f.run(&[b"SELECT", b"0"]);
4550 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4551 }
4552
4553 #[test]
4566 fn the_maintenance_turn_does_not_write_a_shared_cursor() {
4567 let f = Fixture::new();
4568 let before = f.server.next_db.load(Relaxed);
4569 let mine = f.server.mine().compact_db.load(Relaxed);
4570 assert!(f.server.compact_step().is_none());
4574 assert_eq!(
4575 f.server.next_db.load(Relaxed),
4576 before,
4577 "the turn wrote the cursor the over limit path reads"
4578 );
4579 assert_ne!(
4580 f.server.mine().compact_db.load(Relaxed),
4581 mine,
4582 "the turn did not move on, so it will look at the same stripes forever"
4583 );
4584 }
4585
4586 #[test]
4594 fn each_thread_starts_its_compaction_somewhere_else() {
4595 let mut server = Server::new();
4596 server.set_threads(8);
4597 let starts: Vec<usize> = server
4598 .locals
4599 .iter()
4600 .map(|t| t.compact_db.load(Relaxed))
4601 .collect();
4602 assert_eq!(starts, (0..8).collect::<Vec<usize>>());
4603 }
4604
4605 #[test]
4606 fn a_command_goes_from_bytes_to_bytes() {
4607 let mut f = Fixture::new();
4608 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
4609 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
4610 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
4611 assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
4612 assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
4614 assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
4615 }
4616
4617 #[test]
4618 fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
4619 let mut f = Fixture::new();
4620 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
4621 assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
4624 assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
4625 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
4626 assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
4628 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4629 }
4630
4631 #[test]
4632 fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
4633 let mut f = Fixture::new();
4634 f.run(&[b"SET", b"k", b"v"]);
4635 assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
4638 assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
4639 }
4640
4641 #[test]
4642 fn touch_counts_the_way_exists_counts() {
4643 let mut f = Fixture::new();
4644 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4645 assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
4646 assert_eq!(
4647 f.run(&[b"TOUCH", b"a", b"a"]),
4648 ":2\r\n",
4649 "twice counts twice"
4650 );
4651 assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
4652 assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
4653 }
4654
4655 #[test]
4656 fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
4657 let mut f = Fixture::new();
4658 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
4659 f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
4660
4661 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
4662 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
4663 assert_eq!(
4664 f.run(&[b"TTL", b"b"]),
4665 ":100\r\n",
4666 "the source's and not b's"
4667 );
4668 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
4669 }
4670
4671 #[test]
4672 fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
4673 let mut f = Fixture::new();
4674 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
4675 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
4678 }
4679
4680 #[test]
4681 fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
4682 let mut f = Fixture::new();
4683 f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
4684
4685 assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
4686 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
4687 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
4690 assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
4691 assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
4692 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
4693 }
4694
4695 #[test]
4696 fn renaming_a_set_does_not_touch_a_member() {
4697 let mut f = Fixture::new();
4698 for i in 0..300 {
4699 f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
4700 }
4701 let before = f.server.memory_bytes();
4702
4703 assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
4704 assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
4705 assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
4706 assert!(
4707 f.server.memory_bytes().abs_diff(before) < 256,
4708 "the members were copied: {} against {before}",
4709 f.server.memory_bytes()
4710 );
4711 }
4712
4713 #[test]
4714 fn a_copy_is_a_second_value_and_not_a_second_name() {
4715 let mut f = Fixture::new();
4716 f.run(&[b"SADD", b"s", b"m1", b"m2"]);
4717
4718 assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
4719 f.run(&[b"SADD", b"t", b"m3"]);
4720 assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
4721 assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
4722 }
4723
4724 #[test]
4734 fn every_type_can_be_copied() {
4735 let mut f = Fixture::new();
4736 f.run(&[b"SET", b"str", b"v1"]);
4737 f.run(&[b"SADD", b"set", b"m1"]);
4738 f.run(&[b"HSET", b"hash", b"f", b"v"]);
4739 f.run(&[b"RPUSH", b"list", b"a", b"b"]);
4740 f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
4741
4742 for name in [
4743 &b"str"[..],
4744 &b"set"[..],
4745 &b"hash"[..],
4746 &b"list"[..],
4747 &b"zset"[..],
4748 ] {
4749 let dst = [name, b":copy"].concat();
4750 assert_eq!(
4751 f.run(&[b"COPY", name, &dst]),
4752 ":1\r\n",
4753 "copying {}",
4754 String::from_utf8_lossy(name)
4755 );
4756 assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
4757 }
4758
4759 assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
4760 let mut want = String::from("*2\r\n");
4761 want.push_str("$1\r\na\r\n$1\r\nb\r\n");
4762 want
4763 });
4764 assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
4765
4766 f.run(&[b"RPUSH", b"list:copy", b"c"]);
4768 assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
4769 assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
4770 }
4771
4772 #[test]
4773 fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
4774 let mut f = Fixture::new();
4775 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
4776 f.run(&[b"SET", b"b", b"v2"]);
4777
4778 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
4779 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
4780 assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
4781 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
4782 assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
4783 assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
4784 }
4785
4786 #[test]
4787 fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
4788 let mut f = Fixture::new();
4789 f.run(&[b"SET", b"a", b"v1"]);
4790
4791 assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
4794 f.run(&[b"SELECT", b"1"]);
4795 assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
4796 assert_eq!(
4797 f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
4798 ":0\r\n",
4799 "taken"
4800 );
4801 assert_eq!(
4802 f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
4803 ":1\r\n"
4804 );
4805 }
4806
4807 #[test]
4808 fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
4809 let mut f = Fixture::new();
4810 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
4811 assert_eq!(
4812 f.run(&[b"SORT", b"l"]),
4813 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
4814 );
4815 assert_eq!(
4817 f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
4818 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
4819 );
4820 assert_eq!(
4821 f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
4822 "*1\r\n$1\r\n2\r\n"
4823 );
4824 }
4825
4826 #[test]
4827 fn sort_reads_a_key_per_element_for_by_and_for_get() {
4828 let mut f = Fixture::new();
4829 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4830 f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
4831 assert_eq!(
4834 f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
4835 "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
4836 );
4837 }
4838
4839 #[test]
4840 fn sort_store_writes_a_list_and_answers_its_length() {
4841 let mut f = Fixture::new();
4842 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
4843 assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
4844 assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
4845 assert_eq!(
4846 f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
4847 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
4848 );
4849 assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
4852 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
4853 }
4854
4855 #[test]
4856 fn sort_ro_does_not_know_the_word_store() {
4857 let mut f = Fixture::new();
4858 f.run(&[b"RPUSH", b"l", b"2", b"1"]);
4859 assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
4860 assert_eq!(
4861 f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
4862 "-ERR syntax error\r\n"
4863 );
4864 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
4865 }
4866
4867 #[test]
4868 fn sort_refuses_what_it_cannot_sort() {
4869 let mut f = Fixture::new();
4870 assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
4871 f.run(&[b"SET", b"s", b"x"]);
4872 assert_eq!(
4873 f.run(&[b"SORT", b"s"]),
4874 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
4875 );
4876 f.run(&[b"RPUSH", b"words", b"one", b"two"]);
4877 assert_eq!(
4878 f.run(&[b"SORT", b"words"]),
4879 "-ERR One or more scores can't be converted into double\r\n"
4880 );
4881 assert_eq!(
4882 f.run(&[b"SORT", b"words", b"ALPHA"]),
4883 "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
4884 );
4885 assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
4886 }
4887
4888 #[test]
4889 fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
4890 let mut f = Fixture::new();
4891 assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
4892 assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
4893 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
4894 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4895 assert_eq!(
4896 f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
4897 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4898 );
4899 assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
4902 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
4903 }
4904
4905 #[test]
4906 fn move_answers_zero_when_either_end_says_no() {
4907 let mut f = Fixture::new();
4908 assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
4909 assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
4910 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4911 assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
4912 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
4913 assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
4916 assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
4917 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4918 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
4919 }
4920
4921 #[test]
4922 fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
4923 let mut f = Fixture::new();
4924 assert_eq!(
4925 f.run(&[b"MOVE", b"a", b"0"]),
4926 "-ERR source and destination objects are the same\r\n"
4927 );
4928 assert_eq!(
4929 f.run(&[b"MOVE", b"a", b"99"]),
4930 "-ERR DB index is out of range\r\n"
4931 );
4932 assert_eq!(
4933 f.run(&[b"MOVE", b"a", b"-1"]),
4934 "-ERR DB index is out of range\r\n"
4935 );
4936 assert_eq!(
4937 f.run(&[b"MOVE", b"a", b"x"]),
4938 "-ERR value is not an integer or out of range\r\n"
4939 );
4940 }
4941
4942 #[test]
4943 fn swapdb_swaps_what_two_connections_would_see() {
4944 let mut f = Fixture::new();
4945 assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
4946 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4947 assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
4948 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
4949
4950 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
4951 assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
4953 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4954 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
4955 assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
4957 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
4958 }
4959
4960 #[test]
4965 fn a_wide_server_puts_its_databases_on_its_own_clock() {
4966 let mut f = Fixture::striped(8);
4967 f.server.set_clock_ms(1_700_000_000_000);
4968 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
4969 assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
4970 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4971 f.server.set_clock_ms(1_700_000_050_000);
4972 assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
4973 }
4974
4975 #[test]
4980 fn swapdb_swaps_every_stripe_of_a_wide_database() {
4981 let mut f = Fixture::striped(4);
4982 for i in 0..16u32 {
4983 let key = format!("k{i}");
4984 assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
4985 }
4986 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4987 assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
4988 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
4989
4990 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
4991 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4992 assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
4993 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4994 assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
4995 for i in 0..16u32 {
4996 let key = format!("k{i}");
4997 assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
4998 }
4999 }
5000
5001 #[test]
5002 fn swapdb_says_which_index_it_could_not_read() {
5003 let mut f = Fixture::new();
5004 assert_eq!(
5005 f.run(&[b"SWAPDB", b"x", b"1"]),
5006 "-ERR invalid first DB index\r\n"
5007 );
5008 assert_eq!(
5009 f.run(&[b"SWAPDB", b"0", b"y"]),
5010 "-ERR invalid second DB index\r\n"
5011 );
5012 assert_eq!(
5016 f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
5017 "-ERR invalid first DB index\r\n"
5018 );
5019 assert_eq!(
5020 f.run(&[b"SWAPDB", b"0", b"99"]),
5021 "-ERR DB index is out of range\r\n"
5022 );
5023 assert_eq!(
5024 f.run(&[b"SWAPDB", b"-1", b"0"]),
5025 "-ERR DB index is out of range\r\n"
5026 );
5027 }
5028
5029 #[test]
5030 fn wait_answers_zero_replicas_without_waiting() {
5031 let mut f = Fixture::new();
5032 assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
5033 assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
5034 assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
5037 assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
5040 assert_eq!(
5041 f.run(&[b"WAIT", b"x", b"0"]),
5042 "-ERR value is not an integer or out of range\r\n"
5043 );
5044 assert_eq!(
5045 f.run(&[b"WAIT", b"0", b"-1"]),
5046 "-ERR timeout is negative\r\n"
5047 );
5048 assert_eq!(
5049 f.run(&[b"WAIT", b"0", b"1.5"]),
5050 "-ERR timeout is not an integer or out of range\r\n"
5051 );
5052 }
5053
5054 #[test]
5055 fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
5056 let mut f = Fixture::new();
5057 assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
5058 assert_eq!(
5059 f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
5060 "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
5061 );
5062 assert_eq!(
5063 f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
5064 "-ERR value is out of range, value must between 0 and 1\r\n"
5065 );
5066 assert_eq!(
5067 f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
5068 "-ERR value is out of range, must be positive\r\n"
5069 );
5070 assert_eq!(
5073 f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
5074 "-ERR timeout is negative\r\n"
5075 );
5076 }
5077
5078 fn payload(reply: &[u8]) -> Vec<u8> {
5082 let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
5083 reply[head + 2..reply.len() - 2].to_vec()
5084 }
5085
5086 #[test]
5087 fn a_value_survives_a_dump_and_a_restore() {
5088 let mut f = Fixture::new();
5089 f.run(&[b"SET", b"s", b"hello"]);
5090 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
5091 f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
5092 f.run(&[b"SADD", b"u", b"x", b"y"]);
5093 f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
5094 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
5095
5096 for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
5097 let mut copy = key.to_vec();
5098 copy.push(b'2');
5099 let bytes = payload(&f.raw(&[b"DUMP", key]));
5100 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
5101 assert_eq!(f.run(&[b"TYPE", ©]), f.run(&[b"TYPE", key]));
5102 }
5103
5104 assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
5105 assert_eq!(
5106 f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
5107 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
5108 );
5109 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
5110 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
5111 assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
5112 assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
5113 assert_eq!(
5116 f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
5117 f.run(&[b"OBJECT", b"ENCODING", b"t"])
5118 );
5119 }
5120
5121 #[test]
5122 fn a_dumped_hash_keeps_its_field_deadlines() {
5123 let mut f = Fixture::new();
5124 f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
5125 assert_eq!(
5126 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
5127 "*1\r\n:1\r\n"
5128 );
5129 let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
5130 assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
5131 assert_eq!(
5132 f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
5133 "*2\r\n:-1\r\n:100\r\n"
5134 );
5135 }
5136
5137 #[test]
5138 fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
5139 let mut f = Fixture::new();
5140 f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
5141 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
5142 assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
5143 assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
5144 assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
5145 assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
5146 assert_eq!(
5149 f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
5150 "+OK\r\n"
5151 );
5152 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
5153 }
5154
5155 #[test]
5156 fn dump_answers_nothing_for_a_key_that_is_not_there() {
5157 let mut f = Fixture::new();
5158 assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
5159 f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
5160 f.advance(50);
5161 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
5162 }
5163
5164 #[test]
5165 fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
5166 let mut f = Fixture::new();
5167 f.run(&[b"SET", b"a", b"first"]);
5168 f.run(&[b"SET", b"b", b"second"]);
5169 let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
5170 assert_eq!(
5171 f.run(&[b"RESTORE", b"a", b"0", &bytes]),
5172 "-BUSYKEY Target key name already exists.\r\n"
5173 );
5174 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
5175 assert_eq!(
5176 f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
5177 "+OK\r\n"
5178 );
5179 assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
5180 }
5181
5182 #[test]
5186 fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
5187 let mut f = Fixture::new();
5188 f.run(&[b"SET", b"a", b"v"]);
5189 assert_eq!(
5190 f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
5191 "-BUSYKEY Target key name already exists.\r\n"
5192 );
5193 assert_eq!(
5196 f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
5197 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
5198 );
5199 }
5200
5201 #[test]
5202 fn restore_can_tell_a_bad_footer_from_bad_bytes() {
5203 let mut f = Fixture::new();
5204 f.run(&[b"SET", b"a", b"hello"]);
5205 let good = payload(&f.raw(&[b"DUMP", b"a"]));
5206
5207 let mut flipped = good.clone();
5208 flipped[2] ^= 0x40;
5209 assert_eq!(
5210 f.run(&[b"RESTORE", b"b", b"0", &flipped]),
5211 "-ERR DUMP payload version or checksum are wrong\r\n"
5212 );
5213 assert_eq!(
5214 f.run(&[b"RESTORE", b"b", b"0", b"short"]),
5215 "-ERR DUMP payload version or checksum are wrong\r\n"
5216 );
5217 let mut truncated = good[..1].to_vec();
5221 truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
5222 let crc = yo_common::crc::crc64(0, &truncated);
5223 truncated.extend_from_slice(&crc.to_le_bytes());
5224 assert_eq!(
5225 f.run(&[b"RESTORE", b"b", b"0", &truncated]),
5226 "-ERR Bad data format\r\n"
5227 );
5228 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
5229 }
5230
5231 #[test]
5232 fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
5233 let mut f = Fixture::new();
5234 f.run(&[b"SET", b"a", b"v"]);
5235 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
5236 assert_eq!(
5237 f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
5238 "-ERR Invalid TTL value, must be >= 0\r\n"
5239 );
5240 assert_eq!(
5241 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
5242 "-ERR Invalid IDLETIME value, must be >= 0\r\n"
5243 );
5244 assert_eq!(
5245 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
5246 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
5247 );
5248 assert_eq!(
5250 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
5251 "+OK\r\n"
5252 );
5253 assert_eq!(
5254 f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
5255 "+OK\r\n"
5256 );
5257 }
5258
5259 #[test]
5263 fn restore_takes_idletime_or_freq_and_not_both() {
5264 let mut f = Fixture::new();
5265 f.run(&[b"SET", b"a", b"v"]);
5266 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
5267 assert_eq!(
5268 f.run(&[
5269 b"RESTORE",
5270 b"b",
5271 b"0",
5272 &bytes,
5273 b"IDLETIME",
5274 b"1",
5275 b"FREQ",
5276 b"2"
5277 ]),
5278 "-ERR syntax error\r\n"
5279 );
5280 assert_eq!(
5281 f.run(&[
5282 b"RESTORE",
5283 b"b",
5284 b"0",
5285 &bytes,
5286 b"FREQ",
5287 b"2",
5288 b"IDLETIME",
5289 b"1"
5290 ]),
5291 "-ERR syntax error\r\n"
5292 );
5293 assert_eq!(
5294 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
5295 "-ERR syntax error\r\n"
5296 );
5297 assert_eq!(
5298 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
5299 "-ERR syntax error\r\n"
5300 );
5301 }
5302
5303 #[test]
5304 fn copy_checks_its_options_before_it_looks_for_anything() {
5305 let mut f = Fixture::new();
5306 assert_eq!(
5309 f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
5310 "-ERR DB index is out of range\r\n"
5311 );
5312 assert_eq!(
5313 f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
5314 "-ERR DB index is out of range\r\n"
5315 );
5316 assert_eq!(
5317 f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
5318 "-ERR value is not an integer or out of range\r\n"
5319 );
5320 assert_eq!(
5321 f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
5322 "-ERR syntax error\r\n"
5323 );
5324 assert_eq!(
5325 f.run(&[b"COPY", b"a", b"a"]),
5326 "-ERR source and destination objects are the same\r\n"
5327 );
5328 assert_eq!(
5330 f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
5331 ":0\r\n"
5332 );
5333 }
5334
5335 #[test]
5336 fn time_is_two_bulk_strings_and_moves() {
5337 let mut f = Fixture::new();
5338 let first = f.run(&[b"TIME"]);
5339 assert!(first.starts_with("*2\r\n$"), "got {first}");
5340 let parts: Vec<&str> = first.split("\r\n").collect();
5341 let secs: i64 = parts[2].parse().expect("seconds as decimal text");
5342 let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
5343 assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
5344 assert!((0..1_000_000).contains(µs), "got {micros}");
5345 assert_ne!(first, f.run(&[b"TIME"]));
5349 }
5350
5351 #[test]
5352 fn a_keyspace_scan_walks_every_key_once() {
5353 let n = if cfg!(miri) { 96 } else { 500 };
5357 let mut f = Fixture::new();
5358 for i in 0..n {
5359 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
5360 }
5361
5362 let mut seen: Vec<String> = Vec::new();
5363 let mut cursor = "0".to_owned();
5364 let mut calls = 0;
5365 loop {
5366 let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
5367 seen.extend(keys);
5368 cursor = next;
5369 calls += 1;
5370 assert!(calls < 10_000, "the cursor is not advancing");
5371 if cursor == "0" {
5372 break;
5373 }
5374 }
5375
5376 seen.sort();
5377 seen.dedup();
5378 assert_eq!(seen.len(), n, "every key once and only once");
5379 assert!(calls > 1, "{n} keys came back in one batch");
5382 }
5383
5384 #[test]
5385 fn a_scan_narrows_by_pattern_and_by_type() {
5386 let mut f = Fixture::new();
5387 f.run(&[b"SET", b"str", b"v"]);
5388 f.run(&[b"SADD", b"members", b"a"]);
5389 f.run(&[b"HSET", b"fields", b"f", b"v"]);
5390
5391 let all = |f: &mut Fixture, args: &[&[u8]]| {
5392 let mut out: Vec<String> = Vec::new();
5393 let mut cursor = "0".to_owned();
5394 loop {
5395 let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
5396 line.extend_from_slice(args);
5397 let (next, keys) = scan_reply(&f.run(&line));
5398 out.extend(keys);
5399 cursor = next;
5400 if cursor == "0" {
5401 break;
5402 }
5403 }
5404 out.sort();
5405 out
5406 };
5407
5408 assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
5409 assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
5410 assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
5411 assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
5413 assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
5415 assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
5416 assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
5418 }
5419
5420 #[test]
5421 fn a_scan_says_what_is_wrong_with_it() {
5422 let mut f = Fixture::new();
5423 assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
5424 assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
5425 assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
5426 assert_eq!(
5427 f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
5428 "-ERR syntax error\r\n"
5429 );
5430 assert_eq!(
5431 f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
5432 "-ERR value is not an integer or out of range\r\n"
5433 );
5434 assert_eq!(
5435 f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
5436 "-ERR syntax error\r\n"
5437 );
5438 assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
5443 }
5444
5445 #[test]
5446 fn keys_and_randomkey_look_at_the_whole_database() {
5447 let mut f = Fixture::new();
5448 assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
5449 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
5450
5451 for name in ["one", "two", "three"] {
5452 f.run(&[b"SET", name.as_bytes(), b"v"]);
5453 }
5454 assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
5455 assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
5456 assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
5457
5458 for _ in 0..50 {
5459 let got = f.run(&[b"RANDOMKEY"]);
5460 assert!(
5461 ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
5462 "got {got}"
5463 );
5464 }
5465 }
5466
5467 #[test]
5468 fn a_walk_does_not_answer_keys_that_have_expired() {
5469 let mut f = Fixture::new();
5470 f.run(&[b"SET", b"alive", b"v"]);
5471 f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
5472 f.server.advance_clock_ms(2);
5473 assert_eq!(
5474 f.run(&[b"DBSIZE"]),
5475 ":2\r\n",
5476 "nothing has collected it yet"
5477 );
5478
5479 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
5480 let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
5481 assert_eq!(keys, ["alive"]);
5482 for _ in 0..20 {
5483 assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
5484 }
5485 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
5488 }
5489
5490 #[test]
5491 fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
5492 let mut f = Fixture::new();
5493 f.run(&[b"SET", b"k", b"v"]);
5494 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
5495 assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
5496
5497 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
5498 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
5499 let ms = int(&f.run(&[b"PTTL", b"k"]));
5500 assert!((99_000..=100_000).contains(&ms), "got {ms}");
5501
5502 let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
5504 let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
5505 assert_eq!(at, (at_ms + 500) / 1000);
5506 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
5507
5508 assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
5509 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
5510 assert_eq!(
5511 f.run(&[b"PERSIST", b"k"]),
5512 ":0\r\n",
5513 "nothing to take off the second time"
5514 );
5515 assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
5516 assert_eq!(
5517 f.run(&[b"GET", b"k"]),
5518 "$1\r\nv\r\n",
5519 "and the value went through all of that untouched"
5520 );
5521 }
5522
5523 #[test]
5524 fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
5525 let mut f = Fixture::new();
5526 f.run(&[b"SET", b"str", b"v"]);
5527 f.run(&[b"SADD", b"set", b"a", b"b"]);
5528 f.run(&[b"HSET", b"hash", b"f", b"v"]);
5529
5530 for key in [b"str".as_slice(), b"set", b"hash"] {
5531 assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
5532 assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
5533 }
5534 assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
5537 assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
5538 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
5539 }
5540
5541 #[test]
5542 fn a_deadline_that_has_already_gone_deletes_the_key_now() {
5543 let mut f = Fixture::new();
5544 for key in [b"a".as_slice(), b"b", b"c", b"d"] {
5545 f.run(&[b"SET", key, b"v"]);
5546 }
5547 assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
5551 assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
5552 assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
5553 assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
5554 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5555 assert_eq!(
5556 f.run(&[b"EXPIRE", b"a", b"100"]),
5557 ":0\r\n",
5558 "and the key really went, so there is nothing to put a deadline on"
5559 );
5560 }
5561
5562 #[test]
5563 fn the_four_conditions_decide_whether_the_deadline_moves() {
5564 let mut f = Fixture::new();
5565 f.run(&[b"SET", b"k", b"v"]);
5566
5567 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
5568 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
5569 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
5570 assert_eq!(
5571 f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
5572 ":1\r\n",
5573 "no deadline reads as infinitely far away, so LT passes where GT fails"
5574 );
5575
5576 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
5577 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
5578 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
5579 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
5580 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
5581 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
5582
5583 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
5586 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
5587 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
5588 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
5589 }
5590
5591 #[test]
5592 fn the_conditions_are_a_set_and_not_a_keyword() {
5593 let mut f = Fixture::new();
5594 f.run(&[b"SET", b"k", b"v"]);
5595
5596 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
5597 assert_eq!(
5598 f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
5599 ":0\r\n",
5600 "the same keyword twice means it once, and NX now has a deadline to fail on"
5601 );
5602
5603 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
5606 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
5607 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
5608 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
5609 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
5610 f.run(&[b"PERSIST", b"k"]);
5611 assert_eq!(
5612 f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
5613 ":0\r\n",
5614 "where LT on its own would have taken it"
5615 );
5616 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
5617 }
5618
5619 #[test]
5620 fn a_key_is_gone_once_its_moment_passes() {
5621 let mut f = Fixture::new();
5622 f.run(&[b"SET", b"k", b"v"]);
5623 f.run(&[b"EXPIRE", b"k", b"100"]);
5624
5625 let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
5626 f.server.set_clock_ms(at as u64 + 1);
5627 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
5628 assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
5629 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
5630 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5631 }
5632
5633 #[test]
5634 fn the_expiry_commands_refuse_what_a_real_server_refuses() {
5635 let mut f = Fixture::new();
5636 f.run(&[b"SET", b"k", b"v"]);
5637 for (bad, want) in [
5638 (
5639 &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
5640 "-ERR value is not an integer or out of range\r\n",
5641 ),
5642 (
5643 &[b"EXPIRE", b"k", b"100", b"MAYBE"],
5644 "-ERR Unsupported option MAYBE\r\n",
5645 ),
5646 (
5647 &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
5648 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
5649 ),
5650 (
5651 &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
5652 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
5653 ),
5654 (
5655 &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
5656 "-ERR GT and LT options at the same time are not compatible\r\n",
5657 ),
5658 (
5661 &[b"EXPIRE", b"k", b"9223372036854775807"],
5662 "-ERR invalid expire time in 'expire' command\r\n",
5663 ),
5664 (
5665 &[b"EXPIREAT", b"k", b"9223372036854775807"],
5666 "-ERR invalid expire time in 'expireat' command\r\n",
5667 ),
5668 (
5669 &[b"PEXPIRE", b"k", b"9223372036854775807"],
5670 "-ERR invalid expire time in 'pexpire' command\r\n",
5671 ),
5672 ] {
5673 assert_eq!(f.run(bad), want, "for {bad:?}");
5674 }
5675 assert_eq!(
5676 f.run(&[b"TTL", b"k"]),
5677 ":-1\r\n",
5678 "and none of those put a deadline on anything"
5679 );
5680
5681 assert_eq!(
5685 f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
5686 ":1\r\n"
5687 );
5688 assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
5689 }
5690
5691 #[test]
5692 fn flushing_empties_this_database_or_every_one_of_them() {
5693 let mut f = Fixture::new();
5694 f.run(&[b"SELECT", b"0"]);
5695 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
5696 f.run(&[b"SELECT", b"1"]);
5697 f.run(&[b"SET", b"c", b"3"]);
5698 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
5699 assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
5702 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5703 f.run(&[b"SELECT", b"0"]);
5705 assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
5706 assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
5707 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5708 f.run(&[b"SELECT", b"1"]);
5709 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5710 assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
5713 assert_eq!(
5714 f.run(&[b"FLUSHDB", b"sync", b"sync"]),
5715 "-ERR syntax error\r\n"
5716 );
5717 }
5718
5719 #[test]
5720 fn the_script_cache_and_the_library_set_answer_for_being_empty() {
5721 let mut f = Fixture::new();
5722 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
5723 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
5724 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
5725 assert_eq!(
5728 f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
5729 "*2\r\n:0\r\n:0\r\n"
5730 );
5731 assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
5732 assert_eq!(
5733 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
5734 "*0\r\n"
5735 );
5736 assert_eq!(
5737 f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
5738 "-ERR Library not found\r\n"
5739 );
5740
5741 assert_eq!(
5744 f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
5745 "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
5746 );
5747 assert_eq!(
5748 f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
5749 "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
5750 );
5751 assert_eq!(
5756 f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
5757 "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
5758 );
5759 assert_eq!(
5760 f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
5761 "-ERR Unknown argument bogus\r\n"
5762 );
5763 assert_eq!(
5764 f.run(&[b"SCRIPT", b"EXISTS"]),
5765 "-ERR wrong number of arguments for 'script|exists' command\r\n"
5766 );
5767
5768 assert_eq!(
5769 f.run(&[b"FUNCTION", b"NOPE"]),
5770 "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
5771 );
5772 }
5773
5774 #[test]
5775 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5776 fn the_script_cache_holds_what_was_loaded_into_it() {
5777 let mut f = Fixture::new();
5778 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
5781 assert_eq!(
5782 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
5783 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
5784 );
5785 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
5786 assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
5787 assert_eq!(
5790 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
5791 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
5792 );
5793 assert!(
5794 f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
5795 .starts_with("-ERR Error compiling script"),
5796 );
5797
5798 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
5799 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
5800 assert_eq!(
5801 f.run(&[b"EVALSHA", sha, b"0"]),
5802 "-NOSCRIPT No matching script. Please use EVAL.\r\n"
5803 );
5804
5805 assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
5808 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
5809
5810 assert_eq!(
5813 f.run(&[b"SCRIPT", b"KILL"]),
5814 "-NOTBUSY No scripts in execution right now.\r\n"
5815 );
5816 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
5817 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
5818 assert_eq!(
5819 f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
5820 "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
5821 );
5822 }
5823
5824 #[test]
5825 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5826 fn eval_counts_its_keys_before_it_compiles_anything() {
5827 let mut f = Fixture::new();
5828 assert_eq!(
5829 f.run(&[b"EVAL", b"return 1"]),
5830 "-ERR wrong number of arguments for 'eval' command\r\n"
5831 );
5832 assert_eq!(
5833 f.run(&[b"EVAL", b"return 1", b"abc"]),
5834 "-ERR value is not an integer or out of range\r\n"
5835 );
5836 assert_eq!(
5837 f.run(&[b"EVAL", b"return 1", b"-1"]),
5838 "-ERR Number of keys can't be negative\r\n"
5839 );
5840 assert_eq!(
5841 f.run(&[b"EVAL", b"return 1", b"1"]),
5842 "-ERR Number of keys can't be greater than number of args\r\n"
5843 );
5844 assert_eq!(
5846 f.run(&[
5847 b"EVAL",
5848 b"return {KEYS[1],KEYS[2],ARGV[1]}",
5849 b"2",
5850 b"a",
5851 b"b",
5852 b"c"
5853 ]),
5854 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
5855 );
5856 assert_eq!(
5857 f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
5858 ":0\r\n"
5859 );
5860 assert_eq!(
5861 f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
5862 ":2\r\n"
5863 );
5864 }
5865
5866 #[test]
5867 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5868 fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
5869 let mut f = Fixture::new();
5870 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
5871
5872 assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
5875 assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
5876 assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
5877 assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
5878 assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
5879 assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
5880 assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
5881
5882 assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
5883 assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
5884 assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
5886 assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
5887 assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
5888 assert_eq!(eval(&mut f, b""), "$-1\r\n");
5889
5890 assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
5893 assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
5894 assert_eq!(
5895 eval(&mut f, b"return {1,'a',{2}}"),
5896 "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
5897 );
5898
5899 assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
5901 assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
5902 assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
5903 assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
5904 assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a b\r\n");
5907 assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
5910 assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
5911 }
5912
5913 #[test]
5914 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5915 fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
5916 let mut f = Fixture::new();
5917 assert_eq!(
5920 f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
5921 "$3\r\n3.5\r\n"
5922 );
5923 assert_eq!(
5924 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
5925 "$3\r\n123\r\n"
5926 );
5927 assert_eq!(
5928 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
5929 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5930 );
5931 assert_eq!(
5932 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
5933 "*1\r\n$1\r\na\r\n"
5934 );
5935 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
5936
5937 f.out = Out::new(Proto::Resp3);
5938 assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
5939 assert_eq!(
5940 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
5941 "(123\r\n"
5942 );
5943 assert_eq!(
5944 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
5945 "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
5946 );
5947 assert_eq!(
5948 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
5949 "~1\r\n$1\r\na\r\n"
5950 );
5951 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
5952 }
5953
5954 #[test]
5955 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5956 fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
5957 let mut f = Fixture::new();
5958 f.run(&[b"SET", b"s", b"hello"]);
5959 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
5960 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
5961
5962 assert_eq!(
5963 eval(&mut f, b"return type(redis.call('get','s'))"),
5964 "$6\r\nstring\r\n"
5965 );
5966 assert_eq!(
5967 eval(&mut f, b"return type(redis.call('llen','l'))"),
5968 "$6\r\nnumber\r\n"
5969 );
5970 assert_eq!(
5971 eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
5972 "$5\r\ntable\r\n"
5973 );
5974 assert_eq!(
5977 eval(&mut f, b"return redis.call('set','s','v')['ok']"),
5978 "$2\r\nOK\r\n"
5979 );
5980 assert_eq!(
5983 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
5984 "$5\r\nfalse\r\n"
5985 );
5986 assert_eq!(
5987 eval(
5988 &mut f,
5989 b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
5990 ),
5991 "$3\r\nnil\r\n"
5992 );
5993 assert_eq!(
5995 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
5996 "$5\r\nfalse\r\n"
5997 );
5998 }
5999
6000 #[test]
6001 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6002 fn an_error_from_a_script_names_the_line_it_came_from() {
6003 let mut f = Fixture::new();
6004 assert_eq!(
6007 f.run(&[b"EVAL", b"error('boom')", b"0"]),
6008 "-ERR user_script:1: boom script: \
6009 82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
6010 );
6011 assert_eq!(
6013 f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
6014 "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
6015 );
6016 assert!(
6018 f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
6019 .starts_with("-structured script: "),
6020 );
6021 assert_eq!(
6024 f.run(&[b"EVAL", b"return this is not lua", b"0"]),
6025 "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
6026 );
6027
6028 assert_eq!(
6031 f.run(&[
6032 b"EVAL",
6033 b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
6034 b"0"
6035 ]),
6036 "$8\r\nstring:z\r\n"
6037 );
6038 assert_eq!(
6039 f.run(&[
6040 b"EVAL",
6041 b"local a, b = pcall(function() error({a=1}) end) return type(b)",
6042 b"0"
6043 ]),
6044 "$5\r\ntable\r\n"
6045 );
6046 }
6047
6048 #[test]
6049 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6050 fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
6051 let mut f = Fixture::new();
6052 let sentence = |f: &mut Fixture, body: &[u8]| {
6053 let reply = f.run(&[b"EVAL", body, b"0"]);
6054 reply.split(" script: ").next().unwrap().to_owned()
6055 };
6056
6057 assert_eq!(
6058 sentence(&mut f, b"return redis.call()"),
6059 "-ERR Please specify at least one argument for this redis lib call"
6060 );
6061 assert_eq!(
6062 sentence(&mut f, b"return redis.call('get', {})"),
6063 "-ERR Lua redis lib command arguments must be strings or integers"
6064 );
6065 assert_eq!(
6066 sentence(&mut f, b"return redis.call('nosuchcmd')"),
6067 "-ERR Unknown Redis command called from script"
6068 );
6069 assert_eq!(
6070 sentence(&mut f, b"return redis.call('get')"),
6071 "-ERR Wrong number of args calling Redis command from script"
6072 );
6073 for name in [
6077 &b"return redis.call('multi')"[..],
6078 b"return redis.call('exec')",
6079 b"return redis.call('watch','k')",
6080 b"return redis.call('subscribe','c')",
6081 b"return redis.call('debug','jmap')",
6082 b"return redis.call('eval','return 1',0)",
6083 b"return redis.call('config','get','maxmemory')",
6084 ] {
6085 assert_eq!(
6086 sentence(&mut f, name),
6087 "-ERR This Redis command is not allowed from script",
6088 "for {}",
6089 String::from_utf8_lossy(name)
6090 );
6091 }
6092 assert!(
6095 f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
6096 .starts_with('*'),
6097 );
6098
6099 assert_eq!(
6102 f.run(&[
6103 b"EVAL",
6104 b"local x = redis.pcall('nosuchcmd') return x.err",
6105 b"0"
6106 ]),
6107 "$44\r\nERR Unknown Redis command called from script\r\n"
6108 );
6109 assert_eq!(
6111 f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
6112 "-ERR Unknown Redis command called from script\r\n"
6113 );
6114 }
6115
6116 #[test]
6117 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6118 fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
6119 let mut f = Fixture::new();
6120 f.run(&[b"SET", b"k", b"v"]);
6121 assert_eq!(
6122 f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
6123 "$1\r\nv\r\n"
6124 );
6125 assert!(
6126 f.run(&[
6127 b"EVAL_RO",
6128 b"return redis.call('set', KEYS[1], 'x')",
6129 b"1",
6130 b"k"
6131 ])
6132 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
6133 );
6134 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
6136 assert_eq!(
6137 f.run(&[
6138 b"EVAL",
6139 b"return redis.call('set', KEYS[1], 'x')",
6140 b"1",
6141 b"k"
6142 ]),
6143 "+OK\r\n"
6144 );
6145 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
6146
6147 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
6149 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
6150 assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
6151 }
6152
6153 #[test]
6154 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6155 fn a_script_cannot_leave_anything_behind_for_the_next_one() {
6156 let mut f = Fixture::new();
6157 for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
6160 let reply = f.run(&[b"EVAL", body, b"0"]);
6161 assert!(
6162 reply
6163 .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
6164 "{body:?} gave {reply}",
6165 );
6166 }
6167 for body in [
6170 &b"rawset(redis, 'call', 1)"[..],
6171 b"rawset(_G, 'zz', 1)",
6172 b"setmetatable(_G, {})",
6173 b"setmetatable(redis, {})",
6174 ] {
6175 let reply = f.run(&[b"EVAL", body, b"0"]);
6176 assert!(
6177 reply.starts_with("-ERR Attempt to modify a readonly table script: "),
6178 "{body:?} gave {reply}",
6179 );
6180 }
6181 assert!(
6184 f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
6185 .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
6186 );
6187 assert_eq!(
6190 f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
6191 "$3\r\nnil\r\n"
6192 );
6193
6194 assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
6197 assert_eq!(
6198 f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
6199 "$8\r\nfunction\r\n"
6200 );
6201 assert_eq!(
6202 f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
6203 "$8\r\nfunction\r\n"
6204 );
6205 }
6206
6207 #[test]
6208 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6209 fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
6210 let mut f = Fixture::new();
6211 assert_eq!(
6215 f.run(&[
6216 b"EVAL",
6217 b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
6218 b"0",
6219 ]),
6220 ":23\r\n"
6221 );
6222 assert_eq!(
6223 f.run(&[
6224 b"EVAL",
6225 b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
6226 table.sort(t) return table.concat(t, ' ')",
6227 b"0",
6228 ]),
6229 "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
6230 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
6231 acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
6232 set_repl setresp sha1hex status_reply\r\n"
6233 );
6234 assert_eq!(
6237 f.run(&[
6238 b"EVAL",
6239 b"local n = 0 for k, v in pairs(redis) do \
6240 if type(v) == 'function' then n = n + 1 end end return n",
6241 b"0",
6242 ]),
6243 ":12\r\n"
6244 );
6245 assert_eq!(
6247 f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
6248 "$6\r\nstring\r\n"
6249 );
6250 assert_eq!(
6251 f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
6252 "$8\r\nfunction\r\n"
6253 );
6254 assert_eq!(
6255 f.run(&[
6256 b"EVAL",
6257 b"return tostring(rawget(redis, 'nosuchfield'))",
6258 b"0",
6259 ]),
6260 "$3\r\nnil\r\n"
6261 );
6262 for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
6265 assert!(
6266 f.run(&[b"EVAL", body, b"0"])
6267 .contains("Attempt to modify a readonly table script: "),
6268 "{body:?}",
6269 );
6270 }
6271 assert_eq!(
6274 f.run(&[
6275 b"EVAL",
6276 b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
6277 b"0",
6278 ]),
6279 ":2\r\n"
6280 );
6281 assert_eq!(
6282 f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
6283 "$3\r\nnil\r\n"
6284 );
6285 assert_eq!(
6286 f.run(&[
6287 b"EVAL",
6288 b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
6289 return type(f)",
6290 b"0",
6291 ]),
6292 "$8\r\nfunction\r\n"
6293 );
6294 }
6295
6296 #[test]
6297 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6298 fn a_script_gets_the_bit_library_a_real_server_carries() {
6299 let mut f = Fixture::new();
6300 for (body, want) in [
6303 ("bit.tobit(1)", ":1\r\n"),
6304 ("bit.tobit(2^32 + 1)", ":1\r\n"),
6305 ("bit.tobit(2^31)", ":-2147483648\r\n"),
6306 ("bit.tobit(0xffffffff)", ":-1\r\n"),
6307 ("bit.tobit(1.5)", ":2\r\n"),
6309 ("bit.tobit(2.5)", ":2\r\n"),
6310 ("bit.bnot(0)", ":-1\r\n"),
6311 ("bit.band(0xff, 0x0f)", ":15\r\n"),
6312 ("bit.band(1, 2, 3)", ":0\r\n"),
6313 ("bit.bor(1, 2, 4)", ":7\r\n"),
6314 ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
6315 ("bit.lshift(1, 31)", ":-2147483648\r\n"),
6317 ("bit.lshift(1, 32)", ":1\r\n"),
6318 ("bit.lshift(1, 33)", ":2\r\n"),
6319 ("bit.rshift(-1, 1)", ":2147483647\r\n"),
6320 ("bit.arshift(-1, 1)", ":-1\r\n"),
6321 ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
6322 ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
6323 ("bit.bswap(0x12345678)", ":2018915346\r\n"),
6324 ("bit.tobit('0x10')", ":16\r\n"),
6327 ] {
6328 let script = format!("return {body}");
6329 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6330 }
6331 for (body, want) in [
6334 ("bit.tohex(1)", "00000001"),
6335 ("bit.tohex(-1)", "ffffffff"),
6336 ("bit.tohex(255, 2)", "ff"),
6337 ("bit.tohex(255, -8)", "000000FF"),
6338 ("bit.tohex(0x87654321, 4)", "4321"),
6339 ("bit.tohex(1, 0)", ""),
6340 ("bit.tohex(1, 9)", "00000001"),
6341 ] {
6342 let script = format!("return {body}");
6343 assert_eq!(
6344 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6345 format!("${}\r\n{want}\r\n", want.len()),
6346 "{body}",
6347 );
6348 }
6349 for (body, want) in [
6352 (
6353 "return bit.band()",
6354 "bad argument #1 to 'band' (number expected, got no value)",
6355 ),
6356 (
6357 "return bit.band('x')",
6358 "bad argument #1 to 'band' (number expected, got string)",
6359 ),
6360 (
6361 "return bit.tobit(true)",
6362 "bad argument #1 to 'tobit' (number expected, got boolean)",
6363 ),
6364 (
6365 "return bit.lshift(1)",
6366 "bad argument #2 to 'lshift' (number expected, got no value)",
6367 ),
6368 ] {
6369 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6370 assert!(
6371 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6372 "{body} gave {reply}",
6373 );
6374 }
6375 assert_eq!(
6378 f.run(&[
6379 b"EVAL",
6380 b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
6381 b"0",
6382 ]),
6383 "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
6384 );
6385 assert_eq!(
6387 f.run(&[
6388 b"EVAL",
6389 b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
6390 table.sort(t) return table.concat(t, ' ')",
6391 b"0",
6392 ]),
6393 "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
6394 );
6395 for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
6396 assert!(
6397 f.run(&[b"EVAL", body, b"0"])
6398 .contains("Attempt to modify a readonly table script: "),
6399 "{body:?}",
6400 );
6401 }
6402 }
6403
6404 #[test]
6405 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6406 fn a_script_gets_the_cjson_library_a_real_server_carries() {
6407 let mut f = Fixture::new();
6408 for (body, want) in [
6412 ("cjson.encode(nil)", "null"),
6413 ("cjson.encode(true)", "true"),
6414 ("cjson.encode(cjson.null)", "null"),
6415 ("cjson.encode(100)", "100"),
6416 ("cjson.encode(1/3)", "0.33333333333333"),
6417 ("cjson.encode(1e300)", "1e+300"),
6418 ("cjson.encode(2^53)", "9.007199254741e+15"),
6419 ("cjson.encode({})", "{}"),
6420 ("cjson.encode({1,2,3})", "[1,2,3]"),
6421 ("cjson.encode({a=1})", "{\"a\":1}"),
6422 ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
6423 ("cjson.encode({[0]=1})", "{\"0\":1}"),
6424 ("cjson.encode('a\\nb')", "\"a\\nb\""),
6425 ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
6427 ("cjson.encode('\\11')", "\"\\u000b\""),
6428 (
6431 "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
6432 "[1,[2,{\"a\":null}]]",
6433 ),
6434 ("cjson.encode(cjson.decode('[]'))", "{}"),
6437 ] {
6438 let script = format!("return {body}");
6439 assert_eq!(
6440 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6441 format!("${}\r\n{want}\r\n", want.len()),
6442 "{body}",
6443 );
6444 }
6445 for (body, want) in [
6448 ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
6449 ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
6450 ("cjson.decode('0x10')", ":16\r\n"),
6451 ("cjson.decode('+1')", ":1\r\n"),
6452 ("cjson.decode('01')", ":1\r\n"),
6453 ("cjson.decode(1) + 1", ":2\r\n"),
6454 ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
6456 ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
6457 ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
6458 ] {
6459 let script = format!("return {body}");
6460 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6461 }
6462 for (body, want) in [
6464 (
6465 "cjson.encode_number_precision(3) return cjson.encode(1/3)",
6466 "0.333",
6467 ),
6468 (
6469 "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
6470 "null",
6471 ),
6472 (
6473 "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
6474 "inf",
6475 ),
6476 (
6477 "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
6478 "{\"1\":1,\"100\":1}",
6479 ),
6480 (
6481 "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
6482 "[]",
6483 ),
6484 ("return tostring(cjson.encode_max_depth())", "1000"),
6485 ("return tostring(cjson.encode_keep_buffer(false))", "false"),
6486 ("return tostring(cjson.encode_sparse_array())", "false"),
6487 ("return tostring(cjson.encode_number_precision())", "14"),
6490 ] {
6491 assert_eq!(
6492 f.run(&[b"EVAL", body.as_bytes(), b"0"]),
6493 format!("${}\r\n{want}\r\n", want.len()),
6494 "{body}",
6495 );
6496 }
6497 for (body, want) in [
6499 (
6500 "return cjson.encode(1/0)",
6501 "Cannot serialise number: must not be NaN or Inf",
6502 ),
6503 (
6504 "return cjson.encode({[1]=1,[100]=1})",
6505 "Cannot serialise table: excessively sparse array",
6506 ),
6507 (
6508 "return cjson.encode({[true]=1})",
6509 "Cannot serialise boolean: table key must be a number or string",
6510 ),
6511 (
6512 "return cjson.encode(tostring)",
6513 "Cannot serialise function: type not supported",
6514 ),
6515 (
6516 "return cjson.encode()",
6517 "bad argument #1 to 'encode' (expected 1 argument)",
6518 ),
6519 (
6520 "return cjson.decode('[1,2')",
6521 "Expected comma or array end but found T_END at character 5",
6522 ),
6523 (
6524 "return cjson.decode('{\"a\" 1}')",
6525 "Expected colon but found T_NUMBER at character 6",
6526 ),
6527 (
6528 "return cjson.decode('tru')",
6529 "Expected value but found invalid token at character 1",
6530 ),
6531 (
6532 "return cjson.decode('[1] 2')",
6533 "Expected the end but found T_NUMBER at character 5",
6534 ),
6535 (
6536 "return cjson.encode_max_depth(0)",
6537 "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
6538 ),
6539 (
6540 "return cjson.encode_invalid_numbers('yes')",
6541 "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
6542 ),
6543 (
6544 "return cjson.encode_max_depth(1, 2)",
6545 "bad argument #2 to 'encode_max_depth' (found too many arguments)",
6546 ),
6547 ] {
6548 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6549 assert!(
6550 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6551 "{body} gave {reply}",
6552 );
6553 }
6554 assert_eq!(
6557 f.run(&[
6558 b"EVAL",
6559 b"local n = cjson.new() n.encode_number_precision(3) \
6560 return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
6561 b"0",
6562 ]),
6563 "$22\r\n0.33333333333333 0.333\r\n"
6564 );
6565 let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
6567 decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
6568 encode_max_depth encode_number_precision encode_sparse_array new null";
6569 assert_eq!(
6570 f.run(&[
6571 b"EVAL",
6572 b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
6573 table.sort(t) return table.concat(t, ' ')",
6574 b"0",
6575 ]),
6576 format!("${}\r\n{names}\r\n", names.len())
6577 );
6578 for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
6579 assert!(
6580 f.run(&[b"EVAL", body, b"0"])
6581 .contains("Attempt to modify a readonly table script: "),
6582 "{body:?}",
6583 );
6584 }
6585 }
6586
6587 #[test]
6588 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6589 fn a_script_gets_the_struct_library_a_real_server_carries() {
6590 let mut f = Fixture::new();
6591 for (body, want) in [
6594 ("#struct.pack('i4', 1)", ":4\r\n"),
6595 ("#struct.pack('l', 1)", ":8\r\n"),
6596 ("#struct.pack('d', 1)", ":8\r\n"),
6597 ("#struct.pack('f', 1)", ":4\r\n"),
6598 ("#struct.pack('s', 'abc')", ":4\r\n"),
6599 ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
6600 ("#struct.pack('x')", ":1\r\n"),
6601 ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
6602 ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
6603 ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
6604 ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
6607 ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
6608 ("#struct.pack('c0', 'abcd')", ":4\r\n"),
6611 ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
6612 ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
6613 ] {
6614 let script = format!("return {body}");
6615 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6616 }
6617 for (body, want) in [
6620 ("struct.size('i')", ":4\r\n"),
6621 ("struct.size('l')", ":8\r\n"),
6622 ("struct.size('T')", ":8\r\n"),
6623 ("struct.size('h')", ":2\r\n"),
6624 ("struct.size('c10')", ":10\r\n"),
6625 ("struct.size('ic')", ":5\r\n"),
6626 ("struct.size('!8ic')", ":5\r\n"),
6627 ("struct.size('!4i')", ":4\r\n"),
6628 ("struct.size('bd')", ":9\r\n"),
6631 ("struct.size('!bd')", ":16\r\n"),
6632 ("struct.size('A')", ":0\r\n"),
6633 ("struct.size('7')", ":0\r\n"),
6634 ] {
6635 let script = format!("return {body}");
6636 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6637 }
6638 for (body, want) in [
6641 ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
6642 ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
6643 ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
6644 ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
6645 ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
6646 (
6647 "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
6648 ":-70000\r\n",
6649 ),
6650 ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
6651 ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
6655 ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
6656 ] {
6657 let script = format!("return {body}");
6658 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6659 }
6660 for (body, want) in [
6661 ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
6662 ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
6663 ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
6664 ] {
6665 let script = format!("return {body}");
6666 assert_eq!(
6667 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6668 format!("${}\r\n{want}\r\n", want.len()),
6669 "{body}",
6670 );
6671 }
6672 for (body, want) in [
6675 (
6676 "return struct.pack()",
6677 "bad argument #1 to 'pack' (string expected, got no value)",
6678 ),
6679 (
6682 "return struct.pack('i4')",
6683 "bad argument #2 to 'pack' (number expected, got nil)",
6684 ),
6685 (
6688 "return struct.pack('c6', 'abc')",
6689 "bad argument #3 to 'pack' (string too short)",
6690 ),
6691 (
6692 "return struct.pack('A', 'x')",
6693 "bad argument #1 to 'pack' (invalid format option 'A')",
6694 ),
6695 (
6696 "return struct.pack('i33', 1)",
6697 "integral size 33 is larger than limit of 32",
6698 ),
6699 (
6700 "return struct.pack('!3i', 1)",
6701 "alignment 3 is not a power of 2",
6702 ),
6703 (
6704 "return struct.unpack()",
6705 "bad argument #1 to 'unpack' (string expected, got no value)",
6706 ),
6707 (
6708 "return struct.unpack('i4')",
6709 "bad argument #2 to 'unpack' (string expected, got no value)",
6710 ),
6711 (
6712 "return struct.unpack('i4', 'ab')",
6713 "bad argument #2 to 'unpack' (data string too short)",
6714 ),
6715 (
6716 "return struct.unpack('i1', 'abc', 0)",
6717 "bad argument #3 to 'unpack' (offset must be 1 or greater)",
6718 ),
6719 (
6720 "return struct.unpack('c0', 'abc')",
6721 "format 'c0' needs a previous size",
6722 ),
6723 (
6724 "return struct.unpack('s', 'abc')",
6725 "unfinished string in data",
6726 ),
6727 (
6728 "return struct.size()",
6729 "bad argument #1 to 'size' (string expected, got no value)",
6730 ),
6731 (
6732 "return struct.size('s')",
6733 "bad argument #1 to 'size' (option 's' has no fixed size)",
6734 ),
6735 (
6736 "return struct.size('c0')",
6737 "bad argument #1 to 'size' (option 'c0' has no fixed size)",
6738 ),
6739 ] {
6740 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6741 assert!(
6742 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6743 "{body} gave {reply}",
6744 );
6745 }
6746 let names = "pack size unpack";
6748 assert_eq!(
6749 f.run(&[
6750 b"EVAL",
6751 b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
6752 table.sort(t) return table.concat(t, ' ')",
6753 b"0",
6754 ]),
6755 format!("${}\r\n{names}\r\n", names.len())
6756 );
6757 for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
6758 assert!(
6759 f.run(&[b"EVAL", body, b"0"])
6760 .contains("Attempt to modify a readonly table script: "),
6761 "{body:?}",
6762 );
6763 }
6764 }
6765
6766 #[test]
6767 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6768 fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
6769 let mut f = Fixture::new();
6770 let hex = "local function hx(s) return (string.gsub(s, '.', \
6773 function(c) return string.format('%02x', string.byte(c)) end)) end ";
6774 for (body, want) in [
6775 ("cmsgpack.pack(nil)", "c0"),
6776 ("cmsgpack.pack(true)", "c3"),
6777 ("cmsgpack.pack(false)", "c2"),
6778 ("cmsgpack.pack(0)", "00"),
6779 ("cmsgpack.pack(127)", "7f"),
6780 ("cmsgpack.pack(128)", "cc80"),
6781 ("cmsgpack.pack(-1)", "ff"),
6782 ("cmsgpack.pack(-33)", "d0df"),
6783 ("cmsgpack.pack(65535)", "cdffff"),
6784 ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
6785 ("cmsgpack.pack(2^53)", "cf0020000000000000"),
6786 ("cmsgpack.pack(-2^63)", "d38000000000000000"),
6787 ("cmsgpack.pack(2^64)", "ca5f800000"),
6790 ("cmsgpack.pack(1.5)", "ca3fc00000"),
6791 ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
6792 ("cmsgpack.pack('abc')", "a3616263"),
6793 ("cmsgpack.pack('')", "a0"),
6794 ("cmsgpack.pack({})", "90"),
6795 ("cmsgpack.pack({1, 2})", "920102"),
6796 ("cmsgpack.pack({a = 1})", "81a16101"),
6797 ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
6798 (
6801 "(function() local t = {} local c = t \
6802 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
6803 "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
6804 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
6805 ),
6806 ] {
6807 let script = format!("{hex} return hx({body})");
6808 assert_eq!(
6809 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6810 format!("${}\r\n{want}\r\n", want.len()),
6811 "{body}",
6812 );
6813 }
6814 for (body, want) in [
6818 ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
6819 ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
6820 ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
6821 ("select('#', cmsgpack.unpack(''))", 0),
6822 ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
6823 ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
6824 ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
6825 ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
6826 ("select(1, cmsgpack.unpack_one('\\1'))", -1),
6827 ("select(1, cmsgpack.unpack_one('', 0))", -1),
6828 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
6829 ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
6830 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
6833 ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
6834 ] {
6835 let script = format!("return {body}");
6836 assert_eq!(
6837 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6838 format!(":{want}\r\n"),
6839 "{body}",
6840 );
6841 }
6842 for (body, want) in [
6843 ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
6844 ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
6845 ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
6846 (
6847 "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
6848 "inf",
6849 ),
6850 ("cmsgpack._NAME", "cmsgpack"),
6851 ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
6852 (
6853 "cmsgpack._COPYRIGHT",
6854 "Copyright (C) 2012, Salvatore Sanfilippo",
6855 ),
6856 (
6857 "cmsgpack._DESCRIPTION",
6858 "MessagePack C implementation for Lua",
6859 ),
6860 ] {
6861 let script = format!("return {body}");
6862 assert_eq!(
6863 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6864 format!("${}\r\n{want}\r\n", want.len()),
6865 "{body}",
6866 );
6867 }
6868 for (body, want) in [
6869 (
6872 "return cmsgpack.pack()",
6873 "bad argument #0 to 'pack' (MessagePack pack needs input.)",
6874 ),
6875 (
6876 "return cmsgpack.unpack()",
6877 "bad argument #1 to 'unpack' (string expected, got no value)",
6878 ),
6879 (
6880 "return cmsgpack.unpack(string.char(193))",
6881 "Bad data format in input.",
6882 ),
6883 (
6884 "return cmsgpack.unpack(string.char(204))",
6885 "Missing bytes in input.",
6886 ),
6887 (
6888 "return cmsgpack.unpack(string.char(146, 1))",
6889 "Missing bytes in input.",
6890 ),
6891 (
6892 "return cmsgpack.unpack_one('\\1', 5)",
6893 "Start offset 5 greater than input length 1.",
6894 ),
6895 (
6896 "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
6897 "Start offset 5 greater than input length 2.",
6898 ),
6899 (
6902 "return cmsgpack.unpack_one('\\1', -1)",
6903 "Invalid request to unpack with offset of -1 and limit of 1.",
6904 ),
6905 (
6906 "return cmsgpack.unpack_limit('\\1', -1, 0)",
6907 "Invalid request to unpack with offset of 0 and limit of 1.",
6908 ),
6909 ] {
6910 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6911 assert!(
6912 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6913 "{body} gave {reply}",
6914 );
6915 }
6916 let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
6918 assert_eq!(
6919 f.run(&[
6920 b"EVAL",
6921 b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
6922 table.sort(t) return table.concat(t, ' ')",
6923 b"0",
6924 ]),
6925 format!("${}\r\n{names}\r\n", names.len())
6926 );
6927 for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
6928 assert!(
6929 f.run(&[b"EVAL", body, b"0"])
6930 .contains("Attempt to modify a readonly table script: "),
6931 "{body:?}",
6932 );
6933 }
6934 assert_eq!(
6937 f.run(&[
6938 b"EVAL",
6939 b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
6940 b"0",
6941 ]),
6942 "$8\r\ncmsgpack\r\n"
6943 );
6944 }
6945
6946 const LIB: &[u8] = b"#!lua name=mylib\n\
6952 local counter = 0\n\
6953 redis.register_function{function_name = 'ping', description = 'says pong',\n\
6954 callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
6955 redis.register_function('count', function() counter = counter + 1 return counter end)\n\
6956 redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
6957 redis.register_function('setit', function(keys, args) \
6958 return redis.call('SET', keys[1], args[1]) end)\n\
6959 redis.register_function('raise', function() error('boom') end)\n";
6960
6961 const OTHER: &[u8] = b"#!lua name=other\n\
6963 redis.register_function('twice', function(keys, args) return 2 end)\n";
6964
6965 #[test]
6966 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6967 fn a_library_is_loaded_once_and_called_by_name_forever_after() {
6968 let mut f = Fixture::new();
6969 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6970 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6971 assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
6974 assert_eq!(
6978 f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
6979 "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6980 );
6981 assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
6982 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
6983 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
6986 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
6987 assert_eq!(
6990 f.run(&[b"FCALL", b"raise", b"0"]),
6991 "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
6992 );
6993 assert_eq!(
6996 f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
6997 "-ERR Library not found\r\n"
6998 );
6999 assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
7000 assert_eq!(
7001 f.run(&[b"FCALL", b"ping", b"0"]),
7002 "-ERR Function not found\r\n"
7003 );
7004 }
7005
7006 #[test]
7007 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7008 fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
7009 let mut f = Fixture::new();
7010 for (code, want) in [
7011 (&b"return 1"[..], "ERR Missing library metadata"),
7012 (b"#!lua name=x", "ERR Invalid library metadata"),
7013 (b"#!\n", "ERR Library name was not given"),
7014 (b"#!lua\nx", "ERR Library name was not given"),
7015 (
7016 b"#!lua name=a name=b\nx",
7017 "ERR Invalid metadata value, name argument was given multiple times",
7018 ),
7019 (
7020 b"#!lua nome=a\nx",
7021 "ERR Invalid metadata value given: nome=a",
7022 ),
7023 (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
7024 (
7025 b"#!lua name=a-b\nx",
7026 "ERR Library names can only contain letters, numbers, or underscores(_) \
7027 and must be at least one character long",
7028 ),
7029 (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
7030 (
7031 b"#!lua name=c\nthis is not lua",
7032 "ERR Error compiling function: user_function:2: '=' expected near 'is'",
7033 ),
7034 (
7038 b"#!lua name=r\nerror('boom')",
7039 "ERR Error registering functions: ERR user_function:2: \
7040 Script attempted to access nonexistent global variable 'error'",
7041 ),
7042 (
7045 b"#!lua name=r\nredis.call('PING')",
7046 "ERR Error registering functions: ERR user_function:2: \
7047 Script attempted to access nonexistent global variable 'call'",
7048 ),
7049 (
7050 b"#!lua name=r\nx = 1",
7051 "ERR Error registering functions: ERR user_function:2: \
7052 Attempt to modify a readonly table",
7053 ),
7054 (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
7055 ] {
7056 assert_eq!(
7057 f.run(&[b"FUNCTION", b"LOAD", code]),
7058 format!("-{want}\r\n"),
7059 "{}",
7060 String::from_utf8_lossy(code),
7061 );
7062 }
7063 }
7064
7065 #[test]
7066 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7067 fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
7068 let mut f = Fixture::new();
7069 for (call, want) in [
7070 (
7071 &b"redis.register_function()"[..],
7072 "wrong number of arguments to redis.register_function",
7073 ),
7074 (
7075 b"redis.register_function('a', function() end, 1)",
7076 "wrong number of arguments to redis.register_function",
7077 ),
7078 (
7079 b"redis.register_function('a')",
7080 "calling redis.register_function with a single argument is only \
7081 applicable to Lua table (representing named arguments).",
7082 ),
7083 (
7084 b"redis.register_function({foo = 'a'})",
7085 "unknown argument given to redis.register_function",
7086 ),
7087 (
7088 b"redis.register_function({callback = function() end})",
7089 "redis.register_function must get a function name argument",
7090 ),
7091 (
7092 b"redis.register_function({function_name = 'a'})",
7093 "redis.register_function must get a callback argument",
7094 ),
7095 (
7096 b"redis.register_function({function_name = {}, callback = function() end})",
7097 "function_name argument given to redis.register_function must be a string",
7098 ),
7099 (
7100 b"redis.register_function({function_name = 'a', description = {}, \
7101 callback = function() end})",
7102 "description argument given to redis.register_function must be a string",
7103 ),
7104 (
7105 b"redis.register_function({function_name = 'a', callback = 1})",
7106 "callback argument given to redis.register_function must be a function",
7107 ),
7108 (
7109 b"redis.register_function({function_name = 'a', callback = function() end, \
7110 flags = 1})",
7111 "flags argument to redis.register_function must be a table \
7112 representing function flags",
7113 ),
7114 (
7115 b"redis.register_function({function_name = 'a', callback = function() end, \
7116 flags = {'zz'}})",
7117 "unknown flag given",
7118 ),
7119 (
7120 b"redis.register_function({}, function() end)",
7121 "first argument to redis.register_function must be a string",
7122 ),
7123 (
7124 b"redis.register_function('a', 1)",
7125 "second argument to redis.register_function must be a function",
7126 ),
7127 (
7128 b"redis.register_function('a-b', function() end)",
7129 "Library names can only contain letters, numbers, or underscores(_) \
7130 and must be at least one character long",
7131 ),
7132 (
7133 b"redis.register_function('d', function() end) \
7134 redis.register_function('d', function() end)",
7135 "Function already exists in the library",
7136 ),
7137 ] {
7138 let mut code = b"#!lua name=e\n".to_vec();
7139 code.extend_from_slice(call);
7140 assert_eq!(
7144 f.run(&[b"FUNCTION", b"LOAD", &code]),
7145 format!("-ERR Error registering functions: ERR {want}\r\n"),
7146 "{}",
7147 String::from_utf8_lossy(call),
7148 );
7149 }
7150 assert_eq!(
7153 f.run(&[
7154 b"FUNCTION",
7155 b"LOAD",
7156 b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
7157 ]),
7158 "$1\r\nn\r\n"
7159 );
7160 assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
7161 assert_eq!(
7164 f.run(&[
7165 b"FUNCTION",
7166 b"LOAD",
7167 b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
7168 redis.register_function('D', function() return 2 end)",
7169 ]),
7170 "$1\r\nc\r\n"
7171 );
7172 }
7173
7174 #[test]
7175 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7176 fn a_library_cannot_take_a_name_another_library_already_has() {
7177 let mut f = Fixture::new();
7178 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7179 assert_eq!(
7180 f.run(&[b"FUNCTION", b"LOAD", LIB]),
7181 "-ERR Library 'mylib' already exists\r\n"
7182 );
7183 assert_eq!(
7187 f.run(&[
7188 b"FUNCTION",
7189 b"LOAD",
7190 b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
7191 ]),
7192 "-ERR Function PING already exists\r\n"
7193 );
7194 assert_eq!(
7197 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
7198 "$5\r\nmylib\r\n"
7199 );
7200 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
7203 assert_eq!(
7204 f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
7205 "-ERR Unknown option given: NOPE\r\n"
7206 );
7207 assert_eq!(
7210 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
7211 "-ERR Missing library metadata\r\n"
7212 );
7213 assert_eq!(
7214 f.run(&[b"FUNCTION", b"LOAD"]),
7215 "-ERR wrong number of arguments for 'function|load' command\r\n"
7216 );
7217 }
7218
7219 #[test]
7220 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7221 fn fcall_checks_the_name_before_it_looks_at_anything_else() {
7222 let mut f = Fixture::new();
7223 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7224 for (args, want) in [
7225 (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
7226 (&[b"ping", b"x"], "ERR Bad number of keys provided"),
7227 (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
7228 (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
7229 (
7230 &[b"ping", b"99999999999999999999"],
7231 "ERR Bad number of keys provided",
7232 ),
7233 (
7234 &[b"ping", b"3", b"a"],
7235 "ERR Number of keys can't be greater than number of args",
7236 ),
7237 (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
7238 ] {
7239 let mut wire: Vec<&[u8]> = vec![b"FCALL"];
7240 wire.extend_from_slice(args);
7241 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
7242 }
7243 assert_eq!(
7246 f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
7247 "-ERR Can not execute a script with write flag using *_ro command.\r\n"
7248 );
7249 assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
7250 assert_eq!(
7251 f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
7252 "-ERR Function not found\r\n"
7253 );
7254 assert_eq!(
7257 f.run(&[
7258 b"FUNCTION",
7259 b"LOAD",
7260 b"#!lua name=w\nredis.register_function{function_name = 'w', \
7261 flags = {'no-writes'}, callback = function(keys) \
7262 return redis.call('SET', keys[1], 'x') end}",
7263 ]),
7264 "$1\r\nw\r\n"
7265 );
7266 assert!(
7267 f.run(&[b"FCALL", b"w", b"1", b"k"])
7268 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
7269 );
7270 }
7271
7272 #[test]
7273 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7274 fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
7275 let mut f = Fixture::new();
7276 let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
7279 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
7280 acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
7281 status_reply";
7282 let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
7283 gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
7284 rawequal rawget rawset redis select setmetatable string struct table \
7285 tonumber tostring type unpack xpcall";
7286 assert_eq!(
7287 f.run(&[
7288 b"FUNCTION",
7289 b"LOAD",
7290 b"#!lua name=g\n\
7291 local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
7292 table.sort(o) return table.concat(o, ' ') end\n\
7293 redis.register_function('names', function() return sorted(redis) end)\n\
7294 redis.register_function('globals', function() return sorted(_G) end)\n\
7295 redis.register_function('keysg', function() return KEYS[1] end)\n\
7296 redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
7297 redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
7298 redis.register_function('gwr', function() _G.pcall = 1 end)\n",
7299 ]),
7300 "$1\r\ng\r\n"
7301 );
7302 assert_eq!(
7303 f.run(&[b"FCALL", b"names", b"0"]),
7304 format!("${}\r\n{names}\r\n", names.len())
7305 );
7306 assert_eq!(
7307 f.run(&[b"FCALL", b"globals", b"0"]),
7308 format!("${}\r\n{globals}\r\n", globals.len())
7309 );
7310 assert!(
7313 f.run(&[b"FCALL", b"keysg", b"1", b"k"])
7314 .contains("nonexistent global variable 'KEYS'"),
7315 );
7316 assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
7319 for name in [&b"wr"[..], b"gwr"] {
7322 assert!(
7323 f.run(&[b"FCALL", name, b"0"])
7324 .contains("Attempt to modify a readonly table"),
7325 "{}",
7326 String::from_utf8_lossy(name),
7327 );
7328 }
7329 }
7330
7331 #[test]
7332 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7333 fn function_list_says_what_every_library_registered() {
7334 let mut f = Fixture::new();
7335 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7336 f.out = Out::new(Proto::Resp3);
7339 let listed = f.run(&[b"FUNCTION", b"LIST"]);
7340 assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
7341 assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
7342 assert!(listed.contains(
7343 "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
7344 $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
7345 ));
7346 assert!(listed.contains(
7349 "$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"
7350 ));
7351 assert!(!listed.contains("library_code"));
7352 assert!(
7353 f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
7354 .contains("library_code")
7355 );
7356 assert!(
7359 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
7360 .starts_with("*1\r\n")
7361 );
7362 assert_eq!(
7363 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
7364 "*0\r\n"
7365 );
7366 f.out = Out::new(Proto::Resp2);
7369 assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
7370 for (args, want) in [
7371 (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
7372 (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
7373 (
7374 &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
7375 "ERR Unknown argument LIBRARYNAME",
7376 ),
7377 (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
7378 ] {
7379 let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
7380 wire.extend_from_slice(args);
7381 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
7382 }
7383 }
7384
7385 #[test]
7386 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7387 fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
7388 let mut f = Fixture::new();
7389 f.out = Out::new(Proto::Resp3);
7390 assert_eq!(
7391 f.run(&[b"FUNCTION", b"STATS"]),
7392 "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
7393 %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
7394 );
7395 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7396 assert!(
7397 f.run(&[b"FUNCTION", b"STATS"])
7398 .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
7399 );
7400 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
7401 assert!(
7402 f.run(&[b"FUNCTION", b"STATS"])
7403 .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
7404 );
7405 }
7406
7407 #[test]
7408 fn every_function_subcommand_complains_about_its_own_arity() {
7409 let mut f = Fixture::new();
7410 for (args, want) in [
7411 (
7412 &[&b"STATS"[..], b"X"][..],
7413 "ERR wrong number of arguments for 'function|stats' command",
7414 ),
7415 (
7416 &[b"KILL", b"X"],
7417 "ERR wrong number of arguments for 'function|kill' command",
7418 ),
7419 (
7420 &[b"HELP", b"X"],
7421 "ERR wrong number of arguments for 'function|help' command",
7422 ),
7423 (
7424 &[b"DELETE"],
7425 "ERR wrong number of arguments for 'function|delete' command",
7426 ),
7427 (
7428 &[b"DELETE", b"a", b"b"],
7429 "ERR wrong number of arguments for 'function|delete' command",
7430 ),
7431 (
7432 &[b"DUMP", b"X"],
7433 "ERR wrong number of arguments for 'function|dump' command",
7434 ),
7435 (
7436 &[b"RESTORE"],
7437 "ERR wrong number of arguments for 'function|restore' command",
7438 ),
7439 (
7442 &[b"RESTORE", b"a", b"FLUSH", b"X"],
7443 "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
7444 Try FUNCTION HELP.",
7445 ),
7446 (
7447 &[b"RESTORE", b"a", b"ZZ"],
7448 "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
7449 or REPLACE.",
7450 ),
7451 (
7454 &[b"FLUSH", b"SYNC", b"X"],
7455 "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
7456 Try FUNCTION HELP.",
7457 ),
7458 (
7459 &[b"FLUSH", b"ZZ"],
7460 "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
7461 ),
7462 (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
7463 ] {
7464 let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
7465 wire.extend_from_slice(args);
7466 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
7467 }
7468 assert_eq!(
7469 f.run(&[b"FUNCTION"]),
7470 "-ERR wrong number of arguments for 'function' command\r\n"
7471 );
7472 assert_eq!(
7473 f.run(&[b"FUNCTION", b"KILL"]),
7474 "-NOTBUSY No scripts in execution right now.\r\n"
7475 );
7476 }
7477
7478 #[test]
7484 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7485 fn a_library_survives_a_dump_and_a_restore() {
7486 let mut f = Fixture::new();
7487 let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7488 assert_eq!(empty.len(), 10);
7489 assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
7490
7491 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7492 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7493 assert!(full.len() > empty.len());
7494
7495 assert_eq!(
7498 f.run(&[b"FUNCTION", b"RESTORE", &full]),
7499 "-ERR Library mylib already exists\r\n"
7500 );
7501 assert_eq!(
7502 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
7503 "+OK\r\n"
7504 );
7505 assert_eq!(
7506 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
7507 "+OK\r\n"
7508 );
7509 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
7511
7512 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
7515 assert_eq!(
7516 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
7517 "+OK\r\n"
7518 );
7519 assert_eq!(
7520 f.run(&[b"FUNCTION", b"DELETE", b"other"]),
7521 "-ERR Library not found\r\n"
7522 );
7523 }
7524
7525 #[test]
7532 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7533 fn a_restore_that_fails_changes_nothing() {
7534 let mut f = Fixture::new();
7535 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7536 let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7537
7538 let reseal = |body: &[u8], version: u16| {
7542 let mut out = body.to_vec();
7543 out.extend_from_slice(&version.to_le_bytes());
7544 let crc = yo_common::crc::crc64(0, &out);
7545 out.extend_from_slice(&crc.to_le_bytes());
7546 out
7547 };
7548 let body = &good[..good.len() - 10];
7549
7550 let mut torn = good.clone();
7551 let n = torn.len();
7552 torn[n - 1] ^= 0xff;
7553 let future = reseal(body, 999);
7554 let mut pre_ga = body.to_vec();
7557 pre_ga[0] = 246;
7558 let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
7559 let mut other = body.to_vec();
7560 other[0] = 0;
7561 let other = reseal(&other, yo_kv::rdb::VERSION);
7562 let mut cut = body.to_vec();
7564 cut.truncate(body.len() - 1);
7565 let cut = reseal(&cut, yo_kv::rdb::VERSION);
7566
7567 for (bytes, want) in [
7568 (vec![], "ERR DUMP payload version or checksum are wrong"),
7569 (
7570 b"0123456789".to_vec(),
7571 "ERR DUMP payload version or checksum are wrong",
7572 ),
7573 (torn, "ERR DUMP payload version or checksum are wrong"),
7574 (future, "ERR DUMP payload version or checksum are wrong"),
7575 (pre_ga, "ERR Pre-GA function format not supported"),
7576 (other, "ERR given type is not a function"),
7577 (cut, "ERR Failed loading library payload"),
7578 ] {
7579 assert_eq!(
7580 f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
7581 format!("-{want}\r\n")
7582 );
7583 }
7584
7585 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
7587 let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7588 assert_eq!(again, good);
7589 }
7590
7591 #[test]
7594 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7595 fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
7596 let mut f = Fixture::new();
7597 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7598 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7599 let clash =
7601 b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
7602 .as_slice();
7603 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
7604 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
7605 assert_eq!(
7606 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
7607 "-ERR Function ping already exists\r\n"
7608 );
7609 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
7611 }
7612
7613 #[test]
7614 fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
7615 let mut f = Fixture::new();
7616 assert_eq!(
7617 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
7618 "*1\r\n$1\r\nk\r\n"
7619 );
7620 assert_eq!(
7621 f.run(&[
7622 b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
7623 ]),
7624 "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
7625 );
7626 assert_eq!(
7629 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
7630 "*0\r\n"
7631 );
7632 assert_eq!(
7635 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
7636 "*0\r\n"
7637 );
7638 assert_eq!(
7639 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
7640 "*0\r\n"
7641 );
7642 assert_eq!(
7643 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
7644 "*0\r\n"
7645 );
7646 assert_eq!(
7648 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
7649 "-ERR Invalid number of arguments specified for command\r\n"
7650 );
7651 }
7652
7653 #[test]
7654 #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7655 fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
7656 let mut f = Fixture::new();
7657 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
7658
7659 assert_eq!(
7660 eval(&mut f, b"return redis.sha1hex('')"),
7661 "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
7662 );
7663 assert_eq!(
7664 eval(&mut f, b"return redis.sha1hex('return 1')"),
7665 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
7666 );
7667 assert_eq!(
7670 eval(&mut f, b"return redis.error_reply('boom')"),
7671 "-ERR boom\r\n"
7672 );
7673 assert_eq!(
7674 eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
7675 "-WRONGTYPE nope\r\n"
7676 );
7677 assert_eq!(
7678 eval(&mut f, b"return redis.status_reply('fine')"),
7679 "+fine\r\n"
7680 );
7681 assert_eq!(
7684 eval(&mut f, b"return redis.error_reply(1)"),
7685 "-ERR wrong number or type of arguments\r\n"
7686 );
7687 assert_eq!(
7688 eval(&mut f, b"local x = redis.status_reply() return x.err"),
7689 "$37\r\nERR wrong number or type of arguments\r\n"
7690 );
7691
7692 assert_eq!(
7694 eval(
7695 &mut f,
7696 b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
7697 ),
7698 "$4\r\n0123\r\n"
7699 );
7700 assert_eq!(
7701 eval(
7702 &mut f,
7703 b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
7704 ),
7705 "$5\r\n01223\r\n"
7706 );
7707 assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
7709 assert_eq!(
7710 eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
7711 ":1\r\n"
7712 );
7713 assert_eq!(
7714 eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
7715 ":1\r\n"
7716 );
7717 assert_eq!(
7718 eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
7719 ":1\r\n"
7720 );
7721 assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
7723 assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
7724 assert!(
7725 eval(&mut f, b"redis.log('x', 'y')")
7726 .contains("First argument must be a number (log level)."),
7727 );
7728 assert!(
7729 eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
7730 .contains("Invalid command passed to redis.acl_check_cmd()"),
7731 );
7732 assert!(
7733 eval(&mut f, b"return redis.acl_check_cmd('get')")
7734 .contains("Wrong number of args for redis.acl_check_cmd()"),
7735 );
7736 }
7737
7738 #[test]
7739 fn a_counter_is_an_integer_and_not_a_string_of_digits() {
7740 let mut f = Fixture::new();
7741 assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
7742 assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
7743 assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
7744 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
7747 assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
7748 f.run(&[b"SET", b"k", b"hello"]);
7751 assert_eq!(
7752 f.run(&[b"INCR", b"k"]),
7753 "-ERR value is not an integer or out of range\r\n"
7754 );
7755 assert_eq!(
7756 f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
7757 "-ERR increment would produce NaN or Infinity\r\n"
7758 );
7759 }
7760
7761 #[test]
7766 fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
7767 let mut f = Fixture::new();
7768 assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
7769 assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
7772 assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
7773 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
7774 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
7775 assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
7776 assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
7777 assert_eq!(
7778 f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
7779 "*2\r\n:1\r\n:0\r\n",
7780 "a refused increment reports the value it left alone and applied nothing"
7781 );
7782 assert_eq!(
7783 f.run(&[
7784 b"INCREX",
7785 b"n",
7786 b"BYINT",
7787 b"5",
7788 b"UBOUND",
7789 b"3",
7790 b"SATURATE"
7791 ]),
7792 "*2\r\n:3\r\n:2\r\n"
7793 );
7794 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
7795 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
7796 }
7797
7798 #[test]
7799 fn the_same_answers_come_out_in_resp3_spelling() {
7800 let mut f = Fixture::new();
7801 assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
7802 assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
7803 assert_eq!(
7806 f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
7807 "*2\r\n,1.5\r\n,1.5\r\n"
7808 );
7809 assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
7810 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
7813 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
7814 }
7815
7816 #[test]
7817 fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
7818 let mut f = Fixture::new();
7819 let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
7820 assert_eq!(flow, Flow::Continue);
7821 assert_eq!(
7822 reply,
7823 "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
7824 );
7825 let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
7828 assert_eq!(reply.matches("\r\n").count(), 1);
7829 }
7830
7831 #[test]
7832 fn arity_is_checked_before_the_command_is() {
7833 let mut f = Fixture::new();
7834 assert_eq!(
7835 f.run(&[b"GET"]),
7836 "-ERR wrong number of arguments for 'get' command\r\n"
7837 );
7838 assert_eq!(
7839 f.run(&[b"MSET", b"k"]),
7840 "-ERR wrong number of arguments for 'mset' command\r\n"
7841 );
7842 assert_eq!(
7846 f.run(&[b"PING", b"a", b"b"]),
7847 "-ERR wrong number of arguments for 'ping' command\r\n"
7848 );
7849 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
7850 assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
7851 assert_eq!(
7853 f.run(&[b"DELEX", b"k", b"IFEQ"]),
7854 "-ERR wrong number of arguments for 'delex' command\r\n"
7855 );
7856 }
7857
7858 #[test]
7862 fn the_option_combinations_are_the_ones_a_real_server_accepts() {
7863 let mut f = Fixture::new();
7864 let syntax = "-ERR syntax error\r\n";
7865 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
7866 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
7867 assert_eq!(
7868 f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
7869 syntax
7870 );
7871 assert_eq!(
7872 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
7873 syntax
7874 );
7875 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
7876 assert_eq!(
7878 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
7879 "+OK\r\n"
7880 );
7881 assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
7882 assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
7883 assert_eq!(
7885 f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
7886 syntax
7887 );
7888 assert_eq!(
7889 f.run(&[b"INCREX", b"n", b"ENX"]),
7890 "-ERR ENX flag requires an expiration\r\n"
7891 );
7892 assert_eq!(
7893 f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
7894 "-ERR UBOUND is not an integer or out of range\r\n"
7895 );
7896 assert_eq!(
7897 f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
7898 "-ERR LBOUND can't be greater than UBOUND\r\n"
7899 );
7900 assert_eq!(
7901 f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
7902 "-ERR If you want both the length and indexes, please just use IDX.\r\n"
7903 );
7904 }
7905
7906 #[test]
7910 fn the_expiry_rules_are_redis_own() {
7911 let mut f = Fixture::new();
7912 let bad = "-ERR invalid expire time in 'set' command\r\n";
7913 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
7914 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
7915 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
7916 assert_eq!(
7917 f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
7918 bad
7919 );
7920 assert_eq!(
7921 f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
7922 "-ERR value is not an integer or out of range\r\n"
7923 );
7924 assert_eq!(
7925 f.run(&[b"SETEX", b"k", b"0", b"v"]),
7926 "-ERR invalid expire time in 'setex' command\r\n"
7927 );
7928 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
7929 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
7930 assert_eq!(
7931 f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
7932 "-ERR syntax error\r\n",
7933 "the option list is still checked before the key is looked up"
7934 );
7935 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
7937 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
7938 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
7939 }
7940
7941 #[test]
7942 fn mset_takes_its_pairs_from_the_read_buffer() {
7943 let mut f = Fixture::new();
7944 assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
7945 assert_eq!(
7946 f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
7947 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
7948 );
7949 assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
7950 assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
7951 assert_eq!(
7952 f.run(&[b"MSETEX", b"2", b"e", b"5"]),
7953 "-ERR wrong number of key-value pairs\r\n"
7954 );
7955 assert_eq!(
7956 f.run(&[b"MSETEX", b"0", b"e", b"5"]),
7957 "-ERR invalid numkeys value\r\n"
7958 );
7959 assert_eq!(
7960 f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
7961 "-ERR invalid numkeys value\r\n"
7962 );
7963 }
7964
7965 #[test]
7966 fn lcs_answers_the_length_the_string_and_the_runs() {
7967 let mut f = Fixture::new();
7968 f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
7969 assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
7970 assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
7971 assert_eq!(
7972 f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
7973 "*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"
7974 );
7975 assert_eq!(
7978 f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
7979 "$6\r\nmytext\r\n"
7980 );
7981 }
7982
7983 #[test]
7984 fn select_moves_the_connection_and_the_databases_stay_apart() {
7985 let mut f = Fixture::new();
7986 f.run(&[b"SET", b"k", b"zero"]);
7987 assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
7988 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
7989 f.run(&[b"SET", b"k", b"four"]);
7990 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
7991 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
7992 assert_eq!(
7993 f.run(&[b"SELECT", b"99"]),
7994 "-ERR DB index is out of range\r\n"
7995 );
7996 assert_eq!(
7997 f.run(&[b"SELECT", b"-1"]),
7998 "-ERR DB index is out of range\r\n"
7999 );
8000 assert_eq!(
8001 f.run(&[b"SELECT", b"abc"]),
8002 "-ERR value is not an integer or out of range\r\n"
8003 );
8004 f.run(&[b"SELECT", b"4"]);
8006 f.run(&[b"RESET"]);
8007 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
8008 }
8009
8010 #[test]
8011 fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
8012 let mut f = Fixture::new();
8013 let reply = f.run(&[b"HELLO"]);
8014 assert!(reply.starts_with("*14\r\n"), "{reply}");
8015 assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
8016 assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
8017 assert!(
8018 reply.contains(":7\r\n"),
8019 "the connection id is in there: {reply}"
8020 );
8021 assert_eq!(
8022 f.run(&[b"HELLO", b"4"]),
8023 "-NOPROTO unsupported protocol version\r\n"
8024 );
8025 assert_eq!(
8026 f.run(&[b"HELLO", b"abc"]),
8027 "-ERR Protocol version is not an integer or out of range\r\n"
8028 );
8029 assert_eq!(
8030 f.run(&[b"HELLO", b"3", b"SETNAME"]),
8031 "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
8032 );
8033 assert!(
8034 f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
8035 .starts_with("%7\r\n")
8036 );
8037 assert_eq!(f.session.name(), b"bob");
8038 f.run(&[b"RESET"]);
8039 assert_eq!(f.session.name(), b"");
8040 }
8041
8042 #[test]
8043 fn command_describes_this_server_in_the_shape_a_driver_reads() {
8044 let mut f = Fixture::new();
8045 let count = format!(":{}\r\n", COMMANDS.len());
8046 assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
8047 let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
8048 assert_eq!(
8049 info,
8050 "*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\
8051 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*1\r\n*6\r\n\
8052 $5\r\nflags\r\n*2\r\n+RO\r\n+access\r\n\
8053 $12\r\nbegin_search\r\n*4\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
8054 *2\r\n$5\r\nindex\r\n:1\r\n\
8055 $9\r\nfind_keys\r\n*4\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
8056 *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\
8057 *0\r\n"
8058 );
8059 assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
8061 assert_eq!(
8062 f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
8063 "*1\r\n$8\r\ngetrange\r\n"
8064 );
8065 assert_eq!(
8066 f.run(&[b"COMMAND", b"NOPE"]),
8067 "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
8068 );
8069 }
8070
8071 #[test]
8075 fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
8076 let mut f = Fixture::new();
8077 assert_eq!(
8078 f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
8079 "*1\r\n$1\r\nk\r\n"
8080 );
8081 assert_eq!(
8082 f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
8083 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
8084 );
8085 assert_eq!(
8086 f.run(&[
8087 b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
8088 ]),
8089 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
8090 );
8091 assert_eq!(
8092 f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
8093 "-ERR The command has no key arguments\r\n"
8094 );
8095 assert_eq!(
8096 f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
8097 "-ERR Invalid number of arguments specified for command\r\n"
8098 );
8099 }
8100
8101 #[test]
8102 fn config_answers_what_it_can_and_refuses_what_it_cannot() {
8103 let mut f = Fixture::new();
8104 assert_eq!(
8105 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
8106 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
8107 );
8108 let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
8111 assert!(both.starts_with("*6\r\n"), "{both}");
8112 assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
8113 assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
8114 assert_eq!(
8115 f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
8116 "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
8117 );
8118 assert_eq!(
8119 f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
8120 "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
8121 );
8122 assert_eq!(
8123 f.run(&[b"CONFIG", b"GET"]),
8124 "-ERR wrong number of arguments for 'config|get' command\r\n"
8125 );
8126 assert_eq!(
8130 f.run(&[b"CONFIG", b"SET", b"appendonly"]),
8131 "-ERR wrong number of arguments for 'config|set' command\r\n"
8132 );
8133 assert_eq!(
8134 f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
8135 "-ERR syntax error\r\n"
8136 );
8137 assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
8138 assert_eq!(
8139 f.run(&[b"CONFIG", b"REWRITE"]),
8140 "-ERR The server is running without a config file\r\n"
8141 );
8142 }
8143
8144 #[test]
8152 fn a_setting_is_found_whatever_case_it_is_asked_for_in() {
8153 let mut f = Fixture::new();
8154 assert_eq!(
8155 f.run(&[b"CONFIG", b"GET", b"MAXMEMORY"]),
8156 "*2\r\n$9\r\nMAXMEMORY\r\n$1\r\n0\r\n"
8157 );
8158 let starred = f.run(&[b"CONFIG", b"GET", b"MAXMEM*"]);
8159 assert!(starred.starts_with("*6\r\n"), "{starred}");
8160 assert!(starred.contains("maxmemory"), "{starred}");
8161 assert!(!starred.contains("MAXMEM"), "{starred}");
8162 assert_eq!(
8165 f.run(&[b"CONFIG", b"GET", b"MAXMEMORY", b"maxmemory"]),
8166 "*2\r\n$9\r\nMAXMEMORY\r\n$1\r\n0\r\n"
8167 );
8168 let both = f.run(&[b"CONFIG", b"GET", b"maxmem*", b"MAXMEMORY"]);
8169 assert!(both.starts_with("*6\r\n"), "{both}");
8170 assert!(!both.contains("MAXMEMORY"), "{both}");
8171 }
8172
8173 #[test]
8176 fn the_migration_settings_read_and_write_like_the_reference() {
8177 let mut f = Fixture::new();
8178 let group = f.run(&[b"CONFIG", b"GET", b"cluster-slot-migration-*"]);
8179 assert!(group.starts_with("*4\r\n"), "{group}");
8180 assert!(group.contains("handoff-max-lag-bytes"), "{group}");
8181 assert!(group.contains("write-pause-timeout"), "{group}");
8182 assert!(!group.contains("max-archived-tasks"), "{group}");
8183 assert!(!group.contains("sync-buffer-drain-timeout"), "{group}");
8184 assert_eq!(
8186 f.run(&[
8187 b"CONFIG",
8188 b"GET",
8189 b"cluster-slot-migration-max-archived-tasks"
8190 ]),
8191 "*2\r\n$41\r\ncluster-slot-migration-max-archived-tasks\r\n$2\r\n32\r\n"
8192 );
8193 assert_eq!(
8196 f.run(&[
8197 b"CONFIG",
8198 b"SET",
8199 b"cluster-slot-migration-handoff-max-lag-bytes",
8200 b"2mb"
8201 ]),
8202 "+OK\r\n"
8203 );
8204 assert_eq!(
8205 f.run(&[
8206 b"CONFIG",
8207 b"GET",
8208 b"cluster-slot-migration-handoff-max-lag-bytes"
8209 ]),
8210 "*2\r\n$44\r\ncluster-slot-migration-handoff-max-lag-bytes\r\n$7\r\n2097152\r\n"
8211 );
8212 assert_eq!(
8214 f.run(&[
8215 b"CONFIG",
8216 b"SET",
8217 b"cluster-slot-migration-write-pause-timeout",
8218 b"10s"
8219 ]),
8220 "-ERR CONFIG SET failed (possibly related to argument 'cluster-slot-migration-write-pause-timeout') - argument couldn't be parsed into an integer\r\n"
8221 );
8222 assert_eq!(
8223 f.run(&[
8224 b"CONFIG",
8225 b"SET",
8226 b"cluster-slot-migration-handoff-max-lag-bytes",
8227 b"-1"
8228 ]),
8229 "-ERR CONFIG SET failed (possibly related to argument 'cluster-slot-migration-handoff-max-lag-bytes') - argument must be a memory value\r\n"
8230 );
8231 assert_eq!(
8232 f.run(&[
8233 b"CONFIG",
8234 b"SET",
8235 b"cluster-slot-migration-write-pause-timeout",
8236 b"-1"
8237 ]),
8238 "-ERR CONFIG SET failed (possibly related to argument 'cluster-slot-migration-write-pause-timeout') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
8239 );
8240 assert_eq!(
8243 f.run(&[
8244 b"CONFIG",
8245 b"SET",
8246 b"cluster-slot-migration-max-archived-tasks",
8247 b"0"
8248 ]),
8249 "-ERR CONFIG SET failed (possibly related to argument 'cluster-slot-migration-max-archived-tasks') - argument must be between 1 and 2147483647 inclusive\r\n"
8250 );
8251 assert_eq!(
8252 f.run(&[
8253 b"CONFIG",
8254 b"SET",
8255 b"cluster-slot-migration-max-archived-tasks",
8256 b"2147483648"
8257 ]),
8258 "-ERR CONFIG SET failed (possibly related to argument 'cluster-slot-migration-max-archived-tasks') - argument must be between 1 and 2147483647 inclusive\r\n"
8259 );
8260 }
8261
8262 #[test]
8263 fn the_eviction_policy_reads_back_what_was_written_to_it() {
8264 let mut f = Fixture::new();
8265 assert_eq!(
8266 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
8267 "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
8268 );
8269 assert_eq!(
8270 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
8271 "+OK\r\n",
8272 "the name is matched without regard to case, like every other one"
8273 );
8274 assert_eq!(
8275 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
8276 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
8277 );
8278 assert!(
8280 f.run(&[b"INFO", b"memory"])
8281 .contains("maxmemory_policy:allkeys-lfu"),
8282 "INFO and CONFIG disagree about the policy"
8283 );
8284 assert_eq!(
8288 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
8289 "-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"
8290 );
8291 assert_eq!(
8294 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
8295 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
8296 );
8297 f.run(&[
8298 b"CONFIG",
8299 b"SET",
8300 b"hash-max-listpack-entries",
8301 b"7",
8302 b"maxmemory-policy",
8303 b"nonsense",
8304 ]);
8305 assert_eq!(
8306 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
8307 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
8308 );
8309 }
8310
8311 #[test]
8312 fn the_three_eviction_numbers_read_back_too() {
8313 let mut f = Fixture::new();
8314 for (name, default, set) in [
8315 ("maxmemory-samples", "5", "12"),
8316 ("lfu-log-factor", "10", "3"),
8317 ("lfu-decay-time", "1", "60"),
8318 ] {
8319 let get = || {
8320 format!(
8321 "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
8322 name.len(),
8323 default.len()
8324 )
8325 };
8326 assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
8327 assert_eq!(
8328 f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
8329 "+OK\r\n"
8330 );
8331 assert_eq!(
8332 f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
8333 format!(
8334 "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
8335 name.len(),
8336 set.len()
8337 )
8338 );
8339 assert_eq!(
8342 f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
8343 format!(
8344 "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
8345 )
8346 );
8347 }
8348 }
8349
8350 #[test]
8351 fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
8352 let mut f = Fixture::new();
8353 assert_eq!(
8354 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
8355 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
8356 "no limit is the default"
8357 );
8358 for (typed, bytes) in [
8361 (&b"1024"[..], "1024"),
8362 (b"1k", "1000"),
8363 (b"1kb", "1024"),
8364 (b"1M", "1000000"),
8365 (b"1Mb", "1048576"),
8366 (b"1gb", "1073741824"),
8367 (b"100mb", "104857600"),
8368 ] {
8369 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
8370 assert_eq!(
8371 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
8372 format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
8373 "set {}",
8374 String::from_utf8_lossy(typed)
8375 );
8376 }
8377 assert!(
8378 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
8379 "the report agrees with the setting"
8380 );
8381
8382 for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
8385 assert_eq!(
8386 f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
8387 "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
8388 "refused {}",
8389 String::from_utf8_lossy(bad)
8390 );
8391 }
8392 assert!(
8393 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
8394 "and the refusal left the old one alone"
8395 );
8396 }
8397
8398 #[test]
8399 fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
8400 let mut f = Fixture::new();
8401 f.run(&[b"SET", b"here", b"already"]);
8402 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
8406 assert_eq!(
8407 f.run(&[b"SET", b"k", b"v"]),
8408 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
8409 );
8410 assert_eq!(
8411 f.run(&[b"LPUSH", b"l", b"v"]),
8412 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
8413 );
8414 assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
8416 assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
8417 assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
8418
8419 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
8421 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
8422 }
8423
8424 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
8428 #[test]
8429 fn an_allkeys_policy_makes_room_instead_of_refusing() {
8430 let mut f = Fixture::new();
8431 let val = vec![b'v'; 256];
8432 for i in 0..24000u32 {
8433 let k = format!("key:{i:08}");
8434 f.run(&[b"SET", k.as_bytes(), &val]);
8435 }
8436 let full = f.server.memory_bytes();
8437 assert!(
8438 full > 3 * 1024 * 1024,
8439 "the arena is several segments: {full}"
8440 );
8441
8442 let limit = full - 2 * 1024 * 1024;
8446 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
8447 f.run(&[
8448 b"CONFIG",
8449 b"SET",
8450 b"maxmemory",
8451 limit.to_string().as_bytes(),
8452 ]);
8453
8454 for i in 0..2000u32 {
8458 let k = format!("new:{i:08}");
8459 assert_eq!(
8460 f.run(&[b"SET", k.as_bytes(), &val]),
8461 "+OK\r\n",
8462 "write {i} was refused"
8463 );
8464 f.server.refresh_memory();
8465 if f.server.memory_bytes() <= limit {
8466 break;
8467 }
8468 }
8469 assert!(
8470 f.server.memory_bytes() <= limit,
8471 "it never got under: {} against {limit}",
8472 f.server.memory_bytes()
8473 );
8474 let info = f.run(&[b"INFO", b"stats"]);
8475 assert!(!info.contains("evicted_keys:0"), "{info}");
8476 assert!(
8477 f.run(&[b"DBSIZE"]) != ":0\r\n",
8478 "and it did not empty the database to get there"
8479 );
8480 }
8481
8482 #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
8493 #[test]
8494 fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
8495 let mut f = Fixture::new();
8502 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8503 let big = vec![b'v'; 200];
8504
8505 for i in 0..400u32 {
8506 let n = i.to_string();
8507 let n = n.as_bytes();
8508 f.run(&[b"SADD", b"s", n]);
8509 f.run(&[b"SADD", b"s2", &big]);
8510 f.run(&[b"HSET", b"h", n, &big]);
8511 f.run(&[b"RPUSH", b"l", &big]);
8512 f.run(&[b"ZADD", b"z", n, n]);
8513 f.run(&[b"ARSET", b"a", n, &big]);
8514 if i % 7 == 0 {
8515 f.run(&[b"SREM", b"s", n]);
8516 f.run(&[b"HDEL", b"h", n]);
8517 f.run(&[b"LPOP", b"l"]);
8518 f.run(&[b"ZREM", b"z", n]);
8519 f.run(&[b"ARDEL", b"a", n]);
8520 }
8521 if i % 53 == 0 {
8522 f.run(&[b"DEL", b"s2"]);
8525 }
8526 assert_eq!(
8527 f.server.settled_memory(),
8528 f.server.memory_bytes(),
8529 "after round {i}"
8530 );
8531 }
8532
8533 assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
8536 assert!(
8537 f.server.memory_bytes() > 512 * 1024,
8538 "{}",
8539 f.server.memory_bytes()
8540 );
8541
8542 f.run(&[b"FLUSHALL"]);
8544 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
8545 }
8546
8547 #[test]
8548 fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
8549 let mut f = Fixture::new();
8554 for i in 0..200u32 {
8555 let n = i.to_string();
8556 f.run(&[b"SADD", b"s", n.as_bytes()]);
8557 f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
8558 }
8559 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8560 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
8561
8562 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
8563 for i in 200..400u32 {
8564 let n = i.to_string();
8565 f.run(&[b"SADD", b"s", n.as_bytes()]);
8566 }
8567 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8568 assert_eq!(
8569 f.server.settled_memory(),
8570 f.server.memory_bytes(),
8571 "the writes it was not watching are in the number it started from"
8572 );
8573 }
8574
8575 #[test]
8585 fn a_database_nobody_has_touched_is_still_in_the_total() {
8586 let mut f = Fixture::new();
8587 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8588 f.run(&[b"SELECT", b"3"]);
8589 for i in 0..200u32 {
8590 let n = i.to_string();
8591 f.run(&[b"SADD", b"s", n.as_bytes()]);
8592 }
8593 f.run(&[b"SELECT", b"0"]);
8594 for i in 0..200u32 {
8595 let k = format!("key:{i}");
8596 f.run(&[b"SET", k.as_bytes(), b"value"]);
8597 }
8598
8599 for turn in 0..DATABASES * 2 {
8603 assert_eq!(
8604 f.server.settled_memory(),
8605 f.server.memory_bytes(),
8606 "reading {turn}"
8607 );
8608 }
8609
8610 f.run(&[b"SELECT", b"3"]);
8613 f.run(&[b"FLUSHDB"]);
8614 f.run(&[b"SELECT", b"0"]);
8615 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
8616 }
8617
8618 #[test]
8625 fn a_change_nothing_marked_is_found_within_a_turn_of_the_databases() {
8626 let mut f = Fixture::new();
8627 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8628 f.server.settled_memory();
8629 f.run(&[b"SELECT", b"7"]);
8630 for i in 0..200u32 {
8631 let n = i.to_string();
8632 f.run(&[b"SADD", b"s", n.as_bytes()]);
8633 }
8634 f.server.mine().to_weigh();
8635
8636 let walk = f.server.memory_bytes();
8637 let mut readings = 0;
8638 while f.server.settled_memory() != walk {
8639 readings += 1;
8640 assert!(readings < DATABASES, "still out of date after {readings}");
8641 }
8642 }
8643
8644 #[test]
8651 fn a_thread_takes_one_memory_reading_a_millisecond() {
8652 let f = Fixture::new();
8653 let mine = f.server.mine();
8654 assert!(mine.measuring(7));
8655 assert!(!mine.measuring(7));
8656 assert!(!mine.measuring(7));
8657 assert!(mine.measuring(8));
8658 assert!(!mine.measuring(8));
8659 }
8660
8661 #[test]
8662 fn evicted_keys_and_expired_keys_are_different_numbers() {
8663 let mut f = Fixture::new();
8664 f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
8667 f.server.advance_clock_ms(20);
8668 f.run(&[b"GET", b"gone"]);
8669 let info = f.run(&[b"INFO", b"stats"]);
8670 assert!(info.contains("expired_keys:1"), "{info}");
8671 assert!(info.contains("evicted_keys:0"), "{info}");
8672 }
8673
8674 #[test]
8675 fn the_two_counters_count_the_reads_and_nothing_else() {
8676 let mut f = Fixture::new();
8677 f.run(&[b"SET", b"k", b"v"]);
8678 f.run(&[b"GET", b"k"]);
8679 f.run(&[b"GET", b"nope"]);
8680 f.run(&[b"EXISTS", b"k", b"nope"]);
8681 let info = f.run(&[b"INFO", b"stats"]);
8684 assert!(info.contains("keyspace_hits:2"), "{info}");
8685 assert!(info.contains("keyspace_misses:2"), "{info}");
8686
8687 f.run(&[b"CONFIG", b"RESETSTAT"]);
8688 let info = f.run(&[b"INFO", b"stats"]);
8689 assert!(info.contains("keyspace_hits:0"), "{info}");
8690 assert!(info.contains("keyspace_misses:0"), "{info}");
8691 }
8692
8693 #[test]
8696 fn a_read_that_visits_its_key_twice_is_counted_once() {
8697 let mut f = Fixture::new();
8698 f.run(&[b"ZADD", b"z", b"1", b"m"]);
8699 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]);
8700 f.run(&[b"ZMSCORE", b"z", b"m", b"gone", b"also gone"]);
8701 f.run(&[b"OBJECT", b"ENCODING", b"z"]);
8702 f.run(&[b"DUMP", b"z"]);
8703 let info = f.run(&[b"INFO", b"stats"]);
8704 assert!(info.contains("keyspace_hits:4"), "{info}");
8705 assert!(info.contains("keyspace_misses:0"), "{info}");
8708 }
8709
8710 #[test]
8713 fn the_key_a_read_writes_afterwards_is_not_counted() {
8714 let mut f = Fixture::new();
8715 f.run(&[b"SET", b"s", b"v"]);
8716 f.run(&[b"COPY", b"s", b"dst"]);
8717 f.run(&[b"GETEX", b"s", b"EX", b"100"]);
8718 f.run(&[b"BITOP", b"AND", b"into", b"s", b"nope"]);
8719 let info = f.run(&[b"INFO", b"stats"]);
8720 assert!(info.contains("keyspace_hits:3"), "{info}");
8724 assert!(info.contains("keyspace_misses:1"), "{info}");
8725 }
8726
8727 #[test]
8728 fn the_object_subcommands_follow_the_policy() {
8729 let mut f = Fixture::new();
8730 f.run(&[b"SET", b"s", b"v"]);
8731 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
8735 assert!(
8736 f.run(&[b"OBJECT", b"FREQ", b"s"])
8737 .starts_with("-ERR An LFU maxmemory policy is not selected"),
8738 );
8739
8740 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
8741 assert!(
8742 f.run(&[b"OBJECT", b"IDLETIME", b"s"])
8743 .starts_with("-ERR An LFU maxmemory policy is selected"),
8744 );
8745 assert!(
8750 f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
8751 "FREQ should answer under an LFU policy"
8752 );
8753 }
8754
8755 #[test]
8756 fn object_says_which_rung_of_the_ladder_a_key_is_on() {
8757 let mut f = Fixture::new();
8758 f.run(&[b"SET", b"s", b"hello"]);
8759 f.run(&[b"SET", b"n", b"123"]);
8760 f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
8761 f.run(&[b"SADD", b"ss", b"a", b"b"]);
8762 f.run(&[b"HSET", b"h", b"f", b"v"]);
8763 for (key, want) in [
8764 (b"s".as_slice(), "embstr"),
8765 (b"n", "int"),
8766 (b"si", "intset"),
8767 (b"ss", "listpack"),
8768 (b"h", "listpack"),
8769 ] {
8770 let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
8771 assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
8772 }
8773
8774 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
8777 assert_eq!(
8778 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
8779 "$10\r\nlistpackex\r\n"
8780 );
8781
8782 assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
8783 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
8784 assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
8785 }
8786
8787 #[test]
8788 fn object_answers_nil_for_a_key_that_is_not_there() {
8789 let mut f = Fixture::new();
8790 for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
8791 assert_eq!(
8792 f.run(&[b"OBJECT", sub, b"nokey"]),
8793 "$-1\r\n",
8794 "a nil and not an error, which is what 8.10.1 does"
8795 );
8796 }
8797 f.run(&[b"SET", b"s", b"v"]);
8800 assert!(
8801 f.run(&[b"OBJECT", b"FREQ", b"s"])
8802 .starts_with("-ERR An LFU maxmemory policy is not"),
8803 );
8804 assert_eq!(
8805 f.run(&[b"OBJECT", b"NOPE", b"s"]),
8806 "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
8807 );
8808 assert_eq!(
8809 f.run(&[b"OBJECT", b"ENCODING"]),
8810 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
8811 );
8812 assert_eq!(
8813 f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
8814 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
8815 );
8816 assert_eq!(
8817 f.run(&[b"OBJECT"]),
8818 "-ERR wrong number of arguments for 'object' command\r\n"
8819 );
8820 }
8821
8822 #[test]
8823 fn memory_usage_counts_the_record_the_body_and_a_share_of_the_index() {
8824 let mut f = Fixture::new();
8825 assert_eq!(
8826 f.run(&[b"MEMORY", b"USAGE", b"nokey"]),
8827 "$-1\r\n",
8828 "a null and not an error, the same as OBJECT"
8829 );
8830 f.run(&[b"SET", b"s", b"hello"]);
8831 let small = int_of(&f.run(&[b"MEMORY", b"USAGE", b"s"]));
8832 assert!(
8833 small > 5,
8834 "the value is in there and so are the name and the header"
8835 );
8836 f.run(&[b"SET", b"s", &[b'x'; 1000]]);
8844 let big = int_of(&f.run(&[b"MEMORY", b"USAGE", b"s"]));
8845 assert!(
8846 big - small >= 980 && big - small <= 1020,
8847 "{small} then {big}"
8848 );
8849 f.run(&[b"SADD", b"one", b"a"]);
8852 f.run(&[b"SADD", b"many", b"a"]);
8853 for i in 0..100u32 {
8854 f.run(&[b"SADD", b"many", format!("member:{i}").as_bytes()]);
8855 }
8856 assert!(
8857 int_of(&f.run(&[b"MEMORY", b"USAGE", b"many"]))
8858 > int_of(&f.run(&[b"MEMORY", b"USAGE", b"one"]))
8859 );
8860 assert_eq!(
8863 f.run(&[b"MEMORY", b"USAGE", b"many"]),
8864 f.run(&[b"MEMORY", b"USAGE", b"many"])
8865 );
8866 }
8867
8868 #[test]
8869 fn memory_usage_reads_samples_and_does_not_use_it() {
8870 let mut f = Fixture::new();
8871 f.run(&[b"SET", b"s", b"v"]);
8872 let plain = f.run(&[b"MEMORY", b"USAGE", b"s"]);
8873 for count in [b"0".as_slice(), b"1", b"5", b"1000"] {
8874 assert_eq!(
8875 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", count]),
8876 plain
8877 );
8878 }
8879 assert_eq!(
8882 f.run(&[
8883 b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"1", b"SAMPLES", b"2"
8884 ]),
8885 plain
8886 );
8887 assert_eq!(
8888 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES"]),
8889 "-ERR syntax error\r\n"
8890 );
8891 assert_eq!(
8892 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"-1"]),
8893 "-ERR syntax error\r\n"
8894 );
8895 assert_eq!(
8896 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"nine"]),
8897 "-ERR value is not an integer or out of range\r\n"
8898 );
8899 assert_eq!(
8900 f.run(&[b"MEMORY", b"USAGE", b"s", b"BAD", b"1"]),
8901 "-ERR syntax error\r\n"
8902 );
8903 assert_eq!(
8904 f.run(&[b"MEMORY", b"USAGE"]),
8905 "-ERR wrong number of arguments for 'memory|usage' command\r\n"
8906 );
8907 }
8908
8909 #[test]
8910 fn memory_stats_grows_a_field_for_every_database_holding_a_key() {
8911 let mut f = Fixture::new();
8912 assert!(
8913 f.run(&[b"MEMORY", b"STATS"]).starts_with("*72\r\n"),
8914 "thirty six pairs on a server nobody has written to"
8915 );
8916 f.run(&[b"SET", b"a", b"1"]);
8917 assert!(f.run(&[b"MEMORY", b"STATS"]).starts_with("*74\r\n"));
8918 f.run(&[b"SELECT", b"7"]);
8919 f.run(&[b"SET", b"b", b"2"]);
8920 let reply = f.run(&[b"MEMORY", b"STATS"]);
8921 assert!(reply.starts_with("*76\r\n"));
8922 assert!(reply.contains("\r\n$4\r\ndb.0\r\n"));
8923 assert!(reply.contains("\r\n$4\r\ndb.7\r\n"));
8924 assert!(reply.contains("overhead.hashtable.main"));
8926 assert!(reply.contains("overhead.hashtable.expires"));
8927 assert!(reply.contains("fragmentation.bytes"));
8928 }
8929
8930 #[test]
8931 fn memory_answers_the_four_that_only_look() {
8932 let mut f = Fixture::new();
8933 assert!(f.run(&[b"MEMORY", b"HELP"]).starts_with("*14\r\n+MEMORY "));
8934 assert_eq!(f.run(&[b"MEMORY", b"PURGE"]), "+OK\r\n");
8935 assert_eq!(
8936 f.run(&[b"MEMORY", b"MALLOC-STATS"]),
8937 "$45\r\nStats not supported for the current allocator\r\n"
8938 );
8939 assert!(
8942 f.run(&[b"MEMORY", b"DOCTOR"])
8943 .contains("my issues detector can't be used in these conditions")
8944 );
8945 assert_eq!(
8946 f.run(&[b"MEMORY", b"NOPE"]),
8947 "-ERR unknown subcommand 'NOPE'. Try MEMORY HELP.\r\n"
8948 );
8949 for sub in [
8950 b"STATS".as_slice(),
8951 b"DOCTOR",
8952 b"PURGE",
8953 b"MALLOC-STATS",
8954 b"HELP",
8955 ] {
8956 let name = String::from_utf8_lossy(sub).to_lowercase();
8957 assert_eq!(
8958 f.run(&[b"MEMORY", sub, b"extra"]),
8959 format!("-ERR wrong number of arguments for 'memory|{name}' command\r\n"),
8960 "the subcommand is named and not the container"
8961 );
8962 }
8963 assert_eq!(
8964 f.run(&[b"MEMORY"]),
8965 "-ERR wrong number of arguments for 'memory' command\r\n"
8966 );
8967 }
8968
8969 #[test]
8970 fn command_getkeys_finds_the_key_memory_usage_names() {
8971 let mut f = Fixture::new();
8972 assert_eq!(
8973 f.run(&[b"COMMAND", b"GETKEYS", b"MEMORY", b"USAGE", b"k"]),
8974 "*1\r\n$1\r\nk\r\n"
8975 );
8976 assert!(
8979 f.run(&[b"COMMAND", b"GETKEYS", b"MEMORY", b"DOCTOR"])
8980 .starts_with("-ERR ")
8981 );
8982 }
8983
8984 #[test]
8985 fn config_moves_the_ladder_and_object_encoding_agrees() {
8986 let mut f = Fixture::new();
8987 assert_eq!(
8988 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
8989 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
8990 "512 and not the 128 everyone remembers, which is what 8.10.1 says"
8991 );
8992 assert_eq!(
8995 f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
8996 "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
8997 );
8998 assert!(
8999 f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
9000 .starts_with("*8\r\n")
9001 );
9002 assert!(
9003 f.run(&[b"CONFIG", b"GET", b"set-max-*"])
9004 .starts_with("*6\r\n")
9005 );
9006
9007 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
9008 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
9009
9010 assert_eq!(
9011 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
9012 "+OK\r\n",
9013 "written under the old name and read back under the new one"
9014 );
9015 assert_eq!(
9016 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
9017 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
9018 );
9019 assert_eq!(
9020 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
9021 "$8\r\nlistpack\r\n",
9022 "the hash that already exists is left exactly where it was"
9023 );
9024 f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
9025 assert_eq!(
9026 f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
9027 "$9\r\nhashtable\r\n",
9028 "and the next one built goes straight to a table"
9029 );
9030
9031 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
9033 f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
9034 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
9035 f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
9036 f.run(&[b"SADD", b"s2", b"abcdefgh"]);
9037 assert_eq!(
9038 f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
9039 "$9\r\nhashtable\r\n"
9040 );
9041 }
9042
9043 #[test]
9044 fn config_set_takes_all_of_the_ladder_or_none_of_it() {
9045 let mut f = Fixture::new();
9046 assert_eq!(
9047 f.run(&[
9048 b"CONFIG",
9049 b"SET",
9050 b"hash-max-listpack-entries",
9051 b"7",
9052 b"set-max-listpack-entries",
9053 b"abc"
9054 ]),
9055 "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
9056 );
9057 assert_eq!(
9058 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
9059 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
9060 "the pair in front of the bad one did not go in"
9061 );
9062 assert_eq!(
9065 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
9066 "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
9067 );
9068 assert_eq!(
9069 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
9070 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
9071 );
9072 assert_eq!(
9075 f.run(&[
9076 b"CONFIG",
9077 b"SET",
9078 b"set-max-intset-entries",
9079 b"99999999999999999999"
9080 ]),
9081 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
9082 );
9083 assert_eq!(
9084 f.run(&[
9085 b"CONFIG",
9086 b"SET",
9087 b"set-max-intset-entries",
9088 b"9223372036854775807"
9089 ]),
9090 "+OK\r\n"
9091 );
9092 }
9093
9094 #[test]
9095 fn a_setting_moved_on_one_database_moved_on_all_of_them() {
9096 let mut f = Fixture::new();
9097 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
9098 f.run(&[b"SELECT", b"3"]);
9099 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9100 assert_eq!(
9101 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
9102 "$9\r\nhashtable\r\n",
9103 "these are one server wide number in Redis, whatever a Keyspace carries"
9104 );
9105 }
9106
9107 #[test]
9108 fn info_reports_the_numbers_it_can_stand_behind() {
9109 let mut f = Fixture::new();
9110 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
9111 let all = f.run(&[b"INFO"]);
9112 assert!(all.contains("redis_version:8.8.0"), "{all}");
9113 assert!(
9114 all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
9115 "{all}"
9116 );
9117 assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
9118 assert!(all.contains("role:master"), "{all}");
9119 let clients = f.run(&[b"INFO", b"clients"]);
9121 assert!(clients.contains("connected_clients:0"), "{clients}");
9122 assert!(!clients.contains("redis_version"), "{clients}");
9123 assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
9124 }
9125
9126 #[test]
9133 fn commandstats_is_asked_for_and_replication_is_not() {
9134 let mut f = Fixture::new();
9135 for arg in ["", "all", "default", "everything"] {
9136 let info = if arg.is_empty() {
9137 f.run(&[b"INFO"])
9138 } else {
9139 f.run(&[b"INFO", arg.as_bytes()])
9140 };
9141 assert!(info.contains("redis_version"), "{arg}: {info}");
9142 assert!(info.contains("used_cpu_user"), "{arg}: {info}");
9143 assert!(info.contains("used_memory"), "{arg}: {info}");
9144 assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
9145 let asked = arg == "all" || arg == "everything";
9146 assert_eq!(
9147 info.contains("rejected_calls"),
9148 asked,
9149 "{arg} should{} carry the command counters: {info}",
9150 if asked { "" } else { " not" }
9151 );
9152 }
9153
9154 let cpu = f.run(&[b"INFO", b"cpu"]);
9155 assert!(cpu.contains("used_cpu_user"), "{cpu}");
9156 assert!(!cpu.contains("used_memory"), "{cpu}");
9157
9158 let stats = f.run(&[b"INFO", b"commandSTATS"]);
9161 assert!(!stats.contains("used_memory"), "{stats}");
9162 assert!(stats.contains("rejected_calls"), "{stats}");
9163
9164 let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
9166 assert!(pair.contains("used_cpu_user"), "{pair}");
9167 assert!(!pair.contains("master_repl_offset"), "{pair}");
9168
9169 let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
9170 assert!(with_all.contains("used_memory"), "{with_all}");
9171 assert!(with_all.contains("master_repl_offset"), "{with_all}");
9172 assert!(with_all.contains("rejected_calls"), "{with_all}");
9173 assert_eq!(
9175 with_all.matches("used_cpu_user_children").count(),
9176 1,
9177 "{with_all}"
9178 );
9179
9180 let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
9181 assert!(with_default.contains("used_memory"), "{with_default}");
9182 assert!(
9183 with_default.contains("master_repl_offset"),
9184 "{with_default}"
9185 );
9186 assert!(!with_default.contains("rejected_calls"), "{with_default}");
9187 assert_eq!(
9188 with_default.matches("used_cpu_user_children").count(),
9189 1,
9190 "{with_default}"
9191 );
9192 }
9193
9194 #[test]
9204 fn the_threads_section_says_where_the_work_landed() {
9205 let mut server = Server::new();
9206 server.set_threads(4);
9207 let mut f = Fixture::on(server);
9208 for _ in 0..3 {
9209 f.run(&[b"PING"]);
9210 }
9211
9212 assert!(!f.run(&[b"INFO"]).contains("# Threads"));
9213 assert!(f.run(&[b"INFO", b"all"]).contains("# Threads"));
9214
9215 let info = f.run(&[b"INFO", b"threads"]);
9216 assert!(info.contains("io_threads:4"), "{info}");
9217 for at in 0..4 {
9218 assert!(info.contains(&format!("thread_{at}:clients=")), "{info}");
9219 }
9220 assert!(!info.contains("thread_4:"), "{info}");
9221
9222 let per = f.server.per_thread();
9223 assert_eq!(per.len(), 4);
9224 assert_eq!(
9225 per.iter().map(|t| t.commands).sum::<u64>(),
9226 f.server.totals().commands
9227 );
9228 assert_eq!(per.iter().filter(|t| t.commands > 0).count(), 1, "{per:?}");
9229 }
9230
9231 #[test]
9240 fn the_thread_count_is_the_same_number_wherever_it_is_asked_for() {
9241 let mut f = Fixture::new();
9242 assert!(f.run(&[b"INFO", b"server"]).contains("io_threads_active:1"));
9243 assert_eq!(
9244 f.run(&[b"CONFIG", b"GET", b"io-threads"]),
9245 "*2\r\n$10\r\nio-threads\r\n$1\r\n1\r\n"
9246 );
9247
9248 let mut server = Server::new();
9249 server.set_threads(4);
9250 let mut f = Fixture::on(server);
9251 let info = f.run(&[b"INFO", b"all"]);
9252 assert!(info.contains("io_threads_active:4"), "{info}");
9253 assert!(info.contains("io_threads:4"), "{info}");
9254 assert_eq!(
9255 f.run(&[b"CONFIG", b"GET", b"io-threads"]),
9256 "*2\r\n$10\r\nio-threads\r\n$1\r\n4\r\n"
9257 );
9258 assert_eq!(f.run(&[b"CONFIG", b"SET", b"io-threads", b"4"]), "+OK\r\n");
9261 assert_eq!(
9262 f.run(&[b"CONFIG", b"SET", b"io-threads", b"1"]),
9263 "-ERR CONFIG SET failed (possibly related to argument 'io-threads') - can't set immutable config\r\n"
9264 );
9265 assert_eq!(
9266 f.run(&[b"CONFIG", b"SET", b"io-threads", b"lots"]),
9267 "-ERR CONFIG SET failed (possibly related to argument 'io-threads') - can't set immutable config\r\n"
9268 );
9269 }
9270
9271 #[test]
9280 fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
9281 let mut f = Fixture::new();
9282 let info = f.run(&[b"INFO", b"memory"]);
9283 for field in [
9284 "total_system_memory:",
9285 "mem_cgroup_limit:",
9286 "mem_limit:",
9287 "mem_budget:",
9288 ] {
9289 assert!(info.contains(field), "no {field} in {info}");
9290 }
9291
9292 let field = |name: &str| -> u64 {
9293 info.lines()
9294 .find_map(|l| l.strip_prefix(name))
9295 .unwrap_or_else(|| panic!("no {name} in {info}"))
9296 .trim()
9297 .parse()
9298 .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
9299 };
9300 let limit = field("mem_limit:");
9301 assert_eq!(field("mem_budget:"), limit / 4, "{info}");
9302 if limit != 0 {
9305 let host = field("total_system_memory:");
9306 let cgroup = field("mem_cgroup_limit:");
9307 assert!(
9308 limit == host || limit == cgroup,
9309 "the limit came from neither number: {info}"
9310 );
9311 }
9312 }
9313
9314 #[test]
9323 fn a_command_counts_what_it_did_separately_from_what_it_refused() {
9324 let mut f = Fixture::new();
9325 f.run(&[b"SET", b"k", b"v"]);
9326 f.run(&[b"SET", b"k", b"w"]);
9327 f.run(&[b"LPUSH", b"k", b"x"]);
9329 f.run(&[b"LPUSH", b"k"]);
9331
9332 let stats = f.run(&[b"INFO", b"commandstats"]);
9333 assert!(
9334 stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
9335 "{stats}"
9336 );
9337 assert!(
9338 stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
9339 "{stats}"
9340 );
9341 assert!(
9342 !stats.contains("cmdstat_zadd"),
9343 "a command nobody has sent has no row: {stats}"
9344 );
9345 }
9346
9347 #[test]
9351 fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
9352 let (dead, live) = if cfg!(miri) {
9359 (375, 125)
9360 } else {
9361 (3_000, 1_000)
9362 };
9363 let mut f = Fixture::new();
9364 for i in 0..dead {
9365 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
9366 }
9367 for i in 0..live {
9368 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
9369 }
9370 let all = format!(":{}\r\n", dead + live);
9371 assert_eq!(f.run(&[b"DBSIZE"]), all);
9372 f.advance(100);
9373 assert_eq!(
9374 f.run(&[b"DBSIZE"]),
9375 all,
9376 "DBSIZE counts records and nothing has read past the dead ones yet"
9377 );
9378
9379 let rest = format!(":{live}\r\n");
9381 let mut spent = 0;
9382 for _ in 0..2_000 {
9383 spent += f.server.expire_step(4096);
9384 if f.run(&[b"DBSIZE"]) == rest {
9385 break;
9386 }
9387 }
9388 assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
9389 assert!(
9390 f.run(&[b"INFO", b"stats"])
9391 .contains(&format!("expired_keys:{dead}"))
9392 );
9393 for i in 0..live {
9394 assert_eq!(
9395 f.run(&[b"GET", format!("k{i}").as_bytes()]),
9396 "$1\r\nv\r\n",
9397 "it took a key that had no deadline"
9398 );
9399 }
9400 }
9401
9402 #[test]
9403 fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
9404 let n = if cfg!(miri) { 200 } else { 2_000 };
9408 let mut f = Fixture::new();
9409 for i in 0..n {
9410 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
9411 }
9412 assert_eq!(f.server.expire_step(4096), 0);
9413 f.run(&[b"SELECT", b"3"]);
9415 f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
9416 f.advance(100);
9417 for _ in 0..64 {
9418 f.server.expire_step(4096);
9419 }
9420 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9421 f.run(&[b"SELECT", b"0"]);
9422 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
9423 assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
9424 }
9425
9426 #[test]
9429 fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
9430 let mut f = Fixture::new();
9431 for i in 0..500u32 {
9432 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
9433 }
9434 f.advance(100);
9435 let at = f.server.striped(0).now_ms();
9436 f.server.set_clock_ms(at);
9437 assert!(f.server.expire_slice(8) > 0, "the first one works");
9441 for _ in 0..1_000 {
9442 assert_eq!(
9443 f.server.expire_slice(8),
9444 0,
9445 "the millisecond has not moved and neither should this"
9446 );
9447 }
9448 assert!(
9449 f.server.striped(0).expires() > 400,
9450 "there is plenty left to take"
9451 );
9452 f.server.set_clock_ms(at + 1);
9453 assert!(f.server.expire_slice(8) > 0, "and then it goes again");
9454 }
9455
9456 #[test]
9459 fn info_keyspace_counts_the_keys_that_have_a_deadline() {
9460 let mut f = Fixture::new();
9461 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
9462 assert!(
9463 f.run(&[b"INFO", b"keyspace"])
9464 .contains("db0:keys=3,expires=0"),
9465 "none of them has one yet"
9466 );
9467 f.run(&[b"EXPIRE", b"a", b"1000"]);
9468 f.run(&[b"EXPIRE", b"b", b"1000"]);
9469 let two = f.run(&[b"INFO", b"keyspace"]);
9470 assert!(two.contains("db0:keys=3,expires=2"), "{two}");
9471 f.run(&[b"PERSIST", b"a"]);
9472 f.run(&[b"DEL", b"b"]);
9473 let none = f.run(&[b"INFO", b"keyspace"]);
9474 assert!(none.contains("db0:keys=2,expires=0"), "{none}");
9475
9476 f.run(&[b"SELECT", b"1"]);
9478 f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
9479 let both = f.run(&[b"INFO", b"keyspace"]);
9480 assert!(both.contains("db0:keys=2,expires=0"), "{both}");
9481 assert!(both.contains("db1:keys=1,expires=1"), "{both}");
9482 }
9483
9484 #[cfg(unix)]
9491 #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
9492 #[test]
9493 fn info_cpu_reports_processor_time_that_was_really_measured() {
9494 let mut f = Fixture::new();
9495 let cpu = f.run(&[b"INFO", b"cpu"]);
9496 assert!(cpu.contains("# CPU"), "{cpu}");
9497 assert!(cpu.contains("used_cpu_user:"), "{cpu}");
9499 assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
9500 assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
9501 assert!(!cpu.contains("redis_version"), "{cpu}");
9502
9503 let before = used_cpu_user(&cpu);
9507 let mut n = 0u64;
9508 let mut rounds = 0;
9509 while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
9510 for i in 0..1_000_000u64 {
9511 n = n.wrapping_add(i.wrapping_mul(i));
9512 }
9513 rounds += 1;
9514 assert!(rounds < 1_000, "cpu time never moved, n is {n}");
9518 }
9519 }
9520
9521 #[cfg(unix)]
9523 fn used_cpu_user(info: &str) -> f64 {
9524 info.lines()
9525 .find_map(|l| l.strip_prefix("used_cpu_user:"))
9526 .expect("no used_cpu_user in the reply")
9527 .trim()
9528 .parse()
9529 .expect("used_cpu_user is not a number")
9530 }
9531
9532 #[test]
9538 fn a_command_that_fails_leaves_nothing_half_written() {
9539 let mut f = Fixture::new();
9540 let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
9541 assert_eq!(reply, "-ERR offset is out of range\r\n");
9542 assert!(!reply.contains(':'), "no integer went out in front of it");
9543 }
9544
9545 #[test]
9546 fn quit_answers_first_and_closes_after() {
9547 let mut f = Fixture::new();
9548 let (flow, reply) = f.flow(&[b"QUIT"]);
9549 assert_eq!(reply, "+OK\r\n");
9550 assert_eq!(flow, Flow::Close);
9551 }
9552
9553 #[test]
9560 fn shutdown_writes_nothing_and_sets_the_flag() {
9561 let mut f = Fixture::new();
9562 assert!(!f.server.stopping(), "nobody has asked yet");
9563
9564 let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
9565 assert_eq!(reply, "");
9566 assert_eq!(flow, Flow::Close);
9567 assert!(f.server.stopping());
9568 }
9569
9570 #[test]
9582 fn shutdown_takes_the_flags_redis_takes() {
9583 let s = Saves::new("shutdown-flags");
9584 for flags in [
9585 &[b"NOSAVE".as_slice()][..],
9586 &[b"SAVE"],
9587 &[b"NOW"],
9588 &[b"FORCE"],
9589 &[b"nosave"],
9590 &[b"NOW", b"NOW"],
9591 &[b"SAVE", b"SAVE"],
9592 &[b"NOSAVE", b"NOW", b"FORCE"],
9593 ] {
9594 let mut f = Fixture::new();
9595 f.server.set_dir(s.dir.clone());
9596 let mut parts = vec![b"SHUTDOWN".as_slice()];
9597 parts.extend_from_slice(flags);
9598 let (flow, reply) = f.flow(&parts);
9599 assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
9600 assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
9601 assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
9602 }
9603
9604 for flags in [
9605 &[b"BOGUS".as_slice()][..],
9606 &[b"SAVE", b"NOSAVE"],
9607 &[b"NOSAVE", b"SAVE"],
9608 &[b"ABORT", b"NOW"],
9609 &[b"NOSAVE", b"ABORT"],
9610 &[b"NOW", b"FORCE", b"ABORT"],
9611 ] {
9612 let mut f = Fixture::new();
9613 let mut parts = vec![b"SHUTDOWN".as_slice()];
9614 parts.extend_from_slice(flags);
9615 assert_eq!(
9616 f.run(&parts),
9617 "-ERR syntax error\r\n",
9618 "SHUTDOWN {flags:?} was accepted"
9619 );
9620 assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
9621 }
9622 }
9623
9624 #[test]
9632 fn shutdown_abort_never_has_anything_to_abort() {
9633 let mut f = Fixture::new();
9634 for parts in [
9635 &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
9636 &[b"SHUTDOWN", b"ABORT", b"ABORT"],
9637 ] {
9638 assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
9639 assert!(!f.server.stopping(), "an abort stopped the server");
9640 }
9641 }
9642
9643 struct Backups {
9651 f: Fixture,
9652 dir: PathBuf,
9653 }
9654
9655 impl Backups {
9656 fn new(name: &str) -> Backups {
9657 let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
9658 let _ = std::fs::remove_dir_all(&dir);
9659 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
9660 let mut f = Fixture::new();
9661 f.server.set_dir(dir.clone());
9662 Backups { f, dir }
9663 }
9664
9665 fn run(&mut self, parts: &[&[u8]]) -> String {
9666 self.f.run(parts)
9667 }
9668
9669 fn files(&self) -> Vec<String> {
9671 let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
9672 Ok(entries) => entries
9673 .filter_map(|e| e.ok())
9674 .map(|e| e.file_name().to_string_lossy().into_owned())
9675 .collect(),
9676 Err(_) => Vec::new(),
9677 };
9678 names.sort();
9679 names
9680 }
9681
9682 fn read(&self, name: &str) -> Vec<u8> {
9683 std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
9684 }
9685 }
9686
9687 impl Drop for Backups {
9688 fn drop(&mut self) {
9689 let _ = std::fs::remove_dir_all(&self.dir);
9690 }
9691 }
9692
9693 #[test]
9696 fn backup_walks_the_states_the_reference_walks() {
9697 let mut b = Backups::new("states");
9698 let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
9699
9700 assert!(status(&mut b).contains("idle"));
9701 assert!(b.files().is_empty(), "an idle server has written a backup");
9702
9703 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
9704 assert!(status(&mut b).contains("incrementing"));
9705 assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
9706
9707 assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
9708 assert!(status(&mut b).contains("sealed"));
9709 assert_eq!(
9710 b.files(),
9711 [
9712 "appendonly.aof.1.base.rdb",
9713 "appendonly.aof.1.incr.aof",
9714 "appendonly.aof.manifest",
9715 ]
9716 );
9717
9718 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
9719 assert!(status(&mut b).contains("idle"));
9720 assert!(b.files().is_empty(), "cleanup left something behind");
9721 }
9722
9723 #[test]
9725 fn backup_refuses_the_moves_the_reference_refuses() {
9726 let mut b = Backups::new("refusals");
9727
9728 assert_eq!(
9729 b.run(&[b"BACKUP", b"SEAL"]),
9730 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
9731 );
9732 assert_eq!(
9733 b.run(&[b"BACKUP", b"ABORT"]),
9734 "-ERR No backup in progress\r\n"
9735 );
9736 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
9739
9740 b.run(&[b"BACKUP", b"START"]);
9741 assert_eq!(
9742 b.run(&[b"BACKUP", b"START"]),
9743 "-ERR A backup is already in progress, ABORT it first\r\n"
9744 );
9745 assert_eq!(
9746 b.run(&[b"BACKUP", b"CLEANUP"]),
9747 "-ERR Backup is in progress\r\n"
9748 );
9749
9750 b.run(&[b"BACKUP", b"SEAL"]);
9751 assert_eq!(
9752 b.run(&[b"BACKUP", b"START"]),
9753 "-ERR A sealed backup exists, CLEANUP it first\r\n"
9754 );
9755 assert_eq!(
9756 b.run(&[b"BACKUP", b"SEAL"]),
9757 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
9758 );
9759 assert_eq!(
9760 b.run(&[b"BACKUP", b"ABORT"]),
9761 "-ERR No backup in progress\r\n"
9762 );
9763 }
9764
9765 #[test]
9772 fn backup_abort_removes_the_file_and_says_who_did_it() {
9773 let mut b = Backups::new("abort");
9774 b.run(&[b"BACKUP", b"START"]);
9775 assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
9776
9777 let status = b.run(&[b"BACKUP", b"STATUS"]);
9778 assert!(status.contains("failed"), "{status}");
9779 assert!(status.contains("aborted by user"), "{status}");
9780 assert!(b.files().is_empty(), "abort left the base file behind");
9781 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
9782
9783 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
9785 assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
9786 let status = b.run(&[b"BACKUP", b"STATUS"]);
9787 assert!(status.contains("incrementing"), "{status}");
9788 assert!(!status.contains("aborted"), "the old error was kept");
9789 }
9790
9791 #[test]
9793 fn backup_list_names_the_files_that_are_pinned_so_far() {
9794 let mut b = Backups::new("list");
9795 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
9796
9797 b.run(&[b"BACKUP", b"START"]);
9798 let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
9799 let base = base.to_string_lossy().into_owned();
9800 assert_eq!(
9801 b.run(&[b"BACKUP", b"LIST"]),
9802 format!("*1\r\n${}\r\n{base}\r\n", base.len())
9803 );
9804
9805 b.run(&[b"BACKUP", b"SEAL"]);
9806 let listed = b.run(&[b"BACKUP", b"LIST"]);
9807 assert!(listed.starts_with("*3\r\n"), "{listed}");
9808 let names: Vec<&str> = listed
9811 .lines()
9812 .filter(|l| l.starts_with('/') || l.contains(":\\"))
9813 .collect();
9814 assert_eq!(names.len(), 3, "{listed}");
9815 assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
9816 assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
9817 assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
9818 }
9819
9820 #[test]
9828 fn a_backup_holds_the_dataset_as_it_was_at_start() {
9829 let mut b = Backups::new("contents");
9830 b.run(&[b"SET", b"bk", b"v1"]);
9831 b.run(&[b"BACKUP", b"START"]);
9832 b.run(&[b"SET", b"bk", b"v2"]);
9833 b.run(&[b"BACKUP", b"SEAL"]);
9834
9835 let base = b.read("appendonly.aof.1.base.rdb");
9836 assert!(base.starts_with(b"REDIS"), "not an RDB file");
9837 assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
9838 assert!(
9839 !base.windows(2).any(|w| w == b"v2"),
9840 "the base file moved on after START"
9841 );
9842 let at = base
9847 .windows(8)
9848 .position(|w| w == b"aof-base")
9849 .expect("no aof-base aux field");
9850 assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
9851
9852 assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
9853 assert_eq!(
9854 String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
9855 "file appendonly.aof.1.base.rdb seq 1 type b\n\
9856 file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
9857 );
9858 }
9859
9860 #[test]
9863 fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
9864 let mut b = Backups::new("status");
9865 b.f.server.set_clock_ms(1_700_000_000_000);
9866
9867 assert_eq!(
9868 b.run(&[b"BACKUP", b"STATUS"]),
9869 "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
9870 $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
9871 );
9872
9873 b.f.out = Out::new(Proto::Resp3);
9874 b.run(&[b"BACKUP", b"START"]);
9875 assert_eq!(
9876 b.run(&[b"BACKUP", b"STATUS"]),
9877 "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
9878 $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
9879 );
9880
9881 b.run(&[b"BACKUP", b"SEAL"]);
9882 let sealed = b.run(&[b"BACKUP", b"STATUS"]);
9883 assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
9884 }
9885
9886 #[test]
9889 fn a_sealed_backup_is_swept_away_after_the_timeout() {
9890 let mut b = Backups::new("ttl");
9891 b.f.server.set_clock_ms(1_000_000);
9892 assert_eq!(
9893 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
9894 "+OK\r\n"
9895 );
9896 b.run(&[b"BACKUP", b"START"]);
9897 b.run(&[b"BACKUP", b"SEAL"]);
9898
9899 b.f.server.set_clock_ms(1_000_000 + 59_000);
9901 b.f.server.backup_expire();
9902 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
9903 assert_eq!(b.files().len(), 3);
9904
9905 b.f.server.set_clock_ms(1_000_000 + 60_000);
9906 b.f.server.backup_expire();
9907 let status = b.run(&[b"BACKUP", b"STATUS"]);
9908 assert!(status.contains("idle"), "{status}");
9909 assert!(b.files().is_empty(), "the timeout left the files behind");
9910
9911 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
9913 b.run(&[b"BACKUP", b"START"]);
9914 b.run(&[b"BACKUP", b"SEAL"]);
9915 b.f.server.set_clock_ms(9_000_000_000);
9916 b.f.server.backup_expire();
9917 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
9918 }
9919
9920 #[test]
9923 fn the_backup_settings_behave_the_way_the_reference_does() {
9924 let mut b = Backups::new("config");
9925 let dir = b.dir.to_string_lossy().into_owned();
9926
9927 assert_eq!(
9928 b.run(&[b"CONFIG", b"GET", b"dir"]),
9929 format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
9930 );
9931 assert_eq!(
9932 b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
9933 "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
9934 );
9935 assert_eq!(
9936 b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
9937 "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
9938 );
9939
9940 assert_eq!(
9943 b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
9944 "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
9945 );
9946 assert_eq!(
9947 b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
9948 "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
9949 );
9950 assert!(
9951 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
9952 .contains("argument couldn't be parsed into an integer")
9953 );
9954 assert!(
9955 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
9956 .contains("argument must be between 0 and 9223372036854775807 inclusive")
9957 );
9958 }
9959
9960 #[test]
9962 fn backup_help_is_the_text_the_reference_sends() {
9963 let mut f = Fixture::new();
9964 let help = f.run(&[b"BACKUP", b"HELP"]);
9965 assert!(help.starts_with("*17\r\n"), "{help}");
9966 assert!(
9967 help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
9968 );
9969 assert!(help.contains("+ Start a new backup into the configured 'backupdirname'.\r\n"));
9970 assert!(help.contains("+ Freeze the current backup (BASE + INCR + manifest).\r\n"));
9971 assert!(help.contains("+ Return this help.\r\n+HELP\r\n+ Print this help.\r\n"));
9972 }
9973
9974 #[test]
9982 fn backup_refuses_what_it_cannot_read() {
9983 let mut f = Fixture::new();
9984 assert_eq!(
9985 f.run(&[b"BACKUP"]),
9986 "-ERR wrong number of arguments for 'backup' command\r\n"
9987 );
9988 assert_eq!(
9989 f.run(&[b"BACKUP", b"START", b"x"]),
9990 "-ERR wrong number of arguments for 'backup' command\r\n"
9991 );
9992 assert_eq!(
9993 f.run(&[b"BACKUP", b"NOPE"]),
9994 "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
9995 );
9996 }
9997
9998 struct Saves {
10004 f: Fixture,
10005 dir: PathBuf,
10006 }
10007
10008 impl Saves {
10009 fn new(name: &str) -> Saves {
10010 let dir = std::env::temp_dir().join(format!("yo-save-{name}-{}", std::process::id()));
10011 let _ = std::fs::remove_dir_all(&dir);
10012 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
10013 let mut f = Fixture::new();
10014 f.server.set_dir(dir.clone());
10015 Saves { f, dir }
10016 }
10017
10018 fn run(&mut self, parts: &[&[u8]]) -> String {
10019 self.f.run(parts)
10020 }
10021
10022 fn files(&self) -> Vec<String> {
10024 let mut names: Vec<String> = match std::fs::read_dir(&self.dir) {
10025 Ok(entries) => entries
10026 .filter_map(|e| e.ok())
10027 .map(|e| e.file_name().to_string_lossy().into_owned())
10028 .collect(),
10029 Err(_) => Vec::new(),
10030 };
10031 names.sort();
10032 names
10033 }
10034
10035 fn image(&self) -> Vec<u8> {
10036 std::fs::read(self.dir.join("dump.rdb")).expect("could not read the file")
10037 }
10038
10039 fn field(&mut self, name: &str) -> String {
10041 let text = self.run(&[b"INFO", b"persistence"]);
10042 let head = format!("\r\n{name}:");
10043 let at = text.find(&head).expect("the field is not in the section");
10044 let rest = &text[at + head.len()..];
10045 rest[..rest.find("\r\n").expect("the field has no end")].to_owned()
10046 }
10047 }
10048
10049 impl Drop for Saves {
10050 fn drop(&mut self) {
10051 let _ = std::fs::remove_dir_all(&self.dir);
10052 }
10053 }
10054
10055 #[test]
10056 fn save_writes_a_file_that_carries_the_dataset() {
10057 let mut s = Saves::new("writes");
10058 s.run(&[b"SET", b"k", b"v"]);
10059 s.run(&[b"RPUSH", b"l", b"a", b"b"]);
10060 assert!(
10061 s.files().is_empty(),
10062 "a server has saved without being asked"
10063 );
10064
10065 assert_eq!(s.run(&[b"SAVE"]), "+OK\r\n");
10066 assert_eq!(s.files(), ["dump.rdb"]);
10067
10068 let image = s.image();
10073 assert!(
10074 image.starts_with(b"REDIS00"),
10075 "the header is not an RDB one"
10076 );
10077 assert!(
10078 image.windows(1).any(|w| w == [0xFF]),
10079 "there is no end marker"
10080 );
10081 assert!(image.len() > 40, "the file is too small to hold anything");
10082 }
10083
10084 #[test]
10085 fn a_save_leaves_no_temporary_file_behind() {
10086 let mut s = Saves::new("temp");
10087 s.run(&[b"SET", b"k", b"v"]);
10088 s.run(&[b"SAVE"]);
10089 s.run(&[b"BGSAVE"]);
10090 assert_eq!(s.files(), ["dump.rdb"]);
10091 }
10092
10093 #[test]
10094 fn a_save_that_cannot_write_says_so_in_one_word() {
10095 let mut s = Saves::new("nowhere");
10096 s.f.server.set_dir(s.dir.join("gone"));
10099 assert_eq!(s.run(&[b"SAVE"]), "-ERR\r\n");
10100 assert_eq!(s.field("rdb_last_bgsave_status"), "err");
10101 assert_eq!(s.field("rdb_saves"), "1");
10103 }
10104
10105 #[test]
10106 fn lastsave_starts_at_the_time_the_server_did_and_moves_on_a_save() {
10107 let mut s = Saves::new("lastsave");
10108 let started = s.run(&[b"LASTSAVE"]);
10109 assert_eq!(started, format!(":{}\r\n", s.f.server.started_ms / 1_000));
10110
10111 s.f.server.set_clock_ms(s.f.server.started_ms + 5_000);
10112 s.run(&[b"SAVE"]);
10113 let after = s.run(&[b"LASTSAVE"]);
10114 assert_eq!(after, format!(":{}\r\n", s.f.server.started_ms / 1_000 + 5));
10115
10116 s.run(&[b"SET", b"k", b"v"]);
10118 assert_eq!(s.run(&[b"LASTSAVE"]), after);
10119 }
10120
10121 #[test]
10122 fn bgsave_takes_the_one_word_it_takes_and_nothing_else() {
10123 let mut s = Saves::new("bgsave");
10124 for parts in [
10125 &[b"BGSAVE".as_slice()][..],
10126 &[b"BGSAVE", b"SCHEDULE"],
10127 &[b"BGSAVE", b"schedule"],
10128 ] {
10129 assert_eq!(s.run(parts), "+Background saving started\r\n");
10130 }
10131 for parts in [
10132 &[b"BGSAVE".as_slice(), b"x"][..],
10133 &[b"BGSAVE", b"SCHEDULE", b"x"],
10134 &[b"BGSAVE", b"SCHEDULE", b"SCHEDULE"],
10135 ] {
10136 assert_eq!(s.run(parts), "-ERR syntax error\r\n");
10137 }
10138 }
10139
10140 #[test]
10141 fn a_save_inside_a_transaction_says_it_was_scheduled() {
10142 let mut s = Saves::new("queued");
10143 assert_eq!(s.run(&[b"MULTI"]), "+OK\r\n");
10145 assert_eq!(
10146 s.run(&[b"SAVE"]),
10147 "-ERR Command not allowed inside a transaction\r\n"
10148 );
10149 assert_eq!(
10150 s.run(&[b"EXEC"]),
10151 "-EXECABORT Transaction discarded because of previous errors.\r\n"
10152 );
10153
10154 assert_eq!(s.run(&[b"MULTI"]), "+OK\r\n");
10155 assert_eq!(s.run(&[b"BGSAVE"]), "+QUEUED\r\n");
10156 assert_eq!(s.run(&[b"BGREWRITEAOF"]), "+QUEUED\r\n");
10157 assert_eq!(
10158 s.run(&[b"EXEC"]),
10159 "*2\r\n+Background saving scheduled\r\n\
10160 +Background append only file rewriting scheduled\r\n"
10161 );
10162 assert_eq!(s.files(), ["dump.rdb"]);
10164 }
10165
10166 #[test]
10167 fn a_rewrite_counts_itself_and_writes_nothing() {
10168 let mut s = Saves::new("rewrite");
10169 assert_eq!(
10170 s.run(&[b"BGREWRITEAOF"]),
10171 "+Background append only file rewriting started\r\n"
10172 );
10173 assert_eq!(s.field("aof_rewrites"), "1");
10174 assert_eq!(s.field("aof_enabled"), "0");
10175 assert!(s.files().is_empty(), "a rewrite has written a file");
10176 }
10177
10178 #[test]
10179 fn role_says_master_with_nothing_following_it() {
10180 let mut f = Fixture::new();
10181 assert_eq!(f.run(&[b"ROLE"]), "*3\r\n$6\r\nmaster\r\n:0\r\n*0\r\n");
10182 assert_eq!(
10183 f.run(&[b"ROLE", b"x"]),
10184 "-ERR wrong number of arguments for 'role' command\r\n"
10185 );
10186 }
10187
10188 #[test]
10189 fn the_persistence_section_counts_the_saves_that_were_asked_for() {
10190 let mut s = Saves::new("counts");
10191 assert_eq!(s.field("rdb_saves"), "0");
10192 assert_eq!(s.field("rdb_last_bgsave_status"), "ok");
10193 s.run(&[b"SAVE"]);
10194 s.run(&[b"BGSAVE"]);
10195 s.run(&[b"BGSAVE", b"SCHEDULE"]);
10196 assert_eq!(s.field("rdb_saves"), "3");
10197 assert_eq!(s.field("rdb_bgsave_in_progress"), "0");
10198 assert_eq!(s.field("loading"), "0");
10199 }
10200
10201 #[test]
10202 fn the_persistence_section_is_in_a_bare_info_and_not_in_another_one() {
10203 let mut f = Fixture::new();
10204 assert!(f.run(&[b"INFO"]).contains("# Persistence"));
10205 assert!(f.run(&[b"INFO", b"persistence"]).contains("# Persistence"));
10206 assert!(f.run(&[b"INFO", b"all"]).contains("# Persistence"));
10207 assert!(!f.run(&[b"INFO", b"clients"]).contains("# Persistence"));
10208 }
10209
10210 #[test]
10211 fn the_file_name_reads_back_and_cannot_be_written() {
10212 let mut f = Fixture::new();
10213 assert_eq!(
10214 f.run(&[b"CONFIG", b"GET", b"dbfilename"]),
10215 "*2\r\n$10\r\ndbfilename\r\n$8\r\ndump.rdb\r\n"
10216 );
10217 assert_eq!(
10218 f.run(&[b"CONFIG", b"SET", b"dbfilename", b"other.rdb"]),
10219 "-ERR CONFIG SET failed (possibly related to argument 'dbfilename') - can't set protected config\r\n"
10220 );
10221 assert_eq!(
10224 f.run(&[b"CONFIG", b"SET", b"dbfilename", b"dump.rdb"]),
10225 "-ERR CONFIG SET failed (possibly related to argument 'dbfilename') - can't set protected config\r\n"
10226 );
10227 }
10228
10229 #[test]
10230 fn shutdown_save_writes_the_file_and_shutdown_on_its_own_does_not() {
10231 let mut s = Saves::new("shutdown");
10232 s.run(&[b"SET", b"k", b"v"]);
10233 s.run(&[b"SHUTDOWN", b"NOSAVE"]);
10234 assert!(s.files().is_empty(), "a nosave shutdown wrote a file");
10235
10236 let mut s = Saves::new("shutdown-save");
10237 s.run(&[b"SET", b"k", b"v"]);
10238 s.run(&[b"SHUTDOWN", b"SAVE"]);
10239 assert_eq!(s.files(), ["dump.rdb"]);
10240 }
10241
10242 #[test]
10248 fn debug_reload_brings_every_type_back_the_way_it_went_in() {
10249 let mut s = Saves::new("reload-types");
10250 s.run(&[b"SET", b"str", b"hello"]);
10251 s.run(&[b"SET", b"num", b"1234"]);
10252 s.run(&[b"RPUSH", b"list", b"a", b"b", b"c"]);
10253 s.run(&[b"SADD", b"set", b"x", b"y"]);
10254 s.run(&[b"SADD", b"ints", b"1", b"2", b"3"]);
10255 s.run(&[b"HSET", b"hash", b"f", b"v", b"g", b"w"]);
10256 s.run(&[b"ZADD", b"zset", b"1.5", b"m", b"2", b"n"]);
10257 s.run(&[b"XADD", b"stream", b"1-1", b"f", b"v"]);
10258 s.run(&[b"PEXPIREAT", b"str", b"4102444800000"]);
10259 let before = s.run(&[b"DBSIZE"]);
10260
10261 assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "+OK\r\n");
10262
10263 assert_eq!(s.run(&[b"DBSIZE"]), before);
10264 assert_eq!(s.run(&[b"GET", b"str"]), "$5\r\nhello\r\n");
10265 assert_eq!(s.run(&[b"GET", b"num"]), "$4\r\n1234\r\n");
10266 assert_eq!(
10267 s.run(&[b"LRANGE", b"list", b"0", b"-1"]),
10268 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10269 );
10270 assert_eq!(s.run(&[b"SCARD", b"set"]), ":2\r\n");
10271 assert_eq!(s.run(&[b"SISMEMBER", b"set", b"y"]), ":1\r\n");
10272 assert_eq!(s.run(&[b"SCARD", b"ints"]), ":3\r\n");
10273 assert_eq!(s.run(&[b"HGET", b"hash", b"g"]), "$1\r\nw\r\n");
10274 assert_eq!(s.run(&[b"ZSCORE", b"zset", b"m"]), "$3\r\n1.5\r\n");
10275 assert_eq!(s.run(&[b"XLEN", b"stream"]), ":1\r\n");
10276 assert_eq!(s.run(&[b"PEXPIRETIME", b"str"]), ":4102444800000\r\n");
10279 assert_eq!(s.run(&[b"PEXPIRETIME", b"num"]), ":-1\r\n");
10280 }
10281
10282 #[test]
10284 fn debug_reload_puts_every_key_back_in_its_own_database() {
10285 let mut s = Saves::new("reload-dbs");
10286 s.run(&[b"SET", b"home", b"zero"]);
10287 s.run(&[b"SELECT", b"9"]);
10288 s.run(&[b"SET", b"away", b"nine"]);
10289 s.run(&[b"SELECT", b"0"]);
10290
10291 assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "+OK\r\n");
10292
10293 assert_eq!(s.run(&[b"GET", b"home"]), "$4\r\nzero\r\n");
10294 assert_eq!(s.run(&[b"EXISTS", b"away"]), ":0\r\n");
10295 s.run(&[b"SELECT", b"9"]);
10296 assert_eq!(s.run(&[b"GET", b"away"]), "$4\r\nnine\r\n");
10297 assert_eq!(s.run(&[b"EXISTS", b"home"]), ":0\r\n");
10298 }
10299
10300 #[test]
10302 fn debug_reload_nosave_reads_the_file_that_is_already_there() {
10303 let mut s = Saves::new("reload-nosave");
10304 s.run(&[b"SET", b"k", b"first"]);
10305 s.run(&[b"SAVE"]);
10306 s.run(&[b"SET", b"k", b"second"]);
10307 s.run(&[b"SET", b"later", b"x"]);
10308
10309 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), "+OK\r\n");
10310
10311 assert_eq!(s.run(&[b"GET", b"k"]), "$5\r\nfirst\r\n");
10313 assert_eq!(s.run(&[b"EXISTS", b"later"]), ":0\r\n");
10314 }
10315
10316 #[test]
10318 fn debug_reload_noflush_keeps_what_the_file_does_not_mention() {
10319 let mut s = Saves::new("reload-noflush");
10320 s.run(&[b"SET", b"k", b"first"]);
10321 s.run(&[b"SAVE"]);
10322 s.run(&[b"SET", b"k", b"second"]);
10323 s.run(&[b"SET", b"later", b"x"]);
10324
10325 assert_eq!(
10326 s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE", b"NOFLUSH"]),
10327 "+OK\r\n"
10328 );
10329
10330 assert_eq!(s.run(&[b"GET", b"k"]), "$5\r\nfirst\r\n");
10334 assert_eq!(s.run(&[b"GET", b"later"]), "$1\r\nx\r\n");
10335 }
10336
10337 #[test]
10339 fn debug_reload_takes_its_three_words_and_no_others() {
10340 let mut s = Saves::new("reload-words");
10341 s.run(&[b"SET", b"k", b"v"]);
10342 for parts in [
10343 &[b"DEBUG".as_slice(), b"RELOAD"][..],
10344 &[b"DEBUG", b"RELOAD", b"NOSAVE"],
10345 &[b"DEBUG", b"RELOAD", b"nosave"],
10346 &[b"DEBUG", b"RELOAD", b"MERGE"],
10347 &[b"DEBUG", b"RELOAD", b"NOFLUSH"],
10348 &[b"DEBUG", b"RELOAD", b"MERGE", b"NOFLUSH", b"NOSAVE"],
10349 &[b"DEBUG", b"RELOAD", b"NOSAVE", b"NOSAVE"],
10351 ] {
10352 assert_eq!(s.run(parts), "+OK\r\n", "{parts:?}");
10353 }
10354 for parts in [
10355 &[b"DEBUG".as_slice(), b"RELOAD", b"BOGUS"][..],
10356 &[b"DEBUG", b"RELOAD", b"NOSAVE", b"BOGUS"],
10357 &[b"DEBUG", b"RELOAD", b""],
10358 ] {
10359 assert_eq!(
10360 s.run(parts),
10361 "-ERR DEBUG RELOAD only supports the MERGE, NOFLUSH and NOSAVE options.\r\n",
10362 "{parts:?}"
10363 );
10364 }
10365 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10367 }
10368
10369 #[test]
10371 fn debug_reload_that_cannot_write_the_file_says_so_in_one_word() {
10372 let mut s = Saves::new("reload-nowhere");
10373 s.run(&[b"SET", b"k", b"v"]);
10374 s.f.server.set_dir(s.dir.join("gone"));
10375 assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "-ERR\r\n");
10376 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10378 }
10379
10380 #[test]
10386 fn debug_reload_that_cannot_read_the_file_says_to_check_the_log() {
10387 let mut s = Saves::new("reload-unreadable");
10388 s.run(&[b"SET", b"k", b"v"]);
10389 let failed = "-ERR Error trying to load the RDB dump, check server logs.\r\n";
10390 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), failed);
10391 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10393
10394 s.run(&[b"SAVE"]);
10395 std::fs::write(s.dir.join("dump.rdb"), b"not an RDB file at all")
10396 .expect("could not write over the file");
10397 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), failed);
10398 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10399 }
10400
10401 #[test]
10408 fn debug_reload_refuses_to_drop_a_key_with_no_rdb_form() {
10409 let mut s = Saves::new("reload-foreign");
10410 s.run(&[b"SET", b"k", b"v"]);
10411 s.run(&[b"TS.CREATE", b"ts"]);
10412 s.run(&[b"TS.ADD", b"ts", b"1000", b"1.5"]);
10413
10414 assert_eq!(
10415 s.run(&[b"DEBUG", b"RELOAD"]),
10416 "-ERR DEBUG RELOAD would drop 1 key with no RDB form, use NOFLUSH to keep it\r\n"
10417 );
10418 assert_eq!(s.run(&[b"EXISTS", b"ts"]), ":1\r\n");
10419
10420 s.run(&[b"TS.CREATE", b"ts2"]);
10421 assert_eq!(
10422 s.run(&[b"DEBUG", b"RELOAD"]),
10423 "-ERR DEBUG RELOAD would drop 2 keys with no RDB form, use NOFLUSH to keep them\r\n"
10424 );
10425
10426 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOFLUSH"]), "+OK\r\n");
10428 assert_eq!(s.run(&[b"EXISTS", b"ts"]), ":1\r\n");
10429 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10430 assert_eq!(s.run(&[b"TS.GET", b"ts"]), "*2\r\n:1000\r\n+1.5\r\n");
10431 }
10432
10433 #[test]
10435 fn debug_reload_drops_a_key_whose_deadline_went_by() {
10436 let mut s = Saves::new("reload-expired");
10437 s.run(&[b"SET", b"gone", b"v"]);
10438 s.run(&[b"SET", b"stays", b"v"]);
10439 s.run(&[b"PEXPIREAT", b"gone", b"4102444800000"]);
10440 s.run(&[b"SAVE"]);
10441 s.f.server.set_clock_ms(4_102_444_800_001);
10442
10443 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), "+OK\r\n");
10444
10445 assert_eq!(s.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10446 assert_eq!(s.run(&[b"GET", b"stays"]), "$1\r\nv\r\n");
10447 }
10448
10449 #[test]
10458 fn a_file_one_server_wrote_loads_into_a_server_that_has_never_seen_it() {
10459 let mut wrote = Saves::new("restore-across");
10460 wrote.run(&[b"SET", b"s", b"hello"]);
10461 wrote.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10462 wrote.run(&[b"HSET", b"h", b"f", b"v"]);
10463 wrote.run(&[b"ZADD", b"z", b"1.5", b"m"]);
10464 wrote.run(&[b"SELECT", b"7"]);
10465 wrote.run(&[b"SADD", b"far", b"x"]);
10466 assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10467
10468 let mut fresh = Fixture::new();
10469 let done = fresh
10470 .server
10471 .load_image(&wrote.image(), true)
10472 .expect("the file one server wrote is a file another can read");
10473 assert_eq!(done.keys[0], 4);
10474 assert_eq!(done.keys[7], 1);
10475 assert_eq!(done.total(), 5);
10476 assert_eq!(done.expired, 0);
10477
10478 assert_eq!(fresh.run(&[b"GET", b"s"]), "$5\r\nhello\r\n");
10479 assert_eq!(
10480 fresh.run(&[b"LRANGE", b"l", b"0", b"-1"]),
10481 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10482 );
10483 assert_eq!(fresh.run(&[b"HGET", b"h", b"f"]), "$1\r\nv\r\n");
10484 assert_eq!(fresh.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n1.5\r\n");
10485 fresh.run(&[b"SELECT", b"7"]);
10486 assert_eq!(fresh.run(&[b"SMEMBERS", b"far"]), "*1\r\n$1\r\nx\r\n");
10487 }
10488
10489 #[test]
10492 fn a_load_reports_what_it_kept_and_what_had_already_died() {
10493 let mut wrote = Saves::new("restore-counts");
10494 wrote.run(&[b"SET", b"gone", b"v"]);
10495 wrote.run(&[b"SET", b"stays", b"v"]);
10496 wrote.run(&[b"PEXPIREAT", b"gone", b"4102444800000"]);
10497 assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10498
10499 let mut fresh = Saves::new("restore-counts-into");
10500 fresh.f.server.set_clock_ms(4_102_444_800_001);
10501 let done = fresh
10502 .f
10503 .server
10504 .load_image(&wrote.image(), true)
10505 .expect("a file with a dead key in it is still a good file");
10506 assert_eq!(done.total(), 1);
10507 assert_eq!(done.expired, 1);
10508
10509 assert_eq!(fresh.field("rdb_last_load_keys_loaded"), "1");
10510 assert_eq!(fresh.field("rdb_last_load_keys_expired"), "1");
10511 assert_eq!(fresh.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10512 assert_eq!(fresh.run(&[b"GET", b"stays"]), "$1\r\nv\r\n");
10513 }
10514
10515 #[test]
10518 fn a_server_that_has_loaded_nothing_says_so() {
10519 let mut s = Saves::new("restore-never");
10520 assert_eq!(s.field("rdb_last_load_keys_loaded"), "0");
10521 assert_eq!(s.field("rdb_last_load_keys_expired"), "0");
10522 }
10523
10524 #[test]
10529 fn a_file_that_is_not_an_rdb_is_refused_with_the_dataset_still_there() {
10530 let mut f = Fixture::new();
10531 f.run(&[b"SET", b"k", b"v"]);
10532 let refused = f
10533 .server
10534 .load_image(b"this is not a Redis dump at all, not even close", true)
10535 .expect_err("that is not an RDB");
10536 assert_eq!(refused.to_string(), "the file does not start with REDIS");
10537 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10538 }
10539
10540 #[test]
10544 fn a_damaged_file_is_refused_with_the_dataset_still_there() {
10545 let mut wrote = Saves::new("restore-damaged");
10546 wrote.run(&[b"SET", b"a", b"b"]);
10547 assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10548 let mut image = wrote.image();
10549 let middle = image.len() / 2;
10552 image[middle] ^= 0xff;
10553
10554 let mut f = Fixture::new();
10555 f.run(&[b"SET", b"k", b"v"]);
10556 let refused = f
10557 .server
10558 .load_image(&image, true)
10559 .expect_err("the checksum does not match");
10560 assert_eq!(
10561 refused.to_string(),
10562 "the checksum does not match, so the file is damaged"
10563 );
10564 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10565 }
10566
10567 #[test]
10572 fn debug_object_describes_how_a_value_is_written_down() {
10573 let mut f = Fixture::new();
10574 f.run(&[b"SET", b"s", b"hello"]);
10575
10576 let line = f.run(&[b"DEBUG", b"OBJECT", b"s"]);
10577 let line = line
10578 .strip_prefix('+')
10579 .and_then(|l| l.strip_suffix("\r\n"))
10580 .expect("a simple string");
10581 let mut fields = line.split(' ');
10582 assert_eq!(fields.next(), Some("Value"));
10583 assert!(
10584 fields.next().expect("an address").starts_with("at:0x"),
10585 "{line}"
10586 );
10587 assert_eq!(fields.next(), Some("refcount:1"));
10588 assert_eq!(fields.next(), Some("encoding:embstr"));
10589 assert_eq!(fields.next(), Some("serializedlength:6"));
10593 assert!(
10594 fields.next().expect("a clock").starts_with("lru:"),
10595 "{line}"
10596 );
10597 assert_eq!(fields.next(), Some("lru_seconds_idle:0"));
10598 assert_eq!(fields.next(), None);
10599 }
10600
10601 #[test]
10603 fn debug_object_counts_the_nodes_a_list_broke_into() {
10604 let mut f = Fixture::new();
10605 let member = vec![b'x'; 200];
10608 for _ in 0..100 {
10609 f.run(&[b"RPUSH", b"l", &member]);
10610 }
10611 assert_eq!(
10612 f.run(&[b"OBJECT", b"ENCODING", b"l"]),
10613 "$9\r\nquicklist\r\n"
10614 );
10615
10616 let line = f.run(&[b"DEBUG", b"OBJECT", b"l"]);
10617 let nodes: usize = field(&line, "ql_nodes:").parse().expect("a count");
10618 assert!(nodes > 1, "{line}");
10619 let avg: f64 = field(&line, "ql_avg_node:").parse().expect("an average");
10620 assert!((avg - 100.0 / nodes as f64).abs() < 0.01, "{line}");
10621 assert_eq!(field(&line, "ql_listpack_max:"), "-2");
10622 assert_eq!(field(&line, "ql_compressed:"), "0");
10623 let bytes: usize = field(&line, "ql_uncompressed_size:")
10624 .parse()
10625 .expect("a size");
10626 assert!(bytes > 100 * 200, "{line}");
10627
10628 f.run(&[b"RPUSH", b"small", b"a"]);
10630 let line = f.run(&[b"DEBUG", b"OBJECT", b"small"]);
10631 assert!(!line.contains("ql_nodes"), "{line}");
10632 }
10633
10634 #[test]
10638 fn debug_object_does_not_count_as_using_the_key() {
10639 let mut f = Fixture::new();
10640 f.run(&[b"SET", b"s", b"hello"]);
10641 let was = field(&f.run(&[b"DEBUG", b"OBJECT", b"s"]), "lru:").to_owned();
10642
10643 f.server.set_clock_ms(f.server.clock.now_ms() + 60_000);
10644 let line = f.run(&[b"DEBUG", b"OBJECT", b"s"]);
10645
10646 assert_eq!(field(&line, "lru_seconds_idle:"), "60");
10647 assert_eq!(field(&line, "lru:"), was);
10650 }
10651
10652 #[test]
10655 fn debug_sdslen_measures_the_name_and_the_string_under_it() {
10656 let mut f = Fixture::new();
10657 f.run(&[b"SET", b"name", b"hello"]);
10658
10659 assert_eq!(
10660 f.run(&[b"DEBUG", b"SDSLEN", b"name"]),
10661 "+key_sds_len:4, key_sds_avail:0, key_zmalloc: 4, \
10662 val_sds_len:5, val_sds_avail:0, val_zmalloc: 5\r\n"
10663 );
10664 }
10665
10666 #[test]
10668 fn the_inspecting_subcommands_refuse_what_they_cannot_describe() {
10669 let mut f = Fixture::new();
10670 f.run(&[b"SET", b"s", b"hello"]);
10671 f.run(&[b"SET", b"n", b"12345"]);
10672 f.run(&[b"RPUSH", b"l", b"a"]);
10673
10674 for sub in [
10677 b"OBJECT".as_slice(),
10678 b"SDSLEN".as_slice(),
10679 b"LISTPACK".as_slice(),
10680 b"QUICKLIST".as_slice(),
10681 ] {
10682 assert_eq!(
10683 f.run(&[b"DEBUG", sub, b"nosuch"]),
10684 "-ERR no such key\r\n",
10685 "{}",
10686 String::from_utf8_lossy(sub)
10687 );
10688 }
10689
10690 assert_eq!(
10692 f.run(&[b"DEBUG", b"SDSLEN", b"n"]),
10693 "-ERR Not an sds encoded string.\r\n"
10694 );
10695 assert_eq!(
10696 f.run(&[b"DEBUG", b"SDSLEN", b"l"]),
10697 "-ERR Not an sds encoded string.\r\n"
10698 );
10699
10700 assert_eq!(
10703 f.run(&[b"DEBUG", b"LISTPACK", b"l"]),
10704 "+Listpack structure printed on stdout\r\n"
10705 );
10706 assert_eq!(
10707 f.run(&[b"DEBUG", b"QUICKLIST", b"l"]),
10708 "-ERR Not a quicklist encoded object.\r\n"
10709 );
10710 assert_eq!(
10711 f.run(&[b"DEBUG", b"LISTPACK", b"s"]),
10712 "-ERR Not a listpack encoded object.\r\n"
10713 );
10714 }
10715
10716 #[test]
10719 fn debug_listpack_answers_for_anything_written_as_one() {
10720 let mut f = Fixture::new();
10721 f.run(&[b"RPUSH", b"l", b"a"]);
10722 f.run(&[b"HSET", b"h", b"f", b"v"]);
10723 f.run(&[b"SADD", b"st", b"a"]);
10724 f.run(&[b"ZADD", b"z", b"1", b"m"]);
10725 f.run(&[b"SADD", b"ints", b"1", b"2"]);
10726
10727 for key in [b"l".as_slice(), b"h", b"st", b"z"] {
10728 assert_eq!(
10729 f.run(&[b"DEBUG", b"LISTPACK", key]),
10730 "+Listpack structure printed on stdout\r\n",
10731 "{}",
10732 String::from_utf8_lossy(key)
10733 );
10734 }
10735 assert_eq!(
10736 f.run(&[b"OBJECT", b"ENCODING", b"ints"]),
10737 "$6\r\nintset\r\n"
10738 );
10739 assert_eq!(
10740 f.run(&[b"DEBUG", b"LISTPACK", b"ints"]),
10741 "-ERR Not a listpack encoded object.\r\n"
10742 );
10743 }
10744
10745 #[test]
10748 fn debug_quicklist_takes_a_level_it_does_nothing_with() {
10749 let mut f = Fixture::new();
10750 let member = vec![b'x'; 200];
10751 for _ in 0..100 {
10752 f.run(&[b"RPUSH", b"l", &member]);
10753 }
10754
10755 let said = "+Quicklist structure printed on stdout\r\n";
10756 assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l"]), said);
10757 assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"1"]), said);
10758 assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"abc"]), said);
10761
10762 assert_eq!(
10763 f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"1", b"2"]),
10764 "-ERR unknown subcommand or wrong number of arguments for \
10765 'QUICKLIST'. Try DEBUG HELP.\r\n"
10766 );
10767 assert_eq!(
10768 f.run(&[b"DEBUG", b"LISTPACK", b"l", b"0"]),
10769 "-ERR unknown subcommand or wrong number of arguments for \
10770 'LISTPACK'. Try DEBUG HELP.\r\n"
10771 );
10772 }
10773
10774 #[test]
10779 fn a_value_digest_is_the_number_the_reference_computes() {
10780 let mut f = Fixture::new();
10781 f.run(&[b"SET", b"s", b"hello"]);
10782 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10783 f.run(&[b"SADD", b"t", b"a", b"b", b"c"]);
10784 f.run(&[b"HSET", b"h", b"f", b"v"]);
10785 f.run(&[b"ZADD", b"z", b"1", b"a", b"2.5", b"b"]);
10786 f.run(&[b"XADD", b"x", b"1-1", b"f", b"v"]);
10787
10788 for (key, want) in [
10789 (&b"s"[..], "36b23a1456b2dce2c3ed252c456761301dba8060"),
10790 (b"l", "8bf72d812571eea9b927f3c11beb0c4165a6ff89"),
10791 (b"t", "593c2414786d75446e97f4ea5d4b731f3313da72"),
10792 (b"h", "90c76e9e9f4c62d642a34fc97c7dad503b51f906"),
10793 (b"z", "c45c5b051acd64070e5ed1a949939d5145f806c5"),
10794 (b"x", "2ed9a7a81688084b1f7eae33456ef7727d357031"),
10795 ] {
10796 assert_eq!(
10797 f.run(&[b"DEBUG", b"DIGEST-VALUE", key]),
10798 format!("*1\r\n+{want}\r\n"),
10799 "{}",
10800 String::from_utf8_lossy(key)
10801 );
10802 }
10803 }
10804
10805 #[test]
10808 fn a_deadline_shows_up_without_the_time_left_showing_up() {
10809 let mut f = Fixture::new();
10810 f.run(&[b"SET", b"e", b"value"]);
10811 let bare = "*1\r\n+d59ec93db87f4cd915db3cdf44bb63755bc4a635\r\n";
10812 let dated = "*1\r\n+331b37c26446a68dd4cdd72701d1acee416ae7b6\r\n";
10813 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), bare);
10814
10815 f.run(&[b"EXPIRE", b"e", b"1000"]);
10816 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), dated);
10817 f.run(&[b"EXPIRE", b"e", b"999999"]);
10819 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), dated);
10820 f.run(&[b"PERSIST", b"e"]);
10821 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), bare);
10822
10823 f.run(&[b"HSET", b"he", b"f1", b"v1", b"f2", b"v2"]);
10826 f.run(&[b"HEXPIRE", b"he", b"1000", b"FIELDS", b"1", b"f2"]);
10827 assert_eq!(
10828 f.run(&[b"DEBUG", b"DIGEST-VALUE", b"he"]),
10829 "*1\r\n+8911d6d4d198f5e022f80dfefd5e15d6c0eaabe3\r\n"
10830 );
10831 }
10832
10833 #[test]
10836 fn a_value_digest_does_not_know_what_the_key_is_called() {
10837 let mut f = Fixture::new();
10838 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10839 f.run(&[b"COPY", b"l", b"l2"]);
10840 let want = "*1\r\n+8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n";
10841 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l"]), want);
10842 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l2"]), want);
10843
10844 assert_eq!(
10847 f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l", b"gone", b"l2"]),
10848 format!(
10849 "*3\r\n+8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n+{0}\r\n\
10850 +8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n",
10851 "0".repeat(40)
10852 )
10853 );
10854 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE"]), "*0\r\n");
10855 }
10856
10857 #[test]
10860 fn the_whole_digest_folds_in_the_names_and_the_database_numbers() {
10861 let mut f = Fixture::new();
10862 let empty = format!("+{}\r\n", "0".repeat(40));
10863 assert_eq!(f.run(&[b"DEBUG", b"DIGEST"]), empty);
10864
10865 f.run(&[b"SET", b"k", b"hello"]);
10866 assert_eq!(
10867 f.run(&[b"DEBUG", b"DIGEST"]),
10868 "+d101db227d1e3b31616b18b0b8700f84c3ffa5e9\r\n"
10869 );
10870
10871 f.run(&[b"SELECT", b"3"]);
10872 f.run(&[b"SET", b"k", b"hello"]);
10873 assert_eq!(
10874 f.run(&[b"DEBUG", b"DIGEST"]),
10875 "+a541f66c15932c1014da1569ff27c15bcde7d1dc\r\n"
10876 );
10877
10878 f.run(&[b"SELECT", b"0"]);
10881 f.run(&[b"FLUSHDB"]);
10882 assert_eq!(
10883 f.run(&[b"DEBUG", b"DIGEST"]),
10884 "+f9b35ab00ad2f456386a2f73d316bf8266013606\r\n"
10885 );
10886
10887 f.run(&[b"FLUSHALL"]);
10888 assert_eq!(f.run(&[b"DEBUG", b"DIGEST"]), empty);
10889 }
10890
10891 #[test]
10894 fn digesting_does_not_count_as_using_anything() {
10895 let mut f = Fixture::new();
10896 f.run(&[b"SET", b"k", b"value"]);
10897 f.run(&[b"CONFIG", b"RESETSTAT"]);
10898 f.run(&[b"DEBUG", b"DIGEST"]);
10899 f.run(&[b"DEBUG", b"DIGEST-VALUE", b"k", b"gone"]);
10900 let stats = f.run(&[b"INFO", b"stats"]);
10901 assert!(stats.contains("keyspace_hits:0\r\n"), "{stats}");
10902 assert!(stats.contains("keyspace_misses:0\r\n"), "{stats}");
10903
10904 f.run(&[b"GET", b"k"]);
10907 f.run(&[b"GET", b"gone"]);
10908 let stats = f.run(&[b"INFO", b"stats"]);
10909 assert!(stats.contains("keyspace_hits:1\r\n"), "{stats}");
10910 assert!(stats.contains("keyspace_misses:1\r\n"), "{stats}");
10911 }
10912
10913 fn field<'a>(line: &'a str, label: &str) -> &'a str {
10915 let at = line
10916 .find(label)
10917 .unwrap_or_else(|| panic!("no {label} in {line}"));
10918 let rest = &line[at + label.len()..];
10919 rest.split([' ', '\r']).next().expect("a value")
10920 }
10921
10922 fn guarded(password: &[u8]) -> Fixture {
10932 let mut f = Fixture::new();
10933 f.server.set_password(password);
10934 f.session.admit(false);
10935 f
10936 }
10937
10938 #[test]
10941 fn a_server_with_a_password_answers_everything_else_with_noauth() {
10942 let mut f = guarded(b"hunter2");
10943 for parts in [
10944 &[b"PING".as_slice()][..],
10945 &[b"GET", b"k"],
10946 &[b"SET", b"k", b"v"],
10947 &[b"COMMAND", b"COUNT"],
10948 &[b"SUBSCRIBE", b"ch"],
10949 &[b"MULTI"],
10950 &[b"INFO"],
10951 ] {
10952 assert_eq!(
10953 f.run(parts),
10954 "-NOAUTH Authentication required.\r\n",
10955 "{parts:?} got through"
10956 );
10957 }
10958 }
10959
10960 #[test]
10967 fn the_four_commands_that_do_not_need_the_password_get_through() {
10968 for name in ["auth", "hello", "reset", "quit"] {
10969 let spec = table::lookup(name.as_bytes()).expect(name);
10970 assert!(spec.flags.contains(&"no_auth"), "{name} is not no_auth");
10971 }
10972 let mut f = guarded(b"hunter2");
10973 assert_eq!(f.run(&[b"QUIT"]), "+OK\r\n");
10974 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
10975 assert_eq!(
10976 f.run(&[b"AUTH", b"wrong"]),
10977 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
10978 );
10979 assert_eq!(f.run(&[b"AUTH", b"hunter2"]), "+OK\r\n");
10980 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10981 }
10982
10983 #[test]
10985 fn auth_takes_the_password_on_its_own_or_behind_the_user_name() {
10986 let mut f = guarded(b"hunter2");
10987 let wrong = "-WRONGPASS invalid username-password pair or user is disabled.\r\n";
10988 assert_eq!(f.run(&[b"AUTH", b"default", b"hunter2"]), "+OK\r\n");
10989 assert_eq!(f.run(&[b"AUTH", b"default", b"wrong"]), wrong);
10990 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10993 assert_eq!(f.run(&[b"AUTH", b"someone", b"hunter2"]), wrong);
10994 assert_eq!(
10995 f.run(&[b"AUTH"]),
10996 "-ERR wrong number of arguments for 'auth' command\r\n"
10997 );
10998 assert_eq!(f.run(&[b"AUTH", b"a", b"b", b"c"]), "-ERR syntax error\r\n");
10999 }
11000
11001 #[test]
11009 fn auth_on_a_server_with_no_password_says_so_at_length() {
11010 let mut f = Fixture::new();
11011 assert_eq!(
11012 f.run(&[b"AUTH", b"anything"]),
11013 "-ERR AUTH <password> called without any password configured for the \
11014 default user. Are you sure your configuration is correct?\r\n"
11015 );
11016 assert_eq!(f.run(&[b"AUTH", b"default", b"anything"]), "+OK\r\n");
11017 assert_eq!(
11018 f.run(&[b"AUTH", b"nobody", b"anything"]),
11019 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
11020 );
11021 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11022 }
11023
11024 #[test]
11033 fn hello_says_which_option_would_have_worked() {
11034 let long = "-NOAUTH HELLO must be called with the client already authenticated, \
11035 otherwise the HELLO <proto> AUTH <user> <pass> option can be used to \
11036 authenticate the client and select the RESP protocol version at the \
11037 same time\r\n";
11038 let mut f = guarded(b"hunter2");
11039 assert_eq!(f.run(&[b"HELLO"]), long);
11040 assert_eq!(f.run(&[b"HELLO", b"3"]), long);
11041 assert_eq!(
11042 f.run(&[b"HELLO", b"9"]),
11043 "-NOPROTO unsupported protocol version\r\n"
11044 );
11045 assert_eq!(
11048 f.run(&[b"HELLO", b"9", b"AUTH", b"default", b"hunter2"]),
11049 "-NOPROTO unsupported protocol version\r\n"
11050 );
11051 assert_eq!(f.run(&[b"PING"]), "-NOAUTH Authentication required.\r\n");
11052 assert_eq!(
11053 f.run(&[b"HELLO", b"2", b"AUTH", b"default", b"wrong"]),
11054 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
11055 );
11056 assert!(
11057 f.run(&[b"HELLO", b"3", b"AUTH", b"default", b"hunter2"])
11058 .starts_with("%7\r\n"),
11059 "the option did not let it in"
11060 );
11061 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11062 }
11063
11064 #[test]
11072 fn exec_without_the_password_is_an_abort_and_says_why() {
11073 let mut f = guarded(b"hunter2");
11074 assert_eq!(f.run(&[b"MULTI"]), "-NOAUTH Authentication required.\r\n");
11075 assert_eq!(
11076 f.run(&[b"EXEC"]),
11077 "-EXECABORT Transaction discarded because of: NOAUTH Authentication \
11078 required.\r\n"
11079 );
11080 }
11081
11082 #[test]
11085 fn reset_gives_the_password_back_to_the_server_to_ask_for_again() {
11086 let mut f = guarded(b"hunter2");
11087 assert_eq!(f.run(&[b"AUTH", b"hunter2"]), "+OK\r\n");
11088 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11089 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
11090 assert_eq!(f.run(&[b"PING"]), "-NOAUTH Authentication required.\r\n");
11091
11092 let mut f = Fixture::new();
11094 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
11095 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11096 }
11097
11098 #[test]
11105 fn a_password_set_under_an_open_connection_leaves_it_alone() {
11106 let mut f = Fixture::new();
11107 f.session.admit(true);
11108 assert_eq!(
11109 f.run(&[b"CONFIG", b"SET", b"requirepass", b"hunter2"]),
11110 "+OK\r\n"
11111 );
11112 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11113 let mut later = Fixture::on(Server::new());
11115 later.server.set_password(b"hunter2");
11116 later.session.admit(false);
11117 assert_eq!(
11118 later.run(&[b"PING"]),
11119 "-NOAUTH Authentication required.\r\n"
11120 );
11121 later.server.set_password(b"");
11122 assert_eq!(later.run(&[b"PING"]), "+PONG\r\n");
11123 }
11124
11125 #[test]
11128 fn requirepass_reads_back_what_was_written_and_an_empty_one_clears_it() {
11129 let mut f = Fixture::new();
11130 f.session.admit(true);
11133 assert_eq!(
11134 f.run(&[b"CONFIG", b"GET", b"requirepass"]),
11135 "*2\r\n$11\r\nrequirepass\r\n$0\r\n\r\n"
11136 );
11137 f.run(&[b"CONFIG", b"SET", b"requirepass", b"hunter2"]);
11138 assert_eq!(
11139 f.run(&[b"CONFIG", b"GET", b"requirepass"]),
11140 "*2\r\n$11\r\nrequirepass\r\n$7\r\nhunter2\r\n"
11141 );
11142 assert!(f.server.guarded());
11143 f.run(&[b"CONFIG", b"SET", b"requirepass", b""]);
11144 assert!(!f.server.guarded(), "an empty password did not clear it");
11145 assert_eq!(
11146 f.run(&[b"CONFIG", b"GET", b"requirepass"]),
11147 "*2\r\n$11\r\nrequirepass\r\n$0\r\n\r\n"
11148 );
11149 }
11150
11151 #[test]
11153 fn debug_protocol_writes_what_the_reference_writes_on_resp2() {
11154 let mut f = Fixture::new();
11155 for (kind, want) in [
11156 ("string", "$11\r\nHello World\r\n"),
11157 ("integer", ":12345\r\n"),
11158 ("double", "$5\r\n3.141\r\n"),
11159 ("bignum", "$37\r\n1234567999999999999999999999999999999\r\n"),
11160 ("null", "$-1\r\n"),
11161 ("array", "*3\r\n:0\r\n:1\r\n:2\r\n"),
11162 ("set", "*3\r\n:0\r\n:1\r\n:2\r\n"),
11163 ("map", "*6\r\n:0\r\n:0\r\n:1\r\n:1\r\n:2\r\n:0\r\n"),
11164 (
11165 "attrib",
11166 "$39\r\nSome real reply following the attribute\r\n",
11167 ),
11168 ("push", "-ERR RESP2 is not supported by this command\r\n"),
11169 ("verbatim", "$25\r\nThis is a verbatim\nstring\r\n"),
11170 ("true", ":1\r\n"),
11171 ("false", ":0\r\n"),
11172 ] {
11173 assert_eq!(
11174 f.run(&[b"DEBUG", b"PROTOCOL", kind.as_bytes()]),
11175 want,
11176 "{kind}"
11177 );
11178 }
11179 }
11180
11181 #[test]
11183 fn debug_protocol_writes_what_the_reference_writes_on_resp3() {
11184 let mut f = Fixture::new();
11185 f.out = Out::new(Proto::Resp3);
11186 for (kind, want) in [
11187 ("string", "$11\r\nHello World\r\n"),
11188 ("integer", ":12345\r\n"),
11189 ("double", ",3.141\r\n"),
11190 ("bignum", "(1234567999999999999999999999999999999\r\n"),
11191 ("null", "_\r\n"),
11192 ("array", "*3\r\n:0\r\n:1\r\n:2\r\n"),
11193 ("set", "~3\r\n:0\r\n:1\r\n:2\r\n"),
11194 ("map", "%3\r\n:0\r\n#f\r\n:1\r\n#t\r\n:2\r\n#f\r\n"),
11195 (
11196 "attrib",
11197 "|1\r\n$14\r\nkey-popularity\r\n*2\r\n$7\r\nkey:123\r\n:90\r\n\
11198 $39\r\nSome real reply following the attribute\r\n",
11199 ),
11200 (
11201 "push",
11202 "$40\r\nSome real reply following the push reply\r\n\
11203 >2\r\n$16\r\nserver-cpu-usage\r\n:42\r\n",
11204 ),
11205 ("verbatim", "=29\r\ntxt:This is a verbatim\nstring\r\n"),
11206 ("true", "#t\r\n"),
11207 ("false", "#f\r\n"),
11208 ] {
11209 assert_eq!(
11210 f.run(&[b"DEBUG", b"PROTOCOL", kind.as_bytes()]),
11211 want,
11212 "{kind}"
11213 );
11214 }
11215 }
11216
11217 #[test]
11219 fn debug_protocol_names_every_type_when_it_is_given_none_of_them() {
11220 let mut f = Fixture::new();
11221 for kind in [&b"bogus"[..], b""] {
11222 assert_eq!(
11223 f.run(&[b"DEBUG", b"PROTOCOL", kind]),
11224 "-ERR Wrong protocol type name. Please use one of the following: \
11225 string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false\r\n"
11226 );
11227 }
11228 }
11229
11230 #[test]
11237 fn debug_says_the_same_thing_about_a_bad_name_and_a_bad_count() {
11238 let mut f = Fixture::new();
11239 for parts in [
11240 &[b"DEBUG".as_slice(), b"NOSUCH"][..],
11241 &[b"DEBUG", b"PROTOCOL"],
11242 &[b"DEBUG", b"PROTOCOL", b"string", b"extra"],
11243 &[b"DEBUG", b"SLEEP"],
11244 &[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"1", b"2"],
11245 &[b"DEBUG", b"HELP", b"me"],
11246 ] {
11247 let got = f.run(parts);
11248 let name = String::from_utf8_lossy(parts[1]).to_string();
11249 assert_eq!(
11250 got,
11251 format!(
11252 "-ERR unknown subcommand or wrong number of arguments for '{name}'. \
11253 Try DEBUG HELP.\r\n"
11254 ),
11255 "{name}"
11256 );
11257 }
11258 assert_eq!(
11259 f.run(&[b"DEBUG"]),
11260 "-ERR wrong number of arguments for 'debug' command\r\n"
11261 );
11262 }
11263
11264 #[test]
11266 fn debug_error_hands_back_whatever_it_was_given() {
11267 let mut f = Fixture::new();
11268 assert_eq!(f.run(&[b"DEBUG", b"ERROR", b"my error"]), "-my error\r\n");
11269 assert_eq!(f.run(&[b"DEBUG", b"ERROR", b""]), "-\r\n");
11270 assert_eq!(
11273 f.run(&[b"DEBUG", b"ERROR", b"-WEIRD thing"]),
11274 "--WEIRD thing\r\n"
11275 );
11276 assert_eq!(
11278 f.run(&[b"DEBUG", b"ERROR", b"two\nlines"]),
11279 "-two lines\r\n"
11280 );
11281 }
11282
11283 #[test]
11285 fn debug_populate_fills_and_skips_what_is_there() {
11286 let mut f = Fixture::new();
11287 assert_eq!(f.run(&[b"SET", b"key:0", b"mine"]), "+OK\r\n");
11288 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"3"]), "+OK\r\n");
11289 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n");
11290 assert_eq!(f.run(&[b"GET", b"key:0"]), "$4\r\nmine\r\n");
11291 assert_eq!(f.run(&[b"GET", b"key:2"]), "$7\r\nvalue:2\r\n");
11292 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"p:"]), "+OK\r\n");
11295 assert_eq!(f.run(&[b"GET", b"p::0"]), "$7\r\nvalue:0\r\n");
11296 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"q", b"9"]), "+OK\r\n");
11298 assert_eq!(f.raw(&[b"GET", b"q:0"]), b"$9\r\nvalue:0\0\0\r\n");
11299 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"r", b"4"]), "+OK\r\n");
11300 assert_eq!(f.run(&[b"GET", b"r:0"]), "$4\r\nvalu\r\n");
11301 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"s", b"0"]), "+OK\r\n");
11303 assert_eq!(f.run(&[b"GET", b"s:0"]), "$7\r\nvalue:0\r\n");
11304 }
11305
11306 #[test]
11308 fn debug_populate_wants_two_numbers_that_are_not_negative() {
11309 let mut f = Fixture::new();
11310 for parts in [
11311 &[b"DEBUG".as_slice(), b"POPULATE", b"abc"][..],
11312 &[b"DEBUG", b"POPULATE", b"-1"],
11313 &[b"DEBUG", b"POPULATE", b"1.5"],
11314 &[b"DEBUG", b"POPULATE", b"1", b"p", b"-1"],
11315 &[b"DEBUG", b"POPULATE", b"1", b"p", b"x"],
11316 ] {
11317 assert_eq!(
11318 f.run(parts),
11319 "-ERR value is out of range, must be positive\r\n"
11320 );
11321 }
11322 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"0"]), "+OK\r\n");
11323 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11324 }
11325
11326 #[test]
11332 fn debug_quicklist_packed_threshold_takes_what_the_reference_takes() {
11333 let mut f = Fixture::new();
11334 for good in [
11335 &b"1"[..],
11336 b"2",
11337 b"1b",
11338 b"1K",
11339 b"1kb",
11340 b"1G",
11341 b"3gb",
11342 b"0",
11343 b"0b",
11344 ] {
11345 assert_eq!(
11346 f.run(&[b"DEBUG", b"QUICKLIST-PACKED-THRESHOLD", good]),
11347 "+OK\r\n",
11348 "{}",
11349 String::from_utf8_lossy(good)
11350 );
11351 }
11352 for bad in [
11353 &b"4gb"[..],
11354 b"4294967295",
11355 b"4294967296",
11356 b"abc",
11357 b"",
11358 b"+5",
11359 b"1.5",
11360 ] {
11361 assert_eq!(
11362 f.run(&[b"DEBUG", b"QUICKLIST-PACKED-THRESHOLD", bad]),
11363 "-ERR argument must be a memory value bigger than 1 and smaller than 4gb\r\n",
11364 "{}",
11365 String::from_utf8_lossy(bad)
11366 );
11367 }
11368 }
11369
11370 #[test]
11372 fn the_debug_gates_turn_the_things_they_name_off_and_on_again() {
11373 let mut f = Fixture::new();
11374 for (sub, read) in [
11375 (&b"SET-ACTIVE-EXPIRE"[..], 0),
11376 (b"DICT-RESIZING", 1),
11377 (b"PAUSE-CRON", 2),
11378 ] {
11379 let reads: [fn(&Server) -> bool; 3] =
11380 [Server::expiring, Server::resizing, Server::cron_running];
11381 let on = reads[read];
11382 let stop: &[u8] = if read == 2 { b"1" } else { b"0" };
11385 let go: &[u8] = if read == 2 { b"0" } else { b"1" };
11386 assert!(on(&f.server), "{}", String::from_utf8_lossy(sub));
11387 assert_eq!(f.run(&[b"DEBUG", sub, stop]), "+OK\r\n");
11388 assert!(!on(&f.server), "{}", String::from_utf8_lossy(sub));
11389 assert_eq!(f.run(&[b"DEBUG", sub, b"nonsense"]), "+OK\r\n");
11392 assert_eq!(on(&f.server), read == 2);
11393 assert_eq!(f.run(&[b"DEBUG", sub, go]), "+OK\r\n");
11394 assert!(on(&f.server), "{}", String::from_utf8_lossy(sub));
11395 }
11396 }
11397
11398 #[test]
11404 fn the_sweep_stops_when_debug_turns_it_off() {
11405 let mut f = Fixture::new();
11406 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PX", b"10"]), "+OK\r\n");
11407 assert_eq!(f.run(&[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"0"]), "+OK\r\n");
11408 f.advance(50);
11409 assert_eq!(f.server.expire_slice(64), 0);
11410 assert_eq!(f.run(&[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"1"]), "+OK\r\n");
11411 assert_eq!(f.server.expire_slice(64), 1);
11412 }
11413
11414 #[test]
11420 fn command_info_sends_sets_where_the_reference_sends_sets() {
11421 let mut f = Fixture::new();
11422 f.out = Out::new(Proto::Resp3);
11423 assert_eq!(
11424 f.run(&[b"COMMAND", b"INFO", b"get"]),
11425 "*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\
11426 ~3\r\n+@read\r\n+@string\r\n+@fast\r\n~0\r\n~1\r\n%3\r\n\
11427 $5\r\nflags\r\n~2\r\n+RO\r\n+access\r\n\
11428 $12\r\nbegin_search\r\n%2\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
11429 %1\r\n$5\r\nindex\r\n:1\r\n\
11430 $9\r\nfind_keys\r\n%2\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
11431 %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\
11432 ~0\r\n"
11433 );
11434 let mut f = Fixture::new();
11436 assert_eq!(
11437 f.run(&[b"COMMAND", b"INFO", b"get"]),
11438 "*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\
11439 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*1\r\n*6\r\n\
11440 $5\r\nflags\r\n*2\r\n+RO\r\n+access\r\n\
11441 $12\r\nbegin_search\r\n*4\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
11442 *2\r\n$5\r\nindex\r\n:1\r\n\
11443 $9\r\nfind_keys\r\n*4\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
11444 *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\
11445 *0\r\n"
11446 );
11447 }
11448
11449 #[test]
11451 fn debug_is_admin_and_stays_off_a_monitor_feed() {
11452 let spec = table::lookup(b"debug").expect("debug is in the table");
11453 assert!(spec.flags.contains(&"admin"));
11454 assert_eq!(spec.arity, -2);
11455 assert_eq!(spec.acl, ["@admin", "@slow", "@dangerous"]);
11456 }
11457
11458 #[test]
11459 fn the_command_counter_counts_every_command_including_the_bad_ones() {
11460 let mut f = Fixture::new();
11461 f.run(&[b"PING"]);
11462 f.run(&[b"NOPE"]);
11463 f.run(&[b"GET"]);
11464 assert_eq!(f.server.totals().commands, 3);
11465 }
11466
11467 #[test]
11468 fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
11469 let mut server = Server::new();
11470 server.set_threads(2);
11471 server.mine().turn.store(0, Relaxed);
11474 server.locals[1].mark(1 << 9);
11475 server.collect_marks();
11476 assert!(server.mine().wanted(9));
11477 assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
11479 }
11480
11481 #[test]
11482 fn what_two_threads_counted_is_added_up_when_info_asks() {
11483 let mut server = Server::new();
11484 server.set_threads(2);
11485 let ping = lookup(b"PING").expect("PING is a command");
11489 for (at, calls) in [(0, 2), (1, 3)] {
11490 let counters = &server.locals[at];
11491 for _ in 0..calls {
11492 counters.stats.commands.bump();
11493 counters.cmdstats.at(ping).calls.bump();
11494 }
11495 counters.stats.opened();
11496 }
11497 assert_eq!(server.totals().commands, 5);
11498 assert_eq!(server.totals().clients, 2);
11499 assert_eq!(server.totals().connections, 2);
11500 let rows: Vec<_> = server.command_stats().collect();
11501 assert_eq!(rows.len(), 1);
11502 assert_eq!(rows[0].0, "ping");
11503 assert_eq!(rows[0].1.calls, 5);
11504 server.reset_stats();
11507 assert_eq!(server.totals().commands, 0);
11508 assert_eq!(server.totals().connections, 0);
11509 assert_eq!(server.totals().clients, 2);
11510 }
11511
11512 #[test]
11513 fn the_parked_count_says_what_the_waiter_list_says() {
11514 let mut f = Fixture::new();
11515 assert_eq!(f.server.parked(), 0);
11516 for client in 1..=3u64 {
11517 f.session = Session::new(client);
11518 assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
11519 }
11520 assert_eq!(f.server.parked(), 3);
11521 assert_eq!(f.server.waiters().len(), 3);
11522
11523 f.server.forget_waiters(2);
11527 assert_eq!(f.server.parked(), f.server.waiters().len());
11528 f.server.forget_waiters(1);
11529 assert_eq!(f.server.parked(), f.server.waiters().len());
11530 f.run(&[b"RPUSH", b"q", b"v"]);
11531 let mut out = Out::new(Proto::Resp2);
11532 assert!(f.server.serve_waiter(3, 0, &mut out));
11533 f.server.forget_waiters(3);
11534 assert_eq!(f.server.parked(), 0);
11535 assert!(f.server.waiters().is_empty());
11536 }
11537
11538 #[test]
11539 fn a_set_goes_from_bytes_to_bytes() {
11540 let mut f = Fixture::new();
11541 assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
11542 assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
11543 assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
11544 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
11545 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
11546 assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
11547 assert_eq!(
11548 f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
11549 "*3\r\n:1\r\n:0\r\n:1\r\n"
11550 );
11551 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
11552 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
11553 }
11554
11555 #[test]
11556 fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
11557 let mut f = Fixture::new();
11558 assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
11559 assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
11560 assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
11561 assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
11562 assert_eq!(
11563 f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
11564 "*2\r\n:0\r\n:0\r\n"
11565 );
11566 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
11567 }
11568
11569 #[test]
11570 fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
11571 let mut f = Fixture::new();
11575 f.run(&[b"SADD", b"s", b"one"]);
11576 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
11577
11578 f.run(&[b"HELLO", b"3"]);
11579 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
11580 }
11581
11582 #[test]
11583 fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
11584 let mut f = Fixture::new();
11587 f.run(&[b"SADD", b"s", b"42"]);
11588 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
11589 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
11590 assert_eq!(
11591 f.run(&[b"SISMEMBER", b"s", b"042"]),
11592 ":0\r\n",
11593 "the member is the bytes and not the number they parse to"
11594 );
11595 }
11596
11597 #[test]
11598 fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
11599 let mut f = Fixture::new();
11600 f.run(&[b"SET", b"str", b"v"]);
11601 f.run(&[b"SADD", b"set", b"a"]);
11602
11603 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11604 assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
11605 assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
11606 assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
11607 assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
11608 assert_eq!(f.run(&[b"GET", b"set"]), wrong);
11609 assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
11610 assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
11611 assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
11612
11613 assert_eq!(
11616 f.run(&[b"MGET", b"str", b"set", b"nope"]),
11617 "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
11618 );
11619 assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
11621 assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
11622 }
11623
11624 #[test]
11625 fn a_wrongtype_leaves_nothing_half_written() {
11626 let mut f = Fixture::new();
11630 f.run(&[b"SET", b"k", b"v"]);
11631 let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
11632 assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
11633 assert!(!reply.contains('*'), "an array header went out in front");
11634 }
11635
11636 #[test]
11637 fn emptying_a_set_takes_the_key_with_it() {
11638 let mut f = Fixture::new();
11639 f.run(&[b"SADD", b"s", b"a", b"b"]);
11640 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
11641 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
11642 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
11643 assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
11644 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11645 }
11646
11647 fn split_scan(reply: &str) -> (String, Vec<String>) {
11653 let mut lines = reply.split("\r\n");
11654 assert_eq!(lines.next(), Some("*2"), "got {reply}");
11655 lines.next().expect("the cursor header");
11656 let cursor = lines.next().expect("the cursor").to_owned();
11657 let header = lines.next().expect("the member header");
11658 let n: usize = header[1..].parse().expect("a member count");
11659 let mut members = Vec::with_capacity(n);
11660 for _ in 0..n {
11661 lines.next().expect("a member header");
11662 members.push(lines.next().expect("a member").to_owned());
11663 }
11664 (cursor, members)
11665 }
11666
11667 #[test]
11668 fn popping_takes_a_member_off_the_set_and_hands_it_back() {
11669 let mut f = Fixture::new();
11670 f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
11671
11672 let one = f.run(&[b"SPOP", b"s"]);
11673 assert!(
11674 ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
11675 "got {one}"
11676 );
11677 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
11678
11679 let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
11681 assert!(rest.starts_with("*3\r\n"), "got {rest}");
11682 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
11683 assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
11685 assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
11686 }
11687
11688 #[test]
11689 fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
11690 let mut f = Fixture::new();
11695 f.run(&[b"HELLO", b"3"]);
11696 f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
11697
11698 assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
11699 assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
11701
11702 f.run(&[b"SADD", b"one", b"z"]);
11706 assert_eq!(
11707 f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
11708 "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
11709 );
11710 }
11711
11712 #[test]
11713 fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
11714 let mut f = Fixture::new();
11715 f.run(&[b"SADD", b"s", b"only"]);
11716 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
11717 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
11718 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
11719
11720 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
11721 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
11724 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
11725 assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
11727 }
11728
11729 #[test]
11730 fn a_pop_count_that_is_not_a_positive_number_says_so() {
11731 let mut f = Fixture::new();
11732 f.run(&[b"SADD", b"s", b"a"]);
11733 let bad = "-ERR value is out of range, must be positive\r\n";
11734 assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
11735 assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
11736 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
11737 assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
11739 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
11740 }
11741
11742 #[test]
11743 fn a_scan_walks_a_set_of_any_size_exactly_once() {
11744 let mut f = Fixture::new();
11745 let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
11746 let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
11747 .into_iter()
11748 .chain(members.iter().map(Vec::as_slice))
11749 .collect();
11750 f.run(&args);
11751
11752 let mut seen = Vec::new();
11753 let mut cursor = "0".to_owned();
11754 loop {
11755 let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
11756 let (next, got) = split_scan(&reply);
11757 seen.extend(got);
11758 cursor = next;
11759 if cursor == "0" {
11760 break;
11761 }
11762 }
11763 seen.sort();
11764 seen.dedup();
11765 assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
11766
11767 f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
11770 let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
11771 assert_eq!(cursor, "0");
11772 assert_eq!(got.len(), 3);
11773 assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
11775 }
11776
11777 #[test]
11778 fn a_scan_takes_match_and_count_and_refuses_anything_else() {
11779 let mut f = Fixture::new();
11780 f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
11781
11782 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
11783 let mut got = got;
11784 got.sort();
11785 assert_eq!(got, ["aa", "ab"]);
11786
11787 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
11790 let mut got = got;
11791 got.sort();
11792 assert_eq!(got, ["12", "13"]);
11793
11794 assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
11795 assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
11796 assert_eq!(
11797 f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
11798 "-ERR syntax error\r\n"
11799 );
11800 assert_eq!(
11803 f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
11804 "-ERR syntax error\r\n"
11805 );
11806 }
11807
11808 #[test]
11809 fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
11810 let mut f = Fixture::new();
11811 f.run(&[b"SADD", b"src", b"a", b"b"]);
11812 f.run(&[b"SADD", b"dst", b"c"]);
11813
11814 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
11815 assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
11816 assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
11817 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
11819 assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
11820
11821 assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
11824 assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
11825 assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
11826 }
11827
11828 #[test]
11829 fn moving_checks_the_types_in_the_order_redis_checks_them() {
11830 let mut f = Fixture::new();
11834 f.run(&[b"SET", b"str", b"v"]);
11835 f.run(&[b"SADD", b"set", b"a"]);
11836
11837 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11838 assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
11839 assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
11840 assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
11841 assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
11842 assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
11843 assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
11844 assert_eq!(
11845 f.run(&[b"SISMEMBER", b"set", b"a"]),
11846 ":1\r\n",
11847 "and none of that moved anything"
11848 );
11849 }
11850
11851 #[test]
11852 fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
11853 let mut f = Fixture::new();
11856 f.run(&[b"SADD", b"s", b"a"]);
11857 for bad in [
11858 &[b"SSCAN".as_slice(), b"s", b"abc"][..],
11859 &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
11860 &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
11861 ] {
11862 let reply = f.run(bad);
11863 assert!(reply.starts_with("-ERR"), "got {reply}");
11864 assert!(!reply.contains('*'), "an array header went out in front");
11865 }
11866 }
11867
11868 #[test]
11869 fn a_hash_writes_reads_and_deletes_its_fields() {
11870 let mut f = Fixture::new();
11871 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
11872 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
11873 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
11874 assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
11875 assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
11876 assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
11877 assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
11878 assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
11879 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
11880 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
11881
11882 assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
11885
11886 assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
11887 assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
11888 assert_eq!(
11889 f.run(&[b"EXISTS", b"h"]),
11890 ":0\r\n",
11891 "and losing the last field lost the key"
11892 );
11893 }
11894
11895 #[test]
11896 fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
11897 let mut f = Fixture::new();
11898 f.run(&[b"HSET", b"h", b"a", b"1"]);
11899 assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
11900 assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
11901 assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
11902 assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
11903 assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
11904
11905 f.run(&[b"HELLO", b"3"]);
11906 assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
11907 assert_eq!(
11908 f.run(&[b"HGETALL", b"nokey"]),
11909 "%0\r\n",
11910 "a missing key is the empty hash and never a nil"
11911 );
11912 assert_eq!(
11913 f.run(&[b"HKEYS", b"h"]),
11914 "*1\r\n$1\r\na\r\n",
11915 "and the two that answer one side stay arrays"
11916 );
11917 }
11918
11919 #[test]
11920 fn hmget_answers_once_per_field_and_hmset_answers_ok() {
11921 let mut f = Fixture::new();
11922 assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
11923 assert_eq!(
11924 f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
11925 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
11926 "the reply is positional, so b is a nil and not a gap"
11927 );
11928 assert_eq!(
11929 f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
11930 "*2\r\n$-1\r\n$-1\r\n",
11931 "and a missing key is all nils rather than an empty array"
11932 );
11933
11934 assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
11935 assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
11936 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
11937 }
11938
11939 #[test]
11940 fn a_hash_counts_up_and_says_so_when_it_cannot() {
11941 let mut f = Fixture::new();
11942 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
11943 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
11944 assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
11945 assert_eq!(
11946 f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
11947 "$4\r\n10.5\r\n",
11948 "a bulk string and not a double, on both protocols"
11949 );
11950
11951 f.run(&[b"HSET", b"h", b"s", b"words"]);
11952 let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
11953 assert!(
11954 bad.starts_with("-ERR hash value is not an integer"),
11955 "{bad}"
11956 );
11957 let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
11958 assert!(
11959 bad.starts_with("-ERR value is not an integer"),
11960 "a bad argument is not yet a hash value, {bad}"
11961 );
11962 assert_eq!(
11963 f.run(&[b"HGET", b"h", b"s"]),
11964 "$5\r\nwords\r\n",
11965 "and neither of them wrote anything"
11966 );
11967 }
11968
11969 #[test]
11970 fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
11971 let fields = if cfg!(miri) { 96 } else { 500 };
11976 let mut f = Fixture::new();
11977 for i in 0..fields {
11978 let field = format!("field-{i}");
11979 let value = format!("value-{i}");
11980 f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
11981 }
11982
11983 let mut seen: Vec<String> = Vec::new();
11984 let mut cursor = "0".to_owned();
11985 loop {
11986 let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
11987 let (next, items) = scan_reply(&reply);
11988 assert_eq!(items.len() % 2, 0, "a pair went out half written");
11989 for pair in items.chunks(2) {
11990 assert_eq!(
11991 pair[0].strip_prefix("field-"),
11992 pair[1].strip_prefix("value-"),
11993 "a field came back with someone else's value"
11994 );
11995 seen.push(pair[0].clone());
11996 }
11997 cursor = next;
11998 if cursor == "0" {
11999 break;
12000 }
12001 }
12002 seen.sort();
12003 seen.dedup();
12004 assert_eq!(seen.len(), fields, "every field once and only once");
12005
12006 let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
12007 assert!(
12008 items.iter().all(|s| s.starts_with("field-")),
12009 "NOVALUES still sent the values"
12010 );
12011
12012 let last = fields - 1;
12013 let (_, one) = scan_reply(&f.run(&[
12014 b"HSCAN",
12015 b"h",
12016 b"0",
12017 b"MATCH",
12018 format!("field-{last}").as_bytes(),
12019 b"COUNT",
12020 b"1000",
12021 ]));
12022 assert_eq!(
12023 one,
12024 [format!("field-{last}"), format!("value-{last}")],
12025 "MATCH is on the field"
12026 );
12027 }
12028
12029 #[test]
12030 fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
12031 let mut f = Fixture::new();
12032 f.run(&[b"HSET", b"h", b"a", b"1"]);
12033 assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
12034 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
12035 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
12036 assert_eq!(
12037 f.run(&[b"HRANDFIELD", b"h", b"3"]),
12038 "*1\r\n$1\r\na\r\n",
12039 "a positive count is capped at the size of the hash"
12040 );
12041 assert_eq!(
12042 f.run(&[b"HRANDFIELD", b"h", b"-3"]),
12043 "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
12044 "and a negative one repeats itself"
12045 );
12046 assert_eq!(
12047 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
12048 "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
12049 "flat on RESP2"
12050 );
12051
12052 f.run(&[b"HELLO", b"3"]);
12053 assert_eq!(
12054 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
12055 "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
12056 "and nested on RESP3, but still an array and never a map"
12057 );
12058 }
12059
12060 #[test]
12061 fn every_hash_command_says_wrongtype_and_writes_nothing() {
12062 let mut f = Fixture::new();
12063 f.run(&[b"SET", b"str", b"v"]);
12064 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12065
12066 for cmd in [
12067 &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
12068 &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
12069 &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
12070 &[b"HGET".as_slice(), b"str", b"f"][..],
12071 &[b"HMGET".as_slice(), b"str", b"f"][..],
12072 &[b"HDEL".as_slice(), b"str", b"f"][..],
12073 &[b"HLEN".as_slice(), b"str"][..],
12074 &[b"HEXISTS".as_slice(), b"str", b"f"][..],
12075 &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
12076 &[b"HGETALL".as_slice(), b"str"][..],
12077 &[b"HKEYS".as_slice(), b"str"][..],
12078 &[b"HVALS".as_slice(), b"str"][..],
12079 &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
12080 &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
12081 &[b"HRANDFIELD".as_slice(), b"str"][..],
12082 &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
12083 &[b"HSCAN".as_slice(), b"str", b"0"][..],
12084 ] {
12085 let reply = f.run(cmd);
12086 assert_eq!(reply, wrong, "{:?}", cmd[0]);
12087 }
12088 assert_eq!(
12089 f.run(&[b"GET", b"str"]),
12090 "$1\r\nv\r\n",
12091 "and none of them touched the value"
12092 );
12093 }
12094
12095 #[test]
12096 fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
12097 let mut f = Fixture::new();
12098 f.run(&[b"HSET", b"h", b"f", b"v"]);
12099 for bad in [
12100 &[b"HSCAN".as_slice(), b"h", b"abc"][..],
12101 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
12102 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
12103 &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
12104 ] {
12105 let reply = f.run(bad);
12106 assert!(reply.starts_with("-ERR"), "got {reply}");
12107 assert!(!reply.contains('*'), "an array header went out in front");
12108 }
12109 }
12110
12111 #[test]
12112 fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
12113 let mut f = Fixture::new();
12114 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12115 assert_eq!(
12116 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
12117 "*1\r\n:1\r\n"
12118 );
12119 assert_eq!(
12120 f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
12121 "*3\r\n:100\r\n:-1\r\n:-2\r\n",
12122 "one answer per field, and the two sentinels are TTL's own"
12123 );
12124
12125 let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
12128 assert!((99_000..=100_000).contains(&ms), "got {ms}");
12129 let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
12130 let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
12131 assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
12132 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
12133
12134 assert_eq!(
12135 f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
12136 "*3\r\n:1\r\n:-1\r\n:-2\r\n",
12137 "one for the deadline taken off, and it does not say what it was"
12138 );
12139 assert_eq!(
12140 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12141 "*1\r\n:-1\r\n"
12142 );
12143 assert_eq!(
12144 f.run(&[b"HGET", b"h", b"a"]),
12145 "$1\r\n1\r\n",
12146 "and the field is still there with the value it had"
12147 );
12148 }
12149
12150 #[test]
12151 fn a_deadline_that_has_already_gone_deletes_the_field_now() {
12152 let mut f = Fixture::new();
12153 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12154 assert_eq!(
12155 f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
12156 "*1\r\n:2\r\n",
12157 "two, and not one, because nothing was stored"
12158 );
12159 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
12160 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
12161
12162 assert_eq!(
12163 f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
12164 "*1\r\n:2\r\n"
12165 );
12166 assert_eq!(
12167 f.run(&[b"EXISTS", b"h"]),
12168 ":0\r\n",
12169 "and the last field going took the key with it"
12170 );
12171
12172 f.run(&[b"HSET", b"h", b"a", b"1"]);
12175 assert_eq!(
12176 f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
12177 "*1\r\n:2\r\n"
12178 );
12179 }
12180
12181 #[test]
12182 fn a_field_is_gone_once_its_moment_passes() {
12183 let mut f = Fixture::new();
12184 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12185 assert_eq!(
12186 f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
12187 "*1\r\n:1\r\n"
12188 );
12189 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
12190
12191 f.server.advance_clock_ms(60);
12195 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
12196 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
12197 assert_eq!(
12198 f.run(&[b"HGETALL", b"h"]),
12199 "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
12200 "and the walks do not hand back a field that has expired"
12201 );
12202 }
12203
12204 #[test]
12205 fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
12206 let mut f = Fixture::new();
12207 for cmd in [
12208 &[
12209 b"HEXPIRE".as_slice(),
12210 b"nokey",
12211 b"100",
12212 b"FIELDS",
12213 b"2",
12214 b"a",
12215 b"b",
12216 ][..],
12217 &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
12218 &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
12219 &[
12220 b"HEXPIRETIME".as_slice(),
12221 b"nokey",
12222 b"FIELDS",
12223 b"2",
12224 b"a",
12225 b"b",
12226 ][..],
12227 &[
12228 b"HPERSIST".as_slice(),
12229 b"nokey",
12230 b"FIELDS",
12231 b"2",
12232 b"a",
12233 b"b",
12234 ][..],
12235 ] {
12236 assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
12237 }
12238 }
12239
12240 #[test]
12241 fn writing_a_field_clears_the_deadline_that_was_on_it() {
12242 let mut f = Fixture::new();
12243 f.run(&[b"HSET", b"h", b"a", b"1"]);
12244 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
12245 f.run(&[b"HSET", b"h", b"a", b"2"]);
12246 assert_eq!(
12247 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12248 "*1\r\n:-1\r\n",
12249 "Redis has done this since 7.4, and it is why HGETEX exists"
12250 );
12251 }
12252
12253 #[test]
12254 fn the_four_conditions_reach_the_store_the_way_they_were_written() {
12255 let mut f = Fixture::new();
12256 f.run(&[b"HSET", b"h", b"a", b"1"]);
12257 assert_eq!(
12258 f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
12259 "*1\r\n:0\r\n",
12260 "XX on a field with no deadline changes nothing"
12261 );
12262 assert_eq!(
12263 f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
12264 "*1\r\n:1\r\n"
12265 );
12266 assert_eq!(
12267 f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
12268 "*1\r\n:0\r\n",
12269 "and NX will not move one that is already there"
12270 );
12271 assert_eq!(
12272 f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
12273 "*1\r\n:0\r\n"
12274 );
12275 assert_eq!(
12276 f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
12277 "*1\r\n:1\r\n"
12278 );
12279 assert_eq!(
12280 f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
12281 "*1\r\n:1\r\n"
12282 );
12283 assert_eq!(
12284 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12285 "*1\r\n:50\r\n"
12286 );
12287 }
12288
12289 #[test]
12290 fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
12291 let mut f = Fixture::new();
12292 f.run(&[b"HSET", b"h", b"a", b"1"]);
12293 for (bad, want) in [
12294 (
12295 &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
12296 "-ERR invalid expire time, must be >= 0",
12297 ),
12298 (
12299 &[
12300 b"HEXPIRE".as_slice(),
12301 b"h",
12302 b"9999999999999999",
12303 b"FIELDS",
12304 b"1",
12305 b"a",
12306 ][..],
12307 "-ERR invalid expire time in 'hexpire' command",
12308 ),
12309 (
12310 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
12311 "-ERR wrong number of arguments for 'hexpire' command",
12312 ),
12313 (
12314 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
12315 "-ERR Parameter `numFields` should be greater than 0",
12316 ),
12317 (
12318 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
12319 "-ERR wrong number of arguments",
12320 ),
12321 (
12322 &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
12323 "-ERR wrong number of arguments",
12324 ),
12325 ] {
12326 let reply = f.run(bad);
12327 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
12328 assert!(!reply.contains('*'), "an array header went out in front");
12329 }
12330 assert_eq!(
12331 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12332 "*1\r\n:-1\r\n",
12333 "and not one of them put a deadline on anything"
12334 );
12335 }
12336
12337 #[test]
12338 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
12339 let mut f = Fixture::new();
12340 f.run(&[b"SET", b"str", b"v"]);
12341 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12342
12343 for cmd in [
12344 &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
12345 &[
12346 b"HPEXPIRE".as_slice(),
12347 b"str",
12348 b"100",
12349 b"FIELDS",
12350 b"1",
12351 b"f",
12352 ][..],
12353 &[
12354 b"HEXPIREAT".as_slice(),
12355 b"str",
12356 b"9999999999",
12357 b"FIELDS",
12358 b"1",
12359 b"f",
12360 ][..],
12361 &[
12362 b"HPEXPIREAT".as_slice(),
12363 b"str",
12364 b"9999999999999",
12365 b"FIELDS",
12366 b"1",
12367 b"f",
12368 ][..],
12369 &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12370 &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12371 &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12372 &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12373 &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12374 ] {
12375 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12376 }
12377 assert_eq!(
12378 f.run(&[b"GET", b"str"]),
12379 "$1\r\nv\r\n",
12380 "and none of them touched the value"
12381 );
12382 }
12383
12384 #[test]
12385 fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
12386 let mut f = Fixture::new();
12387 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12388 assert_eq!(
12389 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
12390 "*2\r\n$1\r\n1\r\n$-1\r\n",
12391 "positional, so the field that was not there is a nil in its place"
12392 );
12393 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
12394 assert_eq!(
12395 f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
12396 "*1\r\n$-1\r\n"
12397 );
12398 assert_eq!(
12399 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
12400 "*1\r\n$1\r\n2\r\n"
12401 );
12402 assert_eq!(
12403 f.run(&[b"EXISTS", b"h"]),
12404 ":0\r\n",
12405 "and the last field took the key"
12406 );
12407 }
12408
12409 #[test]
12410 fn hgetex_reads_and_moves_the_deadline_in_one_command() {
12411 let mut f = Fixture::new();
12412 f.run(&[b"HSET", b"h", b"a", b"1"]);
12413 assert_eq!(
12414 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
12415 "*1\r\n$1\r\n1\r\n"
12416 );
12417 assert_eq!(
12418 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12419 "*1\r\n:-1\r\n",
12420 "no option means leave it alone, which is the one place this is not GETEX"
12421 );
12422
12423 f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
12424 assert_eq!(
12425 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12426 "*1\r\n:100\r\n"
12427 );
12428 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
12429 assert_eq!(
12430 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12431 "*1\r\n:100\r\n",
12432 "and a plain read really does leave it alone"
12433 );
12434 assert_eq!(
12435 f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
12436 "*1\r\n$1\r\n1\r\n"
12437 );
12438 assert_eq!(
12439 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12440 "*1\r\n:-1\r\n"
12441 );
12442
12443 assert_eq!(
12444 f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
12445 "*1\r\n$1\r\n1\r\n",
12446 "the value goes out before the deadline that has already gone is applied"
12447 );
12448 assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
12449 assert_eq!(
12450 f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
12451 "*1\r\n$-1\r\n"
12452 );
12453 }
12454
12455 #[test]
12456 fn hsetex_writes_all_of_it_or_none_of_it() {
12457 let mut f = Fixture::new();
12458 assert_eq!(
12459 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
12460 ":1\r\n"
12461 );
12462 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12463 assert_eq!(
12464 f.run(&[
12465 b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
12466 ]),
12467 ":0\r\n",
12468 "FNX wants every field named to be missing"
12469 );
12470 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12471 assert_eq!(
12472 f.run(&[b"HEXISTS", b"h", b"new"]),
12473 ":0\r\n",
12474 "and none of the list was written"
12475 );
12476 assert_eq!(
12477 f.run(&[
12478 b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
12479 ]),
12480 ":0\r\n",
12481 "and FXX wants every one of them to be there"
12482 );
12483 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12484 assert_eq!(
12485 f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
12486 ":1\r\n"
12487 );
12488 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
12489
12490 assert_eq!(
12491 f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
12492 ":0\r\n"
12493 );
12494 assert_eq!(
12495 f.run(&[b"EXISTS", b"gone"]),
12496 ":0\r\n",
12497 "a key with no fields cannot meet FXX and is not created trying"
12498 );
12499 }
12500
12501 #[test]
12502 fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
12503 let mut f = Fixture::new();
12504 f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
12505 assert_eq!(
12506 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12507 "*1\r\n:100\r\n"
12508 );
12509
12510 f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
12511 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
12512 assert_eq!(
12513 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12514 "*1\r\n:100\r\n",
12515 "KEEPTTL put back what the write cleared"
12516 );
12517
12518 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
12519 assert_eq!(
12520 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12521 "*1\r\n:-1\r\n",
12522 "and without it a write clears the deadline the way HSET does"
12523 );
12524
12525 assert_eq!(
12528 f.run(&[
12529 b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
12530 ]),
12531 ":1\r\n"
12532 );
12533 assert_eq!(
12534 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12535 "*1\r\n:100\r\n"
12536 );
12537
12538 assert_eq!(
12539 f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
12540 ":1\r\n",
12541 "written, and not the separate code the HEXPIRE family has for this"
12542 );
12543 assert_eq!(
12544 f.run(&[b"EXISTS", b"h"]),
12545 ":0\r\n",
12546 "and storing it and then removing it emptied the hash"
12547 );
12548 }
12549
12550 #[test]
12551 fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
12552 let mut f = Fixture::new();
12553 f.run(&[b"HSET", b"h", b"a", b"1"]);
12554 for (bad, want) in [
12555 (
12557 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
12558 "-ERR Number of fields must be a positive integer",
12559 ),
12560 (
12561 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
12562 "-ERR The `numfields` parameter must match the number of arguments",
12563 ),
12564 (
12565 &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
12566 "-ERR Mandatory argument FIELDS is missing or not at the right position",
12567 ),
12568 (
12570 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
12571 "-ERR invalid number of fields",
12572 ),
12573 (
12574 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
12575 "-ERR wrong number of arguments",
12576 ),
12577 (
12578 &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
12579 "-ERR unknown argument: FIELD",
12580 ),
12581 (
12582 &[
12583 b"HGETEX".as_slice(),
12584 b"h",
12585 b"KEEPTTL",
12586 b"FIELDS",
12587 b"1",
12588 b"a",
12589 ][..],
12590 "-ERR unknown argument: KEEPTTL",
12591 ),
12592 (
12593 &[
12594 b"HGETEX".as_slice(),
12595 b"h",
12596 b"EX",
12597 b"100",
12598 b"PERSIST",
12599 b"FIELDS",
12600 b"1",
12601 b"a",
12602 ][..],
12603 "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
12604 ),
12605 (
12606 &[
12607 b"HSETEX".as_slice(),
12608 b"h",
12609 b"EX",
12610 b"1",
12611 b"KEEPTTL",
12612 b"FIELDS",
12613 b"1",
12614 b"a",
12615 b"1",
12616 ][..],
12617 "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
12618 ),
12619 (
12620 &[
12621 b"HSETEX".as_slice(),
12622 b"h",
12623 b"FNX",
12624 b"FXX",
12625 b"FIELDS",
12626 b"1",
12627 b"a",
12628 b"1",
12629 ][..],
12630 "-ERR Only one of FXX or FNX arguments can be specified",
12631 ),
12632 (
12633 &[
12634 b"HSETEX".as_slice(),
12635 b"h",
12636 b"FIELDS",
12637 b"2",
12638 b"a",
12639 b"1",
12640 b"b",
12641 ][..],
12642 "-ERR wrong number of arguments",
12643 ),
12644 (
12645 &[
12646 b"HGETEX".as_slice(),
12647 b"h",
12648 b"EX",
12649 b"-1",
12650 b"FIELDS",
12651 b"1",
12652 b"a",
12653 ][..],
12654 "-ERR invalid expire time, must be >= 0",
12655 ),
12656 (
12657 &[
12658 b"HGETEX".as_slice(),
12659 b"h",
12660 b"PXAT",
12661 b"99999999999999",
12662 b"FIELDS",
12663 b"1",
12664 b"a",
12665 ][..],
12666 "-ERR invalid expire time in 'hgetex' command",
12667 ),
12668 (
12669 &[
12670 b"HSETEX".as_slice(),
12671 b"h",
12672 b"EX",
12673 b"abc",
12674 b"FIELDS",
12675 b"1",
12676 b"a",
12677 b"1",
12678 ][..],
12679 "-ERR value is not an integer or out of range",
12680 ),
12681 ] {
12682 let reply = f.run(bad);
12683 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
12684 assert!(!reply.contains('*'), "an array header went out in front");
12685 }
12686 assert_eq!(
12687 f.run(&[b"HGET", b"h", b"a"]),
12688 "$1\r\n1\r\n",
12689 "and not one of them wrote anything"
12690 );
12691 assert_eq!(
12692 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12693 "*1\r\n:-1\r\n"
12694 );
12695 }
12696
12697 #[test]
12698 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
12699 let mut f = Fixture::new();
12700 f.run(&[b"SET", b"str", b"v"]);
12701 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12702 for cmd in [
12703 &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12704 &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12705 &[
12706 b"HGETEX".as_slice(),
12707 b"str",
12708 b"EX",
12709 b"100",
12710 b"FIELDS",
12711 b"1",
12712 b"f",
12713 ][..],
12714 &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
12715 ] {
12716 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12717 }
12718 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
12719 }
12720
12721 #[test]
12729 fn himport_writes_declared_values_into_sorted_fields() {
12730 let mut f = Fixture::new();
12731 assert_eq!(
12732 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
12733 "+OK\r\n"
12734 );
12735 assert_eq!(
12736 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
12737 "+OK\r\n"
12738 );
12739 assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
12740 assert_eq!(
12741 f.run(&[b"HGETALL", b"k"]),
12742 bulks(&["a", "3", "b", "1", "aa", "2"])
12743 );
12744 }
12745
12746 #[test]
12749 fn himport_set_replaces_the_whole_key() {
12750 let mut f = Fixture::new();
12751 f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
12752 f.run(&[b"EXPIRE", b"k", b"100"]);
12753 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12754 assert_eq!(
12755 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
12756 "+OK\r\n"
12757 );
12758 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
12759 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
12760 }
12761
12762 #[test]
12765 fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
12766 let mut f = Fixture::new();
12767 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
12768 f.run(&[b"SELECT", b"1"]);
12769 assert_eq!(
12770 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
12771 "+OK\r\n"
12772 );
12773 f.run(&[b"SELECT", b"0"]);
12774 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
12775 assert_eq!(
12776 f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
12777 "-ERR no such fieldset\r\n"
12778 );
12779 }
12780
12781 #[test]
12788 fn himport_complains_in_the_order_a_real_server_does() {
12789 let mut f = Fixture::new();
12790 f.run(&[b"SET", b"str", b"v"]);
12791 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12792 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12793 assert_eq!(
12794 f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
12795 wrong,
12796 "the type beats a missing fieldset"
12797 );
12798 assert_eq!(
12799 f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
12800 wrong,
12801 "and it beats a value count that does not fit"
12802 );
12803 assert_eq!(
12804 f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
12805 "-ERR no such fieldset\r\n"
12806 );
12807 for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
12809 let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
12810 line.extend_from_slice(values);
12811 assert_eq!(
12812 f.run(&line),
12813 "-ERR value count does not match fieldset field count\r\n",
12814 "{} values into two fields",
12815 values.len()
12816 );
12817 }
12818 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
12819 }
12820
12821 #[test]
12823 fn himport_checks_each_subcommand_count_under_its_own_name() {
12824 let mut f = Fixture::new();
12825 assert_eq!(
12826 f.run(&[b"HIMPORT"]),
12827 "-ERR wrong number of arguments for 'himport' command\r\n"
12828 );
12829 for (rest, name) in [
12830 (&["PREPARE"][..], "prepare"),
12831 (&["PREPARE", "fs"][..], "prepare"),
12832 (&["SET"][..], "set"),
12833 (&["SET", "k"][..], "set"),
12834 (&["SET", "k", "fs"][..], "set"),
12835 (&["DISCARD"][..], "discard"),
12836 (&["DISCARD", "a", "b"][..], "discard"),
12837 (&["DISCARDALL", "x"][..], "discardall"),
12838 ] {
12839 let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
12840 line.extend(rest.iter().map(|a| a.as_bytes()));
12841 assert_eq!(
12842 f.run(&line),
12843 format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
12844 "HIMPORT {}",
12845 rest.join(" ")
12846 );
12847 }
12848 assert_eq!(
12849 f.run(&[b"HIMPORT", b"NOPE", b"x"]),
12850 "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
12851 );
12852 }
12853
12854 #[test]
12857 fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
12858 let mut f = Fixture::new();
12859 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12860 assert_eq!(
12861 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
12862 "-ERR duplicate field name in fieldset\r\n"
12863 );
12864 assert_eq!(
12865 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
12866 "+OK\r\n"
12867 );
12868 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
12869 }
12870
12871 #[test]
12874 fn himport_prepare_replaces_and_the_discards_count() {
12875 let mut f = Fixture::new();
12876 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12877 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
12878 assert_eq!(
12879 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
12880 "+OK\r\n"
12881 );
12882 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
12883
12884 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
12885 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
12886 f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
12887 f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
12888 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
12889 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
12890 }
12891
12892 fn int(reply: &str) -> i64 {
12898 let body = reply
12899 .strip_prefix(':')
12900 .and_then(|s| s.strip_suffix("\r\n"))
12901 .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
12902 body.parse().expect("an integer")
12903 }
12904
12905 fn int_reply(reply: &str) -> i64 {
12906 let body = reply
12907 .strip_prefix("*1\r\n:")
12908 .and_then(|s| s.strip_suffix("\r\n"))
12909 .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
12910 body.parse().expect("an integer")
12911 }
12912
12913 fn scan_reply(reply: &str) -> (String, Vec<String>) {
12915 let mut lines = reply.split("\r\n");
12916 assert_eq!(lines.next(), Some("*2"), "got {reply}");
12917 lines.next().expect("the cursor header");
12918 let cursor = lines.next().expect("a cursor").to_owned();
12919 let header = lines.next().expect("an item count");
12920 let n: usize = header[1..].parse().expect("a count");
12921 let mut items = Vec::with_capacity(n);
12922 for _ in 0..n {
12923 lines.next().expect("an item header");
12924 items.push(lines.next().expect("an item").to_owned());
12925 }
12926 (cursor, items)
12927 }
12928
12929 fn sorted(reply: &str) -> Vec<String> {
12932 let mut lines = reply.split("\r\n");
12933 let header = lines.next().expect("a header");
12934 assert!(
12935 header.starts_with('*') || header.starts_with('~'),
12936 "got {reply}"
12937 );
12938 let n: usize = header[1..].parse().expect("a member count");
12939 let mut got = Vec::with_capacity(n);
12940 for _ in 0..n {
12941 lines.next().expect("a member header");
12942 got.push(lines.next().expect("a member").to_owned());
12943 }
12944 got.sort();
12945 got
12946 }
12947
12948 #[test]
12949 fn the_algebra_answers_what_the_sets_share_and_do_not() {
12950 let mut f = Fixture::new();
12951 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
12952 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
12953 f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
12954
12955 assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
12956 assert_eq!(
12957 sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
12958 ["1", "2", "3", "4", "5"]
12959 );
12960 assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
12961 assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
12962
12963 assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
12966 assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
12967 assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
12968 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
12969 }
12970
12971 #[test]
12972 fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
12973 let mut f = Fixture::new();
12974 f.run(&[b"SADD", b"a", b"x"]);
12975 assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
12976 assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
12977 assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
12978
12979 f.run(&[b"HELLO", b"3"]);
12980 assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
12981 assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
12982 assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
12983 assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
12984 }
12985
12986 #[test]
12987 fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
12988 let mut f = Fixture::new();
12989 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
12990 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
12991
12992 assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
12993 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
12994 assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
12995 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
12996 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
12997 assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
12998
12999 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
13002 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
13003 assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
13004 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
13005
13006 f.run(&[b"SET", b"str", b"v"]);
13009 assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
13010 assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
13011 }
13012
13013 #[test]
13014 fn sintercard_counts_without_building_and_stops_at_a_limit() {
13015 let mut f = Fixture::new();
13016 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
13017 f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
13018
13019 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
13020 assert_eq!(
13021 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
13022 ":2\r\n"
13023 );
13024 assert_eq!(
13025 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
13026 ":3\r\n",
13027 "a limit of zero is no limit"
13028 );
13029 assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
13030 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
13031
13032 assert_eq!(
13034 f.run(&[b"SINTERCARD", b"0", b"a"]),
13035 "-ERR numkeys should be greater than 0\r\n"
13036 );
13037 assert_eq!(
13038 f.run(&[b"SINTERCARD", b"abc", b"a"]),
13039 "-ERR numkeys should be greater than 0\r\n"
13040 );
13041 assert_eq!(
13042 f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
13043 "-ERR Number of keys can't be greater than number of args\r\n"
13044 );
13045 assert_eq!(
13046 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
13047 "-ERR LIMIT can't be negative\r\n"
13048 );
13049 assert_eq!(
13050 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
13051 "-ERR syntax error\r\n"
13052 );
13053 f.run(&[b"SADD", b"LIMIT", b"2"]);
13055 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
13056 }
13057
13058 #[test]
13061 fn sunioncard_and_sdiffcard_count_without_building() {
13062 let mut f = Fixture::new();
13063 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
13064 f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
13065
13066 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
13067 assert_eq!(
13068 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
13069 ":2\r\n"
13070 );
13071 assert_eq!(
13072 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
13073 ":6\r\n",
13074 "a limit of zero is no limit"
13075 );
13076 assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
13077 assert_eq!(
13078 f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
13079 ":4\r\n",
13080 "a missing key adds nothing to a union"
13081 );
13082
13083 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
13084 assert_eq!(
13085 f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
13086 ":1\r\n"
13087 );
13088 assert_eq!(
13089 f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
13090 ":2\r\n",
13091 "a difference is not symmetric"
13092 );
13093 assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
13094 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
13095 assert_eq!(
13096 f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
13097 ":0\r\n",
13098 "nothing taken away from nothing"
13099 );
13100
13101 for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
13104 assert_eq!(
13105 f.run(&[name, b"0", b"a"]),
13106 "-ERR numkeys should be greater than 0\r\n"
13107 );
13108 assert_eq!(
13109 f.run(&[name, b"abc", b"a"]),
13110 "-ERR numkeys should be greater than 0\r\n"
13111 );
13112 assert_eq!(
13113 f.run(&[name, b"-1", b"a"]),
13114 "-ERR numkeys should be greater than 0\r\n"
13115 );
13116 assert_eq!(
13117 f.run(&[name, b"3", b"a", b"b"]),
13118 "-ERR Number of keys can't be greater than number of args\r\n"
13119 );
13120 assert_eq!(
13121 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
13122 "-ERR LIMIT can't be negative\r\n"
13123 );
13124 assert_eq!(
13125 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
13126 "-ERR LIMIT can't be negative\r\n",
13127 "a LIMIT that is not a number gets the negative message too"
13128 );
13129 assert_eq!(
13130 f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
13131 "-ERR syntax error\r\n"
13132 );
13133 assert_eq!(
13134 f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
13135 "-ERR syntax error\r\n"
13136 );
13137 assert_eq!(
13138 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
13139 "-ERR syntax error\r\n"
13140 );
13141 }
13142
13143 f.run(&[b"SADD", b"LIMIT", b"2"]);
13145 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
13146 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
13147 }
13148
13149 #[test]
13150 fn the_algebra_answers_wrongtype_before_it_writes_anything() {
13151 let mut f = Fixture::new();
13152 f.run(&[b"SADD", b"a", b"1"]);
13153 f.run(&[b"SADD", b"d", b"old"]);
13154 f.run(&[b"SET", b"str", b"v"]);
13155
13156 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13157 for bad in [
13158 &[b"SINTER".as_slice(), b"a", b"str"][..],
13159 &[b"SUNION".as_slice(), b"str"][..],
13160 &[b"SDIFF".as_slice(), b"a", b"str"][..],
13161 &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
13162 &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
13163 &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
13164 &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
13165 ] {
13166 let reply = f.run(bad);
13167 assert_eq!(reply, wrong, "for {:?}", bad[0]);
13168 }
13169 assert_eq!(
13170 f.run(&[b"SMEMBERS", b"d"]),
13171 "*1\r\n$3\r\nold\r\n",
13172 "and the destination was left alone every time"
13173 );
13174 }
13175
13176 #[cfg_attr(miri, ignore = "the volume is the claim")]
13188 #[test]
13189 fn churning_sets_does_not_grow_the_server() {
13190 let mut f = Fixture::new();
13191 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
13192 let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
13193 .chain(std::iter::once(&b"s"[..]))
13194 .chain(members.iter().map(Vec::as_slice))
13195 .collect();
13196
13197 f.run(&args);
13198 f.run(&[b"DEL", b"s"]);
13199 f.server.compact_step();
13200 let after_first = f.server.memory_bytes();
13201
13202 for _ in 0..200 {
13203 f.run(&args);
13204 f.run(&[b"DEL", b"s"]);
13205 f.server.compact_step();
13206 }
13207 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13208 assert!(
13209 f.server.memory_bytes() <= after_first * 2,
13210 "held {} after two hundred passes against {after_first} after one",
13211 f.server.memory_bytes()
13212 );
13213 }
13214
13215 #[test]
13223 fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
13224 let mut f = Fixture::new();
13225 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
13226 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
13227 assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
13228 assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
13229 assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
13230 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
13231
13232 assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
13234 assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
13235 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
13236
13237 f.run(&[b"SET", b"num", b"12345"]);
13238 assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
13239 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
13240 assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
13241 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
13242 assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
13243 }
13244
13245 #[test]
13250 fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
13251 let mut f = Fixture::new();
13252 f.run(&[b"SET", b"mykey", b"foobar"]);
13253 assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
13254 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
13255 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
13256 assert_eq!(
13257 f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
13258 ":6\r\n"
13259 );
13260 assert_eq!(
13261 f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
13262 ":25\r\n"
13263 );
13264 assert_eq!(
13265 f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
13266 ":17\r\n"
13267 );
13268 assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
13269
13270 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
13273
13274 assert_eq!(
13276 f.run(&[b"BITCOUNT", b"mykey", b"0"]),
13277 "-ERR syntax error\r\n"
13278 );
13279 assert_eq!(
13280 f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
13281 "-ERR syntax error\r\n"
13282 );
13283 }
13284
13285 #[test]
13291 fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
13292 let mut f = Fixture::new();
13293 f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
13294 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
13295 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
13296 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
13297 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
13298 assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
13299
13300 f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
13301 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
13302 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
13303 assert_eq!(
13304 f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
13305 ":8\r\n"
13306 );
13307
13308 assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
13311 assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
13312 }
13313
13314 #[test]
13316 fn the_eight_combinations_write_what_a_real_server_writes() {
13317 let mut f = Fixture::new();
13318 f.run(&[b"SET", b"a", b"abc"]);
13319 f.run(&[b"SET", b"b", b"abd"]);
13320 let cases: &[(&[u8], &str)] = &[
13321 (b"AND", "ab`"),
13322 (b"OR", "abg"),
13323 (b"XOR", "\u{0}\u{0}\u{7}"),
13324 (b"DIFF", "\u{0}\u{0}\u{3}"),
13325 (b"DIFF1", "\u{0}\u{0}\u{4}"),
13326 (b"ANDOR", "ab`"),
13327 (b"ONE", "\u{0}\u{0}\u{7}"),
13328 ];
13329 for (op, want) in cases {
13330 assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
13331 assert_eq!(
13332 f.run(&[b"GET", b"d"]),
13333 format!("$3\r\n{want}\r\n"),
13334 "{op:?}"
13335 );
13336 }
13337 assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
13339 assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
13340
13341 assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
13344 assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
13345
13346 f.run(&[b"SET", b"dest", b"x"]);
13349 assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
13350 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
13351 }
13352
13353 #[test]
13355 fn bitop_names_the_operation_in_its_own_complaints() {
13356 let mut f = Fixture::new();
13357 f.run(&[b"SET", b"a", b"abc"]);
13358 assert_eq!(
13359 f.run(&[b"BITOP", b"nope", b"d", b"a"]),
13360 "-ERR syntax error\r\n"
13361 );
13362 assert_eq!(
13363 f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
13364 "-ERR BITOP NOT must be called with a single source key.\r\n"
13365 );
13366 for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
13367 assert_eq!(
13368 f.run(&[b"BITOP", op, b"d", b"a"]),
13369 format!(
13370 "-ERR BITOP {} must be called with at least two source keys.\r\n",
13371 String::from_utf8_lossy(op)
13372 )
13373 );
13374 }
13375 f.run(&[b"LPUSH", b"l", b"x"]);
13376 assert_eq!(
13377 f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
13378 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13379 );
13380 }
13381
13382 #[test]
13384 fn bitfield_reads_and_writes_packed_fields() {
13385 let mut f = Fixture::new();
13386 assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
13387 assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
13388
13389 assert_eq!(
13390 f.run(&[
13391 b"BITFIELD",
13392 b"bf",
13393 b"INCRBY",
13394 b"u2",
13395 b"100",
13396 b"1",
13397 b"GET",
13398 b"u4",
13399 b"0"
13400 ]),
13401 "*2\r\n:1\r\n:0\r\n"
13402 );
13403 assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
13406
13407 assert_eq!(
13409 f.run(&[
13410 b"BITFIELD",
13411 b"bf",
13412 b"SET",
13413 b"u8",
13414 b"#0",
13415 b"255",
13416 b"GET",
13417 b"u8",
13418 b"#0"
13419 ]),
13420 "*2\r\n:0\r\n:255\r\n"
13421 );
13422
13423 assert_eq!(
13424 f.run(&[
13425 b"BITFIELD",
13426 b"bf",
13427 b"OVERFLOW",
13428 b"SAT",
13429 b"INCRBY",
13430 b"i8",
13431 b"0",
13432 b"120",
13433 b"INCRBY",
13434 b"i8",
13435 b"0",
13436 b"120"
13437 ]),
13438 "*2\r\n:119\r\n:127\r\n"
13439 );
13440 assert_eq!(
13441 f.run(&[
13442 b"BITFIELD",
13443 b"bf2",
13444 b"OVERFLOW",
13445 b"FAIL",
13446 b"INCRBY",
13447 b"u2",
13448 b"0",
13449 b"5"
13450 ]),
13451 "*1\r\n$-1\r\n"
13452 );
13453 assert_eq!(
13454 f.run(&[
13455 b"BITFIELD",
13456 b"bf3",
13457 b"OVERFLOW",
13458 b"WRAP",
13459 b"INCRBY",
13460 b"u2",
13461 b"0",
13462 b"5"
13463 ]),
13464 "*1\r\n:1\r\n"
13465 );
13466 assert_eq!(
13467 f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
13468 "*1\r\n:4611686018427387904\r\n"
13469 );
13470 }
13471
13472 #[test]
13478 fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
13479 let mut f = Fixture::new();
13480 let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
13481 assert_eq!(
13482 f.run(&[
13483 b"BITFIELD",
13484 b"bad",
13485 b"SET",
13486 b"u8",
13487 b"0",
13488 b"1",
13489 b"GET",
13490 b"u99",
13491 b"0"
13492 ]),
13493 bad_type
13494 );
13495 assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
13496 assert_eq!(
13497 f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
13498 bad_type
13499 );
13500 assert_eq!(
13501 f.run(&[b"BITFIELD", b"bad", b"GET"]),
13502 "-ERR syntax error\r\n"
13503 );
13504 assert_eq!(
13505 f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
13506 "-ERR syntax error\r\n"
13507 );
13508 assert_eq!(
13509 f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
13510 "-ERR syntax error\r\n"
13511 );
13512 assert_eq!(
13513 f.run(&[
13514 b"BITFIELD",
13515 b"bad",
13516 b"OVERFLOW",
13517 b"NOPE",
13518 b"GET",
13519 b"u8",
13520 b"0"
13521 ]),
13522 "-ERR Invalid OVERFLOW type specified\r\n"
13523 );
13524 assert_eq!(
13525 f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
13526 "-ERR value is not an integer or out of range\r\n"
13527 );
13528 for at in [&b"#-1"[..], b"abc"] {
13529 assert_eq!(
13530 f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
13531 "-ERR bit offset is not an integer or out of range\r\n"
13532 );
13533 }
13534 }
13535
13536 #[test]
13538 fn bitfield_ro_answers_gets_and_refuses_the_rest() {
13539 let mut f = Fixture::new();
13540 f.run(&[b"SET", b"n", b"123"]);
13541 assert_eq!(
13542 f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
13543 "*1\r\n:49\r\n"
13544 );
13545 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
13547
13548 assert_eq!(
13550 f.run(&[
13551 b"BITFIELD_RO",
13552 b"n",
13553 b"OVERFLOW",
13554 b"SAT",
13555 b"GET",
13556 b"u8",
13557 b"0"
13558 ]),
13559 "*1\r\n:49\r\n"
13560 );
13561 for sub in [&b"SET"[..], b"INCRBY"] {
13562 assert_eq!(
13563 f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
13564 "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
13565 );
13566 }
13567
13568 assert_eq!(
13569 f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
13570 "*1\r\n:0\r\n"
13571 );
13572 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13573 }
13574
13575 #[test]
13577 fn an_offset_off_the_end_of_the_world_is_refused() {
13578 let mut f = Fixture::new();
13579 let bad = "-ERR bit offset is not an integer or out of range\r\n";
13580 for arg in [&b"abc"[..], b"-1", b"4294967296"] {
13581 assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
13582 assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
13583 }
13584 for arg in [&b"2"[..], b"-1"] {
13585 assert_eq!(
13586 f.run(&[b"BITPOS", b"k", arg]),
13587 "-ERR The bit argument must be 1 or 0.\r\n"
13588 );
13589 }
13590 assert_eq!(
13591 f.run(&[b"BITPOS", b"k", b"abc"]),
13592 "-ERR value is not an integer or out of range\r\n"
13593 );
13594 assert_eq!(
13595 f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
13596 "-ERR value is not an integer or out of range\r\n"
13597 );
13598 let bad_bit = "-ERR bit is not an integer or out of range\r\n";
13599 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
13600 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
13601 }
13602
13603 #[test]
13605 fn every_bitmap_command_says_wrongtype() {
13606 let mut f = Fixture::new();
13607 f.run(&[b"LPUSH", b"l", b"x"]);
13608 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13609 let cases: &[&[&[u8]]] = &[
13610 &[b"SETBIT", b"l", b"0", b"1"],
13611 &[b"GETBIT", b"l", b"0"],
13612 &[b"BITCOUNT", b"l"],
13613 &[b"BITPOS", b"l", b"1"],
13614 &[b"BITOP", b"AND", b"d", b"l"],
13615 &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
13616 &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
13617 ];
13618 for case in cases {
13619 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
13620 }
13621 }
13622
13623 #[test]
13626 fn a_sketch_is_added_to_and_counted() {
13627 let mut f = Fixture::new();
13628 assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
13630 assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
13631 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
13632 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
13633 assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
13636 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
13637
13638 assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
13639 assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
13640 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
13641 }
13642
13643 #[test]
13644 fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
13645 let mut f = Fixture::new();
13646 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13647 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";
13649 let mut reply = b"$27\r\n".to_vec();
13650 reply.extend_from_slice(want);
13651 reply.extend_from_slice(b"\r\n");
13652 assert_eq!(f.raw(&[b"GET", b"h"]), reply);
13653 }
13654
13655 #[test]
13656 fn counting_several_keys_counts_their_union() {
13657 let mut f = Fixture::new();
13658 f.run(&[b"PFADD", b"a", b"x", b"y"]);
13659 f.run(&[b"PFADD", b"b", b"y", b"z"]);
13660 assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
13661 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
13662 assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
13665 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
13666 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13667 }
13668
13669 #[test]
13670 fn a_merge_keeps_what_the_destination_had() {
13671 let mut f = Fixture::new();
13672 f.run(&[b"PFADD", b"a", b"x", b"y"]);
13673 f.run(&[b"PFADD", b"b", b"z"]);
13674 assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
13675 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
13676 f.run(&[b"PFADD", b"c", b"w"]);
13678 assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
13679 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
13680 assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
13683 assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
13684 }
13685
13686 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
13695 #[test]
13696 fn the_debug_forms_answer_four_different_shapes() {
13697 let mut f = Fixture::new();
13698 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13699 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
13700 assert_eq!(
13701 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
13702 "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
13703 );
13704 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
13705 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
13706 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
13707 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
13708 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
13709 assert_eq!(
13711 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
13712 "-ERR HLL encoding is not sparse\r\n"
13713 );
13714
13715 let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
13717 assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
13718 assert_eq!(reply.matches(":0\r\n").count(), 16381);
13719 assert_eq!(reply.matches(":1\r\n").count(), 2);
13720 assert_eq!(reply.matches(":2\r\n").count(), 1);
13721
13722 assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
13723 }
13724
13725 #[test]
13726 fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
13727 let mut f = Fixture::new();
13728 f.run(&[b"SET", b"plain", b"not a sketch"]);
13729 let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
13730 assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
13731 assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
13732 assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
13733 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
13734
13735 f.run(&[b"RPUSH", b"l", b"x"]);
13738 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13739 assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
13740 assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
13741 assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
13742 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
13743 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
13744 }
13745
13746 #[test]
13747 fn pfdebug_has_its_own_complaints() {
13748 let mut f = Fixture::new();
13749 f.run(&[b"PFADD", b"h", b"a"]);
13750 assert_eq!(
13753 f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
13754 "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
13755 );
13756 let gone = "-ERR The specified key does not exist\r\n";
13758 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
13759 assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
13760 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
13761 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
13762 assert_eq!(
13763 f.run(&[b"PFDEBUG"]),
13764 "-ERR wrong number of arguments for 'pfdebug' command\r\n"
13765 );
13766 assert_eq!(
13767 f.run(&[b"PFSELFTEST", b"x"]),
13768 "-ERR wrong number of arguments for 'pfselftest' command\r\n"
13769 );
13770 }
13771
13772 #[test]
13773 fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
13774 let mut f = Fixture::new();
13775 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13776 let reply = f.raw(&[b"GET", b"h"]);
13779 let short = reply[5..reply.len() - 3].to_vec();
13780 f.run(&[b"SET", b"h", &short]);
13781 assert_eq!(
13782 f.run(&[b"PFCOUNT", b"h"]),
13783 "-INVALIDOBJ Corrupted HLL object detected\r\n"
13784 );
13785 }
13786
13787 #[test]
13788 fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
13789 let mut f = Fixture::new();
13790 f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
13793 if cfg!(miri) {
13800 f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
13801 f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
13802 } else {
13803 for i in 0..10_000u32 {
13804 let ele = format!("e{i}");
13805 f.run(&[b"PFADD", b"big", ele.as_bytes()]);
13806 }
13807 }
13808 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
13809 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
13810
13811 for key in [&b"small"[..], b"big"] {
13812 let mut copy = key.to_vec();
13813 copy.push(b'2');
13814 let bytes = payload(&f.raw(&[b"DUMP", key]));
13815 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
13816 assert_eq!(f.raw(&[b"GET", ©]), f.raw(&[b"GET", key]));
13819 assert_eq!(
13820 f.run(&[b"PFDEBUG", b"ENCODING", ©]),
13821 f.run(&[b"PFDEBUG", b"ENCODING", key])
13822 );
13823 assert_eq!(f.run(&[b"PFCOUNT", ©]), f.run(&[b"PFCOUNT", key]));
13824 }
13825 assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
13826 assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
13827 }
13828
13829 fn bulk(s: &str) -> String {
13833 format!("${}\r\n{s}\r\n", s.len())
13834 }
13835
13836 fn bulks(parts: &[&str]) -> String {
13839 let mut s = format!("*{}\r\n", parts.len());
13840 for p in parts {
13841 s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
13842 }
13843 s
13844 }
13845
13846 #[test]
13847 fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
13848 let mut f = Fixture::new();
13849 assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
13853 assert_eq!(
13854 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13855 bulks(&["c", "b", "a"])
13856 );
13857 assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
13858 assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
13859 assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
13860 assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
13861 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
13862 assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
13863 }
13864
13865 #[test]
13866 fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
13867 let mut f = Fixture::new();
13868 assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
13869 assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
13870 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
13871 f.run(&[b"RPUSH", b"k", b"a"]);
13872 assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
13873 assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
13874 assert_eq!(
13875 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13876 bulks(&["z", "a", "y"])
13877 );
13878 }
13879
13880 #[test]
13883 fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
13884 let mut f = Fixture::new();
13885 assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
13886 assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
13887 assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
13888 assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
13889 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13890 assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
13893 assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
13894 assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
13896 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
13897 }
13898
13899 #[test]
13900 fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
13901 let mut f = Fixture::new();
13902 f.run(&[b"RPUSH", b"k", b"a"]);
13903 let range = "-ERR value is out of range, must be positive\r\n";
13904 assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
13905 assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
13906 assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
13907 assert_eq!(
13910 f.run(&[b"LPOP", b"k", b"1", b"2"]),
13911 "-ERR wrong number of arguments for 'lpop' command\r\n"
13912 );
13913 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
13914 }
13915
13916 #[test]
13917 fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
13918 let mut f = Fixture::new();
13919 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13920 assert_eq!(
13921 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13922 bulks(&["a", "b", "c"])
13923 );
13924 assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
13925 assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
13926 assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
13927 assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
13928 assert_eq!(
13929 f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
13930 bulks(&["a", "b", "c"])
13931 );
13932 assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
13935 assert_eq!(
13936 f.run(&[b"LRANGE", b"k", b"a", b"b"]),
13937 "-ERR value is not an integer or out of range\r\n"
13938 );
13939 }
13940
13941 #[test]
13942 fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
13943 let mut f = Fixture::new();
13944 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13945 assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
13946 assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
13947 assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
13948 assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
13949 assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
13950 assert_eq!(
13951 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13952 bulks(&["a", "b", "z"])
13953 );
13954 assert_eq!(
13957 f.run(&[b"LSET", b"k", b"99", b"z"]),
13958 "-ERR index out of range\r\n"
13959 );
13960 assert_eq!(
13961 f.run(&[b"LSET", b"nope", b"0", b"z"]),
13962 "-ERR no such key\r\n"
13963 );
13964 }
13965
13966 #[test]
13967 fn linsert_says_three_things_with_one_signed_number() {
13968 let mut f = Fixture::new();
13969 assert_eq!(
13972 f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
13973 ":0\r\n"
13974 );
13975 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
13976 assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
13977 assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
13978 assert_eq!(
13979 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13980 bulks(&["X", "a", "b", "Y"])
13981 );
13982 assert_eq!(
13983 f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
13984 ":-1\r\n"
13985 );
13986 assert_eq!(
13987 f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
13988 "-ERR syntax error\r\n"
13989 );
13990 }
13991
13992 #[test]
13993 fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
13994 let mut f = Fixture::new();
13995 f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
13996 assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
13997 assert_eq!(
13998 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13999 bulks(&["b", "c", "a"])
14000 );
14001 assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
14002 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
14003 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
14004 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
14005 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
14006 assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
14007 }
14008
14009 #[test]
14010 fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
14011 let mut f = Fixture::new();
14012 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
14013 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
14014 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
14015 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
14018 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
14019 assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
14020 }
14021
14022 #[test]
14023 fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
14024 let mut f = Fixture::new();
14025 f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
14026 assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
14027 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
14028 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
14029 assert_eq!(
14030 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
14031 "*2\r\n:0\r\n:3\r\n"
14032 );
14033 assert_eq!(
14034 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
14035 "*3\r\n:6\r\n:3\r\n:0\r\n"
14036 );
14037 assert_eq!(
14040 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
14041 "*1\r\n:0\r\n"
14042 );
14043 assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
14046 assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
14047 assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
14048 assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
14049 }
14050
14051 #[test]
14052 fn lpos_words_its_three_mistakes_the_way_redis_does() {
14053 let mut f = Fixture::new();
14054 f.run(&[b"RPUSH", b"p", b"a"]);
14055 assert_eq!(
14058 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
14059 "-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"
14060 );
14061 assert_eq!(
14062 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
14063 "-ERR COUNT can't be negative\r\n"
14064 );
14065 assert_eq!(
14066 f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
14067 "-ERR MAXLEN can't be negative\r\n"
14068 );
14069 assert_eq!(
14070 f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
14071 "-ERR syntax error\r\n"
14072 );
14073 assert_eq!(
14074 f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
14075 "-ERR syntax error\r\n"
14076 );
14077 }
14078
14079 #[test]
14080 fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
14081 let mut f = Fixture::new();
14082 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
14083 assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
14084 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
14085 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
14086 assert_eq!(
14087 f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
14088 "$1\r\na\r\n"
14089 );
14090 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
14091 f.run(&[b"DEL", b"r"]);
14094 f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
14095 assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
14096 assert_eq!(
14097 f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
14098 bulks(&["3", "1", "2"])
14099 );
14100 assert_eq!(
14101 f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
14102 "$-1\r\n"
14103 );
14104 assert_eq!(
14105 f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
14106 "-ERR syntax error\r\n"
14107 );
14108 }
14109
14110 #[test]
14111 fn a_move_checks_the_destination_before_it_takes_anything() {
14112 let mut f = Fixture::new();
14113 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
14114 f.run(&[b"SET", b"str", b"v"]);
14115 assert_eq!(
14116 f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
14117 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
14118 );
14119 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
14121 }
14122
14123 #[test]
14124 fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
14125 for (from, to, order, want) in [
14129 ("LEFT", "RIGHT", "OBO", ["a", "b"]),
14130 ("LEFT", "RIGHT", "BULK", ["a", "b"]),
14131 ("LEFT", "LEFT", "OBO", ["b", "a"]),
14132 ("LEFT", "LEFT", "BULK", ["a", "b"]),
14133 ("RIGHT", "LEFT", "OBO", ["d", "e"]),
14134 ("RIGHT", "LEFT", "BULK", ["d", "e"]),
14135 ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
14136 ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
14137 ] {
14138 let mut f = Fixture::new();
14139 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
14140 let how = format!("{from} {to} {order}");
14141 let reply = f.run(&[
14142 b"LMOVEM",
14143 b"s",
14144 b"d",
14145 from.as_bytes(),
14146 to.as_bytes(),
14147 b"COUNT",
14148 b"2",
14149 order.as_bytes(),
14150 ]);
14151 assert_eq!(reply, bulks(&want), "the reply for {how}");
14152 assert_eq!(
14153 f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
14154 bulks(&want),
14155 "the destination for {how}"
14156 );
14157 }
14158 }
14159
14160 #[test]
14161 fn a_block_move_of_one_needs_no_count_at_all() {
14162 let mut f = Fixture::new();
14163 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
14164 assert_eq!(
14165 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
14166 bulks(&["a"])
14167 );
14168 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
14169 assert_eq!(
14172 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
14173 "-ERR syntax error\r\n"
14174 );
14175 assert_eq!(
14176 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
14177 "-ERR syntax error\r\n"
14178 );
14179 }
14180
14181 #[test]
14182 fn a_block_move_with_exactly_takes_all_of_them_or_none() {
14183 let mut f = Fixture::new();
14184 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
14185 assert_eq!(
14189 f.run(&[
14190 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
14191 ]),
14192 "*-1\r\n"
14193 );
14194 assert_eq!(
14195 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
14196 bulks(&["a", "b", "c"])
14197 );
14198 assert_eq!(
14200 f.run(&[
14201 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
14202 ]),
14203 bulks(&["a", "b", "c"])
14204 );
14205 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
14206 assert_eq!(
14207 f.run(&[
14208 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
14209 ]),
14210 "*-1\r\n"
14211 );
14212 }
14213
14214 #[test]
14215 fn a_block_move_onto_itself_rotates_by_the_count() {
14216 let mut f = Fixture::new();
14217 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
14218 assert_eq!(
14219 f.run(&[
14220 b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
14221 ]),
14222 bulks(&["a", "b"])
14223 );
14224 assert_eq!(
14225 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
14226 bulks(&["c", "a", "b"])
14227 );
14228 }
14229
14230 #[test]
14231 fn a_block_move_reads_the_count_before_the_ordering_word() {
14232 let mut f = Fixture::new();
14233 f.run(&[b"RPUSH", b"s", b"a", b"b"]);
14234 f.run(&[b"SET", b"str", b"v"]);
14235 let count = "-ERR count should be greater than 0\r\n";
14236 assert_eq!(
14237 f.run(&[
14238 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
14239 ]),
14240 count
14241 );
14242 assert_eq!(
14243 f.run(&[
14244 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
14245 ]),
14246 count
14247 );
14248 assert_eq!(
14249 f.run(&[
14250 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
14251 ]),
14252 "-ERR syntax error\r\n"
14253 );
14254 assert_eq!(
14255 f.run(&[
14256 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
14257 ]),
14258 "-ERR syntax error\r\n"
14259 );
14260 assert_eq!(
14263 f.run(&[
14264 b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
14265 ]),
14266 count
14267 );
14268 assert_eq!(
14269 f.run(&[
14270 b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
14271 ]),
14272 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
14273 );
14274 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
14275 }
14276
14277 #[test]
14278 fn lmpop_answers_from_the_first_key_that_has_anything() {
14279 let mut f = Fixture::new();
14280 f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
14281 assert_eq!(
14284 f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
14285 "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
14286 );
14287 assert_eq!(
14288 f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
14289 "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
14290 );
14291 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
14292 assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
14295 }
14296
14297 #[test]
14298 fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
14299 let mut f = Fixture::new();
14300 f.run(&[b"RPUSH", b"k", b"a"]);
14301 assert_eq!(
14302 f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
14303 "-ERR numkeys should be greater than 0\r\n"
14304 );
14305 assert_eq!(
14306 f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
14307 "-ERR numkeys should be greater than 0\r\n"
14308 );
14309 assert_eq!(
14310 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
14311 "-ERR count should be greater than 0\r\n"
14312 );
14313 assert_eq!(
14316 f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
14317 "-ERR syntax error\r\n"
14318 );
14319 assert_eq!(
14320 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
14321 "-ERR syntax error\r\n"
14322 );
14323 assert_eq!(
14324 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
14325 "-ERR syntax error\r\n"
14326 );
14327 assert_eq!(
14328 f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
14329 "-ERR syntax error\r\n"
14330 );
14331 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
14332 }
14333
14334 #[test]
14335 fn every_list_command_says_wrongtype_and_writes_nothing() {
14336 let mut f = Fixture::new();
14337 f.run(&[b"SET", b"str", b"v"]);
14338 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14339 for cmd in [
14340 &[b"LPUSH".as_slice(), b"str", b"a"][..],
14341 &[b"RPUSH", b"str", b"a"],
14342 &[b"LPUSHX", b"str", b"a"],
14343 &[b"RPUSHX", b"str", b"a"],
14344 &[b"LPOP", b"str"],
14345 &[b"LPOP", b"str", b"2"],
14346 &[b"RPOP", b"str"],
14347 &[b"LLEN", b"str"],
14348 &[b"LRANGE", b"str", b"0", b"-1"],
14349 &[b"LINDEX", b"str", b"0"],
14350 &[b"LSET", b"str", b"0", b"a"],
14351 &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
14352 &[b"LREM", b"str", b"0", b"a"],
14353 &[b"LTRIM", b"str", b"0", b"-1"],
14354 &[b"LPOS", b"str", b"a"],
14355 &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
14356 &[b"RPOPLPUSH", b"str", b"d"],
14357 &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
14358 &[b"LMPOP", b"1", b"str", b"LEFT"],
14359 ] {
14360 assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
14361 }
14362 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
14363 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
14364 }
14365
14366 #[test]
14370 fn a_timeout_has_three_ways_of_being_wrong() {
14371 let mut f = Fixture::new();
14372 let not_float = "-ERR timeout is not a float or out of range\r\n";
14373 let range = "-ERR timeout is out of range\r\n";
14374 for (bad, want) in [
14375 (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
14376 (&[b"BLPOP", b"k", b"nan"], not_float),
14377 (&[b"BLPOP", b"k", b""], not_float),
14378 (&[b"BLPOP", b"k", b" 1"], not_float),
14381 (&[b"BLPOP", b"k", b"1 "], not_float),
14382 (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
14383 (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
14384 (&[b"BLPOP", b"k", b"1e400"], range),
14387 (&[b"BLPOP", b"k", b"inf"], range),
14388 (&[b"BLPOP", b"k", b"9999999999999999"], range),
14389 (&[b"BRPOP", b"k", b"abc"], not_float),
14390 (
14391 &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
14392 not_float,
14393 ),
14394 (
14395 &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
14396 "-ERR timeout is negative\r\n",
14397 ),
14398 (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
14399 ] {
14400 assert_eq!(f.run(bad), want, "for {bad:?}");
14401 }
14402 }
14403
14404 #[test]
14407 fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
14408 let mut f = Fixture::new();
14409 for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
14410 let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
14411 assert_eq!(flow, Flow::Block, "for {timeout:?}");
14412 assert!(out.is_empty(), "for {timeout:?}");
14413 }
14414 let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
14418 assert_eq!(flow, Flow::Block);
14419 assert!(out.is_empty());
14420 }
14421
14422 #[test]
14423 fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
14424 let mut f = Fixture::new();
14425 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
14426
14427 assert_eq!(
14430 f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
14431 (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
14432 );
14433 assert_eq!(
14434 f.run(&[b"BRPOP", b"L", b"0"]),
14435 "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
14436 );
14437 assert_eq!(
14438 f.run(&[
14439 b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
14440 ]),
14441 "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14442 );
14443 assert_eq!(
14444 f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
14445 "$1\r\nd\r\n"
14446 );
14447 assert_eq!(
14448 f.run(&[b"EXISTS", b"L"]),
14449 ":0\r\n",
14450 "and the key went with it"
14451 );
14452 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
14453 f.run(&[b"RPUSH", b"D", b"x"]);
14456 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
14457 assert_eq!(
14458 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
14459 "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
14460 );
14461 }
14462
14463 #[test]
14464 fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
14465 let mut f = Fixture::new();
14466 f.run(&[b"RPUSH", b"k", b"a"]);
14467 for (bad, want) in [
14468 (
14469 &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
14470 "-ERR numkeys should be greater than 0\r\n",
14471 ),
14472 (
14473 &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
14474 "-ERR numkeys should be greater than 0\r\n",
14475 ),
14476 (
14479 &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
14480 "-ERR syntax error\r\n",
14481 ),
14482 (
14483 &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
14484 "-ERR syntax error\r\n",
14485 ),
14486 (
14487 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
14488 "-ERR syntax error\r\n",
14489 ),
14490 (
14491 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
14492 "-ERR syntax error\r\n",
14493 ),
14494 (
14497 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
14498 "-ERR count should be greater than 0\r\n",
14499 ),
14500 (
14501 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
14502 "-ERR count should be greater than 0\r\n",
14503 ),
14504 ] {
14505 assert_eq!(f.run(bad), want, "for {bad:?}");
14506 }
14507 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
14508 }
14509
14510 #[test]
14511 fn a_blocking_move_reads_its_directions_before_its_timeout() {
14512 let mut f = Fixture::new();
14513 assert_eq!(
14516 f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
14517 "-ERR syntax error\r\n"
14518 );
14519 assert_eq!(
14520 f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
14521 "-ERR syntax error\r\n"
14522 );
14523 }
14524
14525 #[test]
14529 fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
14530 let mut f = Fixture::new();
14531 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
14532 assert_eq!(
14533 f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
14534 (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
14535 );
14536 assert_eq!(
14537 f.run(&[
14538 b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
14539 ]),
14540 bulks(&["e", "d"])
14541 );
14542 assert_eq!(
14543 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
14544 bulks(&["a", "e", "d"])
14545 );
14546 assert_eq!(
14548 f.run(&[
14549 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
14550 ]),
14551 bulks(&["b", "c"])
14552 );
14553 assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
14554 }
14555
14556 #[test]
14560 fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
14561 let mut f = Fixture::new();
14562 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
14563 assert_eq!(
14565 f.flow(&[
14566 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
14567 ]),
14568 (Flow::Continue, bulks(&["a", "b"]))
14569 );
14570
14571 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
14572 assert_eq!(
14575 f.flow(&[
14576 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
14577 ])
14578 .0,
14579 Flow::Block
14580 );
14581 assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
14582 }
14583
14584 #[test]
14585 fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
14586 let mut f = Fixture::new();
14587 let syntax = "-ERR syntax error\r\n";
14588 assert_eq!(
14590 f.run(&[
14591 b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
14592 ]),
14593 syntax
14594 );
14595 assert_eq!(
14597 f.run(&[
14598 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
14599 ]),
14600 "-ERR timeout is not a float or out of range\r\n"
14601 );
14602 assert_eq!(
14603 f.run(&[
14604 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
14605 ]),
14606 "-ERR timeout is negative\r\n"
14607 );
14608 assert_eq!(
14610 f.run(&[
14611 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
14612 ]),
14613 "-ERR count should be greater than 0\r\n"
14614 );
14615 assert_eq!(
14616 f.run(&[
14617 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
14618 ]),
14619 syntax
14620 );
14621 assert_eq!(
14624 f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
14625 syntax
14626 );
14627 assert_eq!(
14628 f.run(&[
14629 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
14630 ]),
14631 syntax
14632 );
14633 }
14634
14635 #[test]
14638 fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
14639 let mut f = Fixture::new();
14640 f.run(&[b"SET", b"S", b"v"]);
14641 f.run(&[b"RPUSH", b"D", b"x"]);
14642 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14643
14644 assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
14645 assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
14648 assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
14649 assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
14650 assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
14651 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
14654 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
14655 assert_eq!(
14656 f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
14657 wrong
14658 );
14659 assert_eq!(
14660 f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
14661 wrong
14662 );
14663
14664 assert_eq!(
14668 f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
14669 .0,
14670 Flow::Block
14671 );
14672 assert_eq!(
14676 f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
14677 .0,
14678 Flow::Block
14679 );
14680 f.run(&[b"RPUSH", b"E", b"1", b"2"]);
14681 assert_eq!(
14682 f.flow(&[
14683 b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
14684 ])
14685 .0,
14686 Flow::Block
14687 );
14688 }
14689
14690 #[cfg_attr(miri, ignore = "the volume is the claim")]
14695 #[test]
14696 fn churning_lists_does_not_grow_the_server() {
14697 let mut f = Fixture::new();
14698 let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
14699 let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
14700 .into_iter()
14701 .chain(vals.iter().map(Vec::as_slice))
14702 .collect();
14703
14704 f.run(&args);
14705 f.run(&[b"DEL", b"k"]);
14706 f.server.compact_step();
14707 let after_first = f.server.memory_bytes();
14708
14709 for _ in 0..200 {
14710 f.run(&args);
14711 f.run(&[b"LTRIM", b"k", b"1", b"0"]);
14712 f.server.compact_step();
14713 }
14714 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14715 assert!(
14716 f.server.memory_bytes() <= after_first * 2,
14717 "held {} after two hundred passes against {after_first} after one",
14718 f.server.memory_bytes()
14719 );
14720 }
14721
14722 #[test]
14725 fn a_sorted_set_takes_scores_and_gives_them_back() {
14726 let mut f = Fixture::new();
14727 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
14728 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
14729 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
14730 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
14731 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
14732 assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
14733 assert_eq!(
14734 f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
14735 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
14736 );
14737 assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
14738 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
14739 assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
14741 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
14742 }
14743
14744 #[test]
14745 fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
14746 let mut f = Fixture::new();
14747 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
14748 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
14749 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
14750 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
14751
14752 f.out = Out::new(Proto::Resp3);
14753 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
14754 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
14755 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
14756 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
14757 }
14758
14759 #[test]
14760 fn the_zadd_options_gate_what_gets_written() {
14761 let mut f = Fixture::new();
14762 f.run(&[b"ZADD", b"z", b"5", b"a"]);
14763 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
14765 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
14766 assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
14767 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
14768 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
14770 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
14771 assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
14772 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
14774 assert_eq!(
14775 f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
14776 ":2\r\n"
14777 );
14778 }
14779
14780 #[test]
14781 fn zadd_incr_answers_a_score_or_nothing_at_all() {
14782 let mut f = Fixture::new();
14783 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
14784 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
14785 assert_eq!(
14788 f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
14789 "$-1\r\n"
14790 );
14791 assert_eq!(
14792 f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
14793 "$-1\r\n"
14794 );
14795 assert_eq!(
14796 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
14797 "$-1\r\n"
14798 );
14799 assert_eq!(
14800 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
14801 "$1\r\n8\r\n"
14802 );
14803 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
14804 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
14805 }
14806
14807 #[test]
14808 fn the_two_infinities_will_not_be_added_together() {
14809 let mut f = Fixture::new();
14810 f.run(&[b"ZADD", b"z", b"inf", b"m"]);
14811 let nan = "-ERR resulting score is not a number (NaN)\r\n";
14812 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
14813 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
14814 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
14815 assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
14817 }
14818
14819 #[test]
14820 fn zadd_says_its_mistakes_the_way_redis_says_them() {
14821 let mut f = Fixture::new();
14822 assert_eq!(
14825 f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
14826 "-ERR syntax error\r\n"
14827 );
14828 assert_eq!(
14829 f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
14830 "-ERR XX and NX options at the same time are not compatible\r\n"
14831 );
14832 let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
14833 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
14834 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
14835 assert_eq!(
14836 f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
14837 "-ERR INCR option supports a single increment-element pair\r\n"
14838 );
14839 assert_eq!(
14841 f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
14842 "-ERR syntax error\r\n"
14843 );
14844 assert_eq!(
14846 f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
14847 "-ERR value is not a valid float\r\n"
14848 );
14849 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
14850 }
14851
14852 #[test]
14853 fn a_rank_says_where_a_member_sits_from_either_end() {
14854 let mut f = Fixture::new();
14855 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14856 assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
14857 assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
14858 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
14859 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
14860 assert_eq!(
14862 f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
14863 "*2\r\n:1\r\n$1\r\n2\r\n"
14864 );
14865 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
14866 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
14867 assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
14868 assert_eq!(
14871 f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
14872 "-ERR syntax error\r\n"
14873 );
14874 assert_eq!(
14875 f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
14876 "-ERR wrong number of arguments for 'zrevrank' command\r\n"
14877 );
14878 }
14879
14880 #[test]
14881 fn the_two_counts_read_their_two_kinds_of_bound() {
14882 let mut f = Fixture::new();
14883 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14884 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
14885 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
14886 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
14887 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
14888 assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
14889 assert_eq!(
14890 f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
14891 "-ERR min or max is not a float\r\n"
14892 );
14893
14894 f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
14895 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
14896 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
14897 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
14898 assert_eq!(
14901 f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
14902 "-ERR min or max not valid string range item\r\n"
14903 );
14904 }
14905
14906 #[test]
14912 fn one_range_command_selects_by_rank_or_score_or_name() {
14913 let mut f = Fixture::new();
14914 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14915 assert_eq!(
14916 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
14917 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
14918 );
14919 assert_eq!(
14920 f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
14921 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14922 );
14923 assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
14924 assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
14925 assert_eq!(
14928 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
14929 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14930 );
14931 assert_eq!(
14932 f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
14933 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14934 );
14935 assert_eq!(
14938 f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
14939 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
14940 );
14941 assert_eq!(
14942 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
14943 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
14944 );
14945 assert_eq!(
14946 f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
14947 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14948 );
14949 }
14950
14951 #[test]
14954 fn the_older_range_spellings_name_their_high_end_first() {
14955 let mut f = Fixture::new();
14956 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14957 assert_eq!(
14958 f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
14959 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14960 );
14961 assert_eq!(
14962 f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
14963 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
14964 );
14965 assert_eq!(
14966 f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
14967 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14968 );
14969 assert_eq!(
14970 f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
14971 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
14972 );
14973 assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
14977 assert_eq!(
14978 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
14979 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
14980 );
14981 assert_eq!(
14982 f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
14983 "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
14984 );
14985 for cmd in [
14988 &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
14989 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
14990 &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
14991 ] {
14992 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
14993 }
14994 }
14995
14996 #[test]
14999 fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
15000 let mut f = Fixture::new();
15001 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15002 assert_eq!(
15003 f.run(&[
15004 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
15005 ]),
15006 "*1\r\n$1\r\nb\r\n"
15007 );
15008 assert_eq!(
15010 f.run(&[
15011 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
15012 ]),
15013 "*0\r\n"
15014 );
15015 assert_eq!(
15016 f.run(&[
15017 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
15018 ]),
15019 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
15020 );
15021 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";
15023 assert_eq!(
15024 f.run(&[
15025 b"ZRANGEBYSCORE",
15026 b"z",
15027 b"1",
15028 b"3",
15029 b"WITHSCORES",
15030 b"LIMIT",
15031 b"0",
15032 b"2"
15033 ]),
15034 both
15035 );
15036 assert_eq!(
15037 f.run(&[
15038 b"ZRANGEBYSCORE",
15039 b"z",
15040 b"1",
15041 b"3",
15042 b"LIMIT",
15043 b"0",
15044 b"2",
15045 b"WITHSCORES"
15046 ]),
15047 both
15048 );
15049 let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
15052 assert_eq!(
15053 f.run(&[
15054 b"ZREVRANGE",
15055 b"z",
15056 b"0",
15057 b"-1",
15058 b"WITHSCORES",
15059 b"LIMIT",
15060 b"0",
15061 b"1"
15062 ]),
15063 needs_by
15064 );
15065 assert_eq!(
15066 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
15067 needs_by
15068 );
15069 let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
15070 assert_eq!(
15071 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
15072 not_bylex
15073 );
15074 assert_eq!(
15075 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
15076 not_bylex
15077 );
15078 for cmd in [
15081 &[
15082 b"ZRANGE".as_slice(),
15083 b"z",
15084 b"0",
15085 b"-1",
15086 b"BYSCORE",
15087 b"BYLEX",
15088 ][..],
15089 &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
15090 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
15091 ] {
15092 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15093 }
15094 assert_eq!(
15095 f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
15096 "-ERR min or max is not a float\r\n"
15097 );
15098 assert_eq!(
15099 f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
15100 "-ERR min or max not valid string range item\r\n"
15101 );
15102 assert_eq!(
15103 f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
15104 "-ERR value is not an integer or out of range\r\n"
15105 );
15106 }
15107
15108 #[test]
15111 fn withscores_nests_on_resp3_and_flattens_on_resp2() {
15112 let mut f = Fixture::new();
15113 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15114 assert_eq!(
15115 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
15116 "*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"
15117 );
15118 f.out = Out::new(Proto::Resp3);
15119 assert_eq!(
15120 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
15121 "*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"
15122 );
15123 assert_eq!(
15124 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
15125 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
15126 );
15127 }
15128
15129 #[test]
15131 fn a_range_store_writes_the_window_into_another_key() {
15132 let mut f = Fixture::new();
15133 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15134 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
15135 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
15138 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
15139 assert_eq!(
15140 f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
15141 ":2\r\n"
15142 );
15143 assert_eq!(
15144 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
15145 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15146 );
15147 assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
15150 assert_eq!(
15151 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
15152 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15153 );
15154 assert_eq!(
15157 f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
15158 "-ERR syntax error\r\n"
15159 );
15160 }
15161
15162 #[test]
15165 fn the_three_removals_share_their_window_with_the_reads() {
15166 let mut f = Fixture::new();
15167 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15168 assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
15169 assert_eq!(
15170 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
15171 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
15172 );
15173 assert_eq!(
15174 f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
15175 ":1\r\n"
15176 );
15177 assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
15178 assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
15180 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15181 assert_eq!(
15182 f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
15183 ":0\r\n"
15184 );
15185 assert_eq!(
15186 f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
15187 "-ERR value is not an integer or out of range\r\n"
15188 );
15189 }
15190
15191 #[test]
15193 fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
15194 let mut f = Fixture::new();
15195 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15196 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
15197 assert_eq!(
15198 f.run(&[b"ZUNION", b"2", b"z", b"y"]),
15199 "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
15200 );
15201 assert_eq!(
15204 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
15205 "*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"
15206 );
15207 assert_eq!(
15208 f.run(&[
15209 b"ZUNION",
15210 b"2",
15211 b"z",
15212 b"y",
15213 b"WEIGHTS",
15214 b"2",
15215 b"3",
15216 b"WITHSCORES"
15217 ]),
15218 "*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"
15219 );
15220 assert_eq!(
15221 f.run(&[
15222 b"ZUNION",
15223 b"2",
15224 b"z",
15225 b"y",
15226 b"AGGREGATE",
15227 b"MIN",
15228 b"WITHSCORES"
15229 ]),
15230 "*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"
15231 );
15232 assert_eq!(
15233 f.run(&[
15234 b"ZUNION",
15235 b"2",
15236 b"z",
15237 b"y",
15238 b"AGGREGATE",
15239 b"MAX",
15240 b"WITHSCORES"
15241 ]),
15242 "*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"
15243 );
15244 assert_eq!(
15245 f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
15246 "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
15247 );
15248 assert_eq!(
15249 f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
15250 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
15251 );
15252 assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
15253 f.run(&[b"SADD", b"p", b"a", b"d"]);
15256 assert_eq!(
15257 f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
15258 "*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"
15259 );
15260 for cmd in [
15263 &[
15264 b"ZDIFF".as_slice(),
15265 b"2",
15266 b"z",
15267 b"y",
15268 b"WEIGHTS",
15269 b"1",
15270 b"1",
15271 ][..],
15272 &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
15273 ] {
15274 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15275 }
15276 }
15277
15278 #[test]
15280 fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
15281 let mut f = Fixture::new();
15282 f.run(&[b"ZADD", b"z", b"1", b"a"]);
15283 f.run(&[b"ZADD", b"y", b"2", b"b"]);
15284 assert_eq!(
15286 f.run(&[b"ZUNION", b"0", b"z"]),
15287 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
15288 );
15289 assert_eq!(
15290 f.run(&[b"ZUNION", b"-1", b"z"]),
15291 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
15292 );
15293 assert_eq!(
15294 f.run(&[b"ZINTERCARD", b"0", b"z"]),
15295 "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
15296 );
15297 assert_eq!(
15300 f.run(&[b"ZUNION", b"3", b"z", b"y"]),
15301 "-ERR syntax error\r\n"
15302 );
15303 assert_eq!(
15304 f.run(&[b"ZUNION", b"x", b"z"]),
15305 "-ERR value is not an integer or out of range\r\n"
15306 );
15307 assert_eq!(
15310 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
15311 "-ERR syntax error\r\n"
15312 );
15313 assert_eq!(
15314 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
15315 "-ERR weight value is not a float\r\n"
15316 );
15317 assert_eq!(
15318 f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
15319 "-ERR syntax error\r\n"
15320 );
15321 }
15322
15323 #[test]
15325 fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
15326 let mut f = Fixture::new();
15327 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15328 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
15329 assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
15330 assert_eq!(
15331 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
15332 "*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"
15333 );
15334 assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
15335 assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
15336 assert_eq!(
15339 f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
15340 ":0\r\n"
15341 );
15342 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
15343 assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
15345 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
15346 for cmd in [
15347 &[
15348 b"ZUNIONSTORE".as_slice(),
15349 b"d",
15350 b"2",
15351 b"z",
15352 b"y",
15353 b"WITHSCORES",
15354 ][..],
15355 &[
15356 b"ZDIFFSTORE",
15357 b"d",
15358 b"2",
15359 b"z",
15360 b"y",
15361 b"WEIGHTS",
15362 b"1",
15363 b"1",
15364 ],
15365 ] {
15366 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15367 }
15368 }
15369
15370 #[test]
15372 fn intercard_counts_and_stops_at_its_limit() {
15373 let mut f = Fixture::new();
15374 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15375 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
15376 assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
15377 assert_eq!(
15379 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
15380 ":2\r\n"
15381 );
15382 assert_eq!(
15383 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
15384 ":1\r\n"
15385 );
15386 let bad = "-ERR LIMIT can't be negative\r\n";
15389 assert_eq!(
15390 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
15391 bad
15392 );
15393 assert_eq!(
15394 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
15395 bad
15396 );
15397 for cmd in [
15398 &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
15399 &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
15400 &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
15401 ] {
15402 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15403 }
15404 }
15405
15406 #[test]
15408 fn a_draw_answers_one_member_or_an_array_of_them() {
15409 let mut f = Fixture::new();
15410 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15411 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
15414 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
15415 assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
15416 assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
15417 let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
15420 assert!(all.starts_with("*3\r\n"), "{all}");
15421 for m in ["a", "b", "c"] {
15422 assert!(all.contains(m), "{all}");
15423 }
15424 assert!(
15427 f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
15428 "five draws with replacement"
15429 );
15430 assert!(
15431 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
15432 .starts_with("*4\r\n"),
15433 "two pairs, flat on RESP2"
15434 );
15435 f.out = Out::new(Proto::Resp3);
15436 let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
15437 assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
15438 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
15439 f.out = Out::new(Proto::Resp2);
15440 assert_eq!(
15441 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
15442 "-ERR syntax error\r\n"
15443 );
15444 assert_eq!(
15445 f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
15446 "-ERR value is not an integer or out of range\r\n"
15447 );
15448 }
15449
15450 #[test]
15452 fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
15453 let mut f = Fixture::new();
15454 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15455 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";
15456 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
15457 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
15458 assert_eq!(
15459 f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
15460 "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
15461 );
15462 assert_eq!(
15463 f.run(&[b"ZSCAN", b"nokey", b"0"]),
15464 "*2\r\n$1\r\n0\r\n*0\r\n"
15465 );
15466 f.out = Out::new(Proto::Resp3);
15469 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
15470 f.out = Out::new(Proto::Resp2);
15471 assert_eq!(
15472 f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
15473 "-ERR NOVALUES option can only be used in HSCAN\r\n"
15474 );
15475 assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
15476 assert_eq!(
15477 f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
15478 "-ERR syntax error\r\n"
15479 );
15480 }
15481
15482 #[test]
15484 fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
15485 let mut f = Fixture::new();
15486 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15487 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
15489 assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
15490 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
15491 assert_eq!(
15493 f.run(&[b"ZPOPMIN", b"z", b"2"]),
15494 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
15495 );
15496 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
15499 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
15500 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
15501 assert_eq!(
15503 f.run(&[b"ZPOPMIN", b"z", b"9"]),
15504 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15505 );
15506 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15507
15508 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
15509 f.out = Out::new(Proto::Resp3);
15510 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
15511 assert_eq!(
15512 f.run(&[b"ZPOPMIN", b"z", b"1"]),
15513 "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
15514 );
15515 f.out = Out::new(Proto::Resp2);
15516 let bad = "-ERR value is out of range, must be positive\r\n";
15519 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
15520 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
15521 assert_eq!(
15522 f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
15523 "-ERR syntax error\r\n"
15524 );
15525 }
15526
15527 #[test]
15529 fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
15530 let mut f = Fixture::new();
15531 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15532 assert_eq!(
15533 f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
15534 "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
15535 );
15536 assert_eq!(
15539 f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
15540 "*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"
15541 );
15542 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
15544 f.out = Out::new(Proto::Resp3);
15545 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
15546 f.out = Out::new(Proto::Resp2);
15547 let numkeys = "-ERR numkeys should be greater than 0\r\n";
15548 for bad in [
15549 &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
15550 &[b"ZMPOP", b"-1", b"z", b"MIN"],
15551 &[b"ZMPOP", b"x", b"z", b"MIN"],
15552 ] {
15553 assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
15554 }
15555 let count = "-ERR count should be greater than 0\r\n";
15556 for bad in [
15557 &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
15558 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
15559 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
15560 ] {
15561 assert_eq!(f.run(bad), count, "{:?}", bad[5]);
15562 }
15563 let syntax = "-ERR syntax error\r\n";
15564 for bad in [
15565 &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
15568 &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
15569 &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
15570 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
15571 ] {
15572 assert_eq!(f.run(bad), syntax, "{bad:?}");
15573 }
15574 }
15575
15576 #[test]
15579 fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
15580 let mut f = Fixture::new();
15581 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15582 assert_eq!(
15583 f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
15584 (
15585 Flow::Continue,
15586 "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
15587 )
15588 );
15589 assert_eq!(
15590 f.run(&[b"BZPOPMAX", b"z", b"0"]),
15591 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
15592 );
15593 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
15594 assert_eq!(
15595 f.run(&[
15596 b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
15597 ]),
15598 "*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"
15599 );
15600 f.out = Out::new(Proto::Resp3);
15601 assert_eq!(
15602 f.run(&[b"BZPOPMIN", b"z", b"0"]),
15603 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
15604 );
15605 f.out = Out::new(Proto::Resp2);
15606 assert_eq!(
15608 f.flow(&[b"BZPOPMIN", b"z", b"0"]),
15609 (Flow::Block, String::new())
15610 );
15611 assert_eq!(
15612 f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
15613 (Flow::Block, String::new())
15614 );
15615 assert_eq!(
15618 f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
15619 "-ERR timeout is not a float or out of range\r\n"
15620 );
15621 assert_eq!(
15622 f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
15623 "-ERR numkeys should be greater than 0\r\n"
15624 );
15625 assert_eq!(
15626 f.run(&[b"BZPOPMIN", b"z", b"-1"]),
15627 "-ERR timeout is negative\r\n"
15628 );
15629 }
15630
15631 #[test]
15635 fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
15636 let mut f = Fixture::new();
15637 assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
15638 assert_eq!(f.server.parked(), 1);
15639 f.run(&[b"SET", b"z", b"v"]);
15642 let mut out = Out::new(Proto::Resp2);
15643 assert!(!f.server.serve_waiter(7, 0, &mut out));
15644 assert!(out.as_slice().is_empty());
15645 f.run(&[b"DEL", b"z"]);
15646 f.run(&[b"ZADD", b"z", b"5", b"m"]);
15647 assert!(f.server.serve_waiter(7, 0, &mut out));
15648 assert_eq!(
15649 core::str::from_utf8(out.as_slice()).expect("ascii"),
15650 "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
15651 );
15652 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15655 }
15656
15657 #[test]
15658 fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
15659 let mut f = Fixture::new();
15660 f.run(&[b"SET", b"s", b"v"]);
15661 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
15662 for cmd in [
15663 &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
15664 &[b"ZINCRBY", b"s", b"1", b"a"],
15665 &[b"ZCARD", b"s"],
15666 &[b"ZSCORE", b"s", b"a"],
15667 &[b"ZMSCORE", b"s", b"a"],
15668 &[b"ZREM", b"s", b"a"],
15669 &[b"ZRANK", b"s", b"a"],
15670 &[b"ZREVRANK", b"s", b"a"],
15671 &[b"ZCOUNT", b"s", b"1", b"2"],
15672 &[b"ZLEXCOUNT", b"s", b"-", b"+"],
15673 &[b"ZRANGE", b"s", b"0", b"-1"],
15674 &[b"ZREVRANGE", b"s", b"0", b"-1"],
15675 &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
15676 &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
15677 &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
15678 &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
15679 &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
15680 &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
15681 &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
15682 &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
15683 &[b"ZUNION", b"1", b"s"],
15684 &[b"ZINTER", b"1", b"s"],
15685 &[b"ZDIFF", b"1", b"s"],
15686 &[b"ZUNIONSTORE", b"d", b"1", b"s"],
15687 &[b"ZINTERSTORE", b"d", b"1", b"s"],
15688 &[b"ZDIFFSTORE", b"d", b"1", b"s"],
15689 &[b"ZINTERCARD", b"1", b"s"],
15690 &[b"ZRANDMEMBER", b"s"],
15691 &[b"ZSCAN", b"s", b"0"],
15692 &[b"ZPOPMIN", b"s"],
15693 &[b"ZPOPMAX", b"s", b"2"],
15694 &[b"ZMPOP", b"1", b"s", b"MIN"],
15695 &[b"BZPOPMIN", b"s", b"0"],
15696 &[b"BZPOPMAX", b"s", b"0"],
15697 &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
15698 ] {
15699 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
15700 }
15701 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
15702 }
15703
15704 #[cfg_attr(miri, ignore = "the volume is the claim")]
15709 #[test]
15710 fn churning_sorted_sets_does_not_grow_the_server() {
15711 let mut f = Fixture::new();
15712 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
15713 let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
15714 let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
15715 for i in 0..200 {
15716 args.push(&scores[i]);
15717 args.push(&members[i]);
15718 }
15719
15720 f.run(&args);
15721 f.run(&[b"DEL", b"z"]);
15722 f.server.compact_step();
15723 let after_first = f.server.memory_bytes();
15724
15725 for _ in 0..200 {
15726 f.run(&args);
15727 f.run(&[b"DEL", b"z"]);
15728 f.server.compact_step();
15729 }
15730 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
15731 assert!(
15732 f.server.memory_bytes() <= after_first * 2,
15733 "held {} after two hundred passes against {after_first} after one",
15734 f.server.memory_bytes()
15735 );
15736 }
15737
15738 fn sicily(f: &mut Fixture) {
15746 f.run(&[
15747 b"GEOADD",
15748 b"Sicily",
15749 b"13.361389",
15750 b"38.115556",
15751 b"Palermo",
15752 b"15.087269",
15753 b"37.502669",
15754 b"Catania",
15755 ]);
15756 f.run(&[
15757 b"GEOADD",
15758 b"Sicily",
15759 b"13.583333",
15760 b"37.316667",
15761 b"Agrigento",
15762 ]);
15763 }
15764
15765 #[test]
15766 fn places_go_in_as_scores_and_come_back_as_positions() {
15767 let mut f = Fixture::new();
15768 assert_eq!(
15769 f.run(&[
15770 b"GEOADD",
15771 b"Sicily",
15772 b"13.361389",
15773 b"38.115556",
15774 b"Palermo",
15775 b"15.087269",
15776 b"37.502669",
15777 b"Catania"
15778 ]),
15779 ":2\r\n"
15780 );
15781 assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
15785 assert_eq!(
15786 f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
15787 "$16\r\n3479099956230698\r\n"
15788 );
15789 assert_eq!(
15790 f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
15791 "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
15792 );
15793 assert_eq!(
15794 f.run(&[
15795 b"GEOHASH",
15796 b"Sicily",
15797 b"Palermo",
15798 b"Catania",
15799 b"NonExisting"
15800 ]),
15801 "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
15802 );
15803 assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
15807 assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
15808 }
15809
15810 #[test]
15811 fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
15812 let mut f = Fixture::new();
15813 sicily(&mut f);
15814 assert_eq!(
15815 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
15816 "$11\r\n166274.1516\r\n"
15817 );
15818 assert_eq!(
15819 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
15820 "$8\r\n166.2742\r\n"
15821 );
15822 assert_eq!(
15823 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
15824 "$8\r\n103.3182\r\n"
15825 );
15826 assert_eq!(
15830 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
15831 "$-1\r\n"
15832 );
15833 assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
15834 assert_eq!(
15835 f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
15836 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
15837 );
15838 assert_eq!(
15839 f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
15840 "-ERR syntax error\r\n"
15841 );
15842 }
15843
15844 #[test]
15845 fn a_search_finds_what_is_inside_it_nearest_first() {
15846 let mut f = Fixture::new();
15847 sicily(&mut f);
15848 let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
15849 assert_eq!(
15850 f.run(&[
15851 b"GEOSEARCH",
15852 b"Sicily",
15853 b"FROMLONLAT",
15854 b"15",
15855 b"37",
15856 b"BYRADIUS",
15857 b"200",
15858 b"km",
15859 b"ASC"
15860 ]),
15861 all
15862 );
15863 assert_eq!(
15866 f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
15867 all
15868 );
15869 assert_eq!(
15870 f.run(&[
15871 b"GEORADIUS_RO",
15872 b"Sicily",
15873 b"15",
15874 b"37",
15875 b"200",
15876 b"km",
15877 b"ASC"
15878 ]),
15879 all
15880 );
15881 assert_eq!(
15884 f.run(&[
15885 b"GEORADIUS",
15886 b"Sicily",
15887 b"15",
15888 b"37",
15889 b"200",
15890 b"km",
15891 b"DESC",
15892 b"COUNT",
15893 b"1"
15894 ]),
15895 "*1\r\n$7\r\nPalermo\r\n"
15896 );
15897 assert_eq!(
15898 f.run(&[
15899 b"GEORADIUS",
15900 b"Sicily",
15901 b"15",
15902 b"37",
15903 b"200",
15904 b"km",
15905 b"COUNT",
15906 b"1"
15907 ]),
15908 "*1\r\n$7\r\nCatania\r\n"
15909 );
15910 let empty = "*0\r\n";
15913 assert_eq!(
15914 f.run(&[
15915 b"GEOSEARCH",
15916 b"Sicily",
15917 b"FROMLONLAT",
15918 b"15",
15919 b"37",
15920 b"BYRADIUS",
15921 b"1",
15922 b"km"
15923 ]),
15924 empty
15925 );
15926 assert_eq!(
15927 f.run(&[
15928 b"GEOSEARCH",
15929 b"nokey",
15930 b"FROMLONLAT",
15931 b"15",
15932 b"37",
15933 b"BYRADIUS",
15934 b"1",
15935 b"km"
15936 ]),
15937 empty
15938 );
15939 assert_eq!(
15940 f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
15941 empty
15942 );
15943 }
15944
15945 #[test]
15946 fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
15947 let mut f = Fixture::new();
15948 sicily(&mut f);
15949 assert_eq!(
15950 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
15951 "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
15952 );
15953 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";
15956 assert_eq!(
15957 f.run(&[
15958 b"GEORADIUSBYMEMBER_RO",
15959 b"Sicily",
15960 b"Agrigento",
15961 b"100",
15962 b"km",
15963 b"WITHDIST"
15964 ]),
15965 with_dist
15966 );
15967 assert_eq!(
15968 f.run(&[
15969 b"GEOSEARCH",
15970 b"Sicily",
15971 b"FROMMEMBER",
15972 b"Agrigento",
15973 b"BYRADIUS",
15974 b"100",
15975 b"km",
15976 b"ASC",
15977 b"WITHDIST"
15978 ]),
15979 with_dist
15980 );
15981 assert_eq!(
15982 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
15983 "-ERR could not decode requested zset member\r\n"
15984 );
15985 }
15986
15987 #[test]
15988 fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
15989 let mut f = Fixture::new();
15990 sicily(&mut f);
15991 assert_eq!(
15995 f.run(&[
15996 b"GEOSEARCH",
15997 b"Sicily",
15998 b"FROMLONLAT",
15999 b"15",
16000 b"37",
16001 b"BYBOX",
16002 b"400",
16003 b"400",
16004 b"km",
16005 b"ASC",
16006 b"WITHCOORD",
16007 b"WITHDIST",
16008 b"WITHHASH"
16009 ]),
16010 "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
16011 $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
16012 *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
16013 $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
16014 *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
16015 $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
16016 );
16017 }
16018
16019 #[test]
16020 fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
16021 let mut f = Fixture::new();
16022 sicily(&mut f);
16023 let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
16024 $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
16025 $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
16026 assert_eq!(
16027 f.run(&[
16028 b"GEOSEARCHSTORE",
16029 b"dst",
16030 b"Sicily",
16031 b"FROMLONLAT",
16032 b"15",
16033 b"37",
16034 b"BYRADIUS",
16035 b"200",
16036 b"km",
16037 b"ASC"
16038 ]),
16039 ":3\r\n"
16040 );
16041 assert_eq!(
16042 f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
16043 hashes
16044 );
16045 assert_eq!(
16048 f.run(&[
16049 b"GEORADIUS",
16050 b"Sicily",
16051 b"15",
16052 b"37",
16053 b"200",
16054 b"km",
16055 b"STORE",
16056 b"dst3"
16057 ]),
16058 ":3\r\n"
16059 );
16060 assert_eq!(
16061 f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
16062 hashes
16063 );
16064 assert_eq!(
16073 f.run(&[
16074 b"GEOSEARCHSTORE",
16075 b"dst2",
16076 b"Sicily",
16077 b"FROMLONLAT",
16078 b"15",
16079 b"37",
16080 b"BYRADIUS",
16081 b"200",
16082 b"km",
16083 b"ASC",
16084 b"STOREDIST"
16085 ]),
16086 ":3\r\n"
16087 );
16088 for (member, want) in [
16089 ("Catania", 56.441_257_870_158_19),
16090 ("Agrigento", 130.423_487_067_147_14),
16091 ("Palermo", 190.442_429_847_757_92),
16092 ] {
16093 let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
16094 let got: f64 = reply
16095 .trim_start_matches(|c: char| c != '\n')
16096 .trim()
16097 .parse()
16098 .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
16099 assert!(
16100 (got - want).abs() < 1e-9,
16101 "{member} scored {got} not {want}"
16102 );
16103 }
16104 assert_eq!(
16107 f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
16108 "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
16109 );
16110 assert_eq!(
16114 f.run(&[
16115 b"GEOSEARCHSTORE",
16116 b"dst",
16117 b"nokey",
16118 b"FROMLONLAT",
16119 b"15",
16120 b"37",
16121 b"BYRADIUS",
16122 b"200",
16123 b"km"
16124 ]),
16125 ":0\r\n"
16126 );
16127 assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
16128 }
16129
16130 #[test]
16131 fn the_gates_on_geoadd_are_the_ones_zadd_has() {
16132 let mut f = Fixture::new();
16133 sicily(&mut f);
16134 assert_eq!(
16137 f.run(&[
16138 b"GEOADD",
16139 b"Sicily",
16140 b"XX",
16141 b"CH",
16142 b"13.361389",
16143 b"38.115556",
16144 b"Palermo"
16145 ]),
16146 ":0\r\n"
16147 );
16148 assert_eq!(
16149 f.run(&[
16150 b"GEOADD",
16151 b"Sicily",
16152 b"NX",
16153 b"13.361389",
16154 b"38.9",
16155 b"Palermo"
16156 ]),
16157 ":0\r\n"
16158 );
16159 assert_eq!(
16160 f.run(&[
16161 b"GEOADD",
16162 b"Sicily",
16163 b"CH",
16164 b"13.361389",
16165 b"38.9",
16166 b"Palermo"
16167 ]),
16168 ":1\r\n"
16169 );
16170 assert_eq!(
16173 f.run(&[
16174 b"GEOADD",
16175 b"new",
16176 b"13.361389",
16177 b"38.115556",
16178 b"here",
16179 b"181",
16180 b"38",
16181 b"there"
16182 ]),
16183 "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
16184 );
16185 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
16186 assert_eq!(
16187 f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
16188 "-ERR value is not a valid float\r\n"
16189 );
16190 assert_eq!(
16193 f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
16194 "-ERR syntax error\r\n"
16195 );
16196 assert_eq!(
16197 f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
16198 "-ERR syntax error\r\n"
16199 );
16200 assert_eq!(
16201 f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
16202 "-ERR syntax error\r\n"
16203 );
16204 assert_eq!(
16205 f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
16206 "-ERR wrong number of arguments for 'geoadd' command\r\n"
16207 );
16208 }
16209
16210 #[test]
16213 fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
16214 let mut f = Fixture::new();
16215 sicily(&mut f);
16216 let cases: &[(&[&[u8]], &str)] = &[
16217 (
16218 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
16219 "-ERR need numeric radius\r\n",
16220 ),
16221 (
16222 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
16223 "-ERR radius cannot be negative\r\n",
16224 ),
16225 (
16226 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
16227 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
16228 ),
16229 (
16230 &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
16231 "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
16232 ),
16233 (
16234 &[
16235 b"GEOSEARCH",
16236 b"Sicily",
16237 b"FROMLONLAT",
16238 b"15",
16239 b"37",
16240 b"BYBOX",
16241 b"x",
16242 b"1",
16243 b"km",
16244 ],
16245 "-ERR need numeric width\r\n",
16246 ),
16247 (
16248 &[
16249 b"GEOSEARCH",
16250 b"Sicily",
16251 b"FROMLONLAT",
16252 b"15",
16253 b"37",
16254 b"BYBOX",
16255 b"1",
16256 b"y",
16257 b"km",
16258 ],
16259 "-ERR need numeric height\r\n",
16260 ),
16261 (
16262 &[
16263 b"GEOSEARCH",
16264 b"Sicily",
16265 b"FROMLONLAT",
16266 b"15",
16267 b"37",
16268 b"BYBOX",
16269 b"-1",
16270 b"1",
16271 b"km",
16272 ],
16273 "-ERR height or width cannot be negative\r\n",
16274 ),
16275 (
16276 &[
16277 b"GEOSEARCH",
16278 b"Sicily",
16279 b"FROMLONLAT",
16280 b"15",
16281 b"37",
16282 b"BYRADIUS",
16283 b"1",
16284 b"km",
16285 b"ANY",
16286 ],
16287 "-ERR the ANY argument requires COUNT argument\r\n",
16288 ),
16289 (
16290 &[
16291 b"GEOSEARCH",
16292 b"Sicily",
16293 b"FROMLONLAT",
16294 b"15",
16295 b"37",
16296 b"BYRADIUS",
16297 b"1",
16298 b"km",
16299 b"COUNT",
16300 b"0",
16301 ],
16302 "-ERR COUNT must be > 0\r\n",
16303 ),
16304 (
16305 &[
16306 b"GEOSEARCH",
16307 b"Sicily",
16308 b"BYRADIUS",
16309 b"1",
16310 b"km",
16311 b"BYBOX",
16312 b"1",
16313 b"1",
16314 b"km",
16315 ],
16316 "-ERR syntax error\r\n",
16317 ),
16318 (
16319 &[
16320 b"GEOSEARCH",
16321 b"Sicily",
16322 b"FROMMEMBER",
16323 b"Palermo",
16324 b"FROMLONLAT",
16325 b"1",
16326 b"2",
16327 b"BYRADIUS",
16328 b"1",
16329 b"km",
16330 ],
16331 "-ERR syntax error\r\n",
16332 ),
16333 (
16336 &[
16337 b"geosearch",
16338 b"Sicily",
16339 b"BYRADIUS",
16340 b"1",
16341 b"km",
16342 b"ASC",
16343 b"WITHDIST",
16344 ],
16345 "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
16346 ),
16347 (
16348 &[
16349 b"GEOSEARCH",
16350 b"Sicily",
16351 b"FROMLONLAT",
16352 b"15",
16353 b"37",
16354 b"ASC",
16355 b"WITHDIST",
16356 ],
16357 "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
16358 ),
16359 (
16362 &[
16363 b"GEOSEARCHSTORE",
16364 b"d",
16365 b"Sicily",
16366 b"FROMLONLAT",
16367 b"15",
16368 b"37",
16369 b"BYRADIUS",
16370 b"1",
16371 b"km",
16372 b"WITHCOORD",
16373 ],
16374 "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
16375 ),
16376 (
16377 &[
16378 b"GEORADIUS",
16379 b"Sicily",
16380 b"15",
16381 b"37",
16382 b"1",
16383 b"km",
16384 b"WITHDIST",
16385 b"STORE",
16386 b"d",
16387 ],
16388 "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
16389 ),
16390 (
16393 &[
16394 b"GEORADIUS_RO",
16395 b"Sicily",
16396 b"15",
16397 b"37",
16398 b"1",
16399 b"km",
16400 b"STORE",
16401 b"d",
16402 ],
16403 "-ERR syntax error\r\n",
16404 ),
16405 (
16406 &[
16407 b"GEOSEARCH",
16408 b"Sicily",
16409 b"FROMLONLAT",
16410 b"15",
16411 b"37",
16412 b"BYRADIUS",
16413 b"1",
16414 b"km",
16415 b"STOREDIST",
16416 ],
16417 "-ERR syntax error\r\n",
16418 ),
16419 ];
16420 for (parts, want) in cases {
16421 assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
16422 }
16423 }
16424
16425 #[test]
16428 fn every_geo_command_says_wrongtype() {
16429 let mut f = Fixture::new();
16430 f.run(&[b"SET", b"s", b"v"]);
16431 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16432 let cases: &[&[&[u8]]] = &[
16433 &[b"GEOADD", b"s", b"13", b"38", b"m"],
16434 &[b"GEOPOS", b"s", b"m"],
16435 &[b"GEOHASH", b"s", b"m"],
16436 &[b"GEODIST", b"s", b"a", b"b"],
16437 &[
16438 b"GEOSEARCH",
16439 b"s",
16440 b"FROMLONLAT",
16441 b"15",
16442 b"37",
16443 b"BYRADIUS",
16444 b"1",
16445 b"km",
16446 ],
16447 &[
16448 b"GEOSEARCHSTORE",
16449 b"d",
16450 b"s",
16451 b"FROMLONLAT",
16452 b"15",
16453 b"37",
16454 b"BYRADIUS",
16455 b"1",
16456 b"km",
16457 ],
16458 &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
16459 &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
16460 &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
16461 &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
16462 ];
16463 for case in cases {
16464 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
16465 }
16466 assert_eq!(
16469 f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
16470 wrong
16471 );
16472 }
16473
16474 #[test]
16477 fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
16478 let mut f = Fixture::new();
16479 assert_eq!(
16482 f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
16483 ":3\r\n"
16484 );
16485 assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
16486 assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
16487 assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
16488 assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
16490 assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
16491 assert_eq!(
16492 f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
16493 "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
16494 );
16495 assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
16497 assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
16498 }
16499
16500 #[test]
16503 fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
16504 let mut f = Fixture::new();
16505 assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
16506 assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
16507 f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
16508 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
16509 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
16510 assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
16512 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
16513 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
16514
16515 f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
16519 assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
16520 assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
16521 assert_eq!(
16524 f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
16525 "-ERR array index overflow\r\n"
16526 );
16527 assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
16528 }
16529
16530 #[test]
16533 fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
16534 let mut f = Fixture::new();
16535 f.run(&[b"ARSET", b"a", b"1", b"x"]);
16536 assert_eq!(
16537 f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
16538 "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
16539 );
16540 assert_eq!(
16543 f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
16544 "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
16545 );
16546 assert_eq!(
16548 f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
16549 "*2\r\n$-1\r\n$-1\r\n"
16550 );
16551 assert_eq!(
16555 f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
16556 "-ERR range exceeds maximum of 1000000 items\r\n"
16557 );
16558 }
16559
16560 #[test]
16563 fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
16564 let mut f = Fixture::new();
16565 assert_eq!(
16566 f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
16567 "-ERR invalid array index\r\n"
16568 );
16569 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
16570 f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
16571 assert_eq!(
16572 f.run(&[b"ARDEL", b"a", b"0", b"01"]),
16573 "-ERR invalid array index\r\n"
16574 );
16575 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
16576 assert_eq!(
16579 f.run(&[b"ARGET", b"a", b"-1"]),
16580 "-ERR invalid array index\r\n"
16581 );
16582 assert_eq!(
16585 f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
16586 "-ERR wrong number of arguments for 'armset' command\r\n"
16587 );
16588 assert_eq!(
16589 f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
16590 "-ERR wrong number of arguments for 'ardelrange' command\r\n"
16591 );
16592 }
16593
16594 #[test]
16595 fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
16596 let mut f = Fixture::new();
16597 f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
16598 assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
16599 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
16600 assert_eq!(
16603 f.run(&[
16604 b"ARDELRANGE",
16605 b"a",
16606 b"100",
16607 b"200",
16608 b"0",
16609 b"18446744073709551614"
16610 ]),
16611 ":2\r\n"
16612 );
16613 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
16614 assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
16615 assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
16616 }
16617
16618 #[test]
16621 fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
16622 let mut f = Fixture::new();
16623 let long = vec![b'v'; 200];
16624 f.run(&[
16625 b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
16626 b"short", b"5", &long, b"6", b"-0",
16627 ]);
16628 assert_eq!(
16632 f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
16633 format!(
16634 "*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",
16635 String::from_utf8_lossy(&long)
16636 )
16637 );
16638 }
16639
16640 #[test]
16641 fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
16642 let mut f = Fixture::new();
16643 f.run(&[b"ARSET", b"a", b"0", b"x"]);
16644 assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
16645 assert_eq!(
16646 f.run(&[b"OBJECT", b"ENCODING", b"a"]),
16647 "$12\r\nsliced-array\r\n"
16648 );
16649 assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
16651 assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
16652 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
16653 assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
16654 assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
16655 assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
16656 }
16657
16658 #[test]
16659 fn every_array_command_refuses_a_key_holding_something_else() {
16660 let mut f = Fixture::new();
16661 f.run(&[b"SET", b"s", b"v"]);
16662 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16663 for cmd in [
16664 &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
16665 &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
16666 &[b"ARGET".as_ref(), b"s", b"0"][..],
16667 &[b"ARMGET".as_ref(), b"s", b"0"][..],
16668 &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
16669 &[b"ARLEN".as_ref(), b"s"][..],
16670 &[b"ARCOUNT".as_ref(), b"s"][..],
16671 &[b"ARDEL".as_ref(), b"s", b"0"][..],
16672 &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
16673 &[b"ARINSERT".as_ref(), b"s", b"x"][..],
16674 &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
16675 &[b"ARNEXT".as_ref(), b"s"][..],
16676 &[b"ARSEEK".as_ref(), b"s", b"1"][..],
16677 &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
16678 &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
16679 &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
16680 &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
16681 &[b"ARINFO".as_ref(), b"s"][..],
16682 ] {
16683 assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
16684 }
16685 }
16686
16687 #[test]
16691 fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
16692 let mut f = Fixture::new();
16693 f.run(&[b"SET", b"s", b"v"]);
16694 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16695 let bad = "-ERR invalid array index\r\n";
16696 assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
16697 assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
16698 assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
16699 assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
16700 assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
16701 assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
16702 f.run(&[b"ARSET", b"a", b"0", b"x"]);
16704 assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
16705 assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
16706 }
16707
16708 #[test]
16709 fn an_append_follows_a_cursor_the_client_can_move() {
16710 let mut f = Fixture::new();
16711 assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
16712 assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
16713 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
16714 assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
16715 assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
16716
16717 assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
16720 assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
16721 assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
16722 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
16723 assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
16724 assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
16725 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
16726
16727 assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
16730 assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
16731 assert_eq!(
16732 f.run(&[b"ARINSERT", b"a", b"x"]),
16733 "-ERR insert index overflow\r\n"
16734 );
16735 assert_eq!(
16736 f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
16737 "-ERR invalid array index\r\n"
16738 );
16739 }
16740
16741 #[test]
16742 fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
16743 let mut f = Fixture::new();
16744 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
16745 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
16746 assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
16747 assert_eq!(
16748 f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
16749 "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
16750 );
16751 assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
16754 assert_eq!(
16755 f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
16756 "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
16757 );
16758 assert_eq!(
16761 f.run(&[b"ARRING", b"r", b"0", b"x"]),
16762 "-ERR size must be positive\r\n"
16763 );
16764 assert_eq!(
16765 f.run(&[b"ARRING", b"r", b"big", b"x"]),
16766 "-ERR invalid size\r\n"
16767 );
16768 }
16769
16770 #[test]
16771 fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
16772 let mut f = Fixture::new();
16773 assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
16774 f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
16775 assert_eq!(
16776 f.run(&[b"ARLASTITEMS", b"r", b"3"]),
16777 "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
16778 );
16779 assert_eq!(
16780 f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
16781 "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
16782 );
16783 assert_eq!(
16784 f.run(&[b"ARLASTITEMS", b"r", b"99"]),
16785 "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
16786 "more than there is gets what there is"
16787 );
16788 assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
16791 assert_eq!(
16792 f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
16793 "-ERR syntax error\r\n"
16794 );
16795 assert_eq!(
16796 f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
16797 "-ERR invalid COUNT\r\n"
16798 );
16799
16800 f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
16803 assert_eq!(
16804 f.run(&[b"ARLASTITEMS", b"h", b"5"]),
16805 "*2\r\n$-1\r\n$1\r\nz\r\n"
16806 );
16807 }
16808
16809 #[test]
16810 fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
16811 let mut f = Fixture::new();
16812 assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
16813 f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
16814 assert_eq!(
16817 f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
16818 "*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"
16819 );
16820 assert_eq!(
16821 f.run(&[
16822 b"ARSCAN",
16823 b"a",
16824 b"18446744073709551614",
16825 b"0",
16826 b"LIMIT",
16827 b"1"
16828 ]),
16829 "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
16830 );
16831 assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
16832 assert_eq!(
16833 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
16834 "-ERR LIMIT must be positive\r\n"
16835 );
16836 assert_eq!(
16837 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
16838 "-ERR syntax error\r\n"
16839 );
16840 assert_eq!(
16841 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
16842 "-ERR wrong number of arguments for 'arscan' command\r\n"
16843 );
16844 }
16845
16846 #[test]
16847 fn a_grep_answers_the_indexes_whose_elements_match() {
16848 let mut f = Fixture::new();
16849 assert_eq!(
16850 f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
16851 "*0\r\n"
16852 );
16853 f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
16854
16855 assert_eq!(
16858 f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
16859 "*3\r\n:0\r\n:1\r\n:2\r\n"
16860 );
16861 assert_eq!(
16862 f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
16863 "*3\r\n:2\r\n:1\r\n:0\r\n"
16864 );
16865 assert_eq!(
16866 f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
16867 "*2\r\n:1\r\n:2\r\n"
16868 );
16869
16870 assert_eq!(
16873 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
16874 "*1\r\n:0\r\n"
16875 );
16876 assert_eq!(
16877 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
16878 "*2\r\n:0\r\n:3\r\n"
16879 );
16880 assert_eq!(
16881 f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
16882 "*1\r\n:2\r\n"
16883 );
16884 assert_eq!(
16885 f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
16886 "*2\r\n:1\r\n:2\r\n"
16887 );
16888
16889 let both: &[&[u8]] = &[
16892 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
16893 ];
16894 assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
16895 assert_eq!(
16896 f.run(&[
16897 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
16898 ]),
16899 "*0\r\n"
16900 );
16901 assert_eq!(
16902 f.run(&[
16903 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
16904 ]),
16905 "*2\r\n:0\r\n:1\r\n"
16906 );
16907
16908 assert_eq!(
16911 f.run(&[
16912 b"ARGREP",
16913 b"a",
16914 b"-",
16915 b"+",
16916 b"MATCH",
16917 b"a",
16918 b"WITHVALUES",
16919 b"LIMIT",
16920 b"2"
16921 ]),
16922 "*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"
16923 );
16924 assert_eq!(
16925 f.run(&[
16926 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
16927 ]),
16928 "*1\r\n:3\r\n"
16929 );
16930 }
16931
16932 #[test]
16934 fn a_grep_reports_a_broken_command_the_way_redis_does() {
16935 let mut f = Fixture::new();
16936 f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
16937 let syntax = "-ERR syntax error\r\n";
16938
16939 assert_eq!(
16942 f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
16943 "-ERR invalid array index\r\n"
16944 );
16945 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
16946 assert_eq!(
16948 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
16949 syntax
16950 );
16951 assert_eq!(
16952 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
16953 syntax
16954 );
16955 assert_eq!(
16956 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
16957 syntax,
16958 "a command with no predicate in it at all"
16959 );
16960 assert_eq!(
16961 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
16962 "-ERR LIMIT must be positive\r\n"
16963 );
16964 assert_eq!(
16965 f.run(&[
16966 b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
16967 ]),
16968 "-ERR value is not an integer or out of range\r\n"
16969 );
16970 assert_eq!(
16971 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
16972 "-ERR regular expression is empty\r\n"
16973 );
16974 assert_eq!(
16975 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
16976 "-ERR invalid regular expression: Missing ')'\r\n"
16977 );
16978 assert_eq!(
16979 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
16980 "-ERR regular expression backreferences are not supported\r\n"
16981 );
16982 let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
16985 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
16986 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
16987 }
16988
16989 #[test]
16990 fn an_op_reduces_a_range_to_one_number() {
16991 let mut f = Fixture::new();
16992 f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
16993 assert_eq!(
16994 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
16995 "$4\r\n-0.5\r\n"
16996 );
16997 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
16998 assert_eq!(
16999 f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
17000 "$3\r\n2.5\r\n"
17001 );
17002 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
17003 assert_eq!(
17004 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
17005 ":1\r\n"
17006 );
17007 f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
17010 assert_eq!(
17011 f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
17012 "$19\r\n0.30000000000000004\r\n"
17013 );
17014 assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
17015 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
17016
17017 f.run(&[b"ARSET", b"w", b"0", b"word"]);
17020 assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
17021 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
17022 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
17023
17024 assert_eq!(
17025 f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
17026 "-ERR unknown operation\r\n"
17027 );
17028 assert_eq!(
17029 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
17030 "-ERR MATCH requires a value argument\r\n"
17031 );
17032 assert_eq!(
17033 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
17034 "-ERR wrong number of arguments for 'arop' command\r\n"
17035 );
17036 }
17037
17038 #[test]
17039 fn the_info_is_a_map_and_a_missing_key_is_an_error() {
17040 let mut f = Fixture::new();
17041 assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
17042 f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
17043 let short = f.run(&[b"ARINFO", b"a"]);
17044 assert!(
17045 short.starts_with("*14\r\n"),
17046 "seven pairs on RESP2: {short}"
17047 );
17048 assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
17049 assert!(
17050 short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
17051 "{short}"
17052 );
17053 assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
17054 let full = f.run(&[b"ARINFO", b"a", b"full"]);
17055 assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
17056 assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
17059 assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
17060 assert!(
17061 full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
17062 "{full}"
17063 );
17064 assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
17065
17066 let mut g = Fixture::new();
17068 g.run(&[b"HELLO", b"3"]);
17069 g.run(&[b"ARINSERT", b"a", b"x"]);
17070 let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
17071 assert!(map.starts_with("%12\r\n"), "{map}");
17072 assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
17073 assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
17074 }
17075
17076 #[test]
17077 fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
17078 let mut f = Fixture::new();
17079 for (score, want) in [
17082 ("3", "3"),
17083 ("3.5", "3.5"),
17084 ("0.3", "0.3"),
17085 ("1e30", "1e+30"),
17086 ("1e19", "1e+19"),
17087 ("1e-7", "1e-7"),
17088 ("0.000001", "0.000001"),
17089 ("4611686018427387904", "4611686018427387904"),
17090 ("-0", "-0"),
17091 ] {
17092 f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
17093 assert_eq!(
17094 f.run(&[b"ZSCORE", b"z", b"m"]),
17095 format!("${}\r\n{want}\r\n", want.len()),
17096 "score {score}"
17097 );
17098 }
17099
17100 let mut g = Fixture::new();
17103 g.run(&[b"HELLO", b"3"]);
17104 g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
17105 assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
17106 assert_eq!(
17111 g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
17112 "$31\r\n1000000000000000000000000000000\r\n"
17113 );
17114 assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
17115 assert_eq!(
17116 g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
17117 "$20\r\n10000000000000000000\r\n"
17118 );
17119 }
17120
17121 #[test]
17124 fn a_node_comes_back_with_the_fields_it_went_in_with() {
17125 let mut f = Fixture::new();
17126 assert_eq!(
17127 f.run(&[
17128 b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
17129 ]),
17130 ":1\r\n"
17131 );
17132 assert_eq!(
17138 f.run(&[b"G.NGET", b"social", b"ada"]),
17139 "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
17140 );
17141 assert_eq!(
17144 f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
17145 ":0\r\n"
17146 );
17147 assert_eq!(
17148 f.run(&[b"G.NGET", b"social", b"ada"]),
17149 "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
17150 );
17151 assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
17154 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
17155 assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
17156 assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
17157
17158 assert_eq!(
17161 f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
17162 "-ERR syntax error\r\n"
17163 );
17164 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
17165
17166 let mut g = Fixture::new();
17168 g.run(&[b"HELLO", b"3"]);
17169 g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
17170 assert_eq!(
17171 g.run(&[b"G.NGET", b"social", b"ada"]),
17172 "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
17173 );
17174 }
17175
17176 #[test]
17177 fn an_edge_creates_the_ends_it_needs() {
17178 let mut f = Fixture::new();
17179 assert_eq!(
17180 f.run(&[
17181 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
17182 ]),
17183 ":1\r\n"
17184 );
17185 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
17187 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
17188 assert_eq!(
17189 f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
17190 "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
17191 );
17192 assert_eq!(
17193 f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
17194 "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
17195 );
17196 assert_eq!(
17199 f.run(&[
17200 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
17201 ]),
17202 ":0\r\n"
17203 );
17204 assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
17205 assert_eq!(
17207 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
17208 ":1\r\n"
17209 );
17210 assert_eq!(
17211 f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
17212 ":1\r\n"
17213 );
17214
17215 assert_eq!(
17216 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
17217 ":1\r\n"
17218 );
17219 assert_eq!(
17220 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
17221 ":0\r\n"
17222 );
17223 assert_eq!(
17226 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
17227 ":0\r\n"
17228 );
17229 assert_eq!(
17230 f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
17231 ":0\r\n"
17232 );
17233 assert_eq!(
17234 f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
17235 ":0\r\n"
17236 );
17237 }
17238
17239 #[test]
17242 fn a_hop_answers_a_cursor_and_a_page() {
17243 let mut f = Fixture::new();
17244 for i in 0..25u32 {
17245 let dst = format!("n{i}");
17246 f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
17247 }
17248 let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
17250 assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
17251
17252 let mut seen = 0;
17253 let mut cursor = String::from("0");
17254 loop {
17255 let page = f.run(&[
17256 b"G.OUT",
17257 b"social",
17258 b"hub",
17259 b"FOLLOWS",
17260 b"COUNT",
17261 b"7",
17262 b"CURSOR",
17263 cursor.as_bytes(),
17264 ]);
17265 let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
17266 cursor = head
17267 .rsplit("\r\n")
17268 .next()
17269 .expect("the cursor line")
17270 .to_string();
17271 seen += rest
17272 .split_once("\r\n")
17273 .expect("the page length")
17274 .0
17275 .parse::<usize>()
17276 .expect("a length");
17277 if cursor == "0" {
17278 break;
17279 }
17280 }
17281 assert_eq!(seen, 25, "every neighbour once across the pages");
17282
17283 assert_eq!(
17286 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
17287 "*2\r\n$1\r\n0\r\n*0\r\n"
17288 );
17289 assert_eq!(
17290 f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
17291 "*2\r\n$1\r\n0\r\n*0\r\n"
17292 );
17293 assert_eq!(
17294 f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
17295 "*2\r\n$1\r\n0\r\n*0\r\n"
17296 );
17297 assert_eq!(
17298 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
17299 "-ERR COUNT must be a positive integer\r\n"
17300 );
17301 assert_eq!(
17302 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
17303 "-ERR syntax error\r\n"
17304 );
17305 }
17306
17307 #[test]
17308 fn a_degree_counts_one_way_or_both() {
17309 let mut f = Fixture::new();
17310 f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
17311 f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
17312 f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
17313 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
17314 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
17315 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
17316 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
17317 assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
17318 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
17319 assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
17320 assert_eq!(
17321 f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
17322 "-ERR syntax error\r\n"
17323 );
17324 }
17325
17326 #[test]
17329 fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
17330 let mut f = Fixture::new();
17331 for (src, dst) in [
17332 ("ada", "grace"),
17333 ("ada", "alan"),
17334 ("grace", "edsger"),
17335 ("alan", "edsger"),
17336 ("edsger", "barbara"),
17337 ] {
17338 f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
17339 }
17340 assert_eq!(
17343 f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
17344 "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
17345 );
17346 assert_eq!(
17347 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
17348 "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
17349 );
17350 let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
17351 assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
17352 assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
17353 assert_eq!(
17355 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
17356 "*1\r\n$5\r\ngrace\r\n"
17357 );
17358 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
17360 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
17361 assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
17362 assert_eq!(
17363 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
17364 "-ERR DEPTH must be a positive integer\r\n"
17365 );
17366 assert_eq!(
17367 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
17368 "-ERR syntax error\r\n"
17369 );
17370 }
17371
17372 #[test]
17375 fn a_path_is_the_shortest_one_and_goes_over_any_label() {
17376 let mut f = Fixture::new();
17377 for i in 0..6u32 {
17380 let src = format!("n{i}");
17381 let dst = format!("n{}", i + 1);
17382 f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
17383 }
17384 assert_eq!(
17385 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
17386 "*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"
17387 );
17388 f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
17389 assert_eq!(
17390 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
17391 "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
17392 );
17393 assert_eq!(
17396 f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
17397 "*1\r\n$2\r\nn2\r\n"
17398 );
17399 assert_eq!(
17400 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
17401 "*0\r\n"
17402 );
17403 assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
17405 f.run(&[b"G.NADD", b"road", b"island"]);
17408 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
17409 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
17410 assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
17411 assert_eq!(
17412 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
17413 "-ERR syntax error\r\n"
17414 );
17415 }
17416
17417 #[test]
17421 fn the_keyspace_sees_a_graph_key_like_any_other() {
17422 let mut f = Fixture::new();
17423 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
17424 assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
17425 assert_eq!(
17426 f.run(&[b"OBJECT", b"ENCODING", b"social"]),
17427 "$9\r\nadjacency\r\n"
17428 );
17429 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
17430 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
17431 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
17432 let held = f.server.memory_bytes();
17436 for i in 0..200u32 {
17437 let dst = format!("n{i}");
17438 f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
17439 }
17440 assert!(
17441 f.server.memory_bytes() > held,
17442 "two hundred edges cost something: {held} then {}",
17443 f.server.memory_bytes()
17444 );
17445 f.run(&[b"DEL", b"big"]);
17446
17447 assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
17450 assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
17451 assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
17452 assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
17453 assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
17454 f.run(&[b"SELECT", b"1"]);
17455 assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
17456
17457 assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
17458 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17459 f.run(&[b"G.NADD", b"g", b"n"]);
17460 assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
17461 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17462 }
17463
17464 #[test]
17467 fn a_graph_cannot_be_copied_or_dumped() {
17468 let mut f = Fixture::new();
17469 f.run(&[b"G.NADD", b"social", b"ada"]);
17470 assert_eq!(
17471 f.run(&[b"COPY", b"social", b"other"]),
17472 "-ERR COPY is not supported for a graph\r\n"
17473 );
17474 assert_eq!(
17475 f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
17476 "-ERR COPY is not supported for a graph\r\n"
17477 );
17478 assert_eq!(
17479 f.run(&[b"DUMP", b"social"]),
17480 "-ERR DUMP is not supported for a graph\r\n"
17481 );
17482 assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
17484 }
17485
17486 #[test]
17489 fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
17490 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
17491 let mut f = Fixture::new();
17492 f.run(&[b"G.NADD", b"social", b"ada"]);
17493 assert_eq!(f.run(&[b"GET", b"social"]), wrong);
17494 assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
17495 assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
17496
17497 f.run(&[b"SET", b"str", b"v"]);
17498 for cmd in [
17499 vec![b"G.NADD".as_ref(), b"str", b"n"],
17500 vec![b"G.NGET".as_ref(), b"str", b"n"],
17501 vec![b"G.NDEL".as_ref(), b"str", b"n"],
17502 vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
17503 vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
17504 vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
17505 vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
17506 vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
17507 vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
17508 vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
17509 ] {
17510 assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
17511 }
17512 }
17513
17514 #[test]
17517 fn a_graph_goes_when_its_last_node_does() {
17518 let mut f = Fixture::new();
17519 f.run(&[
17520 b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
17521 ]);
17522 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
17523 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
17525 assert_eq!(
17526 f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
17527 ":0\r\n"
17528 );
17529 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
17530 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
17531
17532 assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
17533 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
17534 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17535 assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
17536
17537 f.run(&[b"G.NADD", b"social", b"first"]);
17540 f.run(&[b"G.NADD", b"social", b"second"]);
17541 f.run(&[b"G.NDEL", b"social", b"first"]);
17542 f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
17543 assert_eq!(
17544 f.run(&[b"G.OUT", b"social", b"third", b"F"]),
17545 "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
17546 );
17547 }
17548
17549 #[test]
17554 fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
17555 let mut f = Fixture::new();
17556 let doc = br#"{"a":1,"b":{"c":true}}"#;
17557 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
17558 assert_eq!(
17561 f.run(&[b"JSON.GET", b"doc"]),
17562 bulk(r#"{"a":1,"b":{"c":true}}"#)
17563 );
17564 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
17565 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
17566 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
17567 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
17570 assert_eq!(
17571 f.run(&[b"JSON.GET", b"doc", b".nope"]),
17572 "-ERR Path does not exist\r\n"
17573 );
17574 assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
17575 assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
17578 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
17579 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
17580 assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
17581 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
17582 }
17583
17584 #[test]
17590 fn the_two_json_errors_that_carry_no_prefix() {
17591 let mut f = Fixture::new();
17592 f.run(&[b"SET", b"plain", b"x"]);
17593 let wrong = "-Existing key has wrong Redis type\r\n";
17594 assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
17595 assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
17596 assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
17597 assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
17598 assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
17599
17600 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
17601 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
17605 assert_eq!(
17606 f.run(&[b"JSON.GET", b"doc"]),
17607 bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
17608 );
17609 assert_eq!(
17610 f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
17611 "-Err wrong static path\r\n"
17612 );
17613 }
17614
17615 #[test]
17617 fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
17618 let mut f = Fixture::new();
17619 assert_eq!(
17621 f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
17622 "-ERR new objects must be created at the root\r\n"
17623 );
17624 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17625 assert_eq!(
17628 f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
17629 "-ERR new objects must be created at the root\r\n"
17630 );
17631 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
17632 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
17633
17634 f.run(&[
17635 b"JSON.SET",
17636 b"doc",
17637 b"$",
17638 br#"{"o":{},"arr":[1,2],"s":"x"}"#,
17639 ]);
17640 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
17642 assert_eq!(
17645 f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
17646 "$-1\r\n"
17647 );
17648 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
17649 assert_eq!(
17651 f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
17652 "-ERR array index out of range\r\n"
17653 );
17654 assert_eq!(
17655 f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
17656 "-ERR array index out of range\r\n"
17657 );
17658 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
17659 assert_eq!(
17662 f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
17663 "$-1\r\n"
17664 );
17665 assert_eq!(
17666 f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
17667 "$-1\r\n"
17668 );
17669 assert_eq!(
17670 f.run(&[b"JSON.GET", b"doc"]),
17671 bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
17672 );
17673 assert!(
17677 f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
17678 .starts_with("-this is not the start of a value")
17679 );
17680 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
17681 }
17682
17683 #[test]
17686 fn the_json_commands_that_do_not_answer_text() {
17687 let mut f = Fixture::new();
17688 let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
17689 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
17690
17691 assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
17692 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
17693 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
17694 assert_eq!(
17695 f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
17696 format!("*1\r\n{}", bulk("integer"))
17697 );
17698 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
17701 assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
17702
17703 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
17706 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
17707 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
17710 assert_eq!(
17711 f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
17712 "-ERR Path does not exist or not a bool\r\n"
17713 );
17714 assert_eq!(
17715 f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
17716 "-ERR Path does not exist or not a bool\r\n"
17717 );
17718 assert_eq!(
17719 f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
17720 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17721 );
17722
17723 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
17726 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
17727 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
17728 assert_eq!(
17729 f.run(&[b"JSON.GET", b"doc"]),
17730 bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
17731 );
17732
17733 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
17736 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
17737 assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
17742 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
17743 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
17744 assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
17745 assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
17746 assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
17747 assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
17748 assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
17749 }
17750
17751 #[test]
17758 fn json_get_lays_out_the_wrapper_it_builds() {
17759 let mut f = Fixture::new();
17760 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
17761
17762 assert_eq!(
17763 f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
17764 bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
17765 );
17766 assert_eq!(
17768 f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
17769 bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
17770 );
17771 let fmt: &[&[u8]] = &[b"INDENT", b" ", b"NEWLINE", b"\n", b"SPACE", b" "];
17772 let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
17773 one.extend_from_slice(fmt);
17774 one.push(b"$.b");
17775 assert_eq!(
17776 f.run(&one),
17777 bulk("[\n [\n 1,\n {\n \"c\": 2\n }\n ]\n]")
17778 );
17779 let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
17780 two.extend_from_slice(fmt);
17781 two.push(b"$.a");
17782 two.push(b"$.nope");
17783 assert_eq!(
17784 f.run(&two),
17785 bulk("{\n \"$.a\": [\n 1\n ],\n \"$.nope\": []\n}")
17786 );
17787 let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
17790 root.push(b".a");
17791 assert_eq!(f.run(&root), bulk("1"));
17792 }
17793
17794 #[test]
17797 fn json_mget_answers_once_per_key_whatever_is_under_them() {
17798 let mut f = Fixture::new();
17799 f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
17800 f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
17801 f.run(&[b"SET", b"plain", b"x"]);
17802 assert_eq!(
17803 f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
17804 format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
17805 );
17806 assert_eq!(
17809 f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
17810 format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
17811 );
17812 assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
17815 }
17816
17817 #[test]
17825 fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
17826 let mut f = Fixture::new();
17827 let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
17828 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
17829
17830 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
17831 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
17832 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
17833 assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
17834 assert_eq!(
17835 f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
17836 format!("*2\r\n{}{}", bulk("x"), bulk("y"))
17837 );
17838 assert_eq!(
17841 f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
17842 "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
17843 );
17844
17845 assert_eq!(
17848 f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
17849 "-ERR Path does not exist\r\n"
17850 );
17851 assert_eq!(
17852 f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
17853 "-ERR Path does not exist\r\n"
17854 );
17855 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
17856 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
17859 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
17862
17863 assert_eq!(
17867 f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
17868 "-ERR Path does not exist or not an array\r\n"
17869 );
17870 assert_eq!(
17871 f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
17872 "-ERR Path does not exist or not an object\r\n"
17873 );
17874 assert_eq!(
17875 f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
17876 "-WRONGTYPE wrong type of path value - expected object\r\n"
17877 );
17878 assert_eq!(
17879 f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
17880 "-WRONGTYPE wrong type of path value - expected string\r\n"
17881 );
17882
17883 assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
17886 assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
17887 assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
17888 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
17889 assert_eq!(
17890 f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
17891 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17892 );
17893 assert_eq!(
17895 f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
17896 "-ERR Path does not exist or not an object\r\n"
17897 );
17898 }
17899
17900 #[test]
17906 fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
17907 let mut f = Fixture::new();
17908 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
17909
17910 assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
17911 assert_eq!(
17912 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
17913 "*1\r\n:6\r\n"
17914 );
17915 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
17916
17917 assert_eq!(
17920 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
17921 ":7\r\n"
17922 );
17923 assert_eq!(
17924 f.run(&[b"JSON.GET", b"doc", b".a"]),
17925 bulk("[1,2,3,4,5,0,6]")
17926 );
17927 assert_eq!(
17928 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
17929 ":8\r\n"
17930 );
17931 assert_eq!(
17933 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
17934 "-ERR index out of bounds\r\n"
17935 );
17936 assert_eq!(
17937 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
17938 "-ERR index out of bounds\r\n"
17939 );
17940
17941 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
17944 assert_eq!(
17945 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
17946 ":3\r\n"
17947 );
17948 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
17949 assert_eq!(
17950 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
17951 ":2\r\n"
17952 );
17953 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
17954 assert_eq!(
17955 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
17956 ":0\r\n"
17957 );
17958 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
17959
17960 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
17963 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
17964 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
17965 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
17966 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
17967
17968 for call in [
17971 &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
17972 &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
17973 &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
17974 &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
17975 ] {
17976 for path in [&b".n"[..], &b".nope"[..]] {
17977 let args: Vec<&[u8]> = call
17978 .iter()
17979 .map(|a| if *a == b"PATH" { path } else { *a })
17980 .collect();
17981 assert_eq!(
17982 f.run(&args),
17983 "-ERR Path does not exist or not an array\r\n",
17984 "{} {}",
17985 String::from_utf8_lossy(call[0]),
17986 String::from_utf8_lossy(path)
17987 );
17988 }
17989 }
17990
17991 assert_eq!(
17994 f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
17995 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17996 );
17997 assert_eq!(
17998 f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
17999 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18000 );
18001
18002 assert!(
18007 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
18008 .starts_with("-this is not the start of a value")
18009 );
18010 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
18011 }
18012
18013 #[test]
18021 fn json_arrinsert_is_all_or_nothing_across_the_matches() {
18022 let mut f = Fixture::new();
18023 let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
18024 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18025 assert_eq!(
18026 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
18027 "-ERR index out of bounds\r\n"
18028 );
18029 assert_eq!(
18030 f.run(&[b"JSON.GET", b"doc"]),
18031 bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
18032 );
18033 assert_eq!(
18035 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
18036 "*3\r\n:4\r\n:3\r\n:2\r\n"
18037 );
18038 assert_eq!(
18039 f.run(&[b"JSON.GET", b"doc"]),
18040 bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
18041 );
18042 }
18043
18044 #[test]
18052 fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
18053 let mut f = Fixture::new();
18054 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
18055
18056 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
18057 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
18058 assert_eq!(
18059 f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
18060 "*1\r\n:1\r\n"
18061 );
18062
18063 assert_eq!(
18066 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
18067 ":3\r\n"
18068 );
18069 assert_eq!(
18072 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
18073 ":-1\r\n"
18074 );
18075
18076 for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
18079 assert_eq!(
18080 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
18081 ":3\r\n",
18082 "{}",
18083 String::from_utf8_lossy(start)
18084 );
18085 }
18086 assert_eq!(
18087 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
18088 ":0\r\n"
18089 );
18090 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
18093 assert_eq!(
18094 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
18095 ":-1\r\n"
18096 );
18097
18098 f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
18102 assert_eq!(
18103 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
18104 ":0\r\n"
18105 );
18106 assert_eq!(
18107 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
18108 ":1\r\n"
18109 );
18110 assert_eq!(
18111 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
18112 ":-1\r\n"
18113 );
18114
18115 assert_eq!(
18119 f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
18120 "-ERR Path does not exist\r\n"
18121 );
18122 assert_eq!(
18123 f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
18124 "-WRONGTYPE wrong type of path value - expected array\r\n"
18125 );
18126 assert_eq!(
18127 f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
18128 "-ERR Path does not exist\r\n"
18129 );
18130 assert_eq!(
18131 f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
18132 "-ERR Path does not exist\r\n"
18133 );
18134 }
18135
18136 #[test]
18139 fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
18140 let mut f = Fixture::new();
18141 let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
18142 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18143
18144 assert_eq!(
18147 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
18148 bulk("9").as_str()
18149 );
18150 assert_eq!(
18152 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
18153 bulk("[11]").as_str()
18154 );
18155 assert_eq!(
18158 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
18159 bulk("13.0").as_str()
18160 );
18161 assert_eq!(
18162 f.run(&[b"JSON.TYPE", b"doc", b".i"]),
18163 bulk("number").as_str()
18164 );
18165 assert_eq!(
18166 f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
18167 bulk("3.0").as_str()
18168 );
18169 assert_eq!(
18170 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
18171 bulk("-8").as_str()
18172 );
18173 f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
18176 assert_eq!(
18177 f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
18178 bulk("1.224744871391589").as_str()
18179 );
18180 assert_eq!(
18181 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
18182 "-ERR result is not a number\r\n"
18183 );
18184 f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
18188 assert_eq!(
18189 f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
18190 "-ERR numeric overflow\r\n"
18191 );
18192 f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
18193 assert_eq!(
18194 f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
18195 "-ERR numeric overflow\r\n"
18196 );
18197 f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
18199 assert_eq!(
18200 f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
18201 "-ERR result is not a number\r\n"
18202 );
18203
18204 assert_eq!(
18208 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
18209 bulk("[null]").as_str()
18210 );
18211 assert_eq!(
18212 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
18213 bulk("[]").as_str()
18214 );
18215 assert_eq!(
18216 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
18217 "-ERR Path does not exist or does not contains a number\r\n"
18218 );
18219 assert_eq!(
18220 f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
18221 "-ERR Path does not exist or does not contains a number\r\n"
18222 );
18223 assert_eq!(
18226 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
18227 "-bad input number\r\n"
18228 );
18229 assert_eq!(
18230 f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
18231 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18232 );
18233 assert_eq!(
18234 f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
18235 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18236 );
18237 }
18238
18239 #[test]
18242 fn json_strappend_reads_its_shape_off_the_argument_count() {
18243 let mut f = Fixture::new();
18244 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
18245
18246 assert_eq!(
18247 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
18248 ":3\r\n"
18249 );
18250 assert_eq!(
18251 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
18252 "*1\r\n:4\r\n"
18253 );
18254 assert_eq!(
18257 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
18258 ":6\r\n"
18259 );
18260 f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
18263 assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
18264 assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
18265
18266 assert_eq!(
18270 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
18271 "-WRONGTYPE wrong type of path value - expected string\r\n"
18272 );
18273 assert_eq!(
18274 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
18275 "*1\r\n$-1\r\n"
18276 );
18277 assert_eq!(
18278 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
18279 "-ERR Path does not exist or not a string\r\n"
18280 );
18281 assert_eq!(
18282 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
18283 "*0\r\n"
18284 );
18285 assert_eq!(
18286 f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
18287 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18288 );
18289 }
18290
18291 #[test]
18294 fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
18295 let mut f = Fixture::new();
18296 let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
18299
18300 f.run(&[b"JSON.SET", b"doc", b"$", three]);
18301 assert_eq!(
18302 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
18303 ":4\r\n"
18304 );
18305 f.run(&[b"JSON.SET", b"doc", b"$", three]);
18306 assert_eq!(
18307 f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
18308 ":2\r\n"
18309 );
18310 f.run(&[b"JSON.SET", b"doc", b"$", three]);
18311 assert_eq!(
18312 f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
18313 ":1\r\n"
18314 );
18315 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
18316 assert_eq!(
18317 f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
18318 bulk("1").as_str()
18319 );
18320 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
18321 assert_eq!(
18322 f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
18323 bulk("13").as_str()
18324 );
18325 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
18326 assert_eq!(
18327 f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
18328 ":4\r\n"
18329 );
18330 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
18331 assert_eq!(
18332 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
18333 bulk("false").as_str()
18334 );
18335 assert_eq!(
18338 f.run(&[b"JSON.GET", b"doc", b".a"]),
18339 bulk("[false,true,false]").as_str()
18340 );
18341
18342 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
18345 assert_eq!(
18346 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
18347 ":3\r\n"
18348 );
18349 assert_eq!(
18350 f.run(&[b"JSON.GET", b"doc", b".a"]),
18351 bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
18352 );
18353 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
18356 assert_eq!(
18357 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
18358 "-ERR Path does not exist or not an array\r\n"
18359 );
18360 assert_eq!(
18361 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
18362 "-ERR Path does not exist or not a bool\r\n"
18363 );
18364 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
18368 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
18369 assert_eq!(
18370 f.run(&[b"JSON.GET", b"doc", b".a"]),
18371 bulk("[[],[2]]").as_str()
18372 );
18373 }
18374
18375 #[test]
18378 fn a_write_reaches_a_match_that_sits_inside_another_match() {
18379 let mut f = Fixture::new();
18380 let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
18381
18382 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
18383 assert_eq!(
18384 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
18385 "*3\r\n:3\r\n:2\r\n:3\r\n"
18386 );
18387 assert_eq!(
18388 f.run(&[b"JSON.GET", b"doc", b"$"]),
18389 bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
18390 );
18391
18392 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
18395 assert_eq!(
18396 f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
18397 "*3\r\n:1\r\n:1\r\n:1\r\n"
18398 );
18399 assert_eq!(
18400 f.run(&[b"JSON.GET", b"doc", b"$"]),
18401 bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
18402 );
18403
18404 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
18407 assert_eq!(
18408 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
18409 bulk("[null,8,8]").as_str()
18410 );
18411 }
18412
18413 #[test]
18416 fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
18417 let mut f = Fixture::new();
18418 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
18419
18420 assert_eq!(
18423 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
18424 bulk("[null]").as_str()
18425 );
18426 assert_eq!(
18427 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
18428 bulk("[null]").as_str()
18429 );
18430 assert_eq!(
18431 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
18432 bulk("[]").as_str()
18433 );
18434 assert_eq!(
18435 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
18436 "-ERR Path does not exist or does not contains a number\r\n"
18437 );
18438 assert_eq!(
18440 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
18441 "-bad input number\r\n"
18442 );
18443
18444 assert_eq!(
18446 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
18447 "*1\r\n$-1\r\n"
18448 );
18449 assert_eq!(
18450 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
18451 "-ERR Path does not exist or not a string\r\n"
18452 );
18453 assert_eq!(
18454 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
18455 "-WRONGTYPE wrong type of path value - expected string\r\n"
18456 );
18457
18458 assert_eq!(
18460 f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
18461 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18462 );
18463 assert_eq!(
18464 f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
18465 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18466 );
18467 }
18468
18469 #[test]
18472 fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
18473 let mut f = Fixture::new();
18474
18475 assert_eq!(
18479 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
18480 "+OK\r\n"
18481 );
18482 assert_eq!(
18483 f.run(&[b"JSON.GET", b"doc", b"$"]),
18484 bulk(r#"[{"x":null,"y":1}]"#).as_str()
18485 );
18486
18487 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
18490 assert_eq!(
18491 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
18492 "+OK\r\n"
18493 );
18494 assert_eq!(
18495 f.run(&[b"JSON.GET", b"doc", b"$"]),
18496 bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
18497 );
18498
18499 assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
18501 assert_eq!(
18502 f.run(&[b"JSON.GET", b"doc", b"$"]),
18503 bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
18504 );
18505
18506 assert_eq!(
18510 f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
18511 "+OK\r\n"
18512 );
18513 assert_eq!(
18514 f.run(&[b"JSON.GET", b"doc", b"$"]),
18515 bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
18516 );
18517
18518 assert_eq!(
18522 f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
18523 "+OK\r\n"
18524 );
18525 assert_eq!(
18526 f.run(&[b"JSON.GET", b"doc", b"$.new"]),
18527 bulk(r#"[{"z":null}]"#).as_str()
18528 );
18529 assert_eq!(
18530 f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
18531 "$-1\r\n"
18532 );
18533 assert_eq!(
18534 f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
18535 "-Err wrong static path\r\n"
18536 );
18537
18538 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
18540 assert_eq!(
18541 f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
18542 "+OK\r\n"
18543 );
18544 assert_eq!(
18545 f.run(&[b"JSON.GET", b"doc", b"$"]),
18546 bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
18547 );
18548
18549 assert_eq!(
18551 f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
18552 "-ERR syntax error\r\n"
18553 );
18554 assert_eq!(
18555 f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
18556 "-ERR new objects must be created at the root\r\n"
18557 );
18558 f.run(&[b"SET", b"str", b"x"]);
18559 assert_eq!(
18560 f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
18561 "-Existing key has wrong Redis type\r\n"
18562 );
18563 }
18564
18565 #[test]
18568 fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
18569 let mut f = Fixture::new();
18570 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
18571 assert_eq!(
18572 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
18573 "+OK\r\n"
18574 );
18575 assert_eq!(
18579 f.run(&[b"JSON.GET", b"doc", b"$"]),
18580 bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
18581 );
18582
18583 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
18586 assert_eq!(
18587 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
18588 "+OK\r\n"
18589 );
18590 assert_eq!(
18591 f.run(&[b"JSON.GET", b"doc", b"$"]),
18592 bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
18593 );
18594 }
18595
18596 #[test]
18599 fn a_filter_path_reads_and_writes_the_members_it_keeps() {
18600 let mut f = Fixture::new();
18601 let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
18602 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18603
18604 assert_eq!(
18605 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
18606 bulk(r#"["a","c"]"#).as_str()
18607 );
18608 assert_eq!(
18611 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
18612 bulk(r#"["a","c"]"#).as_str()
18613 );
18614 assert_eq!(
18616 f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
18617 bulk(r#""a""#).as_str()
18618 );
18619 assert_eq!(
18620 f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
18621 "*1\r\n$6\r\nobject\r\n"
18622 );
18623
18624 assert_eq!(
18628 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
18629 bulk("[9,10]").as_str()
18630 );
18631 assert_eq!(
18632 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
18633 "+OK\r\n"
18634 );
18635 assert_eq!(
18636 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
18637 "-Err wrong static path\r\n"
18638 );
18639 assert_eq!(
18640 f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
18641 ":2\r\n"
18642 );
18643 assert_eq!(
18644 f.run(&[b"JSON.GET", b"doc", b"$"]),
18645 bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
18646 );
18647
18648 assert!(
18651 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
18652 .starts_with("-ERR")
18653 );
18654 assert!(
18655 f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
18656 .starts_with("-ERR")
18657 );
18658 }
18659
18660 #[test]
18663 fn a_filter_takes_the_membership_operators_and_the_methods_too() {
18664 let mut f = Fixture::new();
18665 let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
18666 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18667
18668 for (path, want) in [
18669 (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
18670 (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
18671 (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
18672 (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
18673 (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
18674 (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
18675 (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
18676 (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
18677 (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
18678 (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
18679 (b"$.box[?(@.n~)].t", "[]"),
18680 (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
18681 (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
18682 (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
18683 (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
18684 ] {
18685 assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
18686 }
18687
18688 assert_eq!(
18691 f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
18692 "+OK\r\n"
18693 );
18694 assert_eq!(
18695 f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
18696 bulk(r#"["b"]"#).as_str()
18697 );
18698 }
18699
18700 #[test]
18703 fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
18704 let mut f = Fixture::new();
18705 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
18706 assert_eq!(
18707 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
18708 "+OK\r\n"
18709 );
18710 assert_eq!(
18711 f.run(&[b"JSON.GET", b"doc", b"$"]),
18712 bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
18713 );
18714 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
18717 assert_eq!(
18718 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
18719 "+OK\r\n"
18720 );
18721 assert_eq!(
18722 f.run(&[b"JSON.GET", b"doc", b"$"]),
18723 bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
18724 );
18725 }
18726
18727 #[test]
18730 fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
18731 let mut f = Fixture::new();
18732 assert_eq!(
18733 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
18734 "+OK\r\n"
18735 );
18736 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
18737 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
18738
18739 assert_eq!(
18741 f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
18742 "+OK\r\n"
18743 );
18744 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
18745
18746 f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
18751 assert_eq!(
18752 f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
18753 "$-1\r\n"
18754 );
18755 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
18756 assert_eq!(
18757 f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
18758 "$-1\r\n"
18759 );
18760 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
18761
18762 f.run(&[b"SET", b"str", b"x"]);
18767 assert_eq!(
18768 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
18769 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
18770 );
18771 assert_eq!(
18772 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
18773 "-Existing key has wrong Redis type\r\n"
18774 );
18775 assert_eq!(
18776 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
18777 "-ERR new objects must be created at the root\r\n"
18778 );
18779 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
18780
18781 assert_eq!(
18786 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
18787 "-Err wrong static path\r\n"
18788 );
18789 assert_eq!(
18790 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
18791 "-ERR array index out of range\r\n"
18792 );
18793 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
18794
18795 f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
18799 assert_eq!(
18800 f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
18801 "+OK\r\n"
18802 );
18803 assert_eq!(
18804 f.run(&[b"JSON.GET", b"c", b"$"]),
18805 bulk(r#"[{"n":3}]"#).as_str()
18806 );
18807
18808 assert_eq!(
18811 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
18812 "-ERR wrong number of arguments for 'json.mset' command\r\n"
18813 );
18814 }
18815
18816 #[test]
18819 fn json_resp_answers_the_document_as_resp_types() {
18820 let mut f = Fixture::new();
18821 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
18822 assert_eq!(
18823 f.run(&[b"JSON.RESP", b"doc"]),
18824 "*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"
18825 );
18826 assert_eq!(
18828 f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
18829 "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
18830 );
18831
18832 f.run(&[
18833 b"JSON.SET",
18834 b"doc",
18835 b"$",
18836 br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
18837 ]);
18838 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
18839 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
18840 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
18843 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
18844 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
18845
18846 assert_eq!(
18849 f.run(&[b"JSON.RESP", b"doc", b".nope"]),
18850 "-ERR Path does not exist\r\n"
18851 );
18852 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
18853 assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
18854 assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
18855 }
18856
18857 #[test]
18861 fn json_debug_answers_a_byte_count_and_its_own_help() {
18862 let mut f = Fixture::new();
18863 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
18864 let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
18865 assert!(one.starts_with(':'), "{one}");
18866 assert_eq!(
18867 f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
18868 format!("*1\r\n{one}")
18869 );
18870 let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
18871 assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
18872
18873 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
18877 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
18878 assert_eq!(
18879 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
18880 "-ERR Path does not exist\r\n"
18881 );
18882 assert_eq!(
18883 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
18884 "*0\r\n"
18885 );
18886
18887 assert_eq!(
18888 f.run(&[b"JSON.DEBUG", b"HELP"]),
18889 "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
18890 $34\r\nHELP - this message\r\n"
18891 );
18892 assert_eq!(
18893 f.run(&[b"JSON.DEBUG", b"NOPE"]),
18894 "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
18895 );
18896 assert_eq!(
18897 f.run(&[b"JSON.DEBUG", b"MEMORY"]),
18898 "-ERR wrong number of arguments for 'json.debug' command\r\n"
18899 );
18900 }
18901
18902 #[test]
18907 fn the_first_vadd_decides_how_wide_the_set_is() {
18908 let mut f = Fixture::new();
18909 assert_eq!(
18910 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
18911 ":1\r\n"
18912 );
18913 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
18914 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
18915 assert_eq!(
18918 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
18919 ":0\r\n"
18920 );
18921 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
18922 assert_eq!(
18925 f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
18926 "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
18927 );
18928 assert_eq!(
18931 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
18932 ":1\r\n"
18933 );
18934 assert_eq!(
18935 f.run(&[b"VEMB", b"v", b"nowhere"]),
18936 "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
18937 );
18938 assert_eq!(
18942 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
18943 "-ERR asked quantization mismatch with existing vector set\r\n"
18944 );
18945 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
18948 assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
18949 assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
18950 }
18951
18952 #[test]
18955 fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
18956 let mut f = Fixture::new();
18957 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
18958 assert_eq!(
18964 f.run(&[b"VEMB", b"v", b"a"]),
18965 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
18966 );
18967 f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
18969 assert_eq!(
18970 f.run(&[b"VEMB", b"n", b"a"]),
18971 "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
18972 );
18973 f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
18976 assert_eq!(
18977 f.run(&[b"VEMB", b"b", b"a"]),
18978 "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
18979 );
18980 assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
18981 assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
18982
18983 let mut f = Fixture::new();
18987 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
18988 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
18989 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
18990 assert_eq!(
18991 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
18992 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
18993 $8\r\nopposite\r\n$1\r\n0\r\n"
18994 );
18995 assert_eq!(
18998 f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
18999 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
19000 );
19001 assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
19004 assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
19005 assert_eq!(
19008 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
19009 "*1\r\n$6\r\nacross\r\n"
19010 );
19011 assert_eq!(
19012 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
19013 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
19014 );
19015 assert_eq!(
19019 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
19020 "*1\r\n$6\r\nacross\r\n"
19021 );
19022
19023 let mut g = Fixture::new();
19026 g.run(&[b"HELLO", b"3"]);
19027 g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19028 assert_eq!(
19029 g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
19030 "%1\r\n$4\r\neast\r\n,1\r\n"
19031 );
19032 }
19033
19034 #[test]
19036 fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
19037 let mut f = Fixture::new();
19038 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19039 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
19040 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
19041 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
19042 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
19045 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
19046 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
19048 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
19049 assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
19052 assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
19053 assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
19054 assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
19057 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
19058 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
19059 assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
19060
19061 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
19063 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
19064 assert_eq!(
19065 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
19066 "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
19067 );
19068 }
19069
19070 #[test]
19073 fn vrem_takes_the_attribute_with_it() {
19074 let mut f = Fixture::new();
19075 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19076 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
19077 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
19078 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
19079 assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
19080 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
19083
19084 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19087 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
19088 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
19089 f.run(&[b"VREM", b"v", b"east"]);
19090 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
19091 assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
19092 }
19093
19094 #[test]
19097 fn vinfo_says_partition_first() {
19098 let mut f = Fixture::new();
19099 f.run(&[
19100 b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
19101 ]);
19102 f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
19103 let info = f.run(&[b"VINFO", b"v"]);
19104 assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
19105 assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
19108 assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
19109 assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
19110 assert!(
19113 info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
19114 "{info}"
19115 );
19116 let mut f = Fixture::new();
19117 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
19118 assert!(
19119 f.run(&[b"VINFO", b"v"])
19120 .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
19121 );
19122 assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
19123 }
19124
19125 fn named() -> Fixture {
19127 let mut f = Fixture::new();
19128 for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
19129 .iter()
19130 .enumerate()
19131 {
19132 let x = (i + 1).to_string();
19133 f.run(&[
19134 b"VADD",
19135 b"r",
19136 b"VALUES",
19137 b"2",
19138 x.as_bytes(),
19139 b"1",
19140 name.as_bytes(),
19141 ]);
19142 }
19143 f
19144 }
19145
19146 #[test]
19149 fn vrange_walks_the_names_and_not_the_vectors() {
19150 let mut f = named();
19151 assert_eq!(
19152 f.run(&[b"VRANGE", b"r", b"-", b"+"]),
19153 "*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"
19154 );
19155 assert_eq!(
19156 f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
19157 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
19158 "the high end is a name and not a prefix, so delta is past it"
19159 );
19160 assert_eq!(
19161 f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
19162 "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
19163 );
19164 assert_eq!(
19165 f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
19166 "*1\r\n$4\r\nbeta\r\n"
19167 );
19168 assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
19169 f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
19172 assert_eq!(
19173 f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
19174 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
19175 );
19176 assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
19177 }
19178
19179 #[test]
19182 fn a_vrange_count_of_zero_asks_for_nothing() {
19183 let mut f = named();
19184 assert_eq!(
19185 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
19186 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
19187 );
19188 assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
19189 assert!(
19190 f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
19191 .starts_with("*5\r\n"),
19192 "a negative count is no limit at all"
19193 );
19194 }
19195
19196 #[test]
19199 fn vrange_says_which_end_it_could_not_read() {
19200 let mut f = named();
19201 assert_eq!(
19202 f.run(&[b"VRANGE", b"r", b"x", b"y"]),
19203 "-ERR invalid start range format\r\n"
19204 );
19205 assert_eq!(
19206 f.run(&[b"VRANGE", b"r", b"+", b"x"]),
19207 "-ERR invalid end range format\r\n",
19208 "the high end is spelled wrong, which is worth saying before the \
19209 low end being on the wrong side"
19210 );
19211 assert_eq!(
19212 f.run(&[b"VRANGE", b"r", b"+", b"-"]),
19213 "-ERR '-' can only be used as first argument, '+' only as second\r\n"
19214 );
19215 assert_eq!(
19218 f.run(&[b"VRANGE", b"r", b"[", b"+"]),
19219 "-ERR invalid start range format\r\n"
19220 );
19221 assert_eq!(
19222 f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
19223 "-ERR invalid COUNT value\r\n"
19224 );
19225 assert_eq!(
19226 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
19227 "-ERR wrong number of arguments for 'VRANGE' command\r\n"
19228 );
19229 f.run(&[b"SET", b"s", b"x"]);
19230 assert!(
19231 f.run(&[b"VRANGE", b"s", b"-", b"+"])
19232 .starts_with("-WRONGTYPE")
19233 );
19234 }
19235
19236 #[test]
19239 fn reduce_is_refused_and_not_ignored() {
19240 let mut f = Fixture::new();
19241 let reduce = f.run(&[
19242 b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
19243 ]);
19244 assert!(
19245 reduce.starts_with("-ERR REDUCE is not supported."),
19246 "{reduce}"
19247 );
19248 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
19249 }
19250
19251 #[test]
19254 fn vsim_filter_reads_the_attributes() {
19255 let mut f = Fixture::new();
19256 for (name, x, y, attr) in [
19257 ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
19258 ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
19259 ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
19260 ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
19261 ] {
19262 f.run(&[
19263 b"VADD",
19264 b"v",
19265 b"VALUES",
19266 b"2",
19267 x.as_bytes(),
19268 y.as_bytes(),
19269 name.as_bytes(),
19270 b"SETATTR",
19271 attr.as_bytes(),
19272 ]);
19273 }
19274 assert_eq!(
19277 f.run(&[
19278 b"VSIM",
19279 b"v",
19280 b"VALUES",
19281 b"2",
19282 b"9",
19283 b"1",
19284 b"COUNT",
19285 b"2",
19286 b"FILTER",
19287 b".lang == \"en\"",
19288 ]),
19289 "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
19290 );
19291 assert_eq!(
19294 f.run(&[
19295 b"VSIM",
19296 b"v",
19297 b"VALUES",
19298 b"2",
19299 b"9",
19300 b"1",
19301 b"FILTER",
19302 b".lang == 'en' and .year > 1980",
19303 ]),
19304 "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
19305 );
19306 assert_eq!(
19308 f.run(&[
19309 b"VSIM",
19310 b"v",
19311 b"VALUES",
19312 b"2",
19313 b"9",
19314 b"1",
19315 b"FILTER",
19316 b".lang in ['fr', 'de']",
19317 ]),
19318 "*1\r\n$1\r\nb\r\n"
19319 );
19320 assert_eq!(
19321 f.run(&[
19322 b"VSIM",
19323 b"v",
19324 b"VALUES",
19325 b"2",
19326 b"9",
19327 b"1",
19328 b"FILTER",
19329 b".rating > 3"
19330 ]),
19331 "*0\r\n"
19332 );
19333 assert_eq!(
19336 f.run(&[
19337 b"VSIM",
19338 b"v",
19339 b"VALUES",
19340 b"2",
19341 b"9",
19342 b"1",
19343 b"TRUTH",
19344 b"FILTER",
19345 b".year < 1980",
19346 ]),
19347 "*1\r\n$1\r\nc\r\n"
19348 );
19349 f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
19352 assert_eq!(
19353 f.run(&[
19354 b"VSIM",
19355 b"v",
19356 b"VALUES",
19357 b"2",
19358 b"9",
19359 b"1",
19360 b"COUNT",
19361 b"1",
19362 b"FILTER",
19363 b".lang == \"en\"",
19364 ]),
19365 "*1\r\n$1\r\nb\r\n"
19366 );
19367 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
19370 assert_eq!(
19371 f.run(&[
19372 b"VSIM",
19373 b"v",
19374 b"VALUES",
19375 b"2",
19376 b"9",
19377 b"1",
19378 b"COUNT",
19379 b"1",
19380 b"FILTER",
19381 b".lang == \"en\"",
19382 ]),
19383 "*1\r\n$1\r\nb\r\n"
19384 );
19385
19386 let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
19389 assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
19390 assert_eq!(
19391 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
19392 "-ERR invalid FILTER expression\r\n"
19393 );
19394 assert_eq!(
19397 f.run(&[
19398 b"VSIM",
19399 b"v",
19400 b"VALUES",
19401 b"2",
19402 b"9",
19403 b"1",
19404 b"COUNT",
19405 b"1",
19406 b"FILTER-EF",
19407 b"500",
19408 b"FILTER",
19409 b".lang == 'en'",
19410 ]),
19411 "*1\r\n$1\r\nb\r\n"
19412 );
19413 assert_eq!(
19414 f.run(&[
19415 b"VSIM",
19416 b"v",
19417 b"VALUES",
19418 b"2",
19419 b"9",
19420 b"1",
19421 b"COUNT",
19422 b"1",
19423 b"FILTER-EF",
19424 b"0"
19425 ]),
19426 "*1\r\n$1\r\nb\r\n"
19427 );
19428 assert_eq!(
19429 f.run(&[
19430 b"VSIM",
19431 b"v",
19432 b"VALUES",
19433 b"2",
19434 b"9",
19435 b"1",
19436 b"FILTER-EF",
19437 b"lots"
19438 ]),
19439 "-ERR EF must be a positive integer\r\n"
19440 );
19441 }
19442
19443 #[test]
19446 fn the_keyspace_sees_a_vector_set_key_like_any_other() {
19447 let mut f = Fixture::new();
19448 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19449 assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
19450 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
19451 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
19452 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
19453 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
19454 assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
19455 assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
19456 assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
19457 assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
19458 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
19459
19460 f.run(&[b"SET", b"s", b"1"]);
19462 assert_eq!(
19463 f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
19464 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19465 );
19466 assert_eq!(
19467 f.run(&[b"VCARD", b"s"]),
19468 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19469 );
19470 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19471 assert_eq!(
19472 f.run(&[b"GET", b"v"]),
19473 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19474 );
19475 f.run(&[b"G.NADD", b"social", b"ada"]);
19479 assert_eq!(
19480 f.run(&[b"VCARD", b"social"]),
19481 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19482 );
19483 assert_eq!(
19484 f.run(&[b"G.NGET", b"v", b"ada"]),
19485 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19486 );
19487 }
19488
19489 #[test]
19492 fn vrandmember_has_the_two_shapes_srandmember_has() {
19493 let mut f = Fixture::new();
19494 for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
19495 let x = (i + 1).to_string();
19496 f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
19497 }
19498 let one = f.run(&[b"VRANDMEMBER", b"v"]);
19500 assert!(one.starts_with("$1\r\n"), "{one}");
19501 let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
19503 assert!(all.starts_with("*3\r\n"), "{all}");
19504 for name in ["a", "b", "c"] {
19505 assert!(all.contains(name), "{all} is missing {name}");
19506 }
19507 all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
19508 assert!(all.starts_with("*2\r\n"), "{all}");
19509 let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
19511 assert!(many.starts_with("*5\r\n"), "{many}");
19512 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
19514 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
19515 }
19516
19517 #[test]
19520 fn vlinks_reports_one_layer_of_partition_neighbours() {
19521 let mut f = Fixture::new();
19522 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19523 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
19524 assert_eq!(
19527 f.run(&[b"VLINKS", b"v", b"east"]),
19528 "*1\r\n*1\r\n$5\r\nnorth\r\n"
19529 );
19530 assert_eq!(
19531 f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
19532 "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
19533 );
19534 assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
19535 assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
19536 }
19537
19538 #[test]
19541 fn fp32_and_values_are_the_same_vector() {
19542 let mut f = Fixture::new();
19543 let mut blob = Vec::new();
19544 for x in [3.0f32, 4.0] {
19545 blob.extend_from_slice(&x.to_le_bytes());
19546 }
19547 assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
19548 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
19549 assert_eq!(
19550 f.run(&[b"VEMB", b"v", b"a"]),
19551 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
19552 );
19553 assert_eq!(
19559 f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
19560 "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
19561 );
19562 assert_eq!(
19564 f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
19565 "-ERR invalid vector specification\r\n"
19566 );
19567 assert_eq!(
19569 f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
19570 "-ERR syntax error\r\n"
19571 );
19572 assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
19573 }
19574
19575 #[test]
19580 fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
19581 let mut f = Fixture::new();
19582 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
19583 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
19584 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
19585 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
19586 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
19587 assert_eq!(
19590 f.run(&[b"BF.INFO", b"b"]),
19591 "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
19592 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
19593 +Expansion rate\r\n:2\r\n"
19594 );
19595 assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
19596 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
19597 assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
19600 assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
19601 }
19602
19603 #[test]
19609 fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
19610 let mut f = Fixture::new();
19611 f.run(&[b"SET", b"s", b"text"]);
19612 assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
19613 assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
19614 for cmd in [
19615 vec![&b"BF.ADD"[..], b"s", b"x"],
19616 vec![&b"BF.MADD"[..], b"s", b"x"],
19617 vec![&b"BF.CARD"[..], b"s"],
19618 vec![&b"BF.INFO"[..], b"s"],
19619 vec![&b"BF.DEBUG"[..], b"s"],
19620 vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
19621 ] {
19622 let name = String::from_utf8_lossy(cmd[0]).into_owned();
19623 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
19624 }
19625 assert_eq!(
19628 f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
19629 "-ERR bad error rate\r\n"
19630 );
19631 assert!(
19632 f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
19633 .starts_with("-WRONGTYPE")
19634 );
19635 }
19636
19637 #[test]
19640 fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
19641 let mut f = Fixture::new();
19642 assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
19643 for i in 0..10u32 {
19644 assert_eq!(
19645 f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
19646 ":1\r\n"
19647 );
19648 }
19649 assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
19650 assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
19651 assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
19652 assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
19655 assert_eq!(
19656 f.run(&[b"BF.DEBUG", b"g"]),
19657 "*3\r\n$7\r\nsize:11\r\n\
19658 $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
19659 $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
19660 );
19661
19662 assert_eq!(
19664 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
19665 "+OK\r\n"
19666 );
19667 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
19668 assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
19669 assert_eq!(
19670 f.run(&[b"BF.ADD", b"n", b"c"]),
19671 "-ERR non scaling filter is full\r\n"
19672 );
19673 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
19676 assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
19679 f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
19680 assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
19681 assert_eq!(
19684 f.run(&[
19685 b"BF.RESERVE",
19686 b"q",
19687 b"0.01",
19688 b"2",
19689 b"NONSCALING",
19690 b"EXPANSION",
19691 b"2"
19692 ]),
19693 "-Nonscaling filters cannot expand\r\n"
19694 );
19695 }
19696
19697 #[test]
19700 fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
19701 let mut f = Fixture::new();
19702 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
19703 assert_eq!(
19704 f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
19705 "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
19706 );
19707 assert_eq!(
19708 f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
19709 "*2\r\n:1\r\n:0\r\n"
19710 );
19711 }
19712
19713 #[test]
19716 fn insert_is_a_reserve_and_a_madd_with_different_errors() {
19717 let mut f = Fixture::new();
19718 assert_eq!(
19719 f.run(&[
19720 b"BF.INSERT",
19721 b"i",
19722 b"CAPACITY",
19723 b"50",
19724 b"ERROR",
19725 b"0.001",
19726 b"ITEMS",
19727 b"a",
19728 b"b"
19729 ]),
19730 "*2\r\n:1\r\n:1\r\n"
19731 );
19732 assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
19733 assert_eq!(
19735 f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
19736 "-ERR not found\r\n"
19737 );
19738 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
19739 assert_eq!(
19742 f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
19743 "-Bad capacity\r\n"
19744 );
19745 assert_eq!(
19746 f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
19747 "-Bad error rate\r\n"
19748 );
19749 assert_eq!(
19750 f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
19751 "-Bad expansion\r\n"
19752 );
19753 assert_eq!(
19758 f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
19759 "*1\r\n:1\r\n"
19760 );
19761 assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
19762 assert_eq!(
19766 f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
19767 "*1\r\n:1\r\n"
19768 );
19769 assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
19770 assert_eq!(
19771 f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
19772 "*1\r\n:1\r\n"
19773 );
19774 assert_eq!(
19775 f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
19776 "-ERR not found\r\n"
19777 );
19778 assert_eq!(
19780 f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
19781 "-Unknown argument received\r\n"
19782 );
19783 assert_eq!(
19785 f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
19786 "*1\r\n:1\r\n"
19787 );
19788 assert!(
19790 f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
19791 .contains("wrong number of arguments")
19792 );
19793 }
19794
19795 #[test]
19798 fn a_dump_replays_into_a_filter_that_answers_the_same() {
19799 let mut f = Fixture::new();
19800 f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
19801 for i in 0..25u32 {
19802 f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
19803 }
19804 assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
19805
19806 let mut iter = b"0".to_vec();
19809 let mut chunks = 0;
19810 loop {
19811 let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
19812 let text = String::from_utf8_lossy(&raw).into_owned();
19813 let next = text
19814 .split("\r\n")
19815 .nth(1)
19816 .and_then(|n| n.strip_prefix(':'))
19817 .expect("a two element reply of an iterator and a chunk")
19818 .to_owned();
19819 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
19820 let data = &body[body
19821 .windows(2)
19822 .position(|w| w == b"\r\n")
19823 .expect("a length line")
19824 + 2..body.len() - 2];
19825 if next == "0" {
19826 assert!(data.is_empty(), "the last chunk is empty");
19827 break;
19828 }
19829 let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
19830 assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
19831 iter = next.into_bytes();
19832 chunks += 1;
19833 }
19834 assert_eq!(chunks, 3, "a header and one chunk per link");
19835
19836 assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
19837 assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
19838 for i in 0..25u32 {
19839 assert_eq!(
19840 f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
19841 ":1\r\n"
19842 );
19843 }
19844
19845 assert_eq!(
19848 f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
19849 "-ERR received bad data\r\n"
19850 );
19851 assert_eq!(
19852 f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
19853 "-ERR received bad data\r\n"
19854 );
19855 assert_eq!(
19857 f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
19858 "-ERR invalid offset - no link found\r\n"
19859 );
19860 assert_eq!(
19861 f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
19862 "-ERR Second argument must be numeric\r\n"
19863 );
19864 assert_eq!(
19867 f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
19868 "-Second argument must be numeric\r\n"
19869 );
19870 }
19871
19872 #[test]
19875 fn reserve_reads_its_numbers_the_way_string2ll_does() {
19876 let mut f = Fixture::new();
19877 for (args, want) in [
19878 (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
19879 (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
19880 (
19881 vec![&b"0"[..], b"10"],
19882 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19883 ),
19884 (
19885 vec![&b"1"[..], b"10"],
19886 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19887 ),
19888 (
19889 vec![&b"inf"[..], b"10"],
19890 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19891 ),
19892 (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
19893 (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
19894 (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
19895 (
19896 vec![&b"0.01"[..], b"0"],
19897 "-ERR capacity must be in the range [1, 1073741824]\r\n",
19898 ),
19899 (
19900 vec![&b"0.01"[..], b"1073741825"],
19901 "-ERR capacity must be in the range [1, 1073741824]\r\n",
19902 ),
19903 ] {
19904 let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
19905 cmd.extend(args.iter().copied());
19906 assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
19907 }
19908 assert_eq!(
19909 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
19910 "-ERR no expansion\r\n"
19911 );
19912 assert_eq!(
19913 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
19914 "-ERR bad expansion\r\n"
19915 );
19916 assert_eq!(
19917 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
19918 "-ERR expansion must be in the range [0, 32768]\r\n"
19919 );
19920 assert_eq!(
19922 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
19923 "+OK\r\n"
19924 );
19925 assert_eq!(
19926 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
19927 "-ERR item exists\r\n"
19928 );
19929 assert_eq!(
19930 f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
19931 "-Invalid information value\r\n"
19932 );
19933 assert!(
19934 f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
19935 .contains("wrong number of arguments")
19936 );
19937 }
19938
19939 #[test]
19941 fn the_bloom_family_answers_in_resp3_spelling_too() {
19942 let mut f = Fixture::new();
19943 f.out.set_proto(Proto::Resp3);
19944 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
19945 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
19946 assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
19947 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
19948 assert_eq!(
19949 f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
19950 "*2\r\n#t\r\n#f\r\n"
19951 );
19952 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
19954 assert_eq!(
19955 f.run(&[b"BF.INFO", b"b"]),
19956 "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
19957 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
19958 +Expansion rate\r\n:2\r\n"
19959 );
19960 assert_eq!(
19963 f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
19964 "%1\r\n+Capacity\r\n:100\r\n"
19965 );
19966 }
19967
19968 fn cf_header(
19977 items: u64,
19978 buckets: u64,
19979 deletes: u64,
19980 filters: u64,
19981 geometry: [u16; 3],
19982 ) -> Vec<u8> {
19983 let mut out = Vec::with_capacity(38);
19984 for n in [items, buckets, deletes, filters] {
19985 out.extend_from_slice(&n.to_le_bytes());
19986 }
19987 for n in geometry {
19988 out.extend_from_slice(&n.to_le_bytes());
19989 }
19990 out
19991 }
19992
19993 #[test]
19997 fn cf_add_makes_the_filter_and_counts_the_copies() {
19998 let mut f = Fixture::new();
19999 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
20000 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
20001 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
20002 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
20005 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
20006 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
20007 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
20008 assert_eq!(
20009 f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
20010 "*2\r\n:1\r\n:0\r\n"
20011 );
20012 assert_eq!(
20015 f.run(&[b"CF.INFO", b"d"]),
20016 "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
20017 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
20018 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
20019 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
20020 );
20021 assert_eq!(
20022 f.run(&[b"CF.DEBUG", b"d"]),
20023 "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
20024 max_iterations:20 expansion:1\r\n"
20025 );
20026 assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
20027 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
20028
20029 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
20031 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
20032 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
20033 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
20034 assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
20035
20036 assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
20039 assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
20040 assert_eq!(
20041 f.run(&[b"CF.COMPACT", b"gone"]),
20042 "-Cuckoo filter was not found\r\n"
20043 );
20044 assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
20045 assert!(
20049 f.run(&[b"CF.COMPACT", b"a", b"b"])
20050 .contains("wrong number of arguments")
20051 );
20052 }
20053
20054 #[test]
20057 fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
20058 let mut f = Fixture::new();
20059 f.run(&[b"SET", b"s", b"text"]);
20060 assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
20061 assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
20062 assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
20063 assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
20067 assert_eq!(
20068 f.run(&[b"CF.COMPACT", b"s"]),
20069 "-Cuckoo filter was not found\r\n"
20070 );
20071 for cmd in [
20072 vec![&b"CF.ADD"[..], b"s", b"x"],
20073 vec![&b"CF.ADDNX"[..], b"s", b"x"],
20074 vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
20075 vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
20076 vec![&b"CF.INFO"[..], b"s"],
20077 vec![&b"CF.DEBUG"[..], b"s"],
20078 vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
20079 vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
20080 vec![&b"CF.RESERVE"[..], b"s", b"64"],
20081 ] {
20082 let name = String::from_utf8_lossy(cmd[0]).into_owned();
20083 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
20084 }
20085 }
20086
20087 #[test]
20090 fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
20091 let mut f = Fixture::new();
20092 assert_eq!(
20093 f.run(&[
20094 b"CF.RESERVE",
20095 b"r",
20096 b"64",
20097 b"BUCKETSIZE",
20098 b"1",
20099 b"MAXITERATIONS",
20100 b"7",
20101 b"EXPANSION",
20102 b"4"
20103 ]),
20104 "+OK\r\n"
20105 );
20106 assert_eq!(
20107 f.run(&[b"CF.DEBUG", b"r"]),
20108 "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
20109 max_iterations:7 expansion:4\r\n"
20110 );
20111 assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
20112
20113 assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
20114 assert_eq!(
20115 f.run(&[b"CF.RESERVE", b"q", b"1"]),
20116 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
20117 );
20118 assert_eq!(
20121 f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
20122 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
20123 );
20124 assert_eq!(
20125 f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
20126 "+OK\r\n"
20127 );
20128
20129 assert_eq!(
20134 f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
20135 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
20136 );
20137 assert_eq!(
20138 f.run(&[
20139 b"CF.RESERVE",
20140 b"q2",
20141 b"64",
20142 b"EXPANSION",
20143 b"xx",
20144 b"BUCKETSIZE",
20145 b"0"
20146 ]),
20147 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
20148 );
20149 assert_eq!(
20150 f.run(&[
20151 b"CF.RESERVE",
20152 b"q2",
20153 b"64",
20154 b"MAXITERATIONS",
20155 b"0",
20156 b"BUCKETSIZE",
20157 b"0"
20158 ]),
20159 "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
20160 );
20161 assert_eq!(
20165 f.run(&[
20166 b"CF.RESERVE",
20167 b"q3",
20168 b"64",
20169 b"BUCKETSIZE",
20170 b"1",
20171 b"BUCKETSIZE",
20172 b"0"
20173 ]),
20174 "+OK\r\n"
20175 );
20176 assert_eq!(
20179 f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
20180 "+OK\r\n"
20181 );
20182 assert_eq!(
20183 f.run(&[b"CF.DEBUG", b"q4"]),
20184 "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
20185 max_iterations:20 expansion:1\r\n"
20186 );
20187 assert!(
20190 f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
20191 .contains("wrong number of arguments")
20192 );
20193 }
20194
20195 #[test]
20198 fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
20199 let mut f = Fixture::new();
20200 assert_eq!(
20201 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
20202 "*2\r\n:1\r\n:1\r\n"
20203 );
20204 assert_eq!(
20205 f.run(&[b"CF.DEBUG", b"i"]),
20206 "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
20207 max_iterations:20 expansion:1\r\n"
20208 );
20209 assert_eq!(
20212 f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
20213 "*2\r\n:0\r\n:1\r\n"
20214 );
20215 assert_eq!(
20216 f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
20217 "-ERR not found\r\n"
20218 );
20219 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20220
20221 assert_eq!(
20222 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
20223 "-Bad capacity\r\n"
20224 );
20225 assert_eq!(
20228 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
20229 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
20230 );
20231 assert_eq!(
20235 f.run(&[
20236 b"CF.INSERT",
20237 b"i",
20238 b"CAPACITY",
20239 b"8",
20240 b"CAPACITY",
20241 b"2",
20242 b"ITEMS",
20243 b"a"
20244 ]),
20245 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
20246 );
20247 assert_eq!(
20251 f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
20252 "*1\r\n:1\r\n"
20253 );
20254 assert_eq!(
20255 f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
20256 "*1\r\n:1\r\n"
20257 );
20258 assert_eq!(
20259 f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
20260 "-Unknown argument received\r\n"
20261 );
20262 assert_eq!(
20264 f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
20265 "*1\r\n:1\r\n"
20266 );
20267 assert!(
20269 f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
20270 .contains("wrong number of arguments")
20271 );
20272 assert!(
20273 f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
20274 .contains("wrong number of arguments")
20275 );
20276 }
20277
20278 #[test]
20281 fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
20282 let mut f = Fixture::new();
20283 f.run(&[
20284 b"CF.RESERVE",
20285 b"s",
20286 b"4",
20287 b"BUCKETSIZE",
20288 b"1",
20289 b"EXPANSION",
20290 b"0",
20291 ]);
20292 for i in 0..4u32 {
20293 assert_eq!(
20294 f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
20295 ":1\r\n"
20296 );
20297 }
20298 assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
20299 assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
20300 assert_eq!(
20303 f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
20304 "*2\r\n:-1\r\n:-1\r\n"
20305 );
20306 assert_eq!(
20307 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
20308 "*2\r\n:0\r\n:-1\r\n"
20309 );
20310
20311 let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
20317 assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
20318 assert_eq!(
20319 f.run(&[b"CF.ADD", b"g", b"q"]),
20320 "-Maximum expansions reached\r\n"
20321 );
20322 assert_eq!(
20323 f.run(&[b"CF.INFO", b"g"]),
20324 "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
20325 +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
20326 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
20327 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
20328 );
20329 }
20330
20331 #[test]
20335 fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
20336 let mut f = Fixture::new();
20337 f.run(&[
20338 b"CF.RESERVE",
20339 b"src",
20340 b"8",
20341 b"BUCKETSIZE",
20342 b"2",
20343 b"EXPANSION",
20344 b"2",
20345 ]);
20346 for i in 0..40u32 {
20347 f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
20348 }
20349 let mut pos = b"0".to_vec();
20353 let mut chunks = 0;
20354 loop {
20355 let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
20356 let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
20357 let next = head
20358 .split("\r\n")
20359 .nth(1)
20360 .and_then(|n| n.strip_prefix(':'))
20361 .expect("a two element reply of a position and a chunk")
20362 .to_owned();
20363 if next == "0" {
20364 assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
20365 break;
20366 }
20367 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
20368 let at = body
20369 .windows(2)
20370 .position(|w| w == b"\r\n")
20371 .expect("a length line")
20372 + 2;
20373 let data = &body[at..body.len() - 2];
20374 assert_eq!(
20375 f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
20376 "+OK\r\n",
20377 "loading chunk {chunks}"
20378 );
20379 pos = next.into_bytes();
20380 chunks += 1;
20381 }
20382 assert!(chunks >= 2, "a header and at least one chunk");
20383
20384 assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
20385 assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
20386 for i in 0..40u32 {
20387 assert_eq!(
20388 f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
20389 ":1\r\n"
20390 );
20391 }
20392
20393 f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
20396 assert_eq!(
20397 f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
20398 "*2\r\n:0\r\n$-1\r\n"
20399 );
20400
20401 assert_eq!(
20405 f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
20406 "-Invalid position\r\n"
20407 );
20408 assert_eq!(
20409 f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
20410 "-Invalid position\r\n"
20411 );
20412 assert_eq!(
20413 f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
20414 "-Invalid position\r\n"
20415 );
20416 assert_eq!(
20417 f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
20418 "-Couldn't load chunk!\r\n"
20419 );
20420 let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
20422 assert_eq!(
20423 f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
20424 "-ERR item exists\r\n"
20425 );
20426 assert_eq!(
20430 f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
20431 "-Invalid header\r\n"
20432 );
20433 for (why, bad) in [
20434 ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
20435 ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
20436 (
20437 "a bucket count that is not a power of two",
20438 cf_header(0, 3, 0, 1, [2, 20, 1]),
20439 ),
20440 ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
20441 ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
20442 (
20443 "a growth nobody could reach",
20444 cf_header(0, 8, 0, 1, [2, 20, 32769]),
20445 ),
20446 (
20447 "a chain that cannot grow and did",
20448 cf_header(0, 8, 0, 2, [2, 20, 0]),
20449 ),
20450 (
20453 "a filter count that wraps",
20454 cf_header(0, 8, 0, 65_536, [2, 20, 1]),
20455 ),
20456 ] {
20457 assert_eq!(
20458 f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
20459 "-Couldn't create filter!\r\n",
20460 "{why}"
20461 );
20462 }
20463 }
20464
20465 #[test]
20468 fn the_cuckoo_family_answers_in_resp3_spelling_too() {
20469 let mut f = Fixture::new();
20470 f.out.set_proto(Proto::Resp3);
20471 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
20472 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
20473 assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
20474 assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
20475 assert_eq!(
20476 f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
20477 "*2\r\n#t\r\n#f\r\n"
20478 );
20479 assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
20480 assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
20481 assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
20483 assert_eq!(
20484 f.run(&[b"CF.INFO", b"c"]),
20485 "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
20486 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
20487 +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
20488 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
20489 );
20490
20491 f.run(&[
20496 b"CF.RESERVE",
20497 b"s",
20498 b"4",
20499 b"BUCKETSIZE",
20500 b"1",
20501 b"EXPANSION",
20502 b"0",
20503 ]);
20504 assert_eq!(
20505 f.run(&[
20506 b"CF.INSERT",
20507 b"s",
20508 b"ITEMS",
20509 b"a",
20510 b"b",
20511 b"c",
20512 b"d",
20513 b"e",
20514 b"f"
20515 ]),
20516 "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
20517 );
20518 assert_eq!(
20519 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
20520 "*2\r\n:0\r\n:-1\r\n"
20521 );
20522 assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
20523 assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
20526 }
20527
20528 #[test]
20533 fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
20534 let mut f = Fixture::new();
20535 assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
20536 assert_eq!(
20537 f.run(&[b"CMS.INFO", b"d"]),
20538 "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
20539 );
20540 assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
20541 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
20542 assert_eq!(
20545 f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
20546 "+OK\r\n"
20547 );
20548 assert_eq!(
20549 f.run(&[b"CMS.INFO", b"p"]),
20550 "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
20551 );
20552 assert_eq!(
20555 f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
20556 "-CMS: key already exists\r\n"
20557 );
20558 assert_eq!(
20559 f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
20560 "-CMS: invalid width\r\n"
20561 );
20562 assert_eq!(
20563 f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
20564 "-CMS: invalid depth\r\n"
20565 );
20566 assert_eq!(
20567 f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
20568 "-CMS: invalid overestimation value\r\n"
20569 );
20570 assert_eq!(
20571 f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
20572 "-CMS: invalid prob value\r\n"
20573 );
20574 assert_eq!(
20578 f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
20579 "-CMS: invalid init arguments\r\n"
20580 );
20581 assert_eq!(
20585 f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
20586 "-CMS: Insufficient memory to create the key\r\n"
20587 );
20588 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
20589 }
20590
20591 #[test]
20594 fn increments_are_parsed_whole_and_the_counters_saturate() {
20595 let mut f = Fixture::new();
20596 f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
20597 assert_eq!(
20598 f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
20599 "*2\r\n:3\r\n:4\r\n"
20600 );
20601 assert_eq!(
20604 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
20605 "*2\r\n:4\r\n:5\r\n"
20606 );
20607 assert_eq!(
20609 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
20610 "-CMS: Cannot parse number\r\n"
20611 );
20612 assert_eq!(
20613 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
20614 "-CMS: Number cannot be negative\r\n"
20615 );
20616 assert_eq!(
20617 f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
20618 "*2\r\n:5\r\n:4\r\n"
20619 );
20620 f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
20623 assert_eq!(
20624 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
20625 "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
20626 );
20627 assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
20628 f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
20631 f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
20632 f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
20633 assert_eq!(
20634 f.run(&[b"CMS.INFO", b"w"]),
20635 "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
20636 );
20637 assert!(
20640 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
20641 .contains("wrong number of arguments")
20642 );
20643 assert_eq!(
20644 f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
20645 "-CMS: key does not exist\r\n"
20646 );
20647 assert_eq!(
20648 f.run(&[b"CMS.QUERY", b"nope", b"a"]),
20649 "-CMS: key does not exist\r\n"
20650 );
20651 }
20652
20653 #[test]
20656 fn a_merge_lands_whole_or_not_at_all() {
20657 let mut f = Fixture::new();
20658 for name in [&b"m1"[..], b"m2", b"dst"] {
20659 f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
20660 }
20661 f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
20662 f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
20663 assert_eq!(
20664 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
20665 "+OK\r\n"
20666 );
20667 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
20668 assert_eq!(
20671 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
20672 "+OK\r\n"
20673 );
20674 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
20675 assert_eq!(
20676 f.run(&[
20677 b"CMS.MERGE",
20678 b"dst",
20679 b"2",
20680 b"m1",
20681 b"m2",
20682 b"WEIGHTS",
20683 b"2",
20684 b"3"
20685 ]),
20686 "+OK\r\n"
20687 );
20688 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
20689 assert_eq!(
20692 f.run(&[
20693 b"CMS.MERGE",
20694 b"dst",
20695 b"1",
20696 b"m1",
20697 b"WEIGHTS",
20698 b"4611686018427387904"
20699 ]),
20700 "-CMS: MERGE overflow\r\n"
20701 );
20702 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
20703 f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
20706 assert_eq!(
20707 f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
20708 "-CMS: key does not exist\r\n"
20709 );
20710 assert_eq!(
20711 f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
20712 "-CMS: Number of keys must be positive\r\n"
20713 );
20714 assert_eq!(
20715 f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
20716 "-CMS: wrong number of keys\r\n"
20717 );
20718 assert_eq!(
20719 f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
20720 "-CMS: wrong number of keys/weights\r\n"
20721 );
20722 assert_eq!(
20723 f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
20724 "-CMS: width/depth is not equal\r\n"
20725 );
20726 assert_eq!(
20727 f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
20728 "-CMS: key does not exist\r\n"
20729 );
20730 }
20731
20732 #[test]
20735 fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
20736 let mut f = Fixture::new();
20737 f.run(&[b"SET", b"s", b"text"]);
20738 for cmd in [
20739 vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
20740 vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
20741 vec![&b"CMS.QUERY"[..], b"s", b"a"],
20742 vec![&b"CMS.INFO"[..], b"s"],
20743 vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
20744 ] {
20745 let name = String::from_utf8_lossy(cmd[0]).into_owned();
20746 let reply = f.run(&cmd);
20747 assert!(
20750 reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
20751 "{name}: {reply}"
20752 );
20753 }
20754 f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
20755 assert_eq!(
20759 f.run(&[b"COPY", b"c", b"c2"]),
20760 "-ERR not supported for this module key\r\n"
20761 );
20762 assert_eq!(
20763 f.run(&[b"DUMP", b"c"]),
20764 "-ERR DUMP is not supported for this module key\r\n"
20765 );
20766 f.run(&[b"G.NADD", b"g", b"a"]);
20768 assert_eq!(
20769 f.run(&[b"COPY", b"g", b"g2"]),
20770 "-ERR COPY is not supported for a graph\r\n"
20771 );
20772 assert_eq!(
20773 f.run(&[b"DUMP", b"g"]),
20774 "-ERR DUMP is not supported for a graph\r\n"
20775 );
20776 assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
20779 assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
20780 assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
20781 assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
20782 assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
20783 }
20784
20785 #[test]
20790 fn a_reserve_takes_three_arguments_or_six() {
20791 let mut f = Fixture::new();
20792 assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
20793 assert_eq!(
20794 f.run(&[b"TOPK.INFO", b"t"]),
20795 "*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"
20796 );
20797 for cmd in [
20800 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
20801 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
20802 ] {
20803 assert!(f.run(&cmd).contains("wrong number of arguments"));
20804 }
20805 assert_eq!(
20806 f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
20807 "+OK\r\n"
20808 );
20809 assert_eq!(
20812 f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
20813 "-TopK: key already exists\r\n"
20814 );
20815 assert_eq!(
20816 f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
20817 "-TopK: invalid k\r\n"
20818 );
20819 assert_eq!(
20820 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
20821 "-TopK: invalid width\r\n"
20822 );
20823 assert_eq!(
20824 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
20825 "-TopK: invalid depth\r\n"
20826 );
20827 assert_eq!(
20829 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
20830 "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
20831 );
20832 assert_eq!(
20833 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
20834 "+OK\r\n"
20835 );
20836 assert_eq!(
20838 f.run(&[
20839 b"TOPK.RESERVE",
20840 b"w",
20841 b"1",
20842 b"4294967295",
20843 b"4294967295",
20844 b"0.9"
20845 ]),
20846 "-ERR Insufficient memory to create topk data structure\r\n"
20847 );
20848 }
20849
20850 #[test]
20852 fn the_kept_set_is_what_query_and_list_answer_from() {
20853 let mut f = Fixture::new();
20854 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
20855 assert_eq!(
20858 f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
20859 "*2\r\n$-1\r\n$-1\r\n"
20860 );
20861 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
20862 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
20865 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
20866 assert_eq!(
20867 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
20868 "*3\r\n:1\r\n:0\r\n:1\r\n"
20869 );
20870 assert_eq!(
20872 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
20873 "*3\r\n:11\r\n:1\r\n:6\r\n"
20874 );
20875 assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
20876 assert_eq!(
20877 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
20878 "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
20879 );
20880 assert_eq!(
20883 f.run(&[b"TOPK.LIST", b"t", b"w"]),
20884 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
20885 );
20886 assert_eq!(
20887 f.run(&[b"TOPK.LIST", b"t", b""]),
20888 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
20889 );
20890 assert_eq!(
20891 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
20892 "-WITHCOUNT keyword expected\r\n"
20893 );
20894 assert_eq!(
20897 f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
20898 "-WITHCOUNT keyword expected\r\n"
20899 );
20900 assert_eq!(
20901 f.run(&[b"TOPK.LIST", b"missing"]),
20902 "-TopK: key does not exist\r\n"
20903 );
20904 f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
20906 assert_eq!(
20907 f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
20908 "*1\r\n$-1\r\n"
20909 );
20910 assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
20911 assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
20912 }
20913
20914 #[test]
20917 fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
20918 let mut f = Fixture::new();
20919 f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
20920 assert_eq!(
20924 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
20925 format!(
20926 "*2\r\n$-1\r\n-{}\r\n",
20927 "TopK: increment must be an integer greater or equal to 0 and smaller or equal to 100,000"
20928 )
20929 );
20930 assert_eq!(
20931 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
20932 "*3\r\n:3\r\n:0\r\n:0\r\n"
20933 );
20934 assert_eq!(
20936 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
20937 "*1\r\n$-1\r\n"
20938 );
20939 assert!(
20940 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
20941 .contains("smaller or equal to 100,000")
20942 );
20943 assert!(
20945 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
20946 .contains("wrong number of arguments")
20947 );
20948 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
20949 }
20950
20951 #[test]
20953 fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
20954 let mut f = Fixture::new();
20955 f.run(&[b"HELLO", b"3"]);
20956 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
20957 f.run(&[b"TOPK.ADD", b"t", b"a"]);
20958 assert_eq!(
20959 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
20960 "*2\r\n#t\r\n#f\r\n"
20961 );
20962 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
20964 assert_eq!(
20965 f.run(&[b"TOPK.INFO", b"t"]),
20966 "%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"
20967 );
20968 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
20969 }
20970
20971 #[test]
20974 fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
20975 let mut f = Fixture::new();
20976 f.run(&[b"SET", b"s", b"text"]);
20977 for cmd in [
20978 vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
20979 vec![&b"TOPK.ADD"[..], b"s", b"a"],
20980 vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
20981 vec![&b"TOPK.QUERY"[..], b"s", b"a"],
20982 vec![&b"TOPK.COUNT"[..], b"s", b"a"],
20983 vec![&b"TOPK.LIST"[..], b"s"],
20984 vec![&b"TOPK.INFO"[..], b"s"],
20985 ] {
20986 let name = String::from_utf8_lossy(cmd[0]).into_owned();
20987 let reply = f.run(&cmd);
20988 assert!(
20989 reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
20990 "{name}: {reply}"
20991 );
20992 }
20993 f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
20994 assert_eq!(
20995 f.run(&[b"COPY", b"t", b"t2"]),
20996 "-ERR not supported for this module key\r\n"
20997 );
20998 assert_eq!(
20999 f.run(&[b"DUMP", b"t"]),
21000 "-ERR DUMP is not supported for this module key\r\n"
21001 );
21002 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
21003 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
21004 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
21005 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
21006 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
21007 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
21008 assert_eq!(
21011 f.run(&[b"TOPK.INFO", b"t3"]),
21012 "-TopK: key does not exist\r\n"
21013 );
21014 }
21015
21016 #[test]
21021 fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
21022 let mut f = Fixture::new();
21023 assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
21024 assert_eq!(
21026 f.run(&[b"TDIGEST.INFO", b"t"]),
21027 "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
21028 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
21029 +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
21030 );
21031 assert_eq!(
21032 f.run(&[b"TDIGEST.CREATE", b"t"]),
21033 "-ERR T-Digest: key already exists\r\n"
21034 );
21035 assert!(
21037 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
21038 .contains("wrong number of arguments")
21039 );
21040 assert_eq!(
21041 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
21042 "+OK\r\n"
21043 );
21044 assert_eq!(
21045 f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
21046 "+OK\r\n"
21047 );
21048 assert_eq!(
21052 f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
21053 "-ERR T-Digest: error parsing compression parameter\r\n"
21054 );
21055 assert_eq!(
21056 f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
21057 "-ERR T-Digest: wrong keyword\r\n"
21058 );
21059 assert_eq!(
21060 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
21061 "-ERR T-Digest: error parsing compression parameter\r\n"
21062 );
21063 assert_eq!(
21064 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
21065 "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
21066 );
21067 assert_eq!(
21070 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
21071 "-ERR T-Digest: allocation failed\r\n"
21072 );
21073 assert_eq!(
21075 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
21076 "-ERR T-Digest: allocation failed\r\n"
21077 );
21078 assert_eq!(
21081 f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
21082 "-ERR T-Digest: key already exists\r\n"
21083 );
21084 }
21085
21086 #[test]
21089 fn the_quantile_family_answers_what_the_module_answers() {
21090 let mut f = Fixture::new();
21091 f.run(&[b"TDIGEST.CREATE", b"s"]);
21092 assert_eq!(
21093 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
21094 "+OK\r\n"
21095 );
21096 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
21097 assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
21098 assert_eq!(
21100 f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
21101 "*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"
21102 );
21103 assert_eq!(
21104 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
21105 "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
21106 );
21107 assert_eq!(
21110 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
21111 "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
21112 );
21113 assert_eq!(
21114 f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
21115 "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
21116 );
21117 assert_eq!(
21118 f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
21119 "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
21120 );
21121 assert_eq!(
21122 f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
21123 "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
21124 );
21125 assert_eq!(
21126 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
21127 "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
21128 );
21129 assert_eq!(
21130 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
21131 "$3\r\n2.5\r\n"
21132 );
21133 assert_eq!(
21134 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
21135 "$3\r\n2.5\r\n"
21136 );
21137 assert_eq!(
21139 f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
21140 "-ERR T-Digest: quantile should be in [0,1]\r\n"
21141 );
21142 assert_eq!(
21143 f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
21144 "-ERR T-Digest: error parsing quantile\r\n"
21145 );
21146 assert_eq!(
21147 f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
21148 "-ERR T-Digest: error parsing cdf\r\n"
21149 );
21150 assert_eq!(
21151 f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
21152 "-ERR T-Digest: error parsing value\r\n"
21153 );
21154 assert_eq!(
21155 f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
21156 "-ERR T-Digest: rank needs to be non negative\r\n"
21157 );
21158 assert_eq!(
21159 f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
21160 "-ERR T-Digest: error parsing rank\r\n"
21161 );
21162 assert_eq!(
21165 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
21166 "-ERR T-Digest: error parsing low_cut_percentile\r\n"
21167 );
21168 assert_eq!(
21169 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
21170 "-ERR T-Digest: error parsing high_cut_percentile\r\n"
21171 );
21172 assert_eq!(
21173 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
21174 "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
21175 );
21176 assert_eq!(
21177 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
21178 "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
21179 );
21180 }
21181
21182 #[test]
21185 fn an_empty_digest_has_an_answer_for_everything() {
21186 let mut f = Fixture::new();
21187 f.run(&[b"TDIGEST.CREATE", b"e"]);
21188 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
21189 assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
21190 assert_eq!(
21191 f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
21192 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
21193 );
21194 assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
21195 assert_eq!(
21196 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
21197 "$3\r\nnan\r\n"
21198 );
21199 assert_eq!(
21202 f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
21203 "*2\r\n:-2\r\n:-2\r\n"
21204 );
21205 assert_eq!(
21206 f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
21207 "*2\r\n:-2\r\n:-2\r\n"
21208 );
21209 assert_eq!(
21210 f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
21211 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
21212 );
21213 f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
21215 assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
21216 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
21217 f.run(&[b"TDIGEST.CREATE", b"e2"]);
21220 assert_eq!(
21221 f.run(&[b"TDIGEST.INFO", b"e"]),
21222 f.run(&[b"TDIGEST.INFO", b"e2"])
21223 );
21224 }
21225
21226 #[test]
21229 fn a_sample_is_read_the_way_redis_reads_a_double() {
21230 let mut f = Fixture::new();
21231 f.run(&[b"TDIGEST.CREATE", b"a"]);
21232 for bad in [
21235 &b"nan"[..],
21236 b"1e400",
21237 b"-1e400",
21238 b"1e309",
21239 b"1e-400",
21240 b"",
21241 b" 1",
21242 b"1 ",
21243 b"1e",
21244 b"--1",
21245 ] {
21246 assert_eq!(
21247 f.run(&[b"TDIGEST.ADD", b"a", bad]),
21248 "-ERR T-Digest: error parsing val parameter\r\n",
21249 "{}",
21250 String::from_utf8_lossy(bad)
21251 );
21252 }
21253 for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
21256 assert_eq!(
21257 f.run(&[b"TDIGEST.ADD", b"a", word]),
21258 "-ERR T-Digest: val parameter needs to be a finite number\r\n",
21259 "{}",
21260 String::from_utf8_lossy(word)
21261 );
21262 }
21263 for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
21266 assert_eq!(
21267 f.run(&[b"TDIGEST.ADD", b"a", good]),
21268 "+OK\r\n",
21269 "{}",
21270 String::from_utf8_lossy(good)
21271 );
21272 }
21273 assert!(
21275 f.run(&[b"TDIGEST.INFO", b"a"])
21276 .contains("Observations\r\n:6\r\n")
21277 );
21278 assert_eq!(
21281 f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
21282 "-ERR T-Digest: error parsing val parameter\r\n"
21283 );
21284 assert!(
21285 f.run(&[b"TDIGEST.INFO", b"a"])
21286 .contains("Observations\r\n:6\r\n")
21287 );
21288 }
21289
21290 #[test]
21293 fn a_merge_sweeps_the_destination_between_its_inputs() {
21294 let mut f = Fixture::new();
21295 f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
21296 f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
21297 f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
21298 f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
21299 assert_eq!(
21300 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
21301 "+OK\r\n"
21302 );
21303 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
21308 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
21309 assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
21310 assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
21311 assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
21312 assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
21313 assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
21314 assert!(
21317 f.run(&[b"TDIGEST.INFO", b"m1"])
21318 .contains("Merged nodes\r\n:3\r\n")
21319 );
21320 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
21323 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
21324 assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
21325 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
21326 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
21329 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
21330 assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
21331 assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
21332 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
21334 assert!(
21335 f.run(&[b"TDIGEST.INFO", b"d"])
21336 .contains("Compression\r\n:500\r\n")
21337 );
21338 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
21340 assert!(
21341 f.run(&[b"TDIGEST.INFO", b"d"])
21342 .contains("Observations\r\n:12\r\n")
21343 );
21344 assert_eq!(
21346 f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
21347 "-ERR T-Digest: error parsing numkeys\r\n"
21348 );
21349 assert_eq!(
21350 f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
21351 "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
21352 );
21353 assert!(
21354 f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
21355 .contains("wrong number of arguments")
21356 );
21357 assert!(
21358 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
21359 .contains("wrong number of arguments")
21360 );
21361 assert_eq!(
21362 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
21363 "-ERR T-Digest: wrong keyword\r\n"
21364 );
21365 assert_eq!(
21368 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
21369 "-ERR T-Digest: key does not exist\r\n"
21370 );
21371 assert!(
21372 f.run(&[b"TDIGEST.INFO", b"d"])
21373 .contains("Observations\r\n:12\r\n")
21374 );
21375 assert_eq!(
21378 f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
21379 "-ERR T-Digest: key does not exist\r\n"
21380 );
21381 }
21382
21383 #[test]
21385 fn a_digest_answers_doubles_and_a_map_on_resp3() {
21386 let mut f = Fixture::new();
21387 f.run(&[b"HELLO", b"3"]);
21388 f.run(&[b"TDIGEST.CREATE", b"s"]);
21389 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
21390 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
21391 assert_eq!(
21392 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
21393 "*2\r\n,1\r\n,4\r\n"
21394 );
21395 assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
21396 assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
21398 assert_eq!(
21399 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
21400 "*1\r\n,-inf\r\n"
21401 );
21402 f.run(&[b"TDIGEST.CREATE", b"e"]);
21403 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
21404 assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
21406 assert_eq!(
21409 f.run(&[b"TDIGEST.INFO", b"s"]),
21410 "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
21411 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
21412 +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
21413 );
21414 }
21415
21416 #[test]
21419 fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
21420 let mut f = Fixture::new();
21421 f.run(&[b"SET", b"s", b"text"]);
21422 for cmd in [
21423 vec![&b"TDIGEST.CREATE"[..], b"s"],
21424 vec![&b"TDIGEST.RESET"[..], b"s"],
21425 vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
21426 vec![&b"TDIGEST.MIN"[..], b"s"],
21427 vec![&b"TDIGEST.MAX"[..], b"s"],
21428 vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
21429 vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
21430 vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
21431 vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
21432 vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
21433 vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
21434 vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
21435 vec![&b"TDIGEST.INFO"[..], b"s"],
21436 ] {
21437 let name = String::from_utf8_lossy(cmd[0]).into_owned();
21438 let reply = f.run(&cmd);
21439 assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
21440 }
21441 f.run(&[b"TDIGEST.CREATE", b"t"]);
21443 assert!(
21444 f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
21445 .starts_with("-WRONGTYPE")
21446 );
21447 assert!(
21448 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
21449 .starts_with("-WRONGTYPE")
21450 );
21451 assert_eq!(
21452 f.run(&[b"COPY", b"t", b"t2"]),
21453 "-ERR not supported for this module key\r\n"
21454 );
21455 assert_eq!(
21456 f.run(&[b"DUMP", b"t"]),
21457 "-ERR DUMP is not supported for this module key\r\n"
21458 );
21459 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
21460 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
21461 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
21462 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
21463 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
21464 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
21465 assert_eq!(
21468 f.run(&[b"TDIGEST.INFO", b"t3"]),
21469 "-ERR T-Digest: key does not exist\r\n"
21470 );
21471 assert_eq!(
21474 f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
21475 "-ERR T-Digest: key does not exist\r\n"
21476 );
21477 }
21478
21479 fn without_memory(reply: &str) -> String {
21489 let head = "+memoryUsage\r\n:";
21490 let at = reply.find(head).expect("every TS.INFO reports memory");
21491 let rest = &reply[at + head.len()..];
21492 let end = rest.find("\r\n").expect("and it is a whole number");
21493 format!("{}{}", &reply[..at + head.len()], &rest[end..])
21494 }
21495
21496 #[test]
21499 fn a_series_is_made_empty_and_reports_on_itself() {
21500 let mut f = Fixture::new();
21501 assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
21502 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
21503 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
21504 assert_eq!(
21508 without_memory(&f.run(&[b"TS.INFO", b"t"])),
21509 "*28\r\n\
21510 +totalSamples\r\n:0\r\n\
21511 +memoryUsage\r\n:\r\n\
21512 +firstTimestamp\r\n:0\r\n\
21513 +lastTimestamp\r\n:0\r\n\
21514 +retentionTime\r\n:0\r\n\
21515 +chunkCount\r\n:1\r\n\
21516 +chunkSize\r\n:4096\r\n\
21517 +chunkType\r\n+compressed\r\n\
21518 +duplicatePolicy\r\n+block\r\n\
21519 +labels\r\n*0\r\n\
21520 +sourceKey\r\n$-1\r\n\
21521 +rules\r\n*0\r\n\
21522 +ignoreMaxTimeDiff\r\n:0\r\n\
21523 +ignoreMaxValDiff\r\n$1\r\n0\r\n"
21524 );
21525 assert_eq!(
21528 f.run(&[b"TS.CREATE", b"t"]),
21529 "-ERR TSDB: key already exists\r\n"
21530 );
21531 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21532 assert_eq!(
21533 f.run(&[b"TS.CREATE", b"str"]),
21534 "-ERR TSDB: key already exists\r\n"
21535 );
21536 assert_eq!(
21539 f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
21540 "-ERR TSDB: Couldn't parse RETENTION\r\n"
21541 );
21542 let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
21548 assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
21549 assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
21550 assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
21551 assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
21552 assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
21553 assert_eq!(
21554 f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
21555 "-ERR TSDB: the key is not a TSDB key\r\n"
21556 );
21557 assert_eq!(
21559 f.run(&[b"TS.INFO", b"nope"]),
21560 "-ERR TSDB: the key does not exist\r\n"
21561 );
21562 assert_eq!(
21563 f.run(&[b"TS.GET", b"nope"]),
21564 "-ERR TSDB: the key does not exist\r\n"
21565 );
21566 assert_eq!(
21567 f.run(&[b"TS.ALTER", b"nope"]),
21568 "-ERR TSDB: the key does not exist\r\n"
21569 );
21570 assert_eq!(
21571 f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
21572 "-ERR TSDB: the key does not exist\r\n"
21573 );
21574 }
21575
21576 #[test]
21579 fn the_options_are_a_keyword_scan_and_not_a_grammar() {
21580 let mut f = Fixture::new();
21581 assert_eq!(
21582 f.run(&[
21583 b"TS.CREATE",
21584 b"t",
21585 b"RETENTION",
21586 b"5000",
21587 b"ENCODING",
21588 b"UNCOMPRESSED",
21589 b"CHUNK_SIZE",
21590 b"128",
21591 b"DUPLICATE_POLICY",
21592 b"LAST",
21593 b"IGNORE",
21594 b"10",
21595 b"0.5",
21596 b"LABELS",
21597 b"room",
21598 b"kitchen"
21599 ]),
21600 "+OK\r\n"
21601 );
21602 let info = f.run(&[b"TS.INFO", b"t"]);
21603 assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
21604 assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
21605 assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
21606 assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
21607 assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
21608 assert!(
21611 info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
21612 "{info}"
21613 );
21614 assert!(
21615 info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
21616 "{info}"
21617 );
21618
21619 assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
21621 assert_eq!(
21625 f.run(&[
21626 b"TS.CREATE",
21627 b"g",
21628 b"LABELS",
21629 b"a",
21630 b"b",
21631 b"RETENTION",
21632 b"5"
21633 ]),
21634 "+OK\r\n"
21635 );
21636 let greedy = f.run(&[b"TS.INFO", b"g"]);
21637 assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
21638 assert!(
21639 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"),
21640 "{greedy}"
21641 );
21642
21643 assert_eq!(
21645 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
21646 "-ERR TSDB: Couldn't parse LABELS\r\n"
21647 );
21648 assert_eq!(
21649 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
21650 "-ERR TSDB: Couldn't parse LABELS\r\n"
21651 );
21652 assert_eq!(
21653 f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
21654 "-ERR TSDB: Couldn't parse RETENTION\r\n"
21655 );
21656 assert_eq!(
21659 f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
21660 "-TSDB: Couldn't parse RETENTION\r\n"
21661 );
21662 assert_eq!(
21663 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
21664 "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
21665 );
21666 assert_eq!(
21667 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
21668 "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
21669 );
21670 assert_eq!(
21671 f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
21672 "-ERR TSDB: unknown ENCODING parameter\r\n"
21673 );
21674 assert!(
21677 f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
21678 .contains("wrong number of arguments for 'ts.create' command")
21679 );
21680 assert_eq!(
21681 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
21682 "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
21683 );
21684 assert_eq!(
21685 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
21686 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
21687 );
21688 assert_eq!(
21689 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
21690 "-ERR TSDB: Couldn't parse IGNORE\r\n"
21691 );
21692 assert_eq!(
21693 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
21694 "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
21695 );
21696 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
21697
21698 assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
21701 let after = f.run(&[b"TS.INFO", b"t"]);
21702 assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
21703 assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
21704 assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
21705 assert_eq!(
21706 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
21707 "-ERR TSDB: unknown ENCODING parameter\r\n"
21708 );
21709 assert_eq!(
21711 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
21712 "+OK\r\n"
21713 );
21714 assert!(
21715 f.run(&[b"TS.INFO", b"t"])
21716 .contains("+chunkType\r\n+uncompressed\r\n")
21717 );
21718 }
21719
21720 #[test]
21723 fn samples_land_where_they_are_put_and_the_newest_comes_back() {
21724 let mut f = Fixture::new();
21725 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
21726 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
21728 assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
21729 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
21732 assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
21733 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
21734 assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
21737 assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
21738
21739 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21742 assert_eq!(
21743 f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
21744 "-ERR TSDB: invalid value\r\n"
21745 );
21746 for bad in [
21750 &b".5"[..],
21751 b"1.",
21752 b"+1",
21753 b" 1",
21754 b"0x10",
21755 b"inf",
21756 b"1e400",
21757 b"--1",
21758 b"1e",
21759 ] {
21760 assert_eq!(
21761 f.run(&[b"TS.ADD", b"v", b"1", bad]),
21762 "-ERR TSDB: invalid value\r\n",
21763 "{}",
21764 String::from_utf8_lossy(bad)
21765 );
21766 }
21767 assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
21769
21770 assert_eq!(
21773 f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
21774 "-ERR TSDB: invalid timestamp\r\n"
21775 );
21776 assert_eq!(
21777 f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
21778 "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
21779 );
21780
21781 assert_eq!(
21784 f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
21785 "-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"
21786 );
21787 assert_eq!(
21788 f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
21789 ":300\r\n"
21790 );
21791 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
21792 assert_eq!(
21795 f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
21796 ":1\r\n"
21797 );
21798 assert_eq!(
21799 f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
21800 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
21801 );
21802
21803 assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
21806 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
21807 assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
21808 assert_eq!(
21809 f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
21810 "-ERR TSDB: Timestamp is older than retention\r\n"
21811 );
21812 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
21814 assert!(
21815 f.run(&[b"TS.INFO", b"r"])
21816 .contains("+totalSamples\r\n:1\r\n")
21817 );
21818
21819 assert_eq!(
21822 f.run(&[
21823 b"TS.CREATE",
21824 b"i",
21825 b"DUPLICATE_POLICY",
21826 b"LAST",
21827 b"IGNORE",
21828 b"10",
21829 b"0.5"
21830 ]),
21831 "+OK\r\n"
21832 );
21833 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
21834 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
21835 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
21836 }
21837
21838 #[test]
21841 fn a_madd_answers_each_triple_and_creates_nothing() {
21842 let mut f = Fixture::new();
21843 assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
21844 assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
21845 assert_eq!(
21846 f.run(&[
21847 b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
21848 ]),
21849 "*3\r\n:100\r\n:100\r\n:200\r\n"
21850 );
21851 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21854 assert_eq!(
21855 f.run(&[
21856 b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
21857 ]),
21858 "*3\r\n\
21859 -ERR TSDB: the key is not a TSDB key\r\n\
21860 -ERR TSDB: the key is not a TSDB key\r\n\
21861 :300\r\n"
21862 );
21863 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
21864 assert_eq!(
21866 f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
21867 "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
21868 );
21869 assert!(
21871 f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
21872 .contains("wrong number of arguments for 'ts.madd' command")
21873 );
21874 }
21875
21876 #[test]
21878 fn an_increment_walks_the_newest_value_up_and_down() {
21879 let mut f = Fixture::new();
21880 assert_eq!(
21881 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
21882 ":100\r\n"
21883 );
21884 assert_eq!(
21885 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
21886 ":100\r\n"
21887 );
21888 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
21891 assert_eq!(
21892 f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
21893 ":200\r\n"
21894 );
21895 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
21896 assert_eq!(
21899 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
21900 "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
21901 );
21902 assert_eq!(
21906 f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
21907 ":1\r\n"
21908 );
21909 assert_eq!(
21910 f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
21911 ":1\r\n"
21912 );
21913 assert_eq!(
21914 f.run(&[b"TS.INCRBY", b"t", b"nan"]),
21915 "-ERR TSDB: invalid increase/decrease value\r\n"
21916 );
21917 assert_eq!(
21918 f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
21919 "-ERR TSDB: invalid increase/decrease value\r\n"
21920 );
21921 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21924 assert_eq!(
21925 f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
21926 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21927 );
21928 assert_eq!(
21933 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
21934 "-ERR TSDB: invalid timestamp\r\n"
21935 );
21936 assert_eq!(
21939 f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
21940 format!(":{}\r\n", f.server.now_ms())
21941 );
21942 assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
21944 assert_eq!(
21945 f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
21946 "-ERR TSDB: cannot increment/decrement NaN value\r\n"
21947 );
21948 }
21949
21950 #[test]
21952 fn deleting_takes_out_a_span_and_answers_how_many_went() {
21953 let mut f = Fixture::new();
21954 for at in [b"100".as_slice(), b"200", b"300", b"400"] {
21955 f.run(&[b"TS.ADD", b"t", at, b"1"]);
21956 }
21957 assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
21958 assert!(
21959 f.run(&[b"TS.INFO", b"t"])
21960 .contains("+totalSamples\r\n:2\r\n")
21961 );
21962 assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
21964 assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
21966 let empty = f.run(&[b"TS.INFO", b"t"]);
21969 assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
21970 assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
21971 assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
21972 assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
21973 assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
21974 assert_eq!(
21976 f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
21977 "-ERR TSDB: wrong fromTimestamp\r\n"
21978 );
21979 assert_eq!(
21980 f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
21981 "-ERR TSDB: wrong toTimestamp\r\n"
21982 );
21983 assert_eq!(
21984 f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
21985 "-ERR TSDB: wrong fromTimestamp\r\n"
21986 );
21987 }
21988
21989 #[test]
21992 fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
21993 let mut f = Fixture::new();
21994 f.out = Out::new(Proto::Resp3);
21995 assert_eq!(
21996 f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
21997 "+OK\r\n"
21998 );
21999 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
22000 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
22002 assert_eq!(
22003 without_memory(&f.run(&[b"TS.INFO", b"t"])),
22004 "%14\r\n\
22005 +totalSamples\r\n:1\r\n\
22006 +memoryUsage\r\n:\r\n\
22007 +firstTimestamp\r\n:100\r\n\
22008 +lastTimestamp\r\n:100\r\n\
22009 +retentionTime\r\n:0\r\n\
22010 +chunkCount\r\n:1\r\n\
22011 +chunkSize\r\n:4096\r\n\
22012 +chunkType\r\n+compressed\r\n\
22013 +duplicatePolicy\r\n+block\r\n\
22014 +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
22015 +sourceKey\r\n_\r\n\
22016 +rules\r\n%0\r\n\
22017 +ignoreMaxTimeDiff\r\n:0\r\n\
22018 +ignoreMaxValDiff\r\n,0\r\n"
22019 );
22020 }
22021
22022 #[test]
22025 fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
22026 let mut f = Fixture::new();
22027 for (at, v) in [
22028 (b"100".as_slice(), b"1".as_slice()),
22029 (b"200", b"2"),
22030 (b"300", b"3"),
22031 (b"400", b"4"),
22032 ] {
22033 f.run(&[b"TS.ADD", b"t", at, v]);
22034 }
22035 assert_eq!(
22036 f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
22037 "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
22038 *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
22039 );
22040 assert_eq!(
22042 f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
22043 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
22044 );
22045 assert_eq!(
22048 f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
22049 "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
22050 );
22051 assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
22053 assert_eq!(
22055 f.run(&[
22056 b"TS.RANGE",
22057 b"t",
22058 b"-",
22059 b"+",
22060 b"FILTER_BY_VALUE",
22061 b"2",
22062 b"3"
22063 ]),
22064 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
22065 );
22066 assert_eq!(
22067 f.run(&[
22068 b"TS.RANGE",
22069 b"t",
22070 b"-",
22071 b"+",
22072 b"FILTER_BY_TS",
22073 b"100",
22074 b"400"
22075 ]),
22076 "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
22077 );
22078 assert_eq!(
22080 f.run(&[
22081 b"TS.RANGE",
22082 b"t",
22083 b"-",
22084 b"+",
22085 b"ZZZ",
22086 b"FILTER_BY_TS",
22087 b"400"
22088 ]),
22089 "*1\r\n*2\r\n:400\r\n+4\r\n"
22090 );
22091 assert_eq!(
22093 f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
22094 "*1\r\n*2\r\n:100\r\n+1\r\n"
22095 );
22096 }
22097
22098 #[test]
22100 fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
22101 let mut f = Fixture::new();
22102 for (at, v) in [
22103 (b"100".as_slice(), b"1".as_slice()),
22104 (b"200", b"2"),
22105 (b"300", b"3"),
22106 (b"400", b"4"),
22107 ] {
22108 f.run(&[b"TS.ADD", b"t", at, v]);
22109 }
22110 assert_eq!(
22111 f.run(&[
22112 b"TS.RANGE",
22113 b"t",
22114 b"-",
22115 b"+",
22116 b"AGGREGATION",
22117 b"avg",
22118 b"200"
22119 ]),
22120 "*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"
22121 );
22122 assert_eq!(
22125 f.run(&[
22126 b"TS.RANGE",
22127 b"t",
22128 b"-",
22129 b"+",
22130 b"AGGREGATION",
22131 b"min,max,count",
22132 b"200"
22133 ]),
22134 "*3\r\n\
22135 *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
22136 *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
22137 *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
22138 );
22139 assert_eq!(
22141 f.run(&[
22142 b"TS.RANGE",
22143 b"t",
22144 b"-",
22145 b"+",
22146 b"AGGREGATION",
22147 b"avg",
22148 b"200",
22149 b"BUCKETTIMESTAMP",
22150 b"+"
22151 ]),
22152 "*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"
22153 );
22154 assert_eq!(
22156 f.run(&[
22157 b"TS.RANGE",
22158 b"t",
22159 b"100",
22160 b"400",
22161 b"ALIGN",
22162 b"100",
22163 b"AGGREGATION",
22164 b"sum",
22165 b"200"
22166 ]),
22167 "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
22168 );
22169 assert_eq!(
22172 f.run(&[
22173 b"TS.RANGE",
22174 b"t",
22175 b"-",
22176 b"+",
22177 b"AGGREGATION",
22178 b"count",
22179 b"200"
22180 ]),
22181 "*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"
22182 );
22183 assert_eq!(
22184 f.run(&[
22185 b"TS.RANGE",
22186 b"t",
22187 b"-",
22188 b"+",
22189 b"AGGREGATION",
22190 b"count",
22191 b"200",
22192 b"COUNT",
22193 b"1"
22194 ]),
22195 "*1\r\n*2\r\n:0\r\n+1\r\n"
22196 );
22197 }
22198
22199 #[test]
22202 fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
22203 let mut f = Fixture::new();
22204 for (at, v) in [
22205 (b"0".as_slice(), b"1".as_slice()),
22206 (b"100", b"2"),
22207 (b"500", b"nan"),
22208 (b"600", b"3"),
22209 ] {
22210 f.run(&[b"TS.ADD", b"g", at, v]);
22211 }
22212 assert_eq!(
22215 f.run(&[
22216 b"TS.RANGE",
22217 b"g",
22218 b"-",
22219 b"+",
22220 b"AGGREGATION",
22221 b"avg",
22222 b"100"
22223 ]),
22224 "*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"
22225 );
22226 assert_eq!(
22228 f.run(&[
22229 b"TS.RANGE",
22230 b"g",
22231 b"-",
22232 b"+",
22233 b"AGGREGATION",
22234 b"sum",
22235 b"100",
22236 b"EMPTY"
22237 ]),
22238 "*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\
22239 *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\
22240 *2\r\n:600\r\n+3\r\n"
22241 );
22242 assert_eq!(
22247 f.run(&[
22248 b"TS.RANGE",
22249 b"g",
22250 b"-",
22251 b"+",
22252 b"AGGREGATION",
22253 b"last",
22254 b"100",
22255 b"EMPTY"
22256 ]),
22257 "*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\
22258 *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\
22259 *2\r\n:600\r\n+3\r\n"
22260 );
22261 assert_eq!(
22262 f.run(&[
22263 b"TS.REVRANGE",
22264 b"g",
22265 b"-",
22266 b"+",
22267 b"AGGREGATION",
22268 b"last",
22269 b"100",
22270 b"EMPTY"
22271 ]),
22272 "*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\
22273 *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\
22274 *2\r\n:0\r\n+1\r\n"
22275 );
22276 assert_eq!(
22279 f.run(&[
22280 b"TS.RANGE",
22281 b"g",
22282 b"500",
22283 b"600",
22284 b"AGGREGATION",
22285 b"last",
22286 b"100",
22287 b"EMPTY"
22288 ]),
22289 "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
22290 );
22291 }
22292
22293 #[test]
22296 fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
22297 let mut f = Fixture::new();
22298 f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
22299 f.run(&[b"SET", b"str", b"x"]);
22300 let cases: &[(&[&[u8]], &str)] = &[
22301 (
22302 &[b"TS.RANGE", b"t"],
22303 "-ERR wrong number of arguments for 'ts.range' command\r\n",
22304 ),
22305 (
22307 &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
22308 "-ERR TSDB: the key does not exist\r\n",
22309 ),
22310 (
22311 &[b"TS.RANGE", b"str", b"-", b"+"],
22312 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
22313 ),
22314 (
22315 &[b"TS.RANGE", b"t", b"abc", b"+"],
22316 "-ERR TSDB: wrong fromTimestamp\r\n",
22317 ),
22318 (
22319 &[b"TS.RANGE", b"t", b"-", b"abc"],
22320 "-ERR TSDB: wrong toTimestamp\r\n",
22321 ),
22322 (
22323 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
22324 "-ERR TSDB: COUNT argument is missing\r\n",
22325 ),
22326 (
22327 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
22328 "-ERR TSDB: Couldn't parse COUNT\r\n",
22329 ),
22330 (
22331 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
22332 "-ERR TSDB: Invalid COUNT value\r\n",
22333 ),
22334 (
22335 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
22336 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22337 ),
22338 (
22339 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
22340 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22341 ),
22342 (
22343 &[
22344 b"TS.RANGE",
22345 b"t",
22346 b"-",
22347 b"+",
22348 b"AGGREGATION",
22349 b"nope",
22350 b"100",
22351 ],
22352 "-ERR TSDB: Unknown aggregation type\r\n",
22353 ),
22354 (
22355 &[
22356 b"TS.RANGE",
22357 b"t",
22358 b"-",
22359 b"+",
22360 b"AGGREGATION",
22361 b"avg,,min",
22362 b"100",
22363 ],
22364 "-ERR TSDB: Empty aggregation type in list\r\n",
22365 ),
22366 (
22368 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
22369 "-ERR TSDB: Unknown aggregation type\r\n",
22370 ),
22371 (
22372 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
22373 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
22374 ),
22375 (
22376 &[
22377 b"TS.RANGE",
22378 b"t",
22379 b"-",
22380 b"+",
22381 b"AGGREGATION",
22382 b"avg",
22383 b"100",
22384 b"X",
22385 b"EMPTY",
22386 ],
22387 "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
22388 ),
22389 (
22390 &[
22391 b"TS.RANGE",
22392 b"t",
22393 b"-",
22394 b"+",
22395 b"AGGREGATION",
22396 b"avg",
22397 b"100",
22398 b"BUCKETTIMESTAMP",
22399 b"z",
22400 ],
22401 "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
22402 ),
22403 (
22404 &[
22405 b"TS.RANGE",
22406 b"t",
22407 b"-",
22408 b"+",
22409 b"AGGREGATION",
22410 b"avg",
22411 b"100",
22412 b"X",
22413 b"Y",
22414 b"BUCKETTIMESTAMP",
22415 b"-",
22416 ],
22417 "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
22418 AGGREGATION flag\r\n",
22419 ),
22420 (
22421 &[
22422 b"TS.RANGE",
22423 b"t",
22424 b"-",
22425 b"+",
22426 b"ALIGN",
22427 b"z",
22428 b"AGGREGATION",
22429 b"avg",
22430 b"100",
22431 ],
22432 "-ERR TSDB: unknown ALIGN parameter\r\n",
22433 ),
22434 (
22435 &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
22436 "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
22437 ),
22438 (
22439 &[
22440 b"TS.RANGE",
22441 b"t",
22442 b"-",
22443 b"+",
22444 b"ALIGN",
22445 b"-",
22446 b"AGGREGATION",
22447 b"avg",
22448 b"100",
22449 ],
22450 "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
22451 ),
22452 (
22453 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
22454 "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
22455 ),
22456 (
22457 &[
22458 b"TS.RANGE",
22459 b"t",
22460 b"-",
22461 b"+",
22462 b"FILTER_BY_VALUE",
22463 b"x",
22464 b"2",
22465 ],
22466 "-ERR TSDB: Couldn't parse MIN\r\n",
22467 ),
22468 (
22469 &[
22470 b"TS.RANGE",
22471 b"t",
22472 b"-",
22473 b"+",
22474 b"FILTER_BY_VALUE",
22475 b"1",
22476 b"y",
22477 ],
22478 "-ERR TSDB: Couldn't parse MAX\r\n",
22479 ),
22480 (
22481 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
22482 "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
22483 ),
22484 ];
22485 for (argv, want) in cases {
22486 let got = f.run(argv);
22487 assert_eq!(&got, want, "{:?}", argv.last());
22488 }
22489 f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
22493 f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
22494 assert_eq!(
22495 f.run(&[
22496 b"TS.RANGE",
22497 b"wide",
22498 b"-",
22499 b"+",
22500 b"AGGREGATION",
22501 b"avg",
22502 b"1",
22503 b"EMPTY"
22504 ]),
22505 "-ERR TSDB: the requested range holds too many empty buckets\r\n"
22506 );
22507 }
22508
22509 #[test]
22511 fn resp3_writes_a_read_value_as_a_double() {
22512 let mut f = Fixture::new();
22513 f.out = Out::new(Proto::Resp3);
22514 for (at, v) in [
22515 (b"0".as_slice(), b"1".as_slice()),
22516 (b"100", b"2"),
22517 (b"500", b"nan"),
22518 (b"600", b"3"),
22519 ] {
22520 f.run(&[b"TS.ADD", b"g", at, v]);
22521 }
22522 assert_eq!(
22523 f.run(&[
22524 b"TS.RANGE",
22525 b"g",
22526 b"0",
22527 b"100",
22528 b"AGGREGATION",
22529 b"avg,min",
22530 b"200"
22531 ]),
22532 "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
22533 );
22534 assert_eq!(
22535 f.run(&[
22536 b"TS.RANGE",
22537 b"g",
22538 b"500",
22539 b"600",
22540 b"AGGREGATION",
22541 b"last",
22542 b"100",
22543 b"EMPTY"
22544 ]),
22545 "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
22546 );
22547 }
22548
22549 fn joined() -> Fixture {
22552 let mut f = Fixture::new();
22553 f.run(&[b"TS.CREATE", b"z"]);
22554 for (at, v) in [
22555 (b"10".as_slice(), b"1".as_slice()),
22556 (b"20", b"2"),
22557 (b"40", b"4"),
22558 (b"50", b"5"),
22559 ] {
22560 f.run(&[b"TS.ADD", b"x", at, v]);
22561 }
22562 for (at, v) in [
22563 (b"20".as_slice(), b"20".as_slice()),
22564 (b"30", b"30"),
22565 (b"50", b"50"),
22566 (b"60", b"60"),
22567 ] {
22568 f.run(&[b"TS.ADD", b"y", at, v]);
22569 }
22570 f
22571 }
22572
22573 #[test]
22577 fn an_nrange_joins_its_keys_on_the_timestamp() {
22578 let mut f = joined();
22579 assert_eq!(
22581 f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
22582 "*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\
22583 *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
22584 );
22585 assert_eq!(
22587 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
22588 "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
22589 *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
22590 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
22591 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
22592 *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
22593 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22594 );
22595 assert_eq!(
22598 f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
22599 "*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"
22600 );
22601 assert_eq!(
22602 f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
22603 "*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"
22604 );
22605 assert_eq!(
22608 f.run(&[
22609 b"TS.NREVRANGE",
22610 b"2",
22611 b"x",
22612 b"y",
22613 b"-",
22614 b"+",
22615 b"COUNT",
22616 b"1"
22617 ]),
22618 "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22619 );
22620 assert_eq!(
22621 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
22622 "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
22623 );
22624 assert_eq!(
22626 f.run(&[
22627 b"TS.NRANGE",
22628 b"2",
22629 b"x",
22630 b"y",
22631 b"-",
22632 b"+",
22633 b"FILTER_BY_VALUE",
22634 b"2",
22635 b"30"
22636 ]),
22637 "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
22638 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
22639 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
22640 *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
22641 );
22642 }
22643
22644 #[test]
22648 fn an_nrange_aggregation_names_one_reduction_a_key() {
22649 let mut f = joined();
22650 assert_eq!(
22651 f.run(&[
22652 b"TS.NRANGE",
22653 b"2",
22654 b"x",
22655 b"y",
22656 b"-",
22657 b"+",
22658 b"AGGREGATION",
22659 b"sum",
22660 b"sum",
22661 b"20"
22662 ]),
22663 "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
22664 *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
22665 *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
22666 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22667 );
22668 assert_eq!(
22670 f.run(&[
22671 b"TS.NRANGE",
22672 b"2",
22673 b"x",
22674 b"y",
22675 b"-",
22676 b"+",
22677 b"AGGREGATION",
22678 b"sum,count",
22679 b"avg",
22680 b"20"
22681 ]),
22682 "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
22683 *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
22684 *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
22685 *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
22686 );
22687 assert_eq!(
22690 f.run(&[
22691 b"TS.NRANGE",
22692 b"2",
22693 b"x",
22694 b"y",
22695 b"-",
22696 b"+",
22697 b"AGGREGATION",
22698 b"avg",
22699 b"sum",
22700 b"100",
22701 b"EMPTY",
22702 b"BUCKETTIMESTAMP",
22703 b"end"
22704 ]),
22705 "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
22706 );
22707 assert_eq!(
22710 f.run(&[
22711 b"TS.NRANGE",
22712 b"2",
22713 b"x",
22714 b"y",
22715 b"-",
22716 b"+",
22717 b"AGGREGATION",
22718 b"avg",
22719 b"COUNT",
22720 b"100"
22721 ]),
22722 "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
22723 );
22724 }
22725
22726 #[test]
22729 fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
22730 let mut f = joined();
22731 f.run(&[b"SET", b"str", b"hi"]);
22732 let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
22733 let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
22734 must be equal to numkeys\r\n";
22735 let cases: &[(&[&[u8]], &str)] = &[
22736 (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
22737 (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
22738 (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
22739 (
22742 &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
22743 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
22744 ),
22745 (
22746 &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
22747 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
22748 ),
22749 (
22752 &[
22753 b"TS.NRANGE",
22754 b"2",
22755 b"x",
22756 b"y",
22757 b"abc",
22758 b"+",
22759 b"AGGREGATION",
22760 b"nope",
22761 b"sum",
22762 b"100",
22763 ],
22764 "-ERR TSDB: Unknown aggregation type\r\n",
22765 ),
22766 (
22767 &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
22768 "-ERR TSDB: wrong fromTimestamp\r\n",
22769 ),
22770 (
22771 &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
22772 "-ERR TSDB: wrong toTimestamp\r\n",
22773 ),
22774 (
22778 &[
22779 b"TS.NRANGE",
22780 b"2",
22781 b"x",
22782 b"y",
22783 b"-",
22784 b"+",
22785 b"AGGREGATION",
22786 b"avg",
22787 ],
22788 numkeys,
22789 ),
22790 (
22791 &[
22792 b"TS.NRANGE",
22793 b"2",
22794 b"x",
22795 b"y",
22796 b"-",
22797 b"+",
22798 b"AGGREGATION",
22799 b"100",
22800 b"sum",
22801 b"100",
22802 ],
22803 numkeys,
22804 ),
22805 (
22806 &[
22807 b"TS.NRANGE",
22808 b"2",
22809 b"x",
22810 b"y",
22811 b"-",
22812 b"+",
22813 b"AGGREGATION",
22814 b"avg",
22815 b"sum",
22816 b"sum",
22817 b"100",
22818 ],
22819 numkeys,
22820 ),
22821 (
22822 &[
22823 b"TS.NRANGE",
22824 b"2",
22825 b"x",
22826 b"y",
22827 b"-",
22828 b"+",
22829 b"AGGREGATION",
22830 b"avg",
22831 b"sum",
22832 b"abc",
22833 ],
22834 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22835 ),
22836 (
22837 &[
22838 b"TS.NRANGE",
22839 b"2",
22840 b"x",
22841 b"y",
22842 b"-",
22843 b"+",
22844 b"AGGREGATION",
22845 b"avg",
22846 b"sum",
22847 b"0",
22848 ],
22849 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
22850 ),
22851 (
22854 &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
22855 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22856 ),
22857 (
22858 &[
22859 b"TS.NRANGE",
22860 b"1",
22861 b"x",
22862 b"-",
22863 b"+",
22864 b"AGGREGATION",
22865 b"100",
22866 b"200",
22867 ],
22868 "-ERR TSDB: Unknown aggregation type\r\n",
22869 ),
22870 (
22872 &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
22873 "-ERR TSDB: the key does not exist\r\n",
22874 ),
22875 (
22876 &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
22877 "-ERR WRONGTYPE Operation against a key \
22878 holding the wrong kind of value\r\n",
22879 ),
22880 ];
22881 for (argv, want) in cases {
22882 let got = f.run(argv);
22883 assert_eq!(&got, want, "{argv:?}");
22884 }
22885 }
22886
22887 #[test]
22889 fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
22890 let mut f = joined();
22891 assert_eq!(
22892 f.run(&[b"TS.READ", b"x", b"-"]),
22893 "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
22894 *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
22895 );
22896 assert_eq!(
22899 f.run(&[b"TS.READ", b"x", b"+"]),
22900 "*1\r\n*2\r\n:50\r\n+5\r\n"
22901 );
22902 assert_eq!(
22903 f.run(&[b"TS.READ", b"x", b"25"]),
22904 "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
22905 );
22906 assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
22909 assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
22910 assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
22911 assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
22912 assert_eq!(
22916 f.run(&[b"TS.READ", b"x", b"abc"]),
22917 "-TSDB: invalid timestamp\r\n"
22918 );
22919 assert_eq!(
22920 f.run(&[b"TS.READ", b"x", b"-1"]),
22921 "-TSDB: invalid timestamp\r\n"
22922 );
22923 f.run(&[b"SET", b"str", b"hi"]);
22924 assert_eq!(
22925 f.run(&[b"TS.READ", b"str", b"-"]),
22926 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22927 );
22928 assert_eq!(
22931 f.run(&[b"TS.READ", b"x"]),
22932 "-ERR wrong number of arguments for 'ts.read' command\r\n"
22933 );
22934 assert_eq!(
22935 f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
22936 "-ERR wrong number of arguments for 'ts.read' command\r\n"
22937 );
22938 }
22939
22940 #[test]
22943 fn getkeys_reads_the_count_of_a_joined_read() {
22944 let mut f = Fixture::new();
22945 assert_eq!(
22946 f.run(&[
22947 b"COMMAND",
22948 b"GETKEYS",
22949 b"TS.NRANGE",
22950 b"2",
22951 b"a",
22952 b"b",
22953 b"-",
22954 b"+"
22955 ]),
22956 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
22957 );
22958 assert_eq!(
22959 f.run(&[
22960 b"COMMAND",
22961 b"GETKEYS",
22962 b"TS.NREVRANGE",
22963 b"1",
22964 b"a",
22965 b"-",
22966 b"+"
22967 ]),
22968 "*1\r\n$1\r\na\r\n"
22969 );
22970 for n in [b"0".as_slice(), b"9", b"abc"] {
22973 assert_eq!(
22974 f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
22975 "-ERR Invalid arguments specified for command\r\n"
22976 );
22977 }
22978 }
22979
22980 fn labelled() -> Fixture {
22982 let mut f = Fixture::new();
22983 f.run(&[
22984 b"TS.CREATE",
22985 b"a",
22986 b"LABELS",
22987 b"room",
22988 b"kitchen",
22989 b"x",
22990 b"1",
22991 ]);
22992 f.run(&[
22993 b"TS.CREATE",
22994 b"b",
22995 b"LABELS",
22996 b"room",
22997 b"bedroom",
22998 b"x",
22999 b"2",
23000 ]);
23001 f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
23002 f.run(&[b"TS.CREATE", b"d"]);
23003 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
23004 f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
23005 f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
23006 f
23007 }
23008
23009 #[test]
23012 fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
23013 let mut f = labelled();
23014 let cases: &[(&[&[u8]], &str)] = &[
23015 (
23018 &[b"TS.QUERYINDEX", b"room=kitchen"],
23019 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
23020 ),
23021 (
23022 &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
23023 "*1\r\n$1\r\na\r\n",
23024 ),
23025 (
23026 &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
23027 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
23028 ),
23029 (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
23032 (
23034 &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
23035 "*1\r\n$1\r\nc\r\n",
23036 ),
23037 (
23038 &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
23039 "*1\r\n$1\r\na\r\n",
23040 ),
23041 (
23042 &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
23043 "-ERR TSDB: please provide at least one matcher\r\n",
23044 ),
23045 (
23048 &[b"TS.QUERYINDEX", b"room==kitchen"],
23049 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
23050 ),
23051 (
23052 &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
23053 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
23054 ),
23055 (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
23056 (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
23059 (
23060 &[b"TS.QUERYINDEX", b"=(1)"],
23061 "-ERR TSDB: failed parsing labels\r\n",
23062 ),
23063 (
23064 &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
23065 "-ERR TSDB: failed parsing labels\r\n",
23066 ),
23067 (
23068 &[b"TS.QUERYINDEX", b"room=(kitchen"],
23069 "-ERR TSDB: failed parsing labels\r\n",
23070 ),
23071 (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
23072 (
23073 &[b"TS.QUERYINDEX", b"nonsense"],
23074 "-ERR TSDB: failed parsing labels\r\n",
23075 ),
23076 (
23078 &[b"TS.QUERYINDEX", b"room!=kitchen"],
23079 "-ERR TSDB: please provide at least one matcher\r\n",
23080 ),
23081 (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
23083 (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
23084 (
23085 &[b"TS.QUERYINDEX"],
23086 "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
23087 ),
23088 ];
23089 for (argv, want) in cases {
23090 let got = f.run(argv);
23091 assert_eq!(&got, want, "{:?}", argv.last());
23092 }
23093 }
23094
23095 #[test]
23097 fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
23098 let mut f = labelled();
23099 let cases: &[(&[&[u8]], &str)] = &[
23100 (
23101 &[b"TS.QUERYLABELS", b"LABELS"],
23102 "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
23103 ),
23104 (
23105 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
23106 "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
23107 ),
23108 (
23109 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
23110 "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
23111 ),
23112 (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
23115 (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
23116 (
23117 &[b"TS.QUERYLABELS", b"VALUES"],
23118 "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
23119 ),
23120 (
23121 &[b"TS.QUERYLABELS", b"ZZZ"],
23122 "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
23123 ),
23124 (
23125 &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
23126 "-ERR TSDB: unknown argument, expected FILTER\r\n",
23127 ),
23128 (
23129 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
23130 "-ERR TSDB: FILTER given with no filter expressions\r\n",
23131 ),
23132 (
23136 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
23137 "-ERR TSDB: please provide at least one matcher\r\n",
23138 ),
23139 (
23140 &[
23141 b"TS.QUERYLABELS",
23142 b"LABELS",
23143 b"FILTER",
23144 b"room=kitchen",
23145 b"x=",
23146 ],
23147 "*1\r\n$4\r\nroom\r\n",
23148 ),
23149 ];
23150 for (argv, want) in cases {
23151 let got = f.run(argv);
23152 assert_eq!(&got, want, "{:?}", argv.last());
23153 }
23154 }
23155
23156 #[test]
23159 fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
23160 let mut f = labelled();
23161 let cases: &[(&[&[u8]], &str)] = &[
23162 (
23165 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
23166 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
23167 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
23168 ),
23169 (
23170 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
23171 "*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\
23172 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
23173 *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",
23174 ),
23175 (
23177 &[
23178 b"TS.MGET",
23179 b"SELECTED_LABELS",
23180 b"x",
23181 b"FILTER",
23182 b"room=kitchen",
23183 ],
23184 "*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\
23185 *2\r\n:100\r\n+1.5\r\n\
23186 *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",
23187 ),
23188 (
23191 &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
23192 "*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",
23193 ),
23194 (
23195 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
23196 "*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\
23197 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
23198 ),
23199 (
23202 &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
23203 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
23204 ),
23205 (
23206 &[b"TS.MGET", b"a", b"b", b"c"],
23207 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
23208 ),
23209 (
23210 &[b"TS.MGET", b"FILTER"],
23211 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
23212 ),
23213 (
23216 &[
23217 b"TS.MGET",
23218 b"WITHLABELS",
23219 b"SELECTED_LABELS",
23220 b"x",
23221 b"FILTER",
23222 b"bad",
23223 ],
23224 "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
23225 ),
23226 (
23227 &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
23228 "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
23229 ),
23230 ];
23231 for (argv, want) in cases {
23232 let got = f.run(argv);
23233 assert_eq!(&got, want, "{:?}", argv.last());
23234 }
23235 }
23236
23237 #[test]
23240 fn resp3_writes_the_label_surface_as_sets_and_maps() {
23241 let mut f = labelled();
23242 f.out = Out::new(Proto::Resp3);
23243 let cases: &[(&[&[u8]], &str)] = &[
23244 (
23245 &[b"TS.QUERYINDEX", b"room=kitchen"],
23246 "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
23247 ),
23248 (
23249 &[b"TS.QUERYLABELS", b"LABELS"],
23250 "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
23251 ),
23252 (
23253 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
23254 "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
23255 ),
23256 (
23259 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
23260 "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
23261 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
23262 ),
23263 (
23264 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
23265 "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
23266 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
23267 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
23268 ),
23269 (
23270 &[
23271 b"TS.MGET",
23272 b"SELECTED_LABELS",
23273 b"x",
23274 b"FILTER",
23275 b"room=kitchen",
23276 ],
23277 "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
23278 *2\r\n:100\r\n,1.5\r\n\
23279 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
23280 ),
23281 (
23284 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
23285 "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
23286 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
23287 ),
23288 ];
23289 for (argv, want) in cases {
23290 let got = f.run(argv);
23291 assert_eq!(&got, want, "{:?}", argv.last());
23292 }
23293 }
23294
23295 fn spanned() -> Fixture {
23298 let mut f = labelled();
23299 f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
23300 f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
23301 f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
23302 f
23303 }
23304
23305 #[test]
23308 fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
23309 let mut f = spanned();
23310 let cases: &[(&[&[u8]], &str)] = &[
23311 (
23312 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
23313 "*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\
23314 *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",
23315 ),
23316 (
23319 &[
23320 b"TS.MREVRANGE",
23321 b"-",
23322 b"+",
23323 b"WITHLABELS",
23324 b"FILTER",
23325 b"room=kitchen",
23326 ],
23327 "*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\
23328 *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\
23329 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
23330 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
23331 ),
23332 (
23335 &[
23336 b"TS.MRANGE",
23337 b"-",
23338 b"+",
23339 b"SELECTED_LABELS",
23340 b"x",
23341 b"FILTER",
23342 b"room=kitchen",
23343 ],
23344 "*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\
23345 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
23346 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
23347 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
23348 ),
23349 (
23352 &[
23353 b"TS.MRANGE",
23354 b"-",
23355 b"+",
23356 b"FILTER",
23357 b"room=kitchen",
23358 b"GROUPBY",
23359 b"room",
23360 b"REDUCE",
23361 b"sum",
23362 ],
23363 "*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\
23364 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
23365 ),
23366 (
23369 &[
23370 b"TS.MRANGE",
23371 b"-",
23372 b"+",
23373 b"WITHLABELS",
23374 b"FILTER",
23375 b"room=kitchen",
23376 b"GROUPBY",
23377 b"room",
23378 b"REDUCE",
23379 b"max",
23380 ],
23381 "*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\
23382 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
23383 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
23384 *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",
23385 ),
23386 (
23388 &[
23389 b"TS.MREVRANGE",
23390 b"-",
23391 b"+",
23392 b"COUNT",
23393 b"1",
23394 b"FILTER",
23395 b"room=kitchen",
23396 b"GROUPBY",
23397 b"room",
23398 b"REDUCE",
23399 b"count",
23400 ],
23401 "*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",
23402 ),
23403 (
23405 &[
23406 b"TS.MRANGE",
23407 b"-",
23408 b"+",
23409 b"FILTER",
23410 b"room=kitchen",
23411 b"GROUPBY",
23412 b"nope",
23413 b"REDUCE",
23414 b"sum",
23415 ],
23416 "*0\r\n",
23417 ),
23418 (
23419 &[
23420 b"TS.MRANGE",
23421 b"-",
23422 b"+",
23423 b"AGGREGATION",
23424 b"sum,avg",
23425 b"100",
23426 b"FILTER",
23427 b"room=bedroom",
23428 ],
23429 "*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",
23430 ),
23431 (
23433 &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
23434 "-ERR TSDB: missing FILTER argument\r\n",
23435 ),
23436 (
23437 &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
23438 "-ERR TSDB: missing labels for filter argument\r\n",
23439 ),
23440 (
23441 &[
23442 b"TS.MRANGE",
23443 b"-",
23444 b"+",
23445 b"GROUPBY",
23446 b"room",
23447 b"REDUCE",
23448 b"sum",
23449 b"FILTER",
23450 b"room=kitchen",
23451 ],
23452 "-ERR TSDB: GROUPBY should always come after filter\r\n",
23453 ),
23454 (
23457 &[
23458 b"TS.MRANGE",
23459 b"-",
23460 b"+",
23461 b"FILTER",
23462 b"room=kitchen",
23463 b"GROUPBY",
23464 b"room",
23465 b"REDUCE",
23466 b"sum",
23467 b"x",
23468 ],
23469 "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
23470 ),
23471 (
23473 &[
23474 b"TS.MRANGE",
23475 b"-",
23476 b"+",
23477 b"FILTER",
23478 b"nope",
23479 b"GROUPBY",
23480 b"room",
23481 b"REDUCE",
23482 b"sum",
23483 b"x",
23484 ],
23485 "-ERR TSDB: failed parsing labels\r\n",
23486 ),
23487 (
23488 &[
23489 b"TS.MRANGE",
23490 b"-",
23491 b"+",
23492 b"FILTER",
23493 b"room=kitchen",
23494 b"GROUPBY",
23495 b"room",
23496 b"REDUCE",
23497 b"twa",
23498 ],
23499 "-ERR TSDB: Invalid reducer type\r\n",
23500 ),
23501 (
23502 &[
23503 b"TS.MRANGE",
23504 b"-",
23505 b"+",
23506 b"AGGREGATION",
23507 b"sum,avg",
23508 b"100",
23509 b"FILTER",
23510 b"room=kitchen",
23511 b"GROUPBY",
23512 b"room",
23513 b"REDUCE",
23514 b"sum",
23515 ],
23516 "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
23517 ),
23518 (
23521 &[
23522 b"TS.MRANGE",
23523 b"-",
23524 b"+",
23525 b"SELECTED_LABELS",
23526 b"COUNT",
23527 b"FILTER",
23528 b"room=kitchen",
23529 ],
23530 "-ERR TSDB: Couldn't parse COUNT\r\n",
23531 ),
23532 ];
23533 for (argv, want) in cases {
23534 let got = f.run(argv);
23535 assert_eq!(&got, want, "{argv:?}");
23536 }
23537 }
23538
23539 #[test]
23542 fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
23543 let mut f = spanned();
23544 f.out = Out::new(Proto::Resp3);
23545 let cases: &[(&[&[u8]], &str)] = &[
23546 (
23547 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
23548 "%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\
23549 *1\r\n*2\r\n:200\r\n,2\r\n",
23550 ),
23551 (
23554 &[
23555 b"TS.MRANGE",
23556 b"-",
23557 b"+",
23558 b"AGGREGATION",
23559 b"sum,avg",
23560 b"100",
23561 b"FILTER",
23562 b"room=bedroom",
23563 ],
23564 "%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\
23565 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
23566 ),
23567 (
23568 &[
23569 b"TS.MRANGE",
23570 b"-",
23571 b"+",
23572 b"FILTER",
23573 b"room=kitchen",
23574 b"GROUPBY",
23575 b"room",
23576 b"REDUCE",
23577 b"sum",
23578 ],
23579 "%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\
23580 $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\
23581 *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",
23582 ),
23583 (
23586 &[
23587 b"TS.MRANGE",
23588 b"-",
23589 b"+",
23590 b"WITHLABELS",
23591 b"FILTER",
23592 b"room=kitchen",
23593 b"GROUPBY",
23594 b"room",
23595 b"REDUCE",
23596 b"max",
23597 ],
23598 "%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\
23599 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
23600 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
23601 *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",
23602 ),
23603 (
23604 &[
23605 b"TS.MRANGE",
23606 b"-",
23607 b"+",
23608 b"FILTER",
23609 b"room=kitchen",
23610 b"GROUPBY",
23611 b"nope",
23612 b"REDUCE",
23613 b"sum",
23614 ],
23615 "%0\r\n",
23616 ),
23617 ];
23618 for (argv, want) in cases {
23619 let got = f.run(argv);
23620 assert_eq!(&got, want, "{argv:?}");
23621 }
23622 }
23623
23624 #[test]
23626 fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
23627 let mut f = Fixture::new();
23628 f.run(&[b"TS.CREATE", b"src"]);
23629 f.run(&[b"TS.CREATE", b"dst"]);
23630 f.run(&[b"SET", b"plain", b"v"]);
23631 let cases: &[(&[&[u8]], &str)] = &[
23632 (
23636 &[
23637 b"TS.CREATERULE",
23638 b"src",
23639 b"dst",
23640 b"AGGREGATION",
23641 b"nope",
23642 b"x",
23643 ],
23644 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
23645 ),
23646 (
23647 &[
23648 b"TS.CREATERULE",
23649 b"src",
23650 b"dst",
23651 b"AGGREGATION",
23652 b"nope",
23653 b"10",
23654 ],
23655 "-ERR TSDB: Unknown aggregation type\r\n",
23656 ),
23657 (
23658 &[
23659 b"TS.CREATERULE",
23660 b"src",
23661 b"dst",
23662 b"AGGREGATION",
23663 b"avg",
23664 b"0",
23665 ],
23666 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
23667 ),
23668 (
23669 &[
23670 b"TS.CREATERULE",
23671 b"src",
23672 b"dst",
23673 b"AGGREGATION",
23674 b"avg",
23675 b"10",
23676 b"x",
23677 ],
23678 "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
23679 ),
23680 (
23681 &[
23682 b"TS.CREATERULE",
23683 b"src",
23684 b"src",
23685 b"AGGREGATION",
23686 b"avg",
23687 b"10",
23688 ],
23689 "-ERR TSDB: the source key and destination key should be different\r\n",
23690 ),
23691 (
23695 &[
23696 b"TS.CREATERULE",
23697 b"nope",
23698 b"plain",
23699 b"AGGREGATION",
23700 b"avg",
23701 b"10",
23702 ],
23703 "-ERR TSDB: the key does not exist\r\n",
23704 ),
23705 (
23706 &[
23707 b"TS.CREATERULE",
23708 b"src",
23709 b"nope",
23710 b"AGGREGATION",
23711 b"avg",
23712 b"10",
23713 ],
23714 "-ERR TSDB: the key does not exist\r\n",
23715 ),
23716 (
23719 &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
23720 "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
23721 ),
23722 (
23723 &[
23724 b"TS.CREATERULE",
23725 b"src",
23726 b"dst",
23727 b"AGGREGATION",
23728 b"avg",
23729 b"10",
23730 ],
23731 "+OK\r\n",
23732 ),
23733 (
23736 &[
23737 b"TS.CREATERULE",
23738 b"src",
23739 b"dst",
23740 b"AGGREGATION",
23741 b"avg",
23742 b"10",
23743 ],
23744 "-ERR TSDB: the destination key already has a src rule\r\n",
23745 ),
23746 (
23749 &[
23750 b"TS.CREATERULE",
23751 b"dst",
23752 b"src",
23753 b"AGGREGATION",
23754 b"avg",
23755 b"10",
23756 ],
23757 "-ERR TSDB: the source key already has a source rule\r\n",
23758 ),
23759 (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
23760 (
23761 &[b"TS.DELETERULE", b"src", b"dst"],
23762 "-ERR TSDB: compaction rule does not exist\r\n",
23763 ),
23764 (
23768 &[b"TS.DELETERULE", b"src", b"nope"],
23769 "-ERR TSDB: compaction rule does not exist\r\n",
23770 ),
23771 (
23772 &[b"TS.DELETERULE", b"nope", b"dst"],
23773 "-ERR TSDB: the key does not exist\r\n",
23774 ),
23775 ];
23776 for (argv, want) in cases {
23777 let got = f.run(argv);
23778 assert_eq!(&got, want, "{argv:?}");
23779 }
23780 }
23781
23782 #[test]
23784 fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
23785 let mut f = Fixture::new();
23786 f.run(&[b"TS.CREATE", b"src"]);
23787 f.run(&[b"TS.CREATE", b"dst"]);
23788 f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
23791 f.run(&[
23792 b"TS.CREATERULE",
23793 b"src",
23794 b"dst",
23795 b"AGGREGATION",
23796 b"sum",
23797 b"100",
23798 ]);
23799 f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
23800 assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
23801 assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
23804 f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
23805 assert_eq!(
23806 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23807 "*1\r\n*2\r\n:0\r\n+2\r\n"
23808 );
23809 f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
23812 assert_eq!(
23813 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23814 "*1\r\n*2\r\n:0\r\n+11\r\n"
23815 );
23816 assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
23819 assert_eq!(
23820 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23821 "*1\r\n*2\r\n:0\r\n+8\r\n"
23822 );
23823 assert_eq!(
23824 f.run(&[b"TS.GET", b"dst", b"LATEST"]),
23825 "*2\r\n:100\r\n+4\r\n"
23826 );
23827 assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
23829 f.run(&[b"DEL", b"dst"]);
23830 assert_eq!(
23831 f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
23832 "-ERR TSDB: compaction rule does not exist\r\n"
23833 );
23834 }
23835
23836 #[test]
23839 fn xadd_ids_only_ever_go_up() {
23840 let mut f = Fixture::new();
23841 assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
23843 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
23845 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
23846 assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
23847 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
23848
23849 assert!(
23850 f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
23851 .contains("equal or smaller")
23852 );
23853 assert!(
23854 f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
23855 .contains("must be greater than 0-0")
23856 );
23857 assert!(
23858 f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
23859 .contains("Invalid stream ID")
23860 );
23861 assert!(
23864 f.run(&[b"XADD", b"s", b"*", b"a"])
23865 .contains("wrong number of arguments")
23866 );
23867
23868 assert_eq!(
23871 f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
23872 "$-1\r\n"
23873 );
23874 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
23875 assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
23876 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
23877 }
23878
23879 #[test]
23882 fn trimming_reads_its_options_the_way_redis_does() {
23883 let mut f = Fixture::new();
23884 for i in 1..=10u32 {
23885 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
23886 }
23887 assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
23888 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
23889 assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
23890 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
23891
23892 assert!(
23896 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
23897 .contains("not an integer")
23898 );
23899 assert!(
23900 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
23901 .contains("MAXLEN argument must be >= 0")
23902 );
23903 assert!(
23906 f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
23907 .contains("without specifying a trimming strategy")
23908 );
23909 assert!(
23910 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
23911 .contains("without the special ~ option")
23912 );
23913 assert!(
23914 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
23915 .contains("at the same time are not compatible")
23916 );
23917 assert!(
23919 f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
23920 .contains("syntax error")
23921 );
23922 assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
23923 }
23924
23925 #[test]
23927 fn xrange_looks_the_key_up_before_it_reads_the_count() {
23928 let mut f = Fixture::new();
23929 f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
23930 f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
23931
23932 assert_eq!(
23933 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
23934 "*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\
23935 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
23936 );
23937 assert_eq!(
23938 f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
23939 "*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"
23940 );
23941 assert_eq!(
23945 f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
23946 "*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\
23947 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
23948 );
23949 assert_eq!(
23950 f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
23951 "*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"
23952 );
23953 assert!(
23954 f.run(&[b"XRANGE", b"s", b"(-", b"+"])
23955 .contains("Invalid stream ID")
23956 );
23957
23958 assert_eq!(
23962 f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
23963 "*0\r\n"
23964 );
23965 assert_eq!(
23966 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
23967 "*-1\r\n"
23968 );
23969 f.run(&[b"SET", b"str", b"v"]);
23970 assert!(
23971 f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
23972 .starts_with("-WRONGTYPE")
23973 );
23974 assert_eq!(
23976 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
23977 "*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"
23978 );
23979 }
23980
23981 #[test]
23983 fn a_bad_id_late_in_the_list_stops_the_whole_command() {
23984 let mut f = Fixture::new();
23985 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
23986 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
23987 assert!(
23988 f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
23989 .contains("Invalid stream ID")
23990 );
23991 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
23992 assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
23993 assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
23994 assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
23995 assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
23996 }
23997
23998 #[test]
24000 fn xgroup_has_an_arity_per_subcommand() {
24001 let mut f = Fixture::new();
24002 assert!(
24003 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
24004 .contains("requires the key")
24005 );
24006 assert_eq!(
24007 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
24008 "+OK\r\n"
24009 );
24010 assert!(
24013 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
24014 .starts_with("-BUSYGROUP")
24015 );
24016 assert_eq!(
24017 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
24018 ":1\r\n"
24019 );
24020 assert_eq!(
24021 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
24022 ":0\r\n"
24023 );
24024 assert_eq!(
24025 f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
24026 ":0\r\n"
24027 );
24028
24029 let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
24031 assert!(
24032 short.contains("wrong number of arguments for 'xgroup|destroy' command"),
24033 "{short}"
24034 );
24035 let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
24037 assert!(
24038 odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
24039 "{odd}"
24040 );
24041 assert!(
24042 f.run(&[b"XGROUP", b"NOSUCH", b"s"])
24043 .contains("Try XGROUP HELP")
24044 );
24045
24046 assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
24047 assert!(
24048 f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
24049 .starts_with("-NOGROUP")
24050 );
24051 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
24052 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
24053 assert!(
24054 f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
24055 .contains("requires the key")
24056 );
24057 }
24058
24059 #[test]
24061 fn xreadgroup_hands_out_and_xack_takes_back() {
24062 let mut f = Fixture::new();
24063 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24064 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24065 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24066
24067 let first = f.run(&[
24068 b"XREADGROUP",
24069 b"GROUP",
24070 b"g",
24071 b"c1",
24072 b"COUNT",
24073 b"1",
24074 b"STREAMS",
24075 b"s",
24076 b">",
24077 ]);
24078 assert_eq!(
24079 first,
24080 "*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"
24081 );
24082 assert_eq!(
24085 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
24086 "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
24087 );
24088 assert_eq!(
24089 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
24090 "*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"
24091 );
24092
24093 assert_eq!(
24094 f.run(&[b"XPENDING", b"s", b"g"]),
24095 "*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"
24096 );
24097 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
24098 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
24099 assert_eq!(
24101 f.run(&[b"XPENDING", b"s", b"g"]),
24102 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
24103 );
24104
24105 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24108 f.run(&[b"XDEL", b"s", b"2-1"]);
24109 assert_eq!(
24110 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
24111 "*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"
24112 );
24113
24114 assert!(
24117 f.run(&[
24118 b"XREADGROUP",
24119 b"GROUP",
24120 b"nope",
24121 b"c",
24122 b"STREAMS",
24123 b"s",
24124 b"+"
24125 ])
24126 .starts_with("-NOGROUP")
24127 );
24128 assert!(
24129 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
24130 .contains("meaningless in the context of XREADGROUP")
24131 );
24132 assert!(
24133 f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
24134 .contains("only supported by XREADGROUP")
24135 );
24136 assert!(
24137 f.run(&[
24138 b"XREADGROUP",
24139 b"GROUP",
24140 b"g",
24141 b"c",
24142 b"STREAMS",
24143 b"s",
24144 b"a",
24145 b"b"
24146 ])
24147 .contains("Unbalanced 'xreadgroup' list of streams")
24148 );
24149 }
24150
24151 #[test]
24154 fn xread_with_no_block_writes_the_null_itself() {
24155 let mut f = Fixture::new();
24156 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24157 assert_eq!(
24158 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
24159 "*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"
24160 );
24161 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
24164 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
24165 f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
24166 assert_eq!(
24167 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
24168 "*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"
24169 );
24170 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
24172 assert_eq!(
24174 f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
24175 "*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"
24176 );
24177 assert_eq!(
24180 f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
24181 "*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"
24182 );
24183 assert!(
24185 f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
24186 .contains("not an integer")
24187 );
24188 assert!(
24189 f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
24190 .contains("timeout is negative")
24191 );
24192 assert!(
24193 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
24194 .contains("Unbalanced 'xread' list of streams")
24195 );
24196 }
24197
24198 #[test]
24200 fn a_blocked_xread_wakes_on_the_next_entry() {
24201 let mut f = Fixture::new();
24202 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24203 let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
24204 assert_eq!(flow, Flow::Block);
24205 assert!(reply.is_empty());
24206
24207 f.session = Session::new(8);
24212 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
24213 assert_eq!(flow, Flow::Block);
24214 assert_eq!(f.server.parked(), 2);
24215
24216 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24217 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";
24218 for client in [7, 8] {
24219 let mut out = Out::new(Proto::Resp2);
24220 assert!(f.server.serve_waiter(client, 0, &mut out));
24221 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
24222 }
24223
24224 f.server.forget_waiters(7);
24227 f.server.forget_waiters(8);
24228 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
24229 assert_eq!(flow, Flow::Block);
24230 let mut out = Out::new(Proto::Resp2);
24231 assert!(!f.server.serve_waiter(8, 0, &mut out));
24232 assert!(out.as_slice().is_empty());
24233 assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
24234 assert_eq!(
24235 core::str::from_utf8(out.as_slice()).expect("ascii"),
24236 "*-1\r\n"
24237 );
24238 }
24239
24240 #[test]
24242 fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
24243 let mut f = Fixture::new();
24244 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24245 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
24246 let (flow, _) = f.flow(&[
24247 b"XREADGROUP",
24248 b"GROUP",
24249 b"g",
24250 b"c",
24251 b"BLOCK",
24252 b"0",
24253 b"STREAMS",
24254 b"s",
24255 b">",
24256 ]);
24257 assert_eq!(flow, Flow::Block);
24258
24259 f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
24260 let mut out = Out::new(Proto::Resp2);
24261 assert!(f.server.serve_waiter(7, 0, &mut out));
24262 assert_eq!(
24265 core::str::from_utf8(out.as_slice()).expect("ascii"),
24266 "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
24267 );
24268 }
24269
24270 #[test]
24272 fn xclaim_reads_ids_until_one_will_not_parse() {
24273 let mut f = Fixture::new();
24274 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24275 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24276 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24277 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24278
24279 assert!(
24282 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
24283 .contains("Unrecognized XCLAIM option '-'")
24284 );
24285 assert_eq!(
24286 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
24287 "*1\r\n$3\r\n1-1\r\n"
24288 );
24289 f.run(&[b"XDEL", b"s", b"2-1"]);
24292 assert_eq!(
24293 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
24294 "*0\r\n"
24295 );
24296 assert!(
24297 f.run(&[b"XPENDING", b"s", b"g"])
24298 .starts_with("*4\r\n:1\r\n")
24299 );
24300 assert!(
24301 f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
24302 .starts_with("-NOGROUP")
24303 );
24304 assert!(
24305 f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
24306 .contains("Invalid min-idle-time argument for XCLAIM")
24307 );
24308 }
24309
24310 #[test]
24312 fn xautoclaim_reports_what_it_dropped() {
24313 let mut f = Fixture::new();
24314 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24315 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24316 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24317 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24318 f.run(&[b"XDEL", b"s", b"1-1"]);
24319
24320 assert_eq!(
24323 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
24324 "*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"
24325 );
24326 assert!(
24327 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
24328 .contains("COUNT must be > 0")
24329 );
24330 assert!(
24331 f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
24332 .starts_with("-NOGROUP")
24333 );
24334 }
24335
24336 #[test]
24338 fn xdelex_answers_one_integer_an_id() {
24339 let mut f = Fixture::new();
24340 for i in 1..=4 {
24341 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
24342 }
24343 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24344 f.run(&[
24345 b"XREADGROUP",
24346 b"GROUP",
24347 b"g",
24348 b"c",
24349 b"COUNT",
24350 b"2",
24351 b"STREAMS",
24352 b"s",
24353 b">",
24354 ]);
24355
24356 assert_eq!(
24358 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
24359 "*2\r\n:1\r\n:-1\r\n"
24360 );
24361 assert!(
24364 f.run(&[b"XPENDING", b"s", b"g"])
24365 .starts_with("*4\r\n:2\r\n")
24366 );
24367 assert_eq!(
24369 f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
24370 "*1\r\n:1\r\n"
24371 );
24372 assert_eq!(
24374 f.run(&[b"XPENDING", b"s", b"g"]),
24375 "*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"
24376 );
24377
24378 assert_eq!(
24382 f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
24383 "*2\r\n:2\r\n:2\r\n"
24384 );
24385
24386 assert_eq!(
24388 f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
24389 "*2\r\n:-1\r\n:-1\r\n"
24390 );
24391 assert!(
24393 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
24394 .starts_with("-ERR Invalid stream ID")
24395 );
24396 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
24397
24398 assert!(
24399 f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
24400 .contains("Number of IDs must be a positive integer")
24401 );
24402 assert!(
24403 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
24404 .contains("The `numids` parameter must match the number of arguments")
24405 );
24406 assert!(
24409 f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
24410 .starts_with("-ERR syntax error")
24411 );
24412 assert!(
24413 f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
24414 .starts_with("-ERR syntax error")
24415 );
24416 f.run(&[b"SET", b"str", b"v"]);
24418 assert!(
24419 f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
24420 .starts_with("-WRONGTYPE")
24421 );
24422 }
24423
24424 #[test]
24426 fn xackdel_reports_what_the_group_was_holding() {
24427 let mut f = Fixture::new();
24428 for i in 1..=3 {
24429 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
24430 }
24431 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24432 f.run(&[
24433 b"XREADGROUP",
24434 b"GROUP",
24435 b"g",
24436 b"c",
24437 b"COUNT",
24438 b"1",
24439 b"STREAMS",
24440 b"s",
24441 b">",
24442 ]);
24443
24444 assert_eq!(
24448 f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
24449 "*2\r\n:1\r\n:-1\r\n"
24450 );
24451 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
24452
24453 assert_eq!(
24455 f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
24456 "*1\r\n:-1\r\n"
24457 );
24458 assert_eq!(
24459 f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
24460 "*1\r\n:-1\r\n"
24461 );
24462
24463 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
24466 f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
24467 assert_eq!(
24468 f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
24469 "*1\r\n:2\r\n"
24470 );
24471 assert_eq!(
24472 f.run(&[b"XPENDING", b"s", b"g"]),
24473 "*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"
24474 );
24475 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
24476 }
24477
24478 #[test]
24480 fn xnack_releases_an_entry_for_the_next_claim() {
24481 let mut f = Fixture::new();
24482 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24483 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24484 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24485 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24486 f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
24489
24490 assert_eq!(
24491 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
24492 ":1\r\n"
24493 );
24494 assert_eq!(
24498 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
24499 "*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"
24500 );
24501 assert_eq!(
24503 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
24504 "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
24505 );
24506 assert_eq!(
24508 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
24509 "*-1\r\n"
24510 );
24511 assert_eq!(
24513 f.run(&[
24514 b"XAUTOCLAIM",
24515 b"s",
24516 b"g",
24517 b"c2",
24518 b"99999999",
24519 b"-",
24520 b"JUSTID"
24521 ]),
24522 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
24523 );
24524
24525 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24529 assert!(
24530 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24531 .contains(":-1\r\n:1\r\n")
24532 );
24533 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24535 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24536 assert!(
24537 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24538 .contains(":-1\r\n:0\r\n")
24539 );
24540 f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
24542 assert!(
24543 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24544 .contains(":9223372036854775807\r\n")
24545 );
24546 f.run(&[
24547 b"XNACK",
24548 b"s",
24549 b"g",
24550 b"FATAL",
24551 b"IDS",
24552 b"1",
24553 b"1-1",
24554 b"RETRYCOUNT",
24555 b"3",
24556 ]);
24557 assert!(
24558 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24559 .contains(":-1\r\n:3\r\n")
24560 );
24561
24562 f.run(&[b"XACK", b"s", b"g", b"2-1"]);
24566 assert_eq!(
24567 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
24568 ":0\r\n"
24569 );
24570 assert_eq!(
24571 f.run(&[
24572 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
24573 ]),
24574 ":1\r\n"
24575 );
24576 assert!(
24577 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24578 .contains(":-1\r\n:0\r\n")
24579 );
24580 assert_eq!(
24582 f.run(&[
24583 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
24584 ]),
24585 ":0\r\n"
24586 );
24587
24588 assert_eq!(
24591 f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
24592 "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
24593 );
24594 assert!(
24595 f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
24596 .starts_with("-ERR")
24597 );
24598 assert!(
24600 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
24601 .contains("numids must be a positive integer")
24602 );
24603 assert!(
24604 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
24605 .contains("number of IDs doesn't match numids")
24606 );
24607 assert!(
24610 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
24611 .contains("Unrecognized XNACK option '2-1'")
24612 );
24613 }
24614
24615 #[test]
24617 fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
24618 let mut f = Fixture::new();
24619 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24620 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24621 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24622 f.run(&[
24623 b"XREADGROUP",
24624 b"GROUP",
24625 b"g",
24626 b"c1",
24627 b"COUNT",
24628 b"1",
24629 b"STREAMS",
24630 b"s",
24631 b">",
24632 ]);
24633
24634 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
24635 assert!(info.starts_with("*20\r\n"), "{info}");
24638 assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
24639 assert!(
24640 info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
24641 "{info}"
24642 );
24643 assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
24644 assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
24645
24646 let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
24647 assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
24648 assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
24649 assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
24650 assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
24651
24652 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
24656 let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
24657 assert!(consumers.starts_with("*2\r\n"), "{consumers}");
24658 assert!(
24659 consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
24660 "{consumers}"
24661 );
24662 let c1 = consumers.find("c1").unwrap();
24664 let c2 = consumers.find("c2").unwrap();
24665 assert!(c1 < c2, "{consumers}");
24666
24667 let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
24668 assert!(full.starts_with("*18\r\n"), "{full}");
24669 assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
24670 assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
24671
24672 assert!(
24673 f.run(&[b"XINFO", b"STREAM", b"missing"])
24674 .contains("no such key")
24675 );
24676 assert!(
24677 f.run(&[b"XINFO", b"GROUPS", b"missing"])
24678 .contains("no such key")
24679 );
24680 assert!(
24681 f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
24682 .starts_with("-NOGROUP")
24683 );
24684 assert!(
24685 f.run(&[b"XINFO", b"NOSUCH", b"s"])
24686 .contains("Try XINFO HELP")
24687 );
24688 assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
24689 assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
24690 }
24691
24692 #[test]
24694 fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
24695 let mut f = Fixture::new();
24696 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24697 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24698 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24699
24700 let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
24701 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");
24702 assert_eq!(
24703 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
24704 "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
24705 );
24706 assert_eq!(
24708 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
24709 "*0\r\n"
24710 );
24711 assert_eq!(
24712 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
24713 list
24714 );
24715 assert!(
24717 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
24718 .contains("syntax error")
24719 );
24720 assert!(
24721 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
24722 .contains("syntax error")
24723 );
24724 assert_eq!(
24725 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
24726 "*0\r\n"
24727 );
24728 assert!(
24729 f.run(&[b"XPENDING", b"missing", b"g"])
24730 .starts_with("-NOGROUP")
24731 );
24732 }
24733
24734 #[test]
24736 fn xsetid_will_not_go_below_what_is_there() {
24737 let mut f = Fixture::new();
24738 f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
24739 assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
24740 assert_eq!(
24741 f.run(&[
24742 b"XSETID",
24743 b"s",
24744 b"10-1",
24745 b"ENTRIESADDED",
24746 b"7",
24747 b"MAXDELETEDID",
24748 b"9-1"
24749 ]),
24750 "+OK\r\n"
24751 );
24752 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
24753 assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
24754 assert!(
24755 info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
24756 "{info}"
24757 );
24758
24759 assert!(
24760 f.run(&[b"XSETID", b"s", b"1-1"])
24761 .contains("smaller than the target stream top item")
24762 );
24763 assert!(
24764 f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
24765 .contains("entries_added must be positive")
24766 );
24767 assert!(
24768 f.run(&[b"XSETID", b"missing", b"1-1"])
24769 .contains("no such key")
24770 );
24771 }
24772
24773 #[test]
24775 fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
24776 let mut f = Fixture::new();
24777 f.run(&[b"HELLO", b"3"]);
24778 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24779 assert_eq!(
24782 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
24783 "%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"
24784 );
24785 assert_eq!(
24788 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
24789 "*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"
24790 );
24791 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
24792 }
24793
24794 struct Mem {
24800 blobs: Vec<Vec<u8>>,
24801 }
24802
24803 impl yo_kv::cold::Blocks for Mem {
24804 fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
24805 self.blobs.push(bytes.to_vec());
24806 Ok(yo_common::Addr::new(
24807 yo_common::Space::Log,
24808 (self.blobs.len() - 1) as u64,
24809 ))
24810 }
24811
24812 fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
24813 self.blobs
24814 .get(at.offset() as usize)
24815 .map(Vec::as_slice)
24816 .ok_or_else(|| {
24817 yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
24818 })
24819 }
24820
24821 fn bytes(&self) -> u64 {
24822 self.blobs.iter().map(|b| b.len() as u64).sum()
24823 }
24824 }
24825
24826 fn filled(attach: bool) -> (Fixture, usize) {
24839 let mut f = Fixture::new();
24840 if attach {
24841 f.server
24842 .striped(0)
24843 .hold_stripe(0)
24844 .attach(Box::new(Mem { blobs: Vec::new() }));
24845 }
24846 let val = vec![b'v'; 256];
24847 for i in 0..24000u32 {
24848 let k = format!("key:{i:08}");
24849 f.run(&[b"SET", k.as_bytes(), &val]);
24850 }
24851 let full = f.server.memory_bytes();
24852 assert!(full > 3 * 1024 * 1024, "the arena is several segments");
24853 (f, full)
24854 }
24855
24856 fn press(f: &mut Fixture, limit: usize) {
24862 let val = vec![b'v'; 256];
24863 for i in 0..3000u32 {
24864 let k = format!("new:{i:08}");
24865 assert_eq!(
24866 f.run(&[b"SET", k.as_bytes(), &val]),
24867 "+OK\r\n",
24868 "write {i} was refused"
24869 );
24870 f.server.refresh_memory();
24871 if f.server.memory_bytes() <= limit {
24872 return;
24873 }
24874 }
24875 panic!(
24876 "it never got under: {} against {limit}",
24877 f.server.memory_bytes()
24878 );
24879 }
24880
24881 #[test]
24882 fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
24883 let mut f = Fixture::new();
24884 assert_eq!(
24885 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
24886 "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
24887 "no limit is the default"
24888 );
24889 for (typed, bytes) in [
24892 (&b"0"[..], "0"),
24893 (b"1024", "1024"),
24894 (b"1k", "1000"),
24895 (b"1gb", "1073741824"),
24896 (b"-1", "-1"),
24897 ] {
24898 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
24899 assert_eq!(
24900 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
24901 format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
24902 "set {}",
24903 String::from_utf8_lossy(typed)
24904 );
24905 }
24906 for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
24907 assert_eq!(
24908 f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
24909 "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
24910 "refused {}",
24911 String::from_utf8_lossy(bad)
24912 );
24913 }
24914 let info = f.run(&[b"INFO", b"memory"]);
24916 assert!(info.contains("maxstore:-1"), "{info}");
24917 assert!(info.contains("yo_memory_regime:evict"), "{info}");
24918 assert!(info.contains("yo_store_bytes:0"), "{info}");
24919 }
24920
24921 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24922 #[test]
24923 fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
24924 let (mut f, full) = filled(true);
24928 let keys = f.run(&[b"DBSIZE"]);
24929 assert!(
24930 f.run(&[b"INFO", b"memory"])
24931 .contains("yo_memory_regime:migrate"),
24932 "a database with somewhere to put values migrates"
24933 );
24934
24935 let limit = full - 2 * 1024 * 1024;
24936 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
24937 f.run(&[
24938 b"CONFIG",
24939 b"SET",
24940 b"maxmemory",
24941 limit.to_string().as_bytes(),
24942 ]);
24943 press(&mut f, limit);
24944
24945 assert!(
24946 f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
24947 "nothing was thrown away"
24948 );
24949 let after: usize = f.run(&[b"DBSIZE"])[1..]
24950 .trim_end()
24951 .parse()
24952 .expect("a count");
24953 let before: usize = keys[1..].trim_end().parse().expect("a count");
24954 assert!(after > before, "the keys that came in are all still here");
24955 assert!(
24956 f.server.store_bytes() > 0,
24957 "and what came out of memory went to the file"
24958 );
24959 let val = format!("$256\r\n{}\r\n", "v".repeat(256));
24962 assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
24963 assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
24964 }
24965
24966 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24967 #[test]
24968 fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
24969 let (mut f, full) = filled(true);
24973 f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
24974 assert!(
24975 f.run(&[b"INFO", b"memory"])
24976 .contains("yo_memory_regime:evict"),
24977 "nothing may go to the file"
24978 );
24979
24980 let limit = full - 2 * 1024 * 1024;
24981 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
24982 f.run(&[
24983 b"CONFIG",
24984 b"SET",
24985 b"maxmemory",
24986 limit.to_string().as_bytes(),
24987 ]);
24988 press(&mut f, limit);
24989
24990 assert!(
24991 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
24992 "keys were thrown away, which is what was asked for"
24993 );
24994 assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
24995 }
24996
24997 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24998 #[test]
24999 fn a_full_file_goes_back_to_evicting() {
25000 let (mut f, full) = filled(true);
25004 f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
25005 let limit = full - 2 * 1024 * 1024;
25006 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
25007 f.run(&[
25008 b"CONFIG",
25009 b"SET",
25010 b"maxmemory",
25011 limit.to_string().as_bytes(),
25012 ]);
25013 press(&mut f, limit);
25014
25015 assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
25016 assert!(
25017 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
25018 "and then it started evicting"
25019 );
25020 assert!(
25021 f.run(&[b"INFO", b"memory"])
25022 .contains("yo_memory_regime:evict"),
25023 "and it says so"
25024 );
25025 }
25026 #[test]
25042 fn the_string_group_answers_the_same_however_many_stripes_there_are() {
25043 let script: &[&[&[u8]]] = &[
25044 &[b"SET", b"k1", b"v1"],
25047 &[b"SET", b"k2", b"v2"],
25048 &[b"GET", b"k1"],
25049 &[b"GET", b"nothing"],
25050 &[b"GETSET", b"k1", b"v1b"],
25051 &[b"SETNX", b"k1", b"no"],
25052 &[b"SETNX", b"k3", b"yes"],
25053 &[b"APPEND", b"k3", b"!"],
25054 &[b"STRLEN", b"k3"],
25055 &[b"SETRANGE", b"k3", b"1", b"XY"],
25056 &[b"GETRANGE", b"k3", b"0", b"-1"],
25057 &[b"INCR", b"n1"],
25058 &[b"INCRBY", b"n1", b"41"],
25059 &[b"DECRBY", b"n1", b"2"],
25060 &[b"INCRBYFLOAT", b"f1", b"1.5"],
25061 &[b"SETEX", b"e1", b"100", b"v"],
25062 &[b"PSETEX", b"e2", b"100000", b"v"],
25063 &[b"GETEX", b"e1", b"PERSIST"],
25064 &[b"GETDEL", b"k2"],
25065 &[b"GET", b"k2"],
25066 &[b"DIGEST", b"k1"],
25067 &[b"DELEX", b"k3"],
25068 &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
25071 &[b"MGET", b"a", b"b", b"c", b"missing"],
25072 &[b"MSETNX", b"d", b"4", b"e", b"5"],
25073 &[b"MSETNX", b"e", b"6", b"f", b"7"],
25074 &[b"MGET", b"d", b"e", b"f"],
25075 &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
25076 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
25077 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
25078 &[b"MGET", b"g", b"h"],
25079 &[b"SET", b"s1", b"ohmytext"],
25080 &[b"SET", b"s2", b"mynewtext"],
25081 &[b"LCS", b"s1", b"s2"],
25082 &[b"LCS", b"s1", b"s2", b"LEN"],
25083 &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
25084 &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
25085 &[b"LCS", b"s1", b"gone"],
25086 &[b"MSET", b"odd"],
25088 &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
25089 &[b"MGET"],
25090 ];
25091
25092 let mut one = Fixture::new();
25093 let mut many = Fixture::striped(8);
25094 for parts in script {
25095 let a = one.run(parts);
25096 let b = many.run(parts);
25097 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25098 }
25099 }
25100
25101 #[test]
25106 fn a_striped_database_spreads_the_keys_it_is_given() {
25107 let mut f = Fixture::striped(8);
25108 for i in 0..256 {
25109 let key = format!("key:{i}");
25110 f.run(&[b"SET", key.as_bytes(), b"v"]);
25111 }
25112 assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
25113 }
25114
25115 #[test]
25118 fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
25119 let mut one = Fixture::new();
25120 let mut many = Fixture::striped(8);
25121 for f in [&mut one, &mut many] {
25122 f.run(&[b"SET", b"str", b"v"]);
25123 f.server
25129 .striped(0)
25130 .hold(b"list")
25131 .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
25132 .expect("a new list");
25133 }
25134 assert_eq!(
25135 one.run(&[b"MGET", b"str", b"list", b"gone"]),
25136 many.run(&[b"MGET", b"str", b"list", b"gone"])
25137 );
25138 }
25139
25140 #[test]
25148 fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
25149 let script: &[&[&[u8]]] = &[
25150 &[b"SET", b"k1", b"v1"],
25151 &[b"SET", b"k2", b"v2"],
25152 &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
25153 &[b"TYPE", b"k1"],
25154 &[b"TYPE", b"gone"],
25155 &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
25156 &[b"EXPIRE", b"k1", b"100"],
25157 &[b"TTL", b"k1"],
25158 &[b"EXPIRE", b"k1", b"200", b"NX"],
25159 &[b"PERSIST", b"k1"],
25160 &[b"TTL", b"k1"],
25161 &[b"PEXPIREAT", b"k2", b"1900000000000"],
25162 &[b"EXPIRETIME", b"k2"],
25163 &[b"PEXPIRETIME", b"k2"],
25164 &[b"PERSIST", b"k2"],
25165 &[b"OBJECT", b"ENCODING", b"k1"],
25166 &[b"OBJECT", b"REFCOUNT", b"k1"],
25167 &[b"OBJECT", b"IDLETIME", b"k1"],
25168 &[b"OBJECT", b"FREQ", b"k1"],
25169 &[b"OBJECT", b"ENCODING", b"gone"],
25170 &[b"OBJECT", b"HELP"],
25171 &[b"RENAME", b"k1", b"k9"],
25172 &[b"GET", b"k9"],
25173 &[b"RENAME", b"gone", b"x"],
25174 &[b"RENAMENX", b"k9", b"k2"],
25175 &[b"RENAMENX", b"k9", b"k8"],
25176 &[b"GET", b"k8"],
25177 &[b"COPY", b"k8", b"c1"],
25178 &[b"COPY", b"k8", b"c1"],
25179 &[b"COPY", b"k8", b"c1", b"REPLACE"],
25180 &[b"COPY", b"k8", b"k8"],
25181 &[b"COPY", b"gone", b"c2"],
25182 &[b"COPY", b"k8", b"k8", b"DB", b"1"],
25183 &[b"COPY", b"k8", b"c9", b"DB", b"9"],
25184 &[b"MOVE", b"c1", b"1"],
25185 &[b"MOVE", b"c1", b"1"],
25186 &[b"MOVE", b"k8", b"0"],
25187 &[b"DEL", b"k2", b"gone"],
25188 &[b"UNLINK", b"k8", b"k8"],
25189 &[b"DBSIZE"],
25190 ];
25191
25192 let mut one = Fixture::new();
25193 let mut many = Fixture::striped(8);
25194 for parts in script {
25195 let a = one.run(parts);
25196 let b = many.run(parts);
25197 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25198 }
25199
25200 for f in [&mut one, &mut many] {
25206 f.run(&[b"SET", b"d1", b"payload"]);
25207 let payload = f
25208 .server
25209 .striped(0)
25210 .hold(b"d1")
25211 .dump(b"d1")
25212 .expect("a key that is there");
25213 assert!(
25214 f.run(&[b"DUMP", b"d1"])
25215 .starts_with(&format!("${}", payload.len())),
25216 "a payload of the length the store gave"
25217 );
25218 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
25219 assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
25220 assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
25221 assert_eq!(
25222 f.run(&[b"RESTORE", b"d2", b"0", &payload]),
25223 "-BUSYKEY Target key name already exists.\r\n"
25224 );
25225 assert_eq!(
25226 f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
25227 "-ERR DUMP payload version or checksum are wrong\r\n"
25228 );
25229 }
25230 }
25231
25232 #[test]
25239 fn a_scan_of_a_striped_database_walks_all_of_it() {
25240 let n = if cfg!(miri) { 80 } else { 500 };
25244 let mut f = Fixture::striped(8);
25245 for i in 0..n {
25246 let key = format!("key:{i}");
25247 f.run(&[b"SET", key.as_bytes(), b"v"]);
25248 }
25249
25250 let mut seen = Vec::new();
25251 let mut cursor = "0".to_owned();
25252 let mut calls = 0;
25253 loop {
25254 let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
25255 let (next, keys) = scan_reply(&reply);
25256 seen.extend(keys);
25257 cursor = next;
25258 calls += 1;
25259 assert!(calls < 5_000, "a scan that will not finish");
25260 if cursor == "0" {
25261 break;
25262 }
25263 }
25264 seen.sort();
25265 assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
25266 assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
25267
25268 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
25272 let (_, keys) = scan_reply(&reply);
25273 assert_eq!(keys.len(), 10, "key:40 through key:49");
25274 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
25275 let (_, keys) = scan_reply(&reply);
25276 assert!(keys.is_empty(), "nothing here is a list");
25277 }
25278
25279 #[test]
25285 fn a_random_key_can_come_from_any_stripe() {
25286 let mut f = Fixture::striped(8);
25287 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
25288 for i in 0..200 {
25289 let key = format!("key:{i}");
25290 f.run(&[b"SET", key.as_bytes(), b"v"]);
25291 }
25292 let mut homes = std::collections::HashSet::new();
25293 for _ in 0..200 {
25294 let got = f.run(&[b"RANDOMKEY"]);
25295 let key = got.split("\r\n").nth(1).expect("a key").to_owned();
25296 assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
25297 homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
25298 }
25299 assert_eq!(homes.len(), 8, "some stripe was never drawn from");
25300 }
25301
25302 fn apart(f: &mut Fixture, src: &str) -> String {
25306 let home = f.server.striped(0).stripe_of(src.as_bytes());
25307 for i in 0..1_000 {
25308 let dst = format!("dst:{i}");
25309 if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
25310 return dst;
25311 }
25312 }
25313 panic!("eight stripes and a thousand keys all landed in one place");
25314 }
25315
25316 #[test]
25319 fn a_rename_across_stripes_takes_everything_with_it() {
25320 let mut f = Fixture::striped(8);
25321 let dst = apart(&mut f, "src");
25322 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
25323
25324 f.run(&[b"SET", src, b"v"]);
25325 f.run(&[b"EXPIRE", src, b"100"]);
25326 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
25327 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
25328 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
25329 assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
25330
25331 f.server
25336 .striped(0)
25337 .hold(src)
25338 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
25339 .expect("a new list");
25340 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
25341 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
25342 assert_eq!(
25343 f.server.striped(0).hold(dst).llen(dst).expect("a list"),
25344 2,
25345 "the members are on the stripe the key moved to"
25346 );
25347
25348 f.run(&[b"SET", src, b"v"]);
25351 assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
25352 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
25353 assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
25354 }
25355
25356 #[test]
25358 fn a_copy_across_stripes_leaves_the_source_where_it_was() {
25359 let mut f = Fixture::striped(8);
25360 let dst = apart(&mut f, "src");
25361 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
25362
25363 f.run(&[b"SET", src, b"v"]);
25364 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
25365 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
25366 assert_eq!(
25367 f.run(&[b"COPY", src, dst]),
25368 ":0\r\n",
25369 "the destination is taken"
25370 );
25371 f.run(&[b"SET", src, b"w"]);
25372 assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
25373 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
25374
25375 f.run(&[b"DEL", src, dst]);
25379 f.server
25380 .striped(0)
25381 .hold(src)
25382 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
25383 .expect("a new list");
25384 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
25385 f.server
25386 .striped(0)
25387 .hold(src)
25388 .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
25389 .expect("a list that is there");
25390 assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
25391 assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
25392 }
25393
25394 #[test]
25402 fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
25403 let script: &[&[&[u8]]] = &[
25404 &[b"SET", b"k1", b"foobar"],
25405 &[b"SETBIT", b"b1", b"7", b"1"],
25406 &[b"SETBIT", b"b1", b"7", b"0"],
25407 &[b"GETBIT", b"k1", b"6"],
25408 &[b"GETBIT", b"k1", b"100"],
25409 &[b"BITCOUNT", b"k1"],
25410 &[b"BITCOUNT", b"k1", b"0", b"0"],
25411 &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
25412 &[b"BITPOS", b"k1", b"1"],
25413 &[b"BITPOS", b"k1", b"0", b"2"],
25414 &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
25415 &[
25416 b"BITFIELD",
25417 b"bf",
25418 b"SET",
25419 b"u8",
25420 b"0",
25421 b"255",
25422 b"GET",
25423 b"u8",
25424 b"0",
25425 ],
25426 &[
25427 b"BITFIELD",
25428 b"bf",
25429 b"OVERFLOW",
25430 b"SAT",
25431 b"INCRBY",
25432 b"u8",
25433 b"0",
25434 b"10",
25435 ],
25436 &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
25437 &[b"SET", b"s1", b"abc"],
25440 &[b"SET", b"s2", b"abd"],
25441 &[b"SET", b"s3", b"a"],
25442 &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
25443 &[b"GET", b"d1"],
25444 &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
25445 &[b"GET", b"d2"],
25446 &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
25447 &[b"STRLEN", b"d3"],
25448 &[b"BITOP", b"NOT", b"d4", b"s1"],
25449 &[b"STRLEN", b"d4"],
25450 &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
25451 &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
25452 &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
25453 &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
25454 &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
25457 &[b"EXISTS", b"d1"],
25458 &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
25459 &[b"GET", b"d9"],
25460 &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
25464 &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
25465 &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
25466 &[b"BITOP", b"DIFF", b"d1", b"s1"],
25467 &[b"BITOP", b"NOPE", b"d1", b"s1"],
25468 &[b"BITCOUNT", b"list"],
25469 &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
25470 ];
25471
25472 let mut one = Fixture::new();
25473 let mut many = Fixture::striped(8);
25474 for f in [&mut one, &mut many] {
25475 plant_list(f, b"list");
25476 }
25477 for parts in script {
25478 let a = one.run(parts);
25479 let b = many.run(parts);
25480 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25481 }
25482 }
25483
25484 fn plant_list(f: &mut Fixture, key: &[u8]) {
25490 f.server
25491 .striped(0)
25492 .hold(key)
25493 .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
25494 .expect("a new list");
25495 }
25496
25497 #[test]
25504 fn a_bitop_across_stripes_reads_every_source() {
25505 let mut f = Fixture::striped(8);
25506 let other = apart(&mut f, "src");
25507 let (src, far) = (b"src".as_slice(), other.as_bytes());
25508 assert_ne!(
25509 f.server.striped(0).stripe_of(src),
25510 f.server.striped(0).stripe_of(far),
25511 "the two keys are the point of the test"
25512 );
25513
25514 f.run(&[b"SET", src, b"abc"]);
25515 f.run(&[b"SET", far, b"abd"]);
25516 assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
25517 assert_eq!(
25518 f.run(&[b"GET", far]),
25519 "$3\r\nab`\r\n",
25520 "a destination that is also a source"
25521 );
25522 f.run(&[b"SET", far, b"abd"]);
25523 assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
25524 assert_eq!(
25525 f.run(&[b"GET", src]),
25526 "$3\r\n\0\0\x07\r\n",
25527 "and the other way round"
25528 );
25529
25530 f.run(&[b"SET", src, b"abc"]);
25534 f.run(&[b"DEL", far]);
25535 assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
25536 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
25537 f.run(&[b"SET", src, b"abc"]);
25538 f.run(&[b"DEL", far]);
25539 plant_list(&mut f, far);
25540 assert_eq!(
25541 f.run(&[b"BITOP", b"OR", b"out", src, far]),
25542 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25543 );
25544 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
25545 }
25546
25547 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
25553 #[test]
25554 fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
25555 let script: &[&[&[u8]]] = &[
25556 &[b"PFADD", b"h1", b"a", b"b", b"c"],
25557 &[b"PFADD", b"h1", b"a"],
25558 &[b"PFADD", b"h2"],
25559 &[b"PFADD", b"h2", b"c", b"d", b"e"],
25560 &[b"PFCOUNT", b"h1"],
25561 &[b"PFCOUNT", b"h2"],
25562 &[b"PFCOUNT", b"missing"],
25563 &[b"PFCOUNT", b"h1", b"h2"],
25565 &[b"PFCOUNT", b"h1", b"missing"],
25566 &[b"PFMERGE", b"m", b"h1", b"h2"],
25567 &[b"PFCOUNT", b"m"],
25568 &[b"STRLEN", b"m"],
25569 &[b"PFMERGE", b"m"],
25570 &[b"PFCOUNT", b"m"],
25571 &[b"PFMERGE", b"m2", b"missing"],
25572 &[b"PFCOUNT", b"m2"],
25573 &[b"PFDEBUG", b"ENCODING", b"h1"],
25576 &[b"PFDEBUG", b"DECODE", b"h1"],
25577 &[b"PFDEBUG", b"TODENSE", b"h1"],
25578 &[b"PFDEBUG", b"ENCODING", b"h1"],
25579 &[b"PFDEBUG", b"TODENSE", b"h1"],
25580 &[b"PFCOUNT", b"h1", b"h2"],
25581 &[b"PFSELFTEST"],
25582 &[b"SET", b"plain", b"not a sketch at all"],
25584 &[b"PFADD", b"plain", b"a"],
25585 &[b"PFCOUNT", b"plain"],
25586 &[b"PFCOUNT", b"h1", b"plain"],
25587 &[b"PFMERGE", b"plain", b"h1"],
25588 &[b"PFMERGE", b"m", b"plain"],
25589 &[b"PFDEBUG", b"ENCODING", b"gone"],
25590 &[b"PFDEBUG", b"NOPE", b"h1"],
25591 ];
25592
25593 let mut one = Fixture::new();
25594 let mut many = Fixture::striped(8);
25595 for parts in script {
25596 let a = one.run(parts);
25597 let b = many.run(parts);
25598 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25599 }
25600 }
25601
25602 #[test]
25611 fn the_set_group_answers_the_same_however_many_stripes_there_are() {
25612 const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
25613 let script: &[&[&[u8]]] = &[
25614 &[b"SADD", b"s1", b"a", b"b", b"c"],
25615 &[b"SADD", b"s1", b"a"],
25616 &[b"SADD", b"s2", b"b", b"c", b"d"],
25617 &[b"SADD", b"ints", b"1", b"2", b"3"],
25618 &[b"SCARD", b"s1"],
25619 &[b"SISMEMBER", b"s1", b"a"],
25620 &[b"SISMEMBER", b"s1", b"z"],
25621 &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
25622 &[b"SMEMBERS", b"s1"],
25623 &[b"SREM", b"s1", b"c"],
25624 &[b"SADD", b"s1", b"c"],
25625 &[b"SSCAN", b"s1", b"0"],
25626 &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
25627 &[b"SADD", b"one", b"m"],
25630 &[b"SRANDMEMBER", b"one"],
25631 &[b"SRANDMEMBER", b"one", b"-3"],
25632 &[b"SRANDMEMBER", b"gone"],
25633 &[b"SPOP", b"one"],
25634 &[b"SPOP", b"one"],
25635 &[b"SPOP", b"gone", b"2"],
25636 &[b"SMOVE", b"s1", b"s2", b"a"],
25638 &[b"SMOVE", b"s1", b"s2", b"zzz"],
25639 &[b"SMOVE", b"gone", b"s2", b"a"],
25640 &[b"SMEMBERS", b"s1"],
25641 &[b"SMEMBERS", b"s2"],
25642 &[b"SINTER", b"s1", b"s2"],
25644 &[b"SUNION", b"s1", b"s2"],
25645 &[b"SDIFF", b"s2", b"s1"],
25646 &[b"SINTER", b"s1", b"gone"],
25647 &[b"SUNION", b"s1", b"gone"],
25648 &[b"SDIFF", b"gone", b"s1"],
25649 &[b"SINTER", b"ints", b"s1"],
25650 &[b"SINTERCARD", b"2", b"s1", b"s2"],
25651 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
25652 &[b"SUNIONCARD", b"2", b"s1", b"s2"],
25653 &[b"SDIFFCARD", b"2", b"s2", b"s1"],
25654 &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
25655 &[b"SMEMBERS", b"d1"],
25656 &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
25657 &[b"SCARD", b"d2"],
25658 &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
25659 &[b"SCARD", b"d3"],
25660 &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
25663 &[b"EXISTS", b"d4"],
25664 &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
25666 &[b"SCARD", b"s2"],
25667 &[b"SET", b"str", b"v"],
25669 &[b"SADD", b"str", b"a"],
25670 &[b"SINTER", b"s1", b"str"],
25671 &[b"SINTERSTORE", b"d5", b"s1", b"str"],
25672 &[b"EXISTS", b"d5"],
25673 &[b"SMOVE", b"str", b"s2", b"a"],
25674 &[b"SMOVE", b"s1", b"str", b"b"],
25675 &[b"SMOVE", b"gone", b"str", b"b"],
25676 &[b"SINTERCARD", b"0", b"s1"],
25677 &[b"SINTERCARD", b"3", b"s1", b"s2"],
25678 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
25679 &[b"SPOP", b"s1", b"-1"],
25680 ];
25681
25682 let mut one = Fixture::new();
25683 let mut many = Fixture::striped(8);
25684 for parts in script {
25685 let a = one.run(parts);
25686 let b = many.run(parts);
25687 let name = String::from_utf8_lossy(parts[0]).to_uppercase();
25688 if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
25689 assert_eq!(sorted(&a), sorted(&b), "{name}");
25690 } else {
25691 assert_eq!(a, b, "{name}");
25692 }
25693 }
25694 }
25695
25696 #[test]
25698 fn a_set_operation_across_stripes_reads_every_set() {
25699 let mut f = Fixture::striped(8);
25700 let second = apart(&mut f, "s1");
25701 let third = apart(&mut f, &second);
25702 let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
25703
25704 f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
25705 f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
25706 assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
25707 assert_eq!(
25708 sorted(&f.run(&[b"SUNION", s1, s2])),
25709 ["a", "b", "c", "d"],
25710 "a union of two stripes is both of them"
25711 );
25712 assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
25713 assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
25714 assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
25715 assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
25716
25717 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
25719 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
25720 assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
25721 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
25722 assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
25723 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
25724
25725 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
25728 assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
25729 f.run(&[b"SET", s3, b"v"]);
25730 assert_eq!(
25731 f.run(&[b"SINTER", s1, s3]),
25732 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25733 );
25734 assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
25735 }
25736
25737 #[test]
25739 fn a_move_across_stripes_takes_the_member_with_it() {
25740 let mut f = Fixture::striped(8);
25741 let other = apart(&mut f, "src");
25742 let (src, dst) = (b"src".as_slice(), other.as_bytes());
25743
25744 f.run(&[b"SADD", src, b"a", b"b"]);
25745 f.run(&[b"SADD", dst, b"c"]);
25746 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
25747 assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
25748 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
25749 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
25750
25751 f.run(&[b"DEL", dst]);
25754 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
25755 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
25756 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
25757
25758 f.run(&[b"SET", dst, b"v"]);
25762 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
25763 f.run(&[b"SADD", src, b"b"]);
25764 assert_eq!(
25765 f.run(&[b"SMOVE", src, dst, b"b"]),
25766 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25767 );
25768 }
25769
25770 #[test]
25772 fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
25773 let mut f = Fixture::striped(8);
25774 let other = apart(&mut f, "src");
25775 let (src, far) = (b"src".as_slice(), other.as_bytes());
25776
25777 for i in 0..150 {
25778 let ele = format!("e:{i}");
25779 f.run(&[b"PFADD", src, ele.as_bytes()]);
25780 }
25781 for i in 150..200 {
25782 let ele = format!("e:{i}");
25783 f.run(&[b"PFADD", far, ele.as_bytes()]);
25784 }
25785 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
25788 assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
25789 assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
25790
25791 let dest = apart(&mut f, &other);
25794 assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
25795 assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
25796 assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
25797 assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
25798 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
25799 }
25800
25801 #[test]
25808 fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
25809 let script: &[&[&[u8]]] = &[
25810 &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
25811 &[b"ZADD", b"z1", b"NX", b"9", b"a"],
25812 &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
25813 &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
25814 &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
25815 &[b"ZINCRBY", b"z1", b"1.5", b"b"],
25816 &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
25817 &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
25818 &[b"ZADD", b"one", b"1", b"m"],
25819 &[b"ZCARD", b"z1"],
25820 &[b"ZCARD", b"gone"],
25821 &[b"ZSCORE", b"z1", b"a"],
25822 &[b"ZSCORE", b"z1", b"zz"],
25823 &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
25824 &[b"ZRANK", b"z1", b"c"],
25825 &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
25826 &[b"ZREVRANK", b"z1", b"c"],
25827 &[b"ZRANK", b"z1", b"gone"],
25828 &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
25829 &[b"ZCOUNT", b"z1", b"(1", b"3"],
25830 &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
25831 &[b"ZRANGE", b"z1", b"0", b"-1"],
25833 &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
25834 &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
25835 &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
25836 &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
25837 &[b"ZREVRANGE", b"z1", b"0", b"-1"],
25838 &[
25839 b"ZRANGEBYSCORE",
25840 b"z1",
25841 b"-inf",
25842 b"+inf",
25843 b"LIMIT",
25844 b"1",
25845 b"1",
25846 ],
25847 &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
25848 &[b"ZSCAN", b"z1", b"0"],
25849 &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
25850 &[b"ZRANDMEMBER", b"one"],
25853 &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
25854 &[b"ZRANDMEMBER", b"gone"],
25855 &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
25857 &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
25858 &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
25859 &[b"EXISTS", b"d0"],
25860 &[b"ZUNION", b"2", b"z1", b"z2"],
25862 &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
25863 &[
25864 b"ZUNION",
25865 b"2",
25866 b"z1",
25867 b"z2",
25868 b"WEIGHTS",
25869 b"2",
25870 b"3",
25871 b"AGGREGATE",
25872 b"MAX",
25873 b"WITHSCORES",
25874 ],
25875 &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
25876 &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
25877 &[b"ZDIFF", b"2", b"gone", b"z1"],
25878 &[b"ZINTERCARD", b"2", b"z1", b"z2"],
25879 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
25880 &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
25881 &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
25882 &[
25883 b"ZINTERSTORE",
25884 b"d2",
25885 b"2",
25886 b"z1",
25887 b"z2",
25888 b"AGGREGATE",
25889 b"MIN",
25890 ],
25891 &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
25892 &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
25893 &[b"ZCARD", b"d3"],
25894 &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
25897 &[b"EXISTS", b"d4"],
25898 &[b"SADD", b"plain", b"a", b"x"],
25901 &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
25902 &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
25903 &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
25905 &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
25906 &[b"ZREM", b"d5", b"x", b"nothere"],
25908 &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
25909 &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
25910 &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
25911 &[b"ZPOPMIN", b"z1"],
25912 &[b"ZPOPMAX", b"z1", b"2"],
25913 &[b"ZPOPMIN", b"gone"],
25914 &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
25915 &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
25916 &[b"SET", b"str", b"v"],
25918 &[b"ZADD", b"str", b"1", b"a"],
25919 &[b"ZSCORE", b"str", b"a"],
25920 &[b"ZADD", b"z1", b"nan", b"a"],
25921 &[b"ZUNION", b"2", b"z1", b"str"],
25922 &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
25923 &[b"EXISTS", b"d6"],
25924 &[b"ZINTERCARD", b"0", b"z1"],
25925 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
25926 &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
25927 &[b"ZMPOP", b"1", b"str", b"MIN"],
25928 &[b"ZPOPMIN", b"z1", b"-1"],
25929 ];
25930
25931 let mut one = Fixture::new();
25932 let mut many = Fixture::striped(8);
25933 for parts in script {
25934 let a = one.run(parts);
25935 let b = many.run(parts);
25936 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25937 }
25938 }
25939
25940 #[test]
25942 fn a_sorted_set_operation_across_stripes_reads_every_input() {
25943 let mut f = Fixture::striped(8);
25944 let second = apart(&mut f, "z1");
25945 let third = apart(&mut f, &second);
25946 let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
25947
25948 f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
25949 f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
25950 assert_eq!(
25953 f.run(&[b"ZUNION", b"2", z1, z2]),
25954 "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
25955 );
25956 assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
25957 assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
25958 assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
25959 assert_eq!(
25960 f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
25961 ":1\r\n"
25962 );
25963
25964 assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
25967 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
25968 assert_eq!(
25969 f.run(&[
25970 b"ZUNIONSTORE",
25971 z3,
25972 b"2",
25973 z1,
25974 z2,
25975 b"WEIGHTS",
25976 b"2",
25977 b"3",
25978 b"AGGREGATE",
25979 b"MAX"
25980 ]),
25981 ":3\r\n"
25982 );
25983 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
25984 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
25985 assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
25986 assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
25987 assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
25988
25989 let popped = format!(
25992 "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
25993 second.len()
25994 );
25995 assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
25996 f.run(&[b"ZADD", z2, b"3", b"b"]);
25997
25998 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
26001 assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
26002 f.run(&[b"SET", z3, b"v"]);
26003 assert_eq!(
26004 f.run(&[b"ZUNION", b"2", z1, z3]),
26005 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26006 );
26007 assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
26008
26009 assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
26013 assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
26014 assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
26015 }
26016
26017 #[test]
26019 fn a_range_store_across_stripes_copies_the_window() {
26020 let mut f = Fixture::striped(8);
26021 let other = apart(&mut f, "src");
26022 let third = apart(&mut f, &other);
26023 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
26024
26025 f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
26026 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
26027 assert_eq!(
26028 f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
26029 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
26030 );
26031 assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
26032
26033 assert_eq!(
26036 f.run(&[
26037 b"ZRANGESTORE",
26038 dst,
26039 src,
26040 b"+inf",
26041 b"-inf",
26042 b"BYSCORE",
26043 b"REV",
26044 b"LIMIT",
26045 b"0",
26046 b"2"
26047 ]),
26048 ":2\r\n"
26049 );
26050 assert_eq!(
26051 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
26052 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
26053 );
26054
26055 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
26058 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
26059 f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
26060 f.run(&[b"SET", plain, b"v"]);
26061 assert_eq!(
26062 f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
26063 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26064 );
26065 assert_eq!(
26066 f.run(&[b"ZCARD", dst]),
26067 ":3\r\n",
26068 "and left the destination"
26069 );
26070 }
26071
26072 #[test]
26079 fn the_list_group_answers_the_same_however_many_stripes_there_are() {
26080 let script: &[&[&[u8]]] = &[
26081 &[b"RPUSH", b"l1", b"a", b"b", b"c"],
26082 &[b"LPUSH", b"l1", b"z"],
26083 &[b"RPUSHX", b"l1", b"d"],
26084 &[b"LPUSHX", b"gone", b"x"],
26085 &[b"RPUSHX", b"gone", b"x"],
26086 &[b"LLEN", b"l1"],
26087 &[b"LLEN", b"gone"],
26088 &[b"LRANGE", b"l1", b"0", b"-1"],
26089 &[b"LRANGE", b"l1", b"1", b"2"],
26090 &[b"LRANGE", b"l1", b"5", b"9"],
26091 &[b"LINDEX", b"l1", b"0"],
26092 &[b"LINDEX", b"l1", b"-1"],
26093 &[b"LINDEX", b"l1", b"99"],
26094 &[b"LSET", b"l1", b"0", b"y"],
26095 &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
26096 &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
26097 &[b"LPOS", b"l1", b"b"],
26098 &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
26099 &[b"LPOS", b"l1", b"nothere"],
26100 &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
26101 &[b"LREM", b"l1", b"1", b"aa"],
26102 &[b"LTRIM", b"l1", b"0", b"3"],
26103 &[b"LRANGE", b"l1", b"0", b"-1"],
26104 &[b"LPOP", b"l1"],
26105 &[b"RPOP", b"l1"],
26106 &[b"LPOP", b"l1", b"2"],
26107 &[b"LPOP", b"gone"],
26108 &[b"LPOP", b"gone", b"2"],
26109 &[b"EXISTS", b"l1"],
26110 &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
26113 &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
26114 &[b"RPOPLPUSH", b"src", b"dst"],
26115 &[b"LRANGE", b"dst", b"0", b"-1"],
26116 &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
26117 &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
26118 &[
26119 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
26120 ],
26121 &[
26122 b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
26123 ],
26124 &[b"LRANGE", b"dst", b"0", b"-1"],
26125 &[
26126 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
26127 ],
26128 &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
26129 &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
26130 &[b"LMPOP", b"1", b"gone", b"LEFT"],
26131 &[b"RPUSH", b"q", b"a", b"b", b"c"],
26134 &[b"BLPOP", b"gone", b"q", b"0"],
26135 &[b"BRPOP", b"q", b"0"],
26136 &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
26137 &[b"RPUSH", b"q", b"x", b"y", b"z"],
26138 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
26139 &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
26140 &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
26141 &[b"BLPOP", b"q", b"0"],
26142 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
26143 &[b"SET", b"plain", b"v"],
26145 &[b"LPUSH", b"plain", b"a"],
26146 &[b"LLEN", b"plain"],
26147 &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
26148 &[b"LRANGE", b"dst", b"0", b"-1"],
26149 &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
26150 &[b"LSET", b"gone", b"0", b"v"],
26151 &[b"LSET", b"dst", b"99", b"v"],
26152 &[b"LPOP", b"dst", b"-1"],
26153 &[b"LMPOP", b"0", b"dst", b"LEFT"],
26154 &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
26155 ];
26156
26157 let mut one = Fixture::new();
26158 let mut many = Fixture::striped(8);
26159 for parts in script {
26160 let a = one.run(parts);
26161 let b = many.run(parts);
26162 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26163 }
26164 }
26165
26166 #[test]
26168 fn a_list_move_across_stripes_takes_the_elements_with_it() {
26169 let mut f = Fixture::striped(8);
26170 let other = apart(&mut f, "src");
26171 let third = apart(&mut f, &other);
26172 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
26173
26174 f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
26175 assert_eq!(
26176 f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
26177 "$1\r\na\r\n"
26178 );
26179 assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
26180 assert_eq!(
26181 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
26182 "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
26183 "one went on each end of the destination"
26184 );
26185 assert_eq!(
26186 f.run(&[b"LRANGE", src, b"0", b"-1"]),
26187 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
26188 );
26189
26190 assert_eq!(
26192 f.run(&[
26193 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
26194 ]),
26195 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
26196 );
26197 assert_eq!(
26198 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
26199 "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
26200 );
26201 assert_eq!(
26202 f.run(&[b"EXISTS", src]),
26203 ":0\r\n",
26204 "and the source is gone with its last element"
26205 );
26206
26207 f.run(&[b"RPUSH", src, b"e", b"f"]);
26210 assert_eq!(
26211 f.run(&[
26212 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
26213 ]),
26214 "*-1\r\n"
26215 );
26216 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
26217 assert_eq!(
26218 f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
26219 "$-1\r\n"
26220 );
26221 assert_eq!(
26222 f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
26223 "*-1\r\n"
26224 );
26225
26226 f.run(&[b"SET", plain, b"v"]);
26230 assert_eq!(
26231 f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
26232 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26233 );
26234 assert_eq!(
26235 f.run(&[b"LLEN", src]),
26236 ":2\r\n",
26237 "and left the source alone"
26238 );
26239 assert_eq!(
26240 f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
26241 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26242 );
26243 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
26244 }
26245
26246 #[test]
26252 fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
26253 let mut f = Fixture::striped(8);
26254 let other = apart(&mut f, "q");
26255 let (q, far) = (b"q".as_slice(), other.as_bytes());
26256
26257 assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
26258 assert_eq!(f.server.parked(), 1);
26259 f.run(&[b"RPUSH", far, b"v"]);
26260 let mut out = Out::new(Proto::Resp2);
26261 assert!(f.server.serve_waiter(7, 0, &mut out));
26262 let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
26263 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
26264 assert_eq!(
26265 f.run(&[b"EXISTS", far]),
26266 ":0\r\n",
26267 "and it took the element with it"
26268 );
26269
26270 f.server.forget_waiters(7);
26273 assert_eq!(
26274 f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
26275 Flow::Block
26276 );
26277 f.run(&[b"RPUSH", q, b"w"]);
26278 let mut out = Out::new(Proto::Resp2);
26279 assert!(f.server.serve_waiter(7, 0, &mut out));
26280 assert_eq!(
26281 core::str::from_utf8(out.as_slice()).expect("ascii"),
26282 "$1\r\nw\r\n"
26283 );
26284 assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
26285 }
26286
26287 #[test]
26293 fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
26294 let script: &[&[&[u8]]] = &[
26295 &[b"XADD", b"s", b"1-1", b"a", b"1"],
26296 &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
26297 &[b"XADD", b"s", b"3-1", b"d", b"4"],
26298 &[b"XADD", b"s", b"1-1", b"e", b"5"],
26299 &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
26300 &[b"XLEN", b"s"],
26301 &[b"XLEN", b"gone"],
26302 &[b"XRANGE", b"s", b"-", b"+"],
26303 &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
26304 &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
26305 &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
26306 &[b"XREVRANGE", b"s", b"+", b"-"],
26307 &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
26308 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
26309 &[b"XREAD", b"STREAMS", b"s", b"$"],
26310 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
26312 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
26313 &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
26314 &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
26315 &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
26316 &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
26317 &[
26318 b"XREADGROUP",
26319 b"GROUP",
26320 b"g",
26321 b"c1",
26322 b"COUNT",
26323 b"1",
26324 b"STREAMS",
26325 b"s",
26326 b"0",
26327 ],
26328 &[
26329 b"XREADGROUP",
26330 b"GROUP",
26331 b"nope",
26332 b"c1",
26333 b"STREAMS",
26334 b"s",
26335 b">",
26336 ],
26337 &[b"XPENDING", b"s", b"g"],
26338 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
26339 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
26340 &[b"XPENDING", b"s", b"nope"],
26341 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
26342 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
26343 &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
26344 &[b"XACK", b"s", b"g", b"1-1"],
26345 &[b"XACK", b"s", b"g", b"1-1"],
26346 &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
26347 &[b"XPENDING", b"s", b"g"],
26348 &[b"XINFO", b"STREAM", b"s"],
26349 &[b"XINFO", b"GROUPS", b"s"],
26350 &[b"XINFO", b"CONSUMERS", b"s", b"g"],
26351 &[b"XINFO", b"STREAM", b"gone"],
26352 &[b"XDEL", b"s", b"3-1"],
26354 &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
26355 &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
26356 &[b"XADD", b"s", b"9-1", b"z", b"9"],
26357 &[b"XTRIM", b"s", b"MAXLEN", b"1"],
26358 &[b"XTRIM", b"s", b"MINID", b"9"],
26359 &[b"XSETID", b"s", b"99-1"],
26360 &[b"XSETID", b"s", b"1-1"],
26361 &[b"XLEN", b"s"],
26362 &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
26363 &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
26364 &[b"XGROUP", b"DESTROY", b"s", b"g"],
26365 &[b"XGROUP", b"DESTROY", b"s", b"g"],
26366 &[b"SET", b"plain", b"v"],
26368 &[b"XADD", b"plain", b"1-1", b"a", b"1"],
26369 &[b"XLEN", b"plain"],
26370 &[b"XREAD", b"STREAMS", b"plain", b"0"],
26371 &[b"XRANGE", b"s", b"bogus", b"+"],
26372 &[b"XADD", b"s", b"1-1", b"a"],
26373 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
26374 &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
26375 ];
26376
26377 let mut one = Fixture::new();
26378 let mut many = Fixture::striped(8);
26379 for parts in script {
26380 let a = one.run(parts);
26381 let b = many.run(parts);
26382 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26383 }
26384 }
26385
26386 #[test]
26392 fn a_stream_read_across_stripes_reads_every_key() {
26393 let mut f = Fixture::striped(8);
26394 let other = apart(&mut f, "s1");
26395 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
26396
26397 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
26398 f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
26399 let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
26400 assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
26401 assert!(got.contains("1-1"), "the first one is in there: {got}");
26402 assert!(got.contains("2-1"), "and so is the second: {got}");
26403
26404 f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
26407 let got = f.run(&[
26408 b"XREADGROUP",
26409 b"GROUP",
26410 b"g",
26411 b"c",
26412 b"STREAMS",
26413 s1,
26414 s2,
26415 b">",
26416 b">",
26417 ]);
26418 assert!(got.starts_with("-NOGROUP"), "{got}");
26419 assert_eq!(
26420 f.run(&[b"XPENDING", s1, b"g"]),
26421 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
26422 "and read nothing from the key that did have the group"
26423 );
26424
26425 f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
26426 let got = f.run(&[
26427 b"XREADGROUP",
26428 b"GROUP",
26429 b"g",
26430 b"c",
26431 b"STREAMS",
26432 s1,
26433 s2,
26434 b">",
26435 b">",
26436 ]);
26437 assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
26438 }
26439
26440 #[test]
26442 fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
26443 let mut f = Fixture::striped(8);
26444 let other = apart(&mut f, "s1");
26445 let (s1, far) = (b"s1".as_slice(), other.as_bytes());
26446 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
26447 f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
26448
26449 assert_eq!(
26450 f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
26451 .0,
26452 Flow::Block
26453 );
26454 f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
26455 let mut out = Out::new(Proto::Resp2);
26456 assert!(f.server.serve_waiter(7, 0, &mut out));
26457 let want = format!(
26458 "*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",
26459 other.len()
26460 );
26461 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
26462 }
26463
26464 #[test]
26466 fn the_json_group_answers_the_same_however_many_stripes_there_are() {
26467 let script: &[&[&[u8]]] = &[
26468 &[
26469 b"JSON.SET",
26470 b"d",
26471 b"$",
26472 br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
26473 ],
26474 &[b"JSON.SET", b"d", b"$.a", b"2"],
26475 &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
26476 &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
26477 &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
26478 &[b"JSON.GET", b"d"],
26479 &[b"JSON.GET", b"d", b"$.b"],
26480 &[b"JSON.GET", b"gone", b"$"],
26481 &[b"JSON.TYPE", b"d", b"$.b"],
26482 &[b"JSON.TYPE", b"d", b"$.s"],
26483 &[b"JSON.TOGGLE", b"d", b"$.t"],
26484 &[b"JSON.ARRLEN", b"d", b"$.b"],
26485 &[b"JSON.OBJLEN", b"d", b"$"],
26486 &[b"JSON.OBJKEYS", b"d", b"$"],
26487 &[b"JSON.STRLEN", b"d", b"$.s"],
26488 &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
26489 &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
26490 &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
26491 &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
26492 &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
26493 &[b"JSON.ARRPOP", b"d", b"$.b"],
26494 &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
26495 &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
26496 &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
26497 &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
26498 &[b"JSON.RESP", b"d", b"$.b"],
26499 &[b"JSON.DEBUG", b"MEMORY", b"d"],
26500 &[b"JSON.CLEAR", b"d", b"$.b"],
26501 &[b"JSON.DEL", b"d", b"$.m"],
26502 &[b"JSON.FORGET", b"d", b"$.nothere"],
26503 &[
26505 b"JSON.MSET",
26506 b"m1",
26507 b"$",
26508 b"1",
26509 b"m2",
26510 b"$",
26511 b"2",
26512 b"m3",
26513 b"$",
26514 b"3",
26515 ],
26516 &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
26517 &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
26518 &[b"JSON.GET", b"m1", b"$"],
26519 &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
26520 &[b"JSON.GET", b"m2", b"$"],
26521 &[b"SET", b"plain", b"v"],
26523 &[b"JSON.GET", b"plain", b"$"],
26524 &[b"JSON.SET", b"plain", b"$", b"1"],
26525 &[b"JSON.MGET", b"m1", b"plain", b"$"],
26526 &[b"JSON.SET", b"d", b"$.b", b"["],
26527 &[b"JSON.DEL", b"plain"],
26528 ];
26529
26530 let mut one = Fixture::new();
26531 let mut many = Fixture::striped(8);
26532 for parts in script {
26533 let a = one.run(parts);
26534 let b = many.run(parts);
26535 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26536 }
26537 }
26538
26539 #[test]
26546 fn a_json_multi_write_across_stripes_reaches_every_key() {
26547 let mut f = Fixture::striped(8);
26548 let second = apart(&mut f, "m1");
26549 let third = apart(&mut f, &second);
26550 let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
26551
26552 assert_eq!(
26553 f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
26554 "+OK\r\n"
26555 );
26556 assert_eq!(
26557 f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
26558 "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
26559 );
26560
26561 assert_eq!(
26564 f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
26565 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
26566 );
26567 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
26568
26569 assert_eq!(
26573 f.run(&[
26574 b"JSON.MSET",
26575 m1,
26576 b"$",
26577 b"9",
26578 m2,
26579 b"$.deep",
26580 b"9",
26581 m3,
26582 b"$",
26583 b"7"
26584 ]),
26585 "$-1\r\n"
26586 );
26587 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
26588 assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
26589 assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
26590 }
26591
26592 #[test]
26594 fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
26595 let script: &[&[&[u8]]] = &[
26596 &[
26597 b"GEOADD",
26598 b"g",
26599 b"13.361389",
26600 b"38.115556",
26601 b"palermo",
26602 b"15.087269",
26603 b"37.502669",
26604 b"catania",
26605 ],
26606 &[
26607 b"GEOADD",
26608 b"g",
26609 b"NX",
26610 b"13.361389",
26611 b"38.115556",
26612 b"palermo",
26613 ],
26614 &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
26615 &[b"GEOPOS", b"g", b"palermo", b"nothere"],
26616 &[b"GEOHASH", b"g", b"palermo", b"catania"],
26617 &[b"GEODIST", b"g", b"palermo", b"catania"],
26618 &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
26619 &[b"GEODIST", b"g", b"palermo", b"nothere"],
26620 &[
26621 b"GEOSEARCH",
26622 b"g",
26623 b"FROMLONLAT",
26624 b"15",
26625 b"37",
26626 b"BYRADIUS",
26627 b"200",
26628 b"KM",
26629 b"ASC",
26630 b"WITHCOORD",
26631 b"WITHDIST",
26632 b"WITHHASH",
26633 ],
26634 &[
26635 b"GEOSEARCH",
26636 b"g",
26637 b"FROMMEMBER",
26638 b"palermo",
26639 b"BYBOX",
26640 b"400",
26641 b"400",
26642 b"KM",
26643 b"DESC",
26644 ],
26645 &[
26646 b"GEORADIUS",
26647 b"g",
26648 b"15",
26649 b"37",
26650 b"200",
26651 b"KM",
26652 b"COUNT",
26653 b"1",
26654 ],
26655 &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
26656 &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
26657 &[
26658 b"GEOSEARCHSTORE",
26659 b"dst",
26660 b"g",
26661 b"FROMLONLAT",
26662 b"15",
26663 b"37",
26664 b"BYRADIUS",
26665 b"200",
26666 b"KM",
26667 ],
26668 &[b"ZRANGE", b"dst", b"0", b"-1"],
26669 &[
26670 b"GEOSEARCHSTORE",
26671 b"dst",
26672 b"g",
26673 b"FROMLONLAT",
26674 b"15",
26675 b"37",
26676 b"BYRADIUS",
26677 b"1",
26678 b"M",
26679 b"STOREDIST",
26680 ],
26681 &[b"EXISTS", b"dst"],
26682 &[
26683 b"GEORADIUS",
26684 b"g",
26685 b"15",
26686 b"37",
26687 b"200",
26688 b"KM",
26689 b"STORE",
26690 b"dst",
26691 ],
26692 &[b"ZCARD", b"dst"],
26693 &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
26695 &[b"SET", b"plain", b"v"],
26696 &[b"GEOPOS", b"plain", b"a"],
26697 &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
26698 &[
26699 b"GEOSEARCHSTORE",
26700 b"dst",
26701 b"g",
26702 b"FROMLONLAT",
26703 b"15",
26704 b"37",
26705 b"BYRADIUS",
26706 b"200",
26707 b"KM",
26708 b"WITHCOORD",
26709 ],
26710 ];
26711
26712 let mut one = Fixture::new();
26713 let mut many = Fixture::striped(8);
26714 for parts in script {
26715 let a = one.run(parts);
26716 let b = many.run(parts);
26717 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26718 }
26719 }
26720
26721 #[test]
26723 fn a_geo_search_store_across_stripes_writes_what_it_found() {
26724 let mut f = Fixture::striped(8);
26725 let other = apart(&mut f, "g");
26726 let third = apart(&mut f, &other);
26727 let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
26728
26729 f.run(&[
26730 b"GEOADD",
26731 g,
26732 b"13.361389",
26733 b"38.115556",
26734 b"palermo",
26735 b"15.087269",
26736 b"37.502669",
26737 b"catania",
26738 ]);
26739 assert_eq!(
26740 f.run(&[
26741 b"GEOSEARCHSTORE",
26742 dst,
26743 g,
26744 b"FROMLONLAT",
26745 b"15",
26746 b"37",
26747 b"BYRADIUS",
26748 b"200",
26749 b"KM",
26750 b"ASC",
26751 ]),
26752 ":2\r\n"
26753 );
26754 assert_eq!(
26755 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
26756 "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
26757 "the geohash is the score, so the order is not the search order"
26758 );
26759 assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
26760
26761 assert_eq!(
26764 f.run(&[
26765 b"GEOSEARCHSTORE",
26766 dst,
26767 g,
26768 b"FROMMEMBER",
26769 b"palermo",
26770 b"BYRADIUS",
26771 b"200",
26772 b"KM",
26773 b"STOREDIST",
26774 ]),
26775 ":2\r\n"
26776 );
26777 assert_eq!(
26778 f.run(&[b"ZSCORE", dst, b"palermo"]),
26779 "$1\r\n0\r\n",
26780 "the centre is nought away from itself"
26781 );
26782
26783 assert_eq!(
26787 f.run(&[
26788 b"GEOSEARCHSTORE",
26789 dst,
26790 g,
26791 b"FROMLONLAT",
26792 b"0",
26793 b"0",
26794 b"BYRADIUS",
26795 b"1",
26796 b"M",
26797 ]),
26798 ":0\r\n"
26799 );
26800 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
26801 f.run(&[
26802 b"GEOSEARCHSTORE",
26803 dst,
26804 g,
26805 b"FROMLONLAT",
26806 b"15",
26807 b"37",
26808 b"BYRADIUS",
26809 b"200",
26810 b"KM",
26811 ]);
26812 f.run(&[b"SET", plain, b"v"]);
26813 assert_eq!(
26814 f.run(&[
26815 b"GEOSEARCHSTORE",
26816 dst,
26817 plain,
26818 b"FROMLONLAT",
26819 b"15",
26820 b"37",
26821 b"BYRADIUS",
26822 b"200",
26823 b"KM",
26824 ]),
26825 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26826 );
26827 assert_eq!(
26828 f.run(&[b"ZCARD", dst]),
26829 ":2\r\n",
26830 "and left the destination"
26831 );
26832 }
26833
26834 #[test]
26840 fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
26841 let script: &[&[&[u8]]] = &[
26842 &[
26843 b"TS.CREATE",
26844 b"ts:a",
26845 b"LABELS",
26846 b"sensor",
26847 b"a",
26848 b"room",
26849 b"1",
26850 ],
26851 &[b"TS.CREATE", b"ts:a"],
26852 &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
26853 &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
26854 &[
26855 b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
26856 ],
26857 &[
26858 b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
26859 ],
26860 &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
26861 &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
26862 &[b"TS.GET", b"ts:a"],
26863 &[b"TS.GET", b"gone"],
26864 &[b"TS.RANGE", b"ts:a", b"-", b"+"],
26865 &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
26866 &[
26867 b"TS.RANGE",
26868 b"ts:a",
26869 b"-",
26870 b"+",
26871 b"AGGREGATION",
26872 b"avg",
26873 b"2000",
26874 ],
26875 &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
26876 &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
26877 &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
26878 &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
26879 &[b"TS.READ", b"ts:a", b"0"],
26880 &[b"TS.READ", b"ts:a", b"+"],
26881 &[b"TS.QUERYINDEX", b"sensor=a"],
26883 &[b"TS.QUERYINDEX", b"room=1"],
26884 &[b"TS.QUERYINDEX", b"room=9"],
26885 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
26886 &[
26887 b"TS.QUERYLABELS",
26888 b"VALUES",
26889 b"sensor",
26890 b"FILTER",
26891 b"room=1",
26892 ],
26893 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
26894 &[
26895 b"TS.MGET",
26896 b"SELECTED_LABELS",
26897 b"sensor",
26898 b"FILTER",
26899 b"sensor=a",
26900 ],
26901 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
26902 &[
26903 b"TS.MREVRANGE",
26904 b"-",
26905 b"+",
26906 b"WITHLABELS",
26907 b"FILTER",
26908 b"sensor=a",
26909 ],
26910 &[
26911 b"TS.MRANGE",
26912 b"-",
26913 b"+",
26914 b"FILTER",
26915 b"room=1",
26916 b"GROUPBY",
26917 b"room",
26918 b"REDUCE",
26919 b"max",
26920 ],
26921 &[b"TS.INFO", b"ts:a"],
26922 &[
26924 b"TS.CREATERULE",
26925 b"ts:a",
26926 b"ts:down",
26927 b"AGGREGATION",
26928 b"avg",
26929 b"1000",
26930 ],
26931 &[b"TS.CREATE", b"ts:down"],
26932 &[
26933 b"TS.CREATERULE",
26934 b"ts:a",
26935 b"ts:down",
26936 b"AGGREGATION",
26937 b"avg",
26938 b"1000",
26939 ],
26940 &[b"TS.ADD", b"ts:a", b"5000", b"4"],
26941 &[b"TS.ADD", b"ts:a", b"6000", b"5"],
26942 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
26943 &[b"TS.GET", b"ts:down", b"LATEST"],
26944 &[b"TS.INFO", b"ts:down"],
26945 &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
26946 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
26947 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
26948 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
26949 &[b"TS.DEL", b"ts:a", b"0", b"1000"],
26950 &[b"SET", b"plain", b"v"],
26952 &[b"TS.ADD", b"plain", b"1", b"1"],
26953 &[b"TS.GET", b"plain"],
26954 &[b"TS.READ", b"plain", b"0"],
26955 &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
26956 &[b"TS.RANGE", b"gone", b"-", b"+"],
26957 &[b"TS.INFO", b"gone"],
26958 ];
26959
26960 let mut one = Fixture::new();
26961 let mut many = Fixture::striped(8);
26962 for parts in script {
26963 let a = one.run(parts);
26964 let b = many.run(parts);
26965 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26966 }
26967 }
26968
26969 #[test]
26979 fn a_compaction_rule_across_stripes_reaches_both_ends() {
26980 let mut many = Fixture::striped(8);
26981 let other = apart(&mut many, "src");
26982 let (src, dst) = (b"src".as_slice(), other.as_bytes());
26983 let mut one = Fixture::new();
26984 let mut both = |parts: &[&[u8]]| {
26985 let a = one.run(parts);
26986 let b = many.run(parts);
26987 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26988 a
26989 };
26990
26991 both(&[b"TS.CREATE", src]);
26992 both(&[b"TS.CREATE", dst]);
26993 assert_eq!(
26994 both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
26995 "+OK\r\n"
26996 );
26997 both(&[b"TS.ADD", src, b"1000", b"1"]);
26998 both(&[b"TS.ADD", src, b"1500", b"3"]);
26999 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
27002 let open = both(&[b"TS.GET", dst, b"LATEST"]);
27003 assert!(open.contains(":1000"), "the open bucket is folded: {open}");
27004
27005 both(&[b"TS.ADD", src, b"2000", b"5"]);
27008 let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
27009 assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
27010 assert!(got.contains(":1000"), "{got}");
27011
27012 both(&[b"TS.DEL", src, b"1000", b"1999"]);
27014 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
27015
27016 assert!(
27019 both(&[b"TS.INFO", dst]).contains("src"),
27020 "the source is named"
27021 );
27022 assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
27023 assert_eq!(
27024 both(&[b"TS.DELETERULE", src, dst]),
27025 "-ERR TSDB: compaction rule does not exist\r\n"
27026 );
27027 }
27028
27029 #[test]
27031 fn a_label_query_across_stripes_finds_every_series() {
27032 let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
27033 let mut many = Fixture::striped(8);
27034 let mut homes: Vec<usize> = names
27035 .iter()
27036 .map(|name| many.server.striped(0).stripe_of(name))
27037 .collect();
27038 homes.sort_unstable();
27039 homes.dedup();
27040 assert!(homes.len() > 1, "the six keys are not all on one stripe");
27041
27042 let mut one = Fixture::new();
27043 let mut both = |parts: &[&[u8]]| {
27044 let a = one.run(parts);
27045 let b = many.run(parts);
27046 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27047 a
27048 };
27049 for name in &names {
27050 both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
27051 both(&[b"TS.ADD", name, b"1000", b"1"]);
27052 }
27053
27054 let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
27055 assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
27056 assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
27057 assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
27058 assert_eq!(
27059 both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
27060 "*1\r\n$4\r\nroom\r\n"
27061 );
27062 }
27063
27064 #[test]
27071 fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
27072 let script: &[&[&[u8]]] = &[
27073 &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
27074 &[b"HMSET", b"h", b"c", b"3"],
27075 &[b"HSETNX", b"h", b"a", b"9"],
27076 &[b"HSETNX", b"h", b"d", b"4"],
27077 &[b"HGET", b"h", b"a"],
27078 &[b"HGET", b"h", b"nope"],
27079 &[b"HMGET", b"h", b"a", b"nope"],
27080 &[b"HLEN", b"h"],
27081 &[b"HEXISTS", b"h", b"a"],
27082 &[b"HSTRLEN", b"h", b"a"],
27083 &[b"HGETALL", b"h"],
27084 &[b"HKEYS", b"h"],
27085 &[b"HVALS", b"h"],
27086 &[b"HINCRBY", b"h", b"a", b"5"],
27087 &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
27088 &[b"HSCAN", b"h", b"0"],
27089 &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
27090 &[b"HSCAN", b"h", b"0", b"NOVALUES"],
27091 &[b"HDEL", b"h", b"d"],
27092 &[b"HSET", b"one", b"f", b"v"],
27093 &[b"HRANDFIELD", b"one"],
27094 &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
27095 &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
27097 &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
27098 &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
27099 &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
27100 &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
27101 &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
27102 &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
27103 &[b"HGET", b"h", b"b"],
27104 &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
27106 &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
27107 &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
27108 &[b"HGET", b"h", b"e"],
27109 &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
27111 &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
27112 &[b"HGETALL", b"imp"],
27113 &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
27114 &[b"HIMPORT", b"DISCARD", b"fs"],
27115 &[b"SET", b"plain", b"v"],
27117 &[b"HSET", b"plain", b"a", b"1"],
27118 &[b"HGETALL", b"plain"],
27119 &[b"HGET", b"gone", b"a"],
27120 &[b"HINCRBY", b"h", b"a", b"nan"],
27121 ];
27122
27123 let mut one = Fixture::new();
27124 let mut many = Fixture::striped(8);
27125 one.server.set_clock_ms(1_700_000_000_000);
27129 many.server.set_clock_ms(1_700_000_000_000);
27130 for parts in script {
27131 let a = one.run(parts);
27132 let b = many.run(parts);
27133 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27134 }
27135 }
27136
27137 #[test]
27139 fn the_array_group_answers_the_same_however_many_stripes_there_are() {
27140 let script: &[&[&[u8]]] = &[
27141 &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
27142 &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
27143 &[b"ARGET", b"a", b"1"],
27144 &[b"ARGET", b"a", b"99"],
27145 &[b"ARMGET", b"a", b"0", b"5", b"99"],
27146 &[b"ARGETRANGE", b"a", b"0", b"7"],
27147 &[b"ARLEN", b"a"],
27148 &[b"ARCOUNT", b"a"],
27149 &[b"ARINSERT", b"a", b"m", b"n"],
27150 &[b"ARSCAN", b"a", b"0", b"20"],
27151 &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
27152 &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
27153 &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
27154 &[b"ARLASTITEMS", b"a", b"2"],
27155 &[b"ARLASTITEMS", b"a", b"2", b"REV"],
27156 &[b"ARNEXT", b"a"],
27157 &[b"ARSEEK", b"a", b"3"],
27158 &[b"AROP", b"a", b"0", b"20", b"USED"],
27159 &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
27160 &[b"ARINFO", b"a"],
27161 &[b"ARINFO", b"a", b"FULL"],
27162 &[b"ARDEL", b"a", b"0"],
27163 &[b"ARDELRANGE", b"a", b"1", b"2"],
27164 &[b"ARCOUNT", b"a"],
27165 &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
27166 &[b"ARGETRANGE", b"r", b"0", b"9"],
27167 &[b"SET", b"plain", b"v"],
27169 &[b"ARGET", b"plain", b"0"],
27170 &[b"ARSET", b"plain", b"0", b"v"],
27171 &[b"ARGET", b"gone", b"0"],
27172 &[b"ARSET", b"a", b"bad", b"v"],
27173 ];
27174
27175 let mut one = Fixture::new();
27176 let mut many = Fixture::striped(8);
27177 for parts in script {
27178 let a = one.run(parts);
27179 let b = many.run(parts);
27180 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27181 }
27182 }
27183
27184 #[test]
27190 fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
27191 let script: &[&[&[u8]]] = &[
27192 &[b"G.NADD", b"g", b"n1", b"name", b"one"],
27193 &[b"G.NADD", b"g", b"n2", b"name", b"two"],
27194 &[b"G.NADD", b"g", b"n3"],
27195 &[b"G.NGET", b"g", b"n1"],
27196 &[b"G.NGET", b"g", b"gone"],
27197 &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
27198 &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
27199 &[b"G.OUT", b"g", b"n1", b"knows"],
27200 &[b"G.IN", b"g", b"n2", b"knows"],
27201 &[b"G.DEG", b"g", b"n1", b"knows"],
27202 &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
27203 &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
27204 &[b"G.PATH", b"g", b"n1", b"n3"],
27205 &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
27206 &[b"G.NDEL", b"g", b"n3"],
27207 &[b"G.NGET", b"g", b"n3"],
27208 &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
27210 &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
27211 &[b"VCARD", b"v"],
27212 &[b"VDIM", b"v"],
27213 &[b"VEMB", b"v", b"e1"],
27214 &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
27215 &[b"VSIM", b"v", b"ELE", b"e1"],
27216 &[b"VISMEMBER", b"v", b"e1"],
27217 &[b"VISMEMBER", b"v", b"gone"],
27218 &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
27219 &[b"VGETATTR", b"v", b"e1"],
27220 &[b"VRANGE", b"v", b"-", b"+"],
27221 &[b"VLINKS", b"v", b"e1"],
27222 &[b"VINFO", b"v"],
27223 &[b"VREM", b"v", b"e2"],
27224 &[b"VCARD", b"v"],
27225 &[b"SET", b"plain", b"v"],
27227 &[b"G.NGET", b"plain", b"n1"],
27228 &[b"VCARD", b"plain"],
27229 &[b"G.NADD", b"gone2", b"n"],
27230 &[b"VEMB", b"gone3", b"e"],
27231 ];
27232
27233 let mut one = Fixture::new();
27234 let mut many = Fixture::striped(8);
27235 for parts in script {
27236 let a = one.run(parts);
27237 let b = many.run(parts);
27238 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27239 }
27240 }
27241
27242 #[test]
27245 fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
27246 let script: &[&[&[u8]]] = &[
27247 &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
27249 &[b"BF.ADD", b"bf", b"a"],
27250 &[b"BF.ADD", b"bf", b"a"],
27251 &[b"BF.MADD", b"bf", b"b", b"c"],
27252 &[b"BF.EXISTS", b"bf", b"a"],
27253 &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
27254 &[b"BF.CARD", b"bf"],
27255 &[b"BF.INFO", b"bf"],
27256 &[b"BF.INFO", b"bf", b"CAPACITY"],
27257 &[b"BF.DEBUG", b"bf"],
27258 &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
27259 &[b"BF.EXISTS", b"made", b"x"],
27260 &[b"BF.SCANDUMP", b"bf", b"0"],
27261 &[b"CF.RESERVE", b"cf", b"100"],
27263 &[b"CF.ADD", b"cf", b"a"],
27264 &[b"CF.ADDNX", b"cf", b"a"],
27265 &[b"CF.COUNT", b"cf", b"a"],
27266 &[b"CF.EXISTS", b"cf", b"a"],
27267 &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
27268 &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
27269 &[b"CF.DEL", b"cf", b"a"],
27270 &[b"CF.COMPACT", b"cf"],
27271 &[b"CF.INFO", b"cf"],
27272 &[b"CF.DEBUG", b"cf"],
27273 &[b"CF.SCANDUMP", b"cf", b"0"],
27274 &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
27276 &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
27277 &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
27278 &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
27279 &[b"CMS.INFO", b"cms"],
27280 &[b"TOPK.RESERVE", b"tk", b"3"],
27282 &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
27283 &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
27284 &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
27285 &[b"TOPK.COUNT", b"tk", b"a", b"c"],
27286 &[b"TOPK.LIST", b"tk"],
27287 &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
27288 &[b"TOPK.INFO", b"tk"],
27289 &[b"TDIGEST.CREATE", b"td"],
27291 &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
27292 &[b"TDIGEST.MIN", b"td"],
27293 &[b"TDIGEST.MAX", b"td"],
27294 &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
27295 &[b"TDIGEST.CDF", b"td", b"3"],
27296 &[b"TDIGEST.RANK", b"td", b"3"],
27297 &[b"TDIGEST.REVRANK", b"td", b"3"],
27298 &[b"TDIGEST.BYRANK", b"td", b"0"],
27299 &[b"TDIGEST.BYREVRANK", b"td", b"0"],
27300 &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
27301 &[b"TDIGEST.INFO", b"td"],
27302 &[b"TDIGEST.RESET", b"td"],
27303 &[b"TDIGEST.MIN", b"td"],
27304 &[b"SET", b"plain", b"v"],
27306 &[b"BF.ADD", b"plain", b"a"],
27307 &[b"CF.ADD", b"plain", b"a"],
27308 &[b"CMS.QUERY", b"plain", b"a"],
27309 &[b"TOPK.ADD", b"plain", b"a"],
27310 &[b"TDIGEST.ADD", b"plain", b"1"],
27311 &[b"CMS.INFO", b"gone"],
27312 &[b"TOPK.INFO", b"gone"],
27313 &[b"TDIGEST.INFO", b"gone"],
27314 ];
27315
27316 let mut one = Fixture::new();
27317 let mut many = Fixture::striped(8);
27318 for parts in script {
27319 let a = one.run(parts);
27320 let b = many.run(parts);
27321 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27322 }
27323 }
27324
27325 #[test]
27332 fn a_sketch_merge_across_stripes_reads_every_source() {
27333 let mut many = Fixture::striped(8);
27334 let other = apart(&mut many, "s1");
27335 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
27336 let mut one = Fixture::new();
27337 let mut both = |parts: &[&[u8]]| {
27338 let a = one.run(parts);
27339 let b = many.run(parts);
27340 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27341 a
27342 };
27343
27344 for key in [b"cd".as_slice(), s1, s2] {
27348 both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
27349 }
27350 both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
27351 both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
27352 assert_eq!(
27353 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
27354 "+OK\r\n",
27355 "the merge took both sources"
27356 );
27357 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
27358 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
27360 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
27361 both(&[b"SET", b"plain", b"v"]);
27363 assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
27364 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
27365
27366 both(&[b"FLUSHALL"]);
27369 both(&[b"TDIGEST.CREATE", b"td"]);
27370 both(&[b"TDIGEST.CREATE", s1]);
27371 both(&[b"TDIGEST.CREATE", s2]);
27372 both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
27373 both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
27374 assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
27375 assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
27376 assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
27377 }
27378
27379 #[test]
27386 fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
27387 let script: &[&[&[u8]]] = &[
27388 &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
27389 &[b"SORT", b"l"],
27390 &[b"SORT", b"l", b"DESC"],
27391 &[b"SORT", b"l", b"ALPHA"],
27392 &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
27393 &[b"SORT_RO", b"l"],
27394 &[
27397 b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
27398 ],
27399 &[b"SORT", b"l", b"BY", b"w_*"],
27400 &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
27401 &[b"DEL", b"w_2"],
27402 &[b"SORT", b"l", b"BY", b"w_*"],
27403 &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
27406 &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
27407 &[b"HSET", b"h_1", b"f", b"9"],
27409 &[b"HSET", b"h_2", b"f", b"8"],
27410 &[b"HSET", b"h_3", b"f", b"7"],
27411 &[b"HSET", b"h_10", b"f", b"6"],
27412 &[b"SORT", b"l", b"BY", b"h_*->f"],
27413 &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
27414 &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
27416 &[b"LRANGE", b"out", b"0", b"-1"],
27417 &[b"SORT", b"l", b"STORE", b"l"],
27418 &[b"LRANGE", b"l", b"0", b"-1"],
27419 &[b"SORT", b"missing", b"STORE", b"out"],
27422 &[b"EXISTS", b"out"],
27423 &[b"SADD", b"s", b"c", b"a", b"b"],
27426 &[b"SORT", b"s", b"ALPHA"],
27427 &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
27428 &[b"LRANGE", b"out", b"0", b"-1"],
27429 &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
27430 &[b"SORT", b"z", b"BY", b"nosort"],
27431 &[b"SORT", b"z", b"ALPHA", b"DESC"],
27432 &[b"SET", b"str", b"v"],
27435 &[b"SORT", b"str"],
27436 &[b"RPUSH", b"words", b"one", b"two"],
27437 &[b"SORT", b"words"],
27438 &[b"SORT_RO", b"l", b"STORE", b"out"],
27439 ];
27440
27441 let mut one = Fixture::new();
27442 let mut many = Fixture::striped(8);
27443 for parts in script {
27444 let a = one.run(parts);
27445 let b = many.run(parts);
27446 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27447 }
27448 }
27449
27450 #[test]
27457 fn a_sort_across_stripes_reads_every_pattern_key() {
27458 let mut f = Fixture::striped(8);
27459 let out = apart(&mut f, "l");
27460 let (list, dest) = (b"l".as_slice(), out.as_bytes());
27461
27462 f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
27463 f.run(&[
27464 b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
27465 ]);
27466 f.run(&[
27467 b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
27468 ]);
27469
27470 let db = f.server.striped(0);
27473 let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
27474 .iter()
27475 .map(|k| db.stripe_of(k.as_slice()))
27476 .collect();
27477 assert!(
27478 weights.iter().any(|s| *s != weights[0]),
27479 "the four weight keys all landed on one stripe, so this proves nothing"
27480 );
27481
27482 assert_eq!(
27483 f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
27484 "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
27485 "the order came off the weights and the answer off the data keys"
27486 );
27487 assert_eq!(
27488 f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
27489 ":4\r\n"
27490 );
27491 assert_eq!(
27492 f.run(&[b"LRANGE", dest, b"0", b"-1"]),
27493 "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
27494 "the destination is on a stripe of its own and got the whole answer"
27495 );
27496 }
27497
27498 #[test]
27506 fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
27507 let mut f = Fixture::striped(8);
27508 let other = apart(&mut f, "h");
27509 let (first, second) = (b"h".as_slice(), other.as_bytes());
27510
27511 assert_eq!(
27512 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
27513 "+OK\r\n"
27514 );
27515 assert_eq!(
27516 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
27517 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
27518 "the read comes off one stripe and has to answer for all of them"
27519 );
27520 for key in [first, second] {
27521 f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
27522 assert_eq!(
27523 f.run(&[b"OBJECT", b"ENCODING", key]),
27524 "$8\r\nlistpack\r\n",
27525 "two fields is still under the ladder"
27526 );
27527 f.run(&[b"HSET", key, b"c", b"3"]);
27528 assert_eq!(
27529 f.run(&[b"OBJECT", b"ENCODING", key]),
27530 "$9\r\nhashtable\r\n",
27531 "three fields is over it, on whichever stripe the key is on"
27532 );
27533 }
27534
27535 assert_eq!(
27538 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
27539 "+OK\r\n"
27540 );
27541 let db = f.server.striped(0);
27542 assert!(
27543 (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
27544 "a stripe kept the old policy"
27545 );
27546 }
27547
27548 fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
27554 let search = f.server.search.lock();
27555 let index = search.named(name).expect("the index is there");
27556 (index.held.docs.len(), index.held.docs.last())
27557 }
27558
27559 #[test]
27562 fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
27563 let mut f = Fixture::new();
27564 f.run(&[
27565 b"FT.CREATE",
27566 b"ix",
27567 b"PREFIX",
27568 b"1",
27569 b"p:",
27570 b"SCHEMA",
27571 b"t",
27572 b"TEXT",
27573 ]);
27574 f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
27575 assert_eq!(held(&f, b"ix"), (1, 1));
27576 f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
27577 assert_eq!(held(&f, b"ix"), (1, 1));
27578
27579 f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
27582 f.run(&[b"HDEL", b"p:1", b"u"]);
27583 assert_eq!(held(&f, b"ix"), (1, 3));
27584 let search = f.server.search.lock();
27585 let index = search.named(b"ix").expect("there");
27586 assert_eq!(index.held.docs.id(b"p:1"), Some(3));
27587 }
27588
27589 #[test]
27592 fn a_fresh_index_reads_the_keys_that_were_already_there() {
27593 let mut f = Fixture::new();
27594 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27595 f.run(&[b"SET", b"p:str", b"not a hash"]);
27596 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
27597 f.run(&[
27598 b"FT.CREATE",
27599 b"ix",
27600 b"PREFIX",
27601 b"1",
27602 b"p:",
27603 b"SCHEMA",
27604 b"t",
27605 b"TEXT",
27606 ]);
27607
27608 assert_eq!(held(&f, b"ix"), (1, 1));
27609 let search = f.server.search.lock();
27610 let index = search.named(b"ix").expect("there");
27611 assert_eq!(index.trouble.whole().failures(), 0);
27612 }
27613
27614 #[test]
27617 fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
27618 let mut f = Fixture::new();
27619 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27620 f.run(&[
27621 b"FT.CREATE",
27622 b"ix",
27623 b"PREFIX",
27624 b"1",
27625 b"p:",
27626 b"SKIPINITIALSCAN",
27627 b"SCHEMA",
27628 b"t",
27629 b"TEXT",
27630 ]);
27631 assert_eq!(held(&f, b"ix"), (0, 0));
27632 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27633 assert_eq!(held(&f, b"ix"), (1, 1));
27634 }
27635
27636 #[test]
27644 fn only_a_real_change_gives_the_document_a_new_number() {
27645 let mut f = Fixture::new();
27646 f.run(&[
27647 b"FT.CREATE",
27648 b"ix",
27649 b"PREFIX",
27650 b"1",
27651 b"p:",
27652 b"SCHEMA",
27653 b"t",
27654 b"TEXT",
27655 ]);
27656 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27657 assert_eq!(held(&f, b"ix"), (1, 1));
27658
27659 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27660 assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
27661
27662 for quiet in [
27663 vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
27664 vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
27665 vec![b"HGET".as_slice(), b"p:1", b"t"],
27666 vec![b"HGETALL".as_slice(), b"p:1"],
27667 vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
27668 vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
27669 vec![
27670 b"HGETEX".as_slice(),
27671 b"p:1",
27672 b"EX",
27673 b"100",
27674 b"FIELDS",
27675 b"1",
27676 b"t",
27677 ],
27678 vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
27679 ] {
27680 f.run(&quiet);
27681 assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
27682 }
27683
27684 f.run(&[b"HSET", b"p:2", b"n", b"1"]);
27686 f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
27687 assert_eq!(held(&f, b"ix"), (2, 4));
27688 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
27694 assert_eq!(held(&f, b"ix"), (1, 5));
27695 }
27696
27697 #[test]
27703 fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
27704 fn refused(f: &Fixture, name: &[u8]) -> u64 {
27706 let search = f.server.search.lock();
27707 let index = search.named(name).expect("the index is there");
27708 index.trouble.whole().failures()
27709 }
27710
27711 let mut f = Fixture::new();
27712 f.run(&[
27713 b"FT.CREATE",
27714 b"ix",
27715 b"PREFIX",
27716 b"1",
27717 b"p:",
27718 b"SCHEMA",
27719 b"t",
27720 b"TEXT",
27721 ]);
27722 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27723 assert_eq!(held(&f, b"ix"), (1, 1));
27724 f.run(&[b"HDEL", b"p:1", b"t"]);
27725 assert_eq!(
27726 held(&f, b"ix"),
27727 (0, 1),
27728 "HDEL of the last field spends none"
27729 );
27730 assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
27731
27732 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
27733 assert_eq!(held(&f, b"ix"), (1, 2));
27734 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
27735 assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
27736 assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
27737
27738 f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
27739 assert_eq!(held(&f, b"ix"), (1, 4));
27740 f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
27741 assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
27742
27743 f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
27746 assert_eq!(held(&f, b"ix"), (1, 6));
27747 f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
27748 assert_eq!(held(&f, b"ix"), (0, 7));
27749 assert_eq!(refused(&f, b"ix"), 1);
27750 }
27751
27752 #[test]
27756 fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
27757 let mut f = Fixture::new();
27758 f.run(&[
27759 b"FT.CREATE",
27760 b"ix",
27761 b"PREFIX",
27762 b"1",
27763 b"p:",
27764 b"SCHEMA",
27765 b"t",
27766 b"TEXT",
27767 b"u",
27768 b"TEXT",
27769 ]);
27770 f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
27771 assert_eq!(held(&f, b"ix"), (1, 1));
27772 f.run(&[
27773 b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
27774 ]);
27775 assert_eq!(
27776 held(&f, b"ix"),
27777 (1, 3),
27778 "the key lived and the field did not"
27779 );
27780
27781 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
27783 assert_eq!(held(&f, b"ix"), (2, 4));
27784 f.run(&[
27785 b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
27786 ]);
27787 assert_eq!(held(&f, b"ix"), (1, 6));
27788 }
27789
27790 fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
27793 let search = f.server.search.lock();
27794 let index = search.named(name).expect("the index is there");
27795 index.held.docs.id(key)
27796 }
27797
27798 fn indexed() -> Fixture {
27801 let mut f = Fixture::new();
27802 f.run(&[
27803 b"FT.CREATE",
27804 b"ix",
27805 b"PREFIX",
27806 b"1",
27807 b"p:",
27808 b"SCHEMA",
27809 b"t",
27810 b"TEXT",
27811 ]);
27812 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27813 f
27814 }
27815
27816 #[test]
27819 fn a_key_a_keyspace_command_takes_away_loses_its_document() {
27820 for take in [
27821 vec![b"DEL".as_slice(), b"p:1"],
27822 vec![b"UNLINK".as_slice(), b"p:1"],
27823 vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
27824 vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
27825 ] {
27826 let mut f = indexed();
27827 assert_eq!(held(&f, b"ix"), (1, 1));
27828 f.run(&take);
27829 assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
27830 let search = f.server.search.lock();
27831 let index = search.named(b"ix").expect("the index is there");
27832 assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
27833 }
27834
27835 let mut f = indexed();
27837 f.run(&[b"EXPIRE", b"p:1", b"1000"]);
27838 assert_eq!(held(&f, b"ix"), (1, 1));
27839 f.run(&[b"PERSIST", b"p:1"]);
27840 assert_eq!(held(&f, b"ix"), (1, 1));
27841 }
27842
27843 #[test]
27847 fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
27848 let mut f = indexed();
27849 f.run(&[b"RENAME", b"p:1", b"p:2"]);
27850 assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
27851 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
27852 assert_eq!(number(&f, b"ix", b"p:1"), None);
27853
27854 f.run(&[b"RENAME", b"p:2", b"q:1"]);
27855 assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
27856
27857 f.run(&[b"RENAME", b"q:1", b"p:3"]);
27858 assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
27859 assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
27860
27861 f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
27864 assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
27865 assert_eq!(held(&f, b"ix"), (2, 3));
27866 f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
27867 assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
27868 }
27869
27870 #[test]
27873 fn a_rename_over_a_document_leaves_one_of_them() {
27874 let mut f = indexed();
27875 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
27876 assert_eq!(held(&f, b"ix"), (2, 2));
27877 f.run(&[b"RENAME", b"p:1", b"p:2"]);
27878 assert_eq!(held(&f, b"ix"), (1, 2));
27879 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
27880 }
27881
27882 #[test]
27886 fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
27887 let mut f = indexed();
27888 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
27889 f.run(&[b"COPY", b"q:1", b"p:2"]);
27890 assert_eq!(held(&f, b"ix"), (2, 2));
27891 assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
27892
27893 f.run(&[b"COPY", b"p:1", b"q:2"]);
27895 assert_eq!(held(&f, b"ix"), (2, 2));
27896
27897 f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
27899 assert_eq!(held(&f, b"ix"), (2, 3));
27900 assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
27901
27902 f.run(&[b"SET", b"s:1", b"plain"]);
27905 f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
27906 assert_eq!(held(&f, b"ix"), (1, 3));
27907 let dump = f.run(&[b"DUMP", b"q:1"]);
27908 assert!(dump.starts_with('$'), "{dump}");
27909 }
27910
27911 #[test]
27916 fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
27917 let mut f = indexed();
27918 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
27919 assert_eq!(held(&f, b"ix"), (2, 2));
27920 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
27923 assert_eq!(held(&f, b"ix"), (2, 3));
27924 f.run(&[b"DEL", b"p:2"]);
27927 assert_eq!(held(&f, b"ix"), (1, 3));
27928 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
27929 assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
27930
27931 let dump = f.run(&[b"DUMP", b"p:1"]);
27933 assert!(dump.starts_with('$'), "{dump}");
27934 f.run(&[b"SELECT", b"1"]);
27935 f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
27936 f.run(&[b"RENAME", b"q:1", b"p:3"]);
27937 assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
27938 }
27939
27940 #[test]
27943 fn a_move_leaves_the_document_where_it_is() {
27944 let mut f = indexed();
27945 f.run(&[b"MOVE", b"p:1", b"1"]);
27946 assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
27947 assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
27948
27949 f.run(&[b"SELECT", b"1"]);
27950 f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
27951 assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
27952 f.run(&[b"DEL", b"p:1"]);
27953 assert_eq!(held(&f, b"ix"), (0, 2));
27954 }
27955
27956 #[test]
27958 fn a_flush_drops_the_indexes() {
27959 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
27960 let mut f = indexed();
27961 f.run(&[flush]);
27962 assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
27963 assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
27964 }
27965
27966 let mut f = indexed();
27969 f.run(&[b"SELECT", b"9"]);
27970 f.run(&[b"FLUSHDB"]);
27971 assert!(f.server.search.lock().is_empty());
27972 }
27973
27974 fn tagged() -> Fixture {
27977 let mut f = Fixture::new();
27978 f.run(&[
27979 b"FT.CREATE",
27980 b"tv",
27981 b"PREFIX",
27982 b"1",
27983 b"tv:",
27984 b"SCHEMA",
27985 b"g",
27986 b"AS",
27987 b"gg",
27988 b"TAG",
27989 b"h",
27990 b"TAG",
27991 b"SEPARATOR",
27992 b"|",
27993 b"CASESENSITIVE",
27994 b"n",
27995 b"NUMERIC",
27996 ]);
27997 f.run(&[
27998 b"HSET",
27999 b"tv:1",
28000 b"g",
28001 b"Red, BLUE ",
28002 b"h",
28003 b"Aa|bB",
28004 b"n",
28005 b"1",
28006 ]);
28007 f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
28008 f
28009 }
28010
28011 #[test]
28015 fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
28016 let mut f = tagged();
28017 assert_eq!(
28018 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
28019 "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
28020 );
28021 assert_eq!(
28022 f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
28023 "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
28024 );
28025 }
28026
28027 #[test]
28030 fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
28031 let mut f = tagged();
28032 for (name, want) in [
28033 (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
28034 (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
28035 (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
28036 ] {
28037 assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
28038 }
28039 assert_eq!(
28040 f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
28041 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
28042 );
28043 }
28044
28045 #[test]
28048 fn asking_for_tag_values_counts_a_use_of_the_index() {
28049 let mut f = tagged();
28050 let uses = |f: &mut Fixture| {
28051 let reply = f.run(&[b"FT.INFO", b"tv"]);
28052 let at = reply.find("number_of_uses").expect("the field is reported");
28053 let value = reply[at..].split("\r\n").nth(1).unwrap();
28054 value.trim_start_matches(':').parse::<i64>().unwrap()
28055 };
28056 let before = uses(&mut f);
28057 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
28058 f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
28059 assert_eq!(uses(&mut f), before + 3);
28061 }
28062
28063 #[test]
28066 fn a_tag_field_with_nothing_in_it_answers_empty() {
28067 let mut f = Fixture::new();
28068 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
28069 assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
28070 }
28071
28072 #[test]
28075 fn a_dictionary_is_not_a_key() {
28076 let mut f = Fixture::new();
28077 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
28078 assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
28079 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
28080 assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
28081 }
28082
28083 #[test]
28086 fn a_dictionary_counts_the_terms_it_had_not_seen() {
28087 let mut f = Fixture::new();
28088 assert_eq!(
28089 f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
28090 ":3\r\n"
28091 );
28092 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
28093 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
28094 assert_eq!(
28095 f.run(&[b"FT.DICTDUMP", b"d"]),
28096 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
28097 );
28098 assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
28099 }
28100
28101 #[test]
28105 fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
28106 let mut f = Fixture::new();
28107 assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
28108 assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
28109 }
28110
28111 #[test]
28113 fn a_flush_drops_the_dictionaries() {
28114 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
28115 let mut f = Fixture::new();
28116 f.run(&[b"FT.DICTADD", b"d", b"a"]);
28117 f.run(&[flush]);
28118 assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
28119 }
28120 }
28121
28122 fn profiling() -> Fixture {
28127 let mut f = Fixture::new();
28128 f.run(&[
28129 b"FT.CREATE",
28130 b"ix",
28131 b"PREFIX",
28132 b"1",
28133 b"p:",
28134 b"SCHEMA",
28135 b"t",
28136 b"TEXT",
28137 b"n",
28138 b"NUMERIC",
28139 ]);
28140 f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
28141 f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
28142 f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
28143 f
28144 }
28145
28146 fn timeless(reply: &str) -> String {
28149 const KEYS: &[&str] = &[
28150 "+Total profile time",
28151 "+Parsing time",
28152 "+Workers queue time",
28153 "+Pipeline creation time",
28154 "+Time",
28155 ];
28156 let mut out = String::new();
28157 let mut parts = reply.split("\r\n").peekable();
28158 while let Some(part) = parts.next() {
28159 out.push_str(part);
28160 out.push_str("\r\n");
28161 if !KEYS.contains(&part) {
28162 continue;
28163 }
28164 match parts.next() {
28167 Some(head) if head.starts_with('$') => {
28168 parts.next();
28169 }
28170 _ => {}
28171 }
28172 out.push_str("<t>\r\n");
28173 }
28174 out.truncate(out.len() - 2);
28176 out
28177 }
28178
28179 #[test]
28182 fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
28183 let mut f = profiling();
28184 assert_eq!(
28185 timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
28186 "*2\r\n\
28187 *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\
28188 $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\
28189 *4\r\n+Shards\r\n*1\r\n*14\r\n\
28190 +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
28191 +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
28192 +Warning\r\n*1\r\n+None\r\n\
28193 +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
28194 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
28195 +Estimated number of matches\r\n:2\r\n\
28196 +Result processors profile\r\n*4\r\n\
28197 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28198 *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28199 *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28200 *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28201 +Coordinator\r\n*0\r\n"
28202 );
28203 let mut g = profiling();
28204 g.run(&[b"HELLO", b"3"]);
28205 let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
28206 assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
28207 assert!(
28208 three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
28209 "{three}"
28210 );
28211 assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
28212 assert!(
28213 three.contains(
28214 "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
28215 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
28216 +Estimated number of matches\r\n:2\r\n"
28217 ),
28218 "{three}"
28219 );
28220 }
28221
28222 #[test]
28225 fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
28226 let mut f = profiling();
28227 let tree = |f: &mut Fixture, query: &[u8]| {
28228 let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
28229 let at = reply.find("+Iterators profile").expect("a tree");
28230 let end = reply.find("+Result processors").expect("a list of steps");
28231 reply[at..end].to_string()
28232 };
28233 assert_eq!(
28234 tree(&mut f, b"alpha beta"),
28235 "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
28236 +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
28237 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
28238 +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
28239 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
28240 +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
28241 );
28242 assert!(tree(&mut f, b"alpha|beta").starts_with(
28243 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
28244 +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
28245 ));
28246 assert!(tree(&mut f, b"-alpha").starts_with(
28249 "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
28250 +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
28251 ));
28252 assert!(tree(&mut f, b"~alpha").starts_with(
28253 "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
28254 +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
28255 ));
28256 assert_eq!(
28258 tree(&mut f, b"*"),
28259 "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
28260 +Number of reading operations\r\n:3\r\n"
28261 );
28262 assert!(tree(&mut f, b"@n:[1 2]").starts_with(
28263 "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
28264 $19\r\n1.000000 - 2.000000\r\n"
28265 ));
28266 }
28267
28268 #[test]
28271 fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
28272 let mut f = profiling();
28273 f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
28274 let tree = |f: &mut Fixture, words: &[&[u8]]| {
28275 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
28276 argv.extend_from_slice(words);
28277 let reply = timeless(&f.run(&argv));
28278 let at = reply.find("+Iterators profile").expect("a tree");
28279 let end = reply.find("+Result processors").expect("a list of steps");
28280 reply[at..end].to_string()
28281 };
28282 assert_eq!(
28283 tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
28284 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
28285 +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
28286 +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
28287 +The number of iterators in the union is 2\r\n"
28288 );
28289 assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
28290 assert!(
28291 tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
28292 .contains("+Child iterators\r\n*2\r\n")
28293 );
28294 assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
28298 assert!(
28299 tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
28300 );
28301 }
28302
28303 #[test]
28306 fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
28307 let mut f = profiling();
28308 let steps = |f: &mut Fixture, words: &[&[u8]]| {
28309 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
28310 argv.extend_from_slice(words);
28311 let reply = timeless(&f.run(&argv));
28312 let at = reply.find("+Result processors").expect("a list of steps");
28313 let end = reply.find("+Coordinator").expect("an end");
28314 let mut out = Vec::new();
28315 let mut parts = reply[at..end].split("\r\n").peekable();
28316 while let Some(part) = parts.next() {
28317 if part == "+Type" {
28318 out.push(parts.next().unwrap_or_default().to_string());
28319 }
28320 }
28321 out
28322 };
28323 assert_eq!(
28324 steps(&mut f, &[]),
28325 ["+Index", "+Scorer", "+Sorter", "+Loader"]
28326 );
28327 assert_eq!(
28328 steps(&mut f, &[b"NOCONTENT"]),
28329 ["+Index", "+Scorer", "+Sorter"]
28330 );
28331 assert_eq!(
28334 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
28335 ["+Index", "+Counter"]
28336 );
28337 assert_eq!(
28340 steps(&mut f, &[b"SORTBY", b"n"]),
28341 ["+Index", "+Sorter", "+Loader"]
28342 );
28343 assert_eq!(
28344 steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
28345 ["+Index", "+Scorer", "+Sorter", "+Loader"]
28346 );
28347 assert_eq!(
28348 steps(&mut f, &[b"HIGHLIGHT"]),
28349 ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
28350 );
28351 assert_eq!(
28352 steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
28353 ["+Index", "+Scorer", "+Sorter"]
28354 );
28355 }
28356
28357 #[test]
28360 fn a_pipeline_names_every_step_after_what_it_runs() {
28361 let mut f = profiling();
28362 let steps = |f: &mut Fixture, words: &[&[u8]]| {
28363 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
28364 argv.extend_from_slice(words);
28365 let reply = timeless(&f.run(&argv));
28366 let at = reply.find("+Result processors").expect("a list of steps");
28367 let end = reply.find("+Coordinator").expect("an end");
28368 let mut out = Vec::new();
28369 let mut parts = reply[at..end].split("\r\n").peekable();
28370 while let Some(part) = parts.next() {
28371 if part == "+Type" {
28372 out.push(parts.next().unwrap_or_default().to_string());
28373 }
28374 }
28375 out
28376 };
28377 assert_eq!(steps(&mut f, &[]), ["+Index"]);
28378 assert_eq!(
28379 steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
28380 ["+Index", "+Projector - Literal 1"]
28381 );
28382 assert_eq!(
28383 steps(
28384 &mut f,
28385 &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
28386 ),
28387 ["+Index", "+Loader", "+Projector - Operator *"]
28388 );
28389 assert_eq!(
28390 steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
28391 ["+Index", "+Loader", "+Filter - Predicate >"]
28392 );
28393 assert_eq!(
28394 steps(
28395 &mut f,
28396 &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
28397 ),
28398 ["+Index", "+Loader", "+Grouper"]
28399 );
28400 assert_eq!(
28401 steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
28402 ["+Index", "+Loader", "+Sorter"]
28403 );
28404 assert_eq!(
28405 steps(&mut f, &[b"LIMIT", b"0", b"2"]),
28406 ["+Index", "+Pager/Limiter"]
28407 );
28408 assert_eq!(
28411 steps(
28412 &mut f,
28413 &[
28414 b"ADDSCORES",
28415 b"LOAD",
28416 b"1",
28417 b"@n",
28418 b"APPLY",
28419 b"@__score",
28420 b"AS",
28421 b"s"
28422 ]
28423 ),
28424 [
28425 "+Index",
28426 "+Scorer",
28427 "+Loader",
28428 "+Projector - Property __score"
28429 ]
28430 );
28431 }
28432
28433 #[test]
28441 fn a_sortable_field_is_read_without_the_key_being_opened() {
28442 let mut f = Fixture::new();
28443 f.run(&[
28444 b"FT.CREATE",
28445 b"sx",
28446 b"PREFIX",
28447 b"1",
28448 b"s:",
28449 b"SCHEMA",
28450 b"n",
28451 b"NUMERIC",
28452 b"SORTABLE",
28453 b"g",
28454 b"TAG",
28455 ]);
28456 f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
28457 f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
28458 let loads = |f: &mut Fixture, words: &[&[u8]]| {
28459 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
28460 argv.extend_from_slice(words);
28461 f.run(&argv).contains("+Loader")
28462 };
28463 assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
28464 assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
28465 assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
28466 assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
28467 assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
28468 assert!(loads(
28469 &mut f,
28470 &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
28471 ));
28472 assert!(loads(&mut f, &[b"LOAD", b"*"]));
28473 }
28474
28475 #[test]
28478 fn a_profile_checks_its_own_words_before_it_runs_anything() {
28479 let mut f = profiling();
28480 assert_eq!(
28481 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
28482 "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
28483 );
28484 assert_eq!(
28485 f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
28486 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
28487 );
28488 assert_eq!(
28491 f.run(&[
28492 b"FT.PROFILE",
28493 b"ix",
28494 b"LIMITED",
28495 b"SEARCH",
28496 b"QUERY",
28497 b"alpha"
28498 ]),
28499 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
28500 );
28501 assert_eq!(
28502 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
28503 "-The QUERY keyword is expected\r\n"
28504 );
28505 assert_eq!(
28506 f.run(&[
28507 b"FT.PROFILE",
28508 b"ix",
28509 b"AGGREGATE",
28510 b"QUERY",
28511 b"alpha",
28512 b"WITHCURSOR"
28513 ]),
28514 "-FT.PROFILE does not support cursor\r\n"
28515 );
28516 assert_eq!(
28519 f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
28520 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
28521 );
28522 assert_eq!(
28523 f.run(&[
28524 b"FT.PROFILE",
28525 b"ix",
28526 b"SEARCH",
28527 b"QUERY",
28528 b"alpha",
28529 b"extra"
28530 ]),
28531 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
28532 );
28533 }
28534
28535 #[test]
28537 fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
28538 let mut f = profiling();
28539 let one = f.run(&[
28540 b"FT.PROFILE",
28541 b"ix",
28542 b"search",
28543 b"limited",
28544 b"query",
28545 b"alpha",
28546 ]);
28547 let two = f.run(&[
28548 b"FT.PROFILE",
28549 b"ix",
28550 b"SEARCH",
28551 b"LIMITED",
28552 b"QUERY",
28553 b"alpha",
28554 ]);
28555 assert_eq!(timeless(&one), timeless(&two));
28556 }
28557
28558 #[test]
28563 fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
28564 let mut f = profiling();
28565 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
28566 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28567
28568 let mut f = profiling();
28569 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28570 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28571
28572 let mut f = profiling();
28573 assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
28574 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28575
28576 let mut f = profiling();
28577 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
28578 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28579 }
28580
28581 #[test]
28584 fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
28585 let mut f = profiling();
28586 let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
28587 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
28588 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
28589 assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
28591 }
28592
28593 #[test]
28596 fn dropping_the_documents_leaves_a_key_the_index_never_read() {
28597 let mut f = profiling();
28598 f.run(&[b"SET", b"p:4", b"alpha"]);
28599 f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
28600 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28601 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28602 assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
28603 }
28604
28605 #[test]
28608 fn another_index_over_the_same_keys_loses_the_documents_too() {
28609 let mut f = profiling();
28610 f.run(&[
28611 b"FT.CREATE",
28612 b"other",
28613 b"PREFIX",
28614 b"1",
28615 b"p:",
28616 b"SCHEMA",
28617 b"t",
28618 b"TEXT",
28619 ]);
28620 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28621 assert_eq!(
28622 f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
28623 "*1\r\n:0\r\n"
28624 );
28625 }
28626
28627 #[test]
28630 fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
28631 let mut f = profiling();
28632 assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
28633 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28634 assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
28635 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28636 }
28637
28638 #[test]
28643 fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
28644 let mut f = Fixture::new();
28645 assert_eq!(
28646 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28647 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28648 );
28649 assert_eq!(
28650 f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
28651 "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
28652 );
28653 let mut g = Fixture::new();
28654 g.run(&[b"HELLO", b"3"]);
28655 assert_eq!(
28656 g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28657 "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28658 );
28659 assert_eq!(
28660 g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
28661 "%1\r\n+EXTLOAD\r\n_\r\n"
28662 );
28663 }
28664
28665 #[test]
28668 fn a_help_row_carries_the_description_and_the_value_together() {
28669 let mut f = Fixture::new();
28670 assert_eq!(
28671 f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
28672 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
28673 +Value\r\n$3\r\n500\r\n"
28674 );
28675 let mut g = Fixture::new();
28676 g.run(&[b"HELLO", b"3"]);
28677 assert_eq!(
28678 g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
28679 "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
28680 +Value\r\n$3\r\n500\r\n"
28681 );
28682 }
28683
28684 #[test]
28687 fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
28688 let mut f = Fixture::new();
28689 assert_eq!(
28690 f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
28691 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28692 );
28693 for name in [
28694 b"TIMEOUT*".as_slice(),
28695 b"?IMEOUT",
28696 b"*TIMEOUT*",
28697 b"TIME",
28698 b"NOSUCH",
28699 b"",
28700 ] {
28701 assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
28702 }
28703 assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
28704 assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
28705 }
28706
28707 #[test]
28710 fn a_read_ignores_whatever_follows_the_name() {
28711 let mut f = Fixture::new();
28712 assert_eq!(
28713 f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
28714 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28715 );
28716 assert_eq!(
28717 f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
28718 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
28719 +Value\r\n$3\r\n500\r\n"
28720 );
28721 }
28722
28723 #[test]
28726 fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
28727 let mut f = Fixture::new();
28728 assert_eq!(
28729 f.run(&[b"FT.CONFIG"]),
28730 "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
28731 );
28732 for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
28733 let want = format!(
28734 "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
28735 String::from_utf8_lossy(sub)
28736 );
28737 assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
28738 }
28739 assert_eq!(
28740 f.run(&[b"ft.config", b"get"]),
28741 "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
28742 );
28743 assert_eq!(
28744 f.run(&[b"FT.CONFIG", b"bogus"]),
28745 "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
28746 );
28747 }
28748
28749 #[test]
28752 fn a_write_checks_the_name_then_the_setting_then_the_value() {
28753 let mut f = Fixture::new();
28754 for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
28755 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
28756 cmd.extend(tail);
28757 assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
28758 }
28759 for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
28760 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
28761 cmd.extend(tail);
28762 assert_eq!(
28763 f.run(&cmd),
28764 "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
28765 );
28766 }
28767 assert_eq!(
28768 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
28769 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
28770 );
28771 }
28772
28773 #[test]
28776 fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
28777 let mut f = Fixture::new();
28778 assert_eq!(
28779 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
28780 "+OK\r\n"
28781 );
28782 assert_eq!(
28783 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
28784 "+EXCESSARGS\r\n"
28785 );
28786 assert_eq!(
28787 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28788 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
28789 );
28790 }
28791
28792 #[test]
28795 fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
28796 let mut f = Fixture::new();
28797 for (given, want) in [
28798 (b"0x10".as_slice(), "16"),
28799 (b"0X1f", "31"),
28800 (b"+0x10", "16"),
28801 (b"+5", "5"),
28802 (b"010", "10"),
28803 (b"08", "8"),
28804 (b"0777", "777"),
28805 (b"1e3", "1000"),
28806 (b"0.0", "0"),
28807 (b"-0.0", "0"),
28808 ] {
28809 assert_eq!(
28810 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28811 "+OK\r\n",
28812 "{given:?}"
28813 );
28814 let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
28815 assert_eq!(
28816 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28817 want,
28818 "{given:?}"
28819 );
28820 }
28821 for given in [
28822 b" 5".as_slice(),
28823 b"5 ",
28824 b"1.5",
28825 b"1e-3",
28826 b"x",
28827 b"",
28828 b"0b11",
28829 b"0xg",
28830 b"nan",
28831 b"inf",
28832 b"1e100",
28833 b"99999999999999999999",
28834 ] {
28835 assert_eq!(
28836 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28837 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
28838 "{given:?}"
28839 );
28840 }
28841 }
28842
28843 #[test]
28846 fn a_negative_is_answered_by_whichever_reader_found_it() {
28847 let mut f = Fixture::new();
28848 for given in [b"-1".as_slice(), b"-16"] {
28849 assert_eq!(
28850 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28851 "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
28852 "{given:?}"
28853 );
28854 }
28855 for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
28856 assert_eq!(
28857 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28858 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
28859 "{given:?}"
28860 );
28861 }
28862 let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
28863 for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
28864 assert_eq!(
28865 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
28866 "+OK\r\n",
28867 "{given:?}"
28868 );
28869 assert_eq!(
28870 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
28871 unlimited,
28872 "{given:?}"
28873 );
28874 }
28875 }
28876
28877 #[test]
28880 fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
28881 let mut f = Fixture::new();
28882 for (given, want) in [
28883 (b"2147483647".as_slice(), "2147483647"),
28884 (b"2147483648", "unlimited"),
28885 (b"4294967295", "unlimited"),
28886 (b"9223372036854775806", "unlimited"),
28887 (b"0", "0"),
28888 ] {
28889 assert_eq!(
28890 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
28891 "+OK\r\n",
28892 "{given:?}"
28893 );
28894 let want = format!(
28895 "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
28896 want.len()
28897 );
28898 assert_eq!(
28899 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
28900 want,
28901 "{given:?}"
28902 );
28903 }
28904 }
28905
28906 #[test]
28909 fn a_number_out_of_range_names_the_limit_it_crossed() {
28910 let mut f = Fixture::new();
28911 let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
28912 for (name, given) in [
28913 (b"MINPREFIX".as_slice(), b"0".as_slice()),
28914 (b"MAX_AGGREGATE_GROUPS", b"0"),
28915 (b"BM25STD_TANH_FACTOR", b"0"),
28916 (b"DEFAULT_DIALECT", b"0"),
28917 (b"MINSTEMLEN", b"4294967296"),
28918 (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
28919 (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
28920 (b"CONNECT_TIMEOUT", b"2147483648"),
28921 ] {
28922 assert_eq!(
28923 f.run(&[b"FT.CONFIG", b"SET", name, given]),
28924 bounds,
28925 "{name:?}"
28926 );
28927 }
28928 for (name, given, want) in [
28929 (
28930 b"MINSTEMLEN".as_slice(),
28931 b"1".as_slice(),
28932 "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
28933 ),
28934 (
28935 b"MAX_AGGREGATE_GROUPS",
28936 b"67108865",
28937 "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
28938 ),
28939 (
28940 b"WORKERS",
28941 b"17",
28942 "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
28943 ),
28944 (
28945 b"_NUMERIC_RANGES_PARENTS",
28946 b"3",
28947 "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
28948 depth for balance\r\n",
28949 ),
28950 (
28951 b"DEFAULT_DIALECT",
28952 b"5",
28953 "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
28954 ),
28955 (
28956 b"_BG_INDEX_MEM_PCT_THR",
28957 b"101",
28958 "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
28959 100%\r\n",
28960 ),
28961 (
28962 b"BM25STD_TANH_FACTOR",
28963 b"10001",
28964 "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
28965 inclusive\r\n",
28966 ),
28967 (
28968 b"BG_INDEX_SLEEP_DURATION_US",
28969 b"1000000",
28970 "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
28971 999999 (usleep POSIX limit)\r\n",
28972 ),
28973 ] {
28974 assert_eq!(
28975 f.run(&[b"FT.CONFIG", b"SET", name, given]),
28976 want,
28977 "{name:?}"
28978 );
28979 }
28980 }
28981
28982 #[test]
28985 fn the_trimming_delays_are_checked_against_one_another() {
28986 let mut f = Fixture::new();
28987 assert_eq!(
28988 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
28989 "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
28990 _MAX_TRIM_DELAY_MS (5000)\r\n"
28991 );
28992 assert_eq!(
28993 f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
28994 "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
28995 _MIN_TRIM_DELAY_MS (2000)\r\n"
28996 );
28997 assert_eq!(
28998 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
28999 "+OK\r\n"
29000 );
29001 }
29002
29003 #[test]
29006 fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
29007 let mut f = Fixture::new();
29008 assert_eq!(
29009 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
29010 "+OK\r\n"
29011 );
29012 assert_eq!(
29013 f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
29014 "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
29015 );
29016 assert_eq!(
29017 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
29018 "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
29019 );
29020 assert_eq!(
29021 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
29022 "+OK\r\n"
29023 );
29024 assert_eq!(
29025 f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
29026 "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
29027 );
29028 assert_eq!(
29029 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
29030 "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
29031 );
29032 let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
29033 for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
29034 assert_eq!(
29035 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
29036 bad,
29037 "{given:?}"
29038 );
29039 }
29040 assert_eq!(
29041 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
29042 "+OK\r\n"
29043 );
29044 }
29045
29046 #[test]
29049 fn a_yes_or_no_setting_takes_those_two_words_only() {
29050 let mut f = Fixture::new();
29051 assert_eq!(
29052 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
29053 "+OK\r\n"
29054 );
29055 assert_eq!(
29056 f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
29057 "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
29058 );
29059 for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
29060 assert_eq!(
29061 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
29062 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
29063 "{given:?}"
29064 );
29065 }
29066 }
29067
29068 #[test]
29071 fn two_names_for_one_setting_move_together() {
29072 let mut f = Fixture::new();
29073 f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
29074 assert_eq!(
29075 f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
29076 "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
29077 );
29078 f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
29079 assert_eq!(
29080 f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
29081 "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
29082 );
29083 let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
29084 let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
29085 f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
29086 assert_eq!(
29087 f.run(&[b"FT.CONFIG", b"GET", short]),
29088 "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
29089 );
29090 assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
29091 assert_eq!(
29092 f.run(&[b"FT.CONFIG", b"GET", long]),
29093 "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
29094 );
29095 }
29096
29097 #[test]
29099 fn a_password_reads_back_as_stars_whatever_was_written() {
29100 let mut f = Fixture::new();
29101 assert_eq!(
29102 f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
29103 "+OK\r\n"
29104 );
29105 assert_eq!(
29106 f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
29107 "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
29108 );
29109 }
29110
29111 #[test]
29114 fn a_flush_leaves_the_settings_alone() {
29115 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
29116 let mut f = Fixture::new();
29117 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
29118 f.run(&[flush]);
29119 assert_eq!(
29120 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
29121 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
29122 "{flush:?}"
29123 );
29124 }
29125 }
29126
29127 fn debugging() -> Fixture {
29132 let mut f = Fixture::new();
29133 f.run(&[
29134 b"FT.CREATE",
29135 b"dx",
29136 b"PREFIX",
29137 b"1",
29138 b"d:",
29139 b"SCHEMA",
29140 b"t",
29141 b"TEXT",
29142 b"g",
29143 b"TAG",
29144 b"n",
29145 b"NUMERIC",
29146 b"s",
29147 b"TEXT",
29148 b"SORTABLE",
29149 ]);
29150 f.run(&[
29151 b"HSET",
29152 b"d:1",
29153 b"t",
29154 b"running dogs",
29155 b"g",
29156 b"red,blue",
29157 b"n",
29158 b"1",
29159 b"s",
29160 b"Alpha",
29161 ]);
29162 f.run(&[
29163 b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
29164 ]);
29165 f.run(&[
29166 b"HSET",
29167 b"d:3",
29168 b"t",
29169 b"dogs alpha",
29170 b"g",
29171 b"green",
29172 b"n",
29173 b"3",
29174 ]);
29175 f
29176 }
29177
29178 #[test]
29181 fn a_term_dump_lists_the_stems_beside_the_words() {
29182 let mut f = debugging();
29183 assert_eq!(
29184 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
29185 "*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\
29186 $4\r\ndogs\r\n$7\r\nrunning\r\n"
29187 );
29188 }
29189
29190 #[test]
29193 fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
29194 let mut f = debugging();
29195 assert_eq!(
29196 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29197 "*2\r\n:1\r\n:2\r\n"
29198 );
29199 assert_eq!(
29200 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
29201 "*2\r\n:1\r\n:2\r\n"
29202 );
29203 for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
29204 assert_eq!(
29205 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
29206 "-Can not find the inverted index\r\n",
29207 "{term:?}"
29208 );
29209 }
29210 }
29211
29212 #[test]
29215 fn a_tag_dump_pairs_every_value_with_its_documents() {
29216 let mut f = debugging();
29217 assert_eq!(
29218 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
29219 "*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\
29220 *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
29221 );
29222 }
29223
29224 #[test]
29227 fn a_number_dump_answers_a_single_range() {
29228 let mut f = debugging();
29229 assert_eq!(
29230 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
29231 "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
29232 );
29233 }
29234
29235 #[test]
29238 fn a_geo_field_is_dumped_as_a_numeric_one() {
29239 let mut f = Fixture::new();
29240 f.run(&[
29241 b"FT.CREATE",
29242 b"gx",
29243 b"PREFIX",
29244 b"1",
29245 b"q:",
29246 b"SCHEMA",
29247 b"loc",
29248 b"GEO",
29249 b"gg",
29250 b"AS",
29251 b"tag",
29252 b"TAG",
29253 ]);
29254 f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
29255 f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
29256 assert_eq!(
29257 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
29258 "*1\r\n*2\r\n:1\r\n:2\r\n"
29259 );
29260 assert_eq!(
29261 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
29262 "-Could not find given field in index spec\r\n"
29263 );
29264 }
29265
29266 #[test]
29269 fn a_dump_takes_the_attribute_and_not_the_identifier() {
29270 let mut f = Fixture::new();
29271 f.run(&[
29272 b"FT.CREATE",
29273 b"zx",
29274 b"PREFIX",
29275 b"1",
29276 b"z:",
29277 b"SCHEMA",
29278 b"gg",
29279 b"AS",
29280 b"tag",
29281 b"TAG",
29282 ]);
29283 f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
29284 assert_eq!(
29285 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
29286 "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
29287 );
29288 assert_eq!(
29289 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
29290 "-Could not find given field in index spec\r\n"
29291 );
29292 }
29293
29294 #[test]
29297 fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
29298 let mut f = debugging();
29299 assert_eq!(
29300 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
29301 "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
29302 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
29303 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
29304 +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\
29305 $5\r\nvalue\r\n$5\r\nalpha\r\n"
29306 );
29307 let mut g = debugging();
29308 g.run(&[b"HELLO", b"3"]);
29309 assert_eq!(
29310 g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
29311 "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
29312 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
29313 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
29314 +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\
29315 $5\r\nvalue\r\n$5\r\nalpha\r\n"
29316 );
29317 }
29318
29319 #[test]
29322 fn a_document_with_no_sortable_value_drops_the_key() {
29323 let mut f = debugging();
29324 assert_eq!(
29325 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
29326 "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
29327 +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"
29328 );
29329 }
29330
29331 #[test]
29334 fn the_flag_word_spells_out_the_bits_it_carries() {
29335 let mut f = Fixture::new();
29336 f.run(&[
29337 b"FT.CREATE",
29338 b"nx",
29339 b"NOOFFSETS",
29340 b"PREFIX",
29341 b"1",
29342 b"o:",
29343 b"SCHEMA",
29344 b"t",
29345 b"TEXT",
29346 ]);
29347 f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
29348 assert!(
29349 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
29350 .contains("$6\r\n(0x0):\r\n")
29351 );
29352 }
29353
29354 #[test]
29357 fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
29358 let mut f = debugging();
29359 assert!(
29360 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
29361 .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
29362 );
29363 }
29364
29365 #[test]
29368 fn a_document_row_reads_its_keyword_at_a_fixed_place() {
29369 let mut f = debugging();
29370 let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
29371 assert_eq!(
29372 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
29373 want
29374 );
29375 assert_eq!(
29376 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
29377 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
29378 );
29379 assert_eq!(
29380 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
29381 "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
29382 );
29383 }
29384
29385 #[test]
29388 fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
29389 let mut f = debugging();
29390 assert_eq!(
29391 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
29392 "-Document not found in index\r\n"
29393 );
29394 assert_eq!(
29395 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
29396 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
29397 );
29398 }
29399
29400 #[test]
29403 fn a_document_number_goes_both_ways() {
29404 let mut f = debugging();
29405 assert_eq!(
29406 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
29407 "$3\r\nd:2\r\n"
29408 );
29409 assert_eq!(
29410 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
29411 ":2\r\n"
29412 );
29413 assert_eq!(
29414 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
29415 ":0\r\n"
29416 );
29417 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
29418 for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
29419 assert_eq!(
29420 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
29421 "-document was removed\r\n",
29422 "{id:?}"
29423 );
29424 }
29425 }
29426
29427 #[test]
29430 fn a_document_number_is_read_the_strict_way() {
29431 let mut f = debugging();
29432 for id in [
29433 b"x".as_slice(),
29434 b"1.5",
29435 b" 1",
29436 b"+1",
29437 b"01",
29438 b"0x1",
29439 b"",
29440 b"9223372036854775808",
29441 b"18446744073709551615",
29442 ] {
29443 assert_eq!(
29444 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
29445 "-bad id given\r\n",
29446 "{id:?}"
29447 );
29448 }
29449 }
29450
29451 #[test]
29454 fn a_dump_keeps_a_number_the_table_has_given_up() {
29455 let mut f = debugging();
29456 f.run(&[b"DEL", b"d:2"]);
29457 assert_eq!(
29458 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29459 "*2\r\n:1\r\n:2\r\n"
29460 );
29461 assert_eq!(
29462 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
29463 "-document was removed\r\n"
29464 );
29465 assert_eq!(
29466 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
29467 ":0\r\n"
29468 );
29469 }
29470
29471 #[test]
29474 fn a_rewrite_takes_a_number_of_its_own() {
29475 let mut f = debugging();
29476 f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
29477 assert_eq!(
29478 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
29479 ":4\r\n"
29480 );
29481 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
29482 assert_eq!(
29483 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
29484 "-document was removed\r\n"
29485 );
29486 assert_eq!(
29487 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29488 "*2\r\n:1\r\n:2\r\n"
29489 );
29490 }
29491
29492 #[test]
29494 fn a_dump_follows_an_alias() {
29495 let mut f = debugging();
29496 f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
29497 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
29498 assert_eq!(
29499 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
29500 "$3\r\nd:1\r\n"
29501 );
29502 }
29503
29504 #[test]
29507 fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
29508 let mut f = debugging();
29509 assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
29510 assert_eq!(
29511 f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
29512 "-Can not create a search ctx\r\n"
29513 );
29514 assert_eq!(
29515 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
29516 "-Can not create a search ctx\r\n"
29517 );
29518 }
29519
29520 #[test]
29523 fn an_empty_field_dumps_as_nothing_at_all() {
29524 let mut f = Fixture::new();
29525 f.run(&[
29526 b"FT.CREATE",
29527 b"ex",
29528 b"PREFIX",
29529 b"1",
29530 b"e:",
29531 b"SCHEMA",
29532 b"t",
29533 b"TEXT",
29534 b"g",
29535 b"TAG",
29536 b"n",
29537 b"NUMERIC",
29538 ]);
29539 assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
29540 assert_eq!(
29541 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
29542 "*0\r\n"
29543 );
29544 assert_eq!(
29545 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
29546 "*0\r\n"
29547 );
29548 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
29549 }
29550
29551 #[test]
29554 fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
29555 let mut f = debugging();
29556 for (sub, extra) in [
29557 (b"DUMP_TERMS".as_slice(), 1),
29558 (b"GET_MAX_DOC_ID", 1),
29559 (b"DUMP_INVIDX", 2),
29560 (b"DUMP_TAGIDX", 2),
29561 (b"DUMP_NUMIDX", 2),
29562 (b"IDTODOCID", 2),
29563 (b"DOCIDTOID", 2),
29564 ] {
29565 let want = format!(
29566 "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
29567 str::from_utf8(sub).unwrap()
29568 );
29569 for given in [extra - 1, extra + 1] {
29570 let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
29571 cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
29572 assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
29573 }
29574 let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
29575 right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
29576 assert_ne!(f.run(&right), want, "{sub:?}");
29577 }
29578 assert_eq!(
29579 f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
29580 "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
29581 );
29582 }
29583
29584 #[test]
29587 fn the_help_names_the_subcommands_that_answer() {
29588 let mut f = Fixture::new();
29589 let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
29590 $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
29591 $14\r\nGET_MAX_DOC_ID\r\n";
29592 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
29593 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
29594 }
29595
29596 #[test]
29601 fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
29602 let mut f = Fixture::new();
29603 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29604 assert_eq!(
29605 f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
29606 "+OK\r\n"
29607 );
29608 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
29609 assert_eq!(
29610 f.run(&[b"FT.SYNDUMP", b"e"]),
29611 "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
29612 $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
29613 );
29614 }
29615
29616 #[test]
29620 fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
29621 let mut f = Fixture::new();
29622 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29623 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
29624 assert_eq!(
29625 f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
29626 "$69\r\nUNION {\n jogging\n ~gr(expanded)\n +jog(expanded)\n jog(expanded)\n}\n\r\n"
29627 );
29628 }
29629
29630 #[test]
29639 fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
29640 let mut f = Fixture::new();
29641 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29642 f.run(&[b"HSET", b"d1", b"t", b"boy"]);
29643 f.run(&[b"HSET", b"d2", b"t", b"runs"]);
29644 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
29645 assert_eq!(
29646 f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
29647 "*2\r\n:1\r\n$2\r\nd1\r\n"
29648 );
29649 assert_eq!(
29650 f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
29651 "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
29652 );
29653 }
29654
29655 #[test]
29658 fn a_synonym_command_on_a_name_that_is_not_there_fails() {
29659 let mut f = Fixture::new();
29660 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
29661 assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
29662 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
29663 }
29664
29665 #[test]
29669 fn params_counts_the_words_before_it_pairs_them_up() {
29670 let mut f = Fixture::new();
29671 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29672 let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
29673 Expected an argument, but none provided\r\n";
29674 let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
29675 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
29676 assert_eq!(
29677 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
29678 none
29679 );
29680 assert_eq!(
29681 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
29682 odd
29683 );
29684 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
29685 assert_eq!(
29686 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
29687 "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
29688 );
29689 }
29690
29691 fn vectored(f: &mut Fixture) {
29701 f.run(&[
29702 b"FT.CREATE",
29703 b"h",
29704 b"SCHEMA",
29705 b"t",
29706 b"TEXT",
29707 b"v",
29708 b"VECTOR",
29709 b"FLAT",
29710 b"6",
29711 b"TYPE",
29712 b"FLOAT32",
29713 b"DIM",
29714 b"2",
29715 b"DISTANCE_METRIC",
29716 b"L2",
29717 ]);
29718 let at: [&[u8]; 5] = [
29719 b"\x00\x00\x80\x40\x00\x00\x00\x00",
29720 b"\x00\x00\x40\x40\x00\x00\x00\x00",
29721 b"\x00\x00\x00\x40\x00\x00\x00\x00",
29722 b"\x00\x00\x80\x3f\x00\x00\x00\x00",
29723 b"\x00\x00\x00\x00\x00\x00\x00\x00",
29724 ];
29725 for (n, point) in at.iter().enumerate() {
29726 let key = format!("d{}", n + 1);
29727 let word: &[u8] = match n {
29728 0 | 1 | 3 => b"alpha",
29729 _ => b"beta",
29730 };
29731 f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
29732 }
29733 }
29734
29735 const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
29737
29738 #[test]
29742 fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
29743 let mut f = Fixture::new();
29744 vectored(&mut f);
29745 assert_eq!(
29746 f.run(&[
29747 b"FT.SEARCH",
29748 b"h",
29749 b"*=>[KNN 5 @v $vec]",
29750 b"PARAMS",
29751 b"2",
29752 b"vec",
29753 ORIGIN,
29754 b"DIALECT",
29755 b"2",
29756 b"NOCONTENT",
29757 ]),
29758 "*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"
29759 );
29760 assert_eq!(
29761 f.run(&[
29762 b"FT.SEARCH",
29763 b"h",
29764 b"*=>[KNN 3 @v $vec]",
29765 b"PARAMS",
29766 b"2",
29767 b"vec",
29768 ORIGIN,
29769 b"DIALECT",
29770 b"2",
29771 b"NOCONTENT",
29772 ]),
29773 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
29774 );
29775 }
29776
29777 #[test]
29780 fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
29781 let mut f = Fixture::new();
29782 vectored(&mut f);
29783 for (radius, want) in [
29784 ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
29785 ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
29786 (
29787 "9",
29788 "*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",
29789 ),
29790 ] {
29791 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
29792 assert_eq!(
29793 f.run(&[
29794 b"FT.SEARCH",
29795 b"h",
29796 query.as_bytes(),
29797 b"PARAMS",
29798 b"2",
29799 b"vec",
29800 ORIGIN,
29801 b"DIALECT",
29802 b"2",
29803 b"NOCONTENT",
29804 ]),
29805 want,
29806 "radius {radius}"
29807 );
29808 }
29809 }
29810
29811 #[test]
29815 fn a_knn_measures_what_the_query_in_front_of_it_matched() {
29816 let mut f = Fixture::new();
29817 vectored(&mut f);
29818 assert_eq!(
29819 f.run(&[
29820 b"FT.SEARCH",
29821 b"h",
29822 b"alpha=>[KNN 2 @v $vec]",
29823 b"PARAMS",
29824 b"2",
29825 b"vec",
29826 ORIGIN,
29827 b"DIALECT",
29828 b"2",
29829 b"NOCONTENT",
29830 ]),
29831 "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
29832 );
29833 }
29834
29835 #[test]
29844 fn a_count_and_a_radius_are_refused_in_their_own_words() {
29845 let mut f = Fixture::new();
29846 vectored(&mut f);
29847 let ask = |f: &mut Fixture, query: &str| {
29848 f.run(&[
29849 b"FT.SEARCH",
29850 b"h",
29851 query.as_bytes(),
29852 b"PARAMS",
29853 b"2",
29854 b"vec",
29855 ORIGIN,
29856 b"DIALECT",
29857 b"2",
29858 b"NOCONTENT",
29859 ])
29860 };
29861 for (query, at, near) in [
29862 ("*=>[KNN -1 @v $vec]", 8, "-1"),
29863 ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
29864 ("*=>[KNN +3 @v $vec]", 8, "+3"),
29865 ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
29866 ("*=>[KNN abc @v $vec]", 8, "abc"),
29867 ("*=>[KNN 3 $vec]", 10, "vec"),
29868 ("*=>[KNN 3 @v vec]", 13, "vec"),
29869 ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
29870 ] {
29871 assert_eq!(
29872 ask(&mut f, query),
29873 format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
29874 "{query}"
29875 );
29876 }
29877
29878 let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29881 query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
29882 assert_eq!(
29883 ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
29884 "*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"
29885 );
29886 assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
29887 assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
29888
29889 for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
29890 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
29891 assert_eq!(
29892 ask(&mut f, &query),
29893 format!(
29894 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29895 negative radius ({printed}) given in a range query\r\n"
29896 ),
29897 "{query}"
29898 );
29899 }
29900 assert_eq!(
29902 ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
29903 "*2\r\n:1\r\n$2\r\nd5\r\n"
29904 );
29905 }
29906
29907 #[test]
29910 fn a_count_that_came_from_params_is_read_by_its_own_rules() {
29911 let mut f = Fixture::new();
29912 vectored(&mut f);
29913 let ask = |f: &mut Fixture, count: &[u8]| {
29914 f.run(&[
29915 b"FT.SEARCH",
29916 b"h",
29917 b"*=>[KNN $k @v $vec]",
29918 b"PARAMS",
29919 b"4",
29920 b"vec",
29921 ORIGIN,
29922 b"k",
29923 count,
29924 b"DIALECT",
29925 b"2",
29926 b"NOCONTENT",
29927 ])
29928 };
29929 let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
29930 assert_eq!(ask(&mut f, b"3"), three);
29931 assert_eq!(ask(&mut f, b" 3"), three);
29932 assert_eq!(ask(&mut f, b"+3"), three);
29933 for bad in [
29934 &b"3.0"[..],
29935 b"0x3",
29936 b"-1",
29937 b"abc",
29938 b"",
29939 b"99999999999999999999",
29940 ] {
29941 let value = String::from_utf8_lossy(bad).into_owned();
29942 assert_eq!(
29943 ask(&mut f, bad),
29944 format!(
29945 "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
29946 for parameter `k`\r\n"
29947 ),
29948 "{value}"
29949 );
29950 }
29951 assert_eq!(
29952 ask(&mut f, b"288230376151711777"),
29953 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29954 query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
29955 );
29956 }
29957
29958 #[test]
29961 fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
29962 let mut f = Fixture::new();
29963 vectored(&mut f);
29964 assert_eq!(
29965 f.run(&[
29966 b"FT.SEARCH",
29967 b"h",
29968 b"*=>[KNN 5 @v $vec]",
29969 b"PARAMS",
29970 b"2",
29971 b"vec",
29972 b"abc",
29973 b"DIALECT",
29974 b"2",
29975 b"NOCONTENT",
29976 ]),
29977 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29978 query vector blob size (3) does not match index's expected size (8).\r\n"
29979 );
29980 }
29981
29982 #[test]
29987 fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
29988 let mut f = Fixture::new();
29989 vectored(&mut f);
29990 let ask = |f: &mut Fixture, query: &str| {
29991 f.run(&[
29992 b"FT.SEARCH",
29993 b"h",
29994 query.as_bytes(),
29995 b"PARAMS",
29996 b"2",
29997 b"vec",
29998 ORIGIN,
29999 b"DIALECT",
30000 b"2",
30001 b"LIMIT",
30002 b"0",
30003 b"1",
30004 ])
30005 };
30006 assert_eq!(
30007 ask(&mut f, "*=>[KNN 3 @v $vec]"),
30008 "*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"
30009 );
30010 assert_eq!(
30011 ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
30012 "*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"
30013 );
30014 assert_eq!(
30015 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
30016 "*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"
30017 );
30018 assert_eq!(
30019 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
30020 "*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"
30021 );
30022 }
30023
30024 #[test]
30031 fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
30032 let mut f = Fixture::new();
30033 vectored(&mut f);
30034 let ask = |f: &mut Fixture, ret: &[&[u8]]| {
30035 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
30036 args.extend_from_slice(ret);
30037 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
30038 f.run(&args)
30039 };
30040 assert_eq!(
30041 ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
30042 "*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"
30043 );
30044 assert_eq!(
30045 ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
30046 "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
30047 );
30048 assert_eq!(
30049 ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
30050 "*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"
30051 );
30052 assert_eq!(
30053 ask(&mut f, &[b"RETURN", b"1", b"t"]),
30054 "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
30055 );
30056 assert_eq!(
30059 ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
30060 "*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"
30061 );
30062 assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
30063 }
30064
30065 #[test]
30069 fn a_sortby_can_name_a_distance_the_query_yielded() {
30070 let mut f = Fixture::new();
30071 vectored(&mut f);
30072 let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
30073 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
30074 if desc {
30075 args.push(b"DESC");
30076 }
30077 args.extend_from_slice(&[
30078 b"PARAMS",
30079 b"2",
30080 b"vec",
30081 ORIGIN,
30082 b"DIALECT",
30083 b"2",
30084 b"NOCONTENT",
30085 ]);
30086 f.run(&args)
30087 };
30088 assert_eq!(
30089 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
30090 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
30091 );
30092 assert_eq!(
30093 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
30094 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
30095 );
30096 assert_eq!(
30097 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
30098 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
30099 );
30100 let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
30103 not loaded nor in schema\r\n";
30104 assert_eq!(
30105 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
30106 missing
30107 );
30108 assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
30109 assert_eq!(
30112 ask(&mut f, "foo(", b"zz", false),
30113 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
30114 );
30115 }
30116
30117 #[test]
30119 fn two_vector_clauses_answer_two_distances() {
30120 let mut f = Fixture::new();
30121 vectored(&mut f);
30122 assert_eq!(
30123 f.run(&[
30124 b"FT.SEARCH",
30125 b"h",
30126 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
30127 b"RETURN",
30128 b"2",
30129 b"rr",
30130 b"__v_score",
30131 b"PARAMS",
30132 b"2",
30133 b"vec",
30134 ORIGIN,
30135 b"DIALECT",
30136 b"2",
30137 ]),
30138 "*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"
30139 );
30140 }
30141
30142 #[test]
30146 fn an_aggregation_answers_a_distance_nothing_asked_for() {
30147 let mut f = Fixture::new();
30148 vectored(&mut f);
30149 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
30150 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
30151 args.extend_from_slice(rest);
30152 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
30153 f.run(&args)
30154 };
30155 assert_eq!(
30156 ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
30157 "*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"
30158 );
30159 assert_eq!(
30160 ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
30161 "*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"
30162 );
30163 assert_eq!(
30164 ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
30165 "*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"
30166 );
30167 assert_eq!(
30169 ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
30170 "*3\r\n:1\r\n*0\r\n*0\r\n"
30171 );
30172 assert_eq!(
30173 ask(
30174 &mut f,
30175 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
30176 &[]
30177 ),
30178 "*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"
30179 );
30180 }
30181
30182 #[test]
30186 fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
30187 let mut f = Fixture::new();
30188 vectored(&mut f);
30189 const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
30191 let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
30192 f.run(&[
30193 b"FT.AGGREGATE",
30194 b"h",
30195 query.as_bytes(),
30196 b"LOAD",
30197 b"1",
30198 b"@t",
30199 b"PARAMS",
30200 b"2",
30201 b"vec",
30202 vec,
30203 b"DIALECT",
30204 b"2",
30205 ])
30206 };
30207 assert_eq!(
30208 ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
30209 "*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"
30210 );
30211 assert_eq!(
30213 ask(
30214 &mut f,
30215 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
30216 MIDDLE
30217 ),
30218 "*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"
30219 );
30220 }
30221
30222 #[test]
30226 fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
30227 let mut f = Fixture::new();
30228 vectored(&mut f);
30229 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
30230 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
30231 args.extend_from_slice(rest);
30232 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
30233 f.run(&args)
30234 };
30235 let knn = "*=>[KNN 2 @v $vec]";
30236 assert_eq!(
30237 ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
30238 "*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"
30239 );
30240 assert_eq!(
30241 ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
30242 "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
30243 );
30244 assert_eq!(
30245 ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
30246 "*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"
30247 );
30248 assert_eq!(
30249 ask(
30250 &mut f,
30251 knn,
30252 &[
30253 b"GROUPBY",
30254 b"1",
30255 b"@t",
30256 b"REDUCE",
30257 b"MAX",
30258 b"1",
30259 b"@__v_score",
30260 b"AS",
30261 b"m"
30262 ]
30263 ),
30264 "*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"
30265 );
30266 assert_eq!(
30267 ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
30268 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
30269 `__v_score`\r\n"
30270 );
30271 assert_eq!(
30272 ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
30273 "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
30274 );
30275 assert_eq!(
30276 ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
30277 "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
30278 schema\r\n"
30279 );
30280 }
30281
30282 #[test]
30290 fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
30291 let mut f = Fixture::new();
30292 vectored(&mut f);
30293 let ask = |f: &mut Fixture, rest: &[&[u8]]| {
30294 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
30295 args.extend_from_slice(rest);
30296 f.run(&args)
30297 };
30298 assert_eq!(
30299 ask(
30300 &mut f,
30301 &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
30302 ),
30303 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
30304 );
30305 assert_eq!(
30306 ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
30307 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
30308 );
30309 assert_eq!(
30310 ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
30311 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
30312 );
30313 assert_eq!(
30316 ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
30317 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
30318 );
30319 assert_eq!(
30320 ask(
30321 &mut f,
30322 &[
30323 b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
30324 ]
30325 ),
30326 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
30327 );
30328 assert_eq!(
30329 ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
30330 "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
30331 );
30332 }
30333
30334 #[test]
30337 fn a_vector_step_says_which_way_it_was_answered() {
30338 let mut f = Fixture::new();
30339 vectored(&mut f);
30340 let tree = |f: &mut Fixture, query: &[u8]| {
30341 let reply = timeless(&f.run(&[
30342 b"FT.PROFILE",
30343 b"h",
30344 b"AGGREGATE",
30345 b"QUERY",
30346 query,
30347 b"PARAMS",
30348 b"2",
30349 b"vec",
30350 ORIGIN,
30351 b"DIALECT",
30352 b"2",
30353 ]));
30354 let at = reply.find("+Iterators profile").expect("a tree");
30355 let end = reply.find("+Result processors").expect("a list of steps");
30356 reply[at..end].to_string()
30357 };
30358 assert_eq!(
30359 tree(&mut f, b"*=>[KNN 3 @v $vec]"),
30360 "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
30361 +Number of reading operations\r\n:3\r\n\
30362 +Vector search mode\r\n+STANDARD_KNN\r\n"
30363 );
30364 assert_eq!(
30366 tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
30367 tree(&mut f, b"*=>[KNN 3 @v $vec]")
30368 );
30369 assert_eq!(
30372 tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
30373 "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
30374 +Number of reading operations\r\n:4\r\n"
30375 );
30376 assert_eq!(
30377 tree(
30378 &mut f,
30379 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
30380 ),
30381 "+Iterators profile\r\n*8\r\n\
30382 +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
30383 +Number of reading operations\r\n:4\r\n\
30384 +Vector search mode\r\n+RANGE_QUERY\r\n"
30385 );
30386 }
30387
30388 #[test]
30392 fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
30393 let mut f = Fixture::new();
30394 vectored(&mut f);
30395 let ask = |f: &mut Fixture, query: &[u8]| {
30396 timeless(&f.run(&[
30397 b"FT.PROFILE",
30398 b"h",
30399 b"AGGREGATE",
30400 b"QUERY",
30401 query,
30402 b"PARAMS",
30403 b"2",
30404 b"vec",
30405 ORIGIN,
30406 b"DIALECT",
30407 b"2",
30408 ]))
30409 };
30410 let cut = |reply: &str| {
30411 let at = reply.find("+Iterators profile").expect("a tree");
30412 reply[at..].to_string()
30413 };
30414 assert_eq!(
30415 cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
30416 "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
30417 +Number of reading operations\r\n:3\r\n\
30418 +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
30419 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
30420 +Number of reading operations\r\n:3\r\n\
30421 +Estimated number of matches\r\n:3\r\n\
30422 +Result processors profile\r\n*2\r\n\
30423 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
30424 *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
30425 +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
30426 );
30427 assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
30430 "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
30431 +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
30432 ));
30433 assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
30436 }
30437
30438 #[test]
30442 fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
30443 let mut f = profiling();
30444 let steps = |f: &mut Fixture, words: &[&[u8]]| {
30445 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
30446 argv.extend_from_slice(words);
30447 let reply = timeless(&f.run(&argv));
30448 let at = reply.find("+Result processors").expect("a list of steps");
30449 reply[at..].to_string()
30450 };
30451 assert_eq!(
30452 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
30453 "+Result processors profile\r\n*2\r\n\
30454 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
30455 *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
30456 +Coordinator\r\n*0\r\n"
30457 );
30458 assert!(
30459 steps(
30460 &mut f,
30461 &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
30462 )
30463 .contains("+Type\r\n+Counter\r\n")
30464 );
30465 assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
30467 "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
30468 +Results processed\r\n:2\r\n"
30469 ));
30470 }
30471
30472 #[test]
30478 fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
30479 let mut f = Fixture::new();
30480 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30481 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30482 f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
30483 assert_eq!(
30484 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
30485 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
30486 *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"
30487 );
30488 }
30489
30490 #[test]
30494 fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
30495 let mut f = Fixture::new();
30496 f.run(&[b"HELLO", b"3"]);
30497 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30498 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30499 assert_eq!(
30500 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
30501 "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
30502 *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
30503 );
30504 }
30505
30506 #[test]
30510 fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
30511 let mut f = Fixture::new();
30512 f.run(&[
30513 b"FT.CREATE",
30514 b"e",
30515 b"SCHEMA",
30516 b"a",
30517 b"TEXT",
30518 b"NOSTEM",
30519 b"b",
30520 b"TEXT",
30521 b"NOSTEM",
30522 ]);
30523 f.run(&[b"HSET", b"d1", b"b", b"world"]);
30524 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
30525 assert_eq!(
30526 f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
30527 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
30528 );
30529 }
30530
30531 #[test]
30535 fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
30536 let mut f = Fixture::new();
30537 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30538 f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
30539 assert_eq!(
30540 f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
30541 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
30542 *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"
30543 );
30544 assert_eq!(
30545 f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
30546 "*0\r\n"
30547 );
30548 assert_eq!(
30549 f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
30550 "-Dict does not exist: nope\r\n"
30551 );
30552 }
30553
30554 #[test]
30558 fn a_spellcheck_reads_its_arguments_leniently() {
30559 let mut f = Fixture::new();
30560 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30561 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30562 let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
30563 *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
30564 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
30565 let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
30566 let args: &[&[u8]] = &[
30567 b"FT.SPELLCHECK",
30568 b"e",
30569 b"helqp",
30570 b"DISTANCE",
30571 b"1",
30572 b"DISTANCE",
30573 b"4",
30574 ];
30575 assert_eq!(f.run(args), none);
30576 assert_eq!(
30577 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
30578 "-bad distance given, distance must be a natural number between 1 to 4\r\n"
30579 );
30580 assert_eq!(
30581 f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
30582 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
30583 );
30584 }
30585
30586 #[test]
30591 fn an_add_answers_how_many_suggestions_are_in_there_now() {
30592 let mut f = Fixture::new();
30593 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
30594 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
30595 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
30596 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
30597 assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
30598 }
30599
30600 #[test]
30603 fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
30604 let mut f = Fixture::new();
30605 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30606 assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
30607 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
30608 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
30609 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
30610 assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
30611 assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
30612 assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
30613 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
30614 }
30615
30616 #[test]
30619 fn deleting_the_last_suggestion_deletes_the_key() {
30620 let mut f = Fixture::new();
30621 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30622 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
30623 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
30624 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
30625 assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
30626 }
30627
30628 #[test]
30631 fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
30632 let mut f = Fixture::new();
30633 f.run(&[b"SET", b"s", b"x"]);
30634 for cmd in [
30635 vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
30636 vec![&b"FT.SUGGET"[..], b"s", b"t"],
30637 vec![&b"FT.SUGDEL"[..], b"s", b"t"],
30638 vec![&b"FT.SUGLEN"[..], b"s"],
30639 ] {
30640 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
30641 }
30642 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
30643 }
30644
30645 #[test]
30648 fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
30649 let mut f = Fixture::new();
30650 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30651 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
30652 f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
30653 assert_eq!(
30654 f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
30655 "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
30656 $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
30657 $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
30658 );
30659 assert_eq!(
30660 f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
30661 "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
30662 );
30663 assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
30664 }
30665
30666 #[test]
30668 fn fuzzy_allows_one_edit_and_nothing_allows_two() {
30669 let mut f = Fixture::new();
30670 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
30671 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
30672 assert_eq!(
30673 f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
30674 "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
30675 );
30676 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
30677 }
30678
30679 #[test]
30681 fn a_lookup_answers_five_unless_it_is_told_otherwise() {
30682 let mut f = Fixture::new();
30683 for (term, score) in [
30684 (&b"a1"[..], &b"1"[..]),
30685 (b"a2", b"2"),
30686 (b"a3", b"3"),
30687 (b"a4", b"4"),
30688 (b"a5", b"5"),
30689 (b"a6", b"6"),
30690 ] {
30691 f.run(&[b"FT.SUGADD", b"s", term, score]);
30692 }
30693 assert_eq!(
30694 f.run(&[b"FT.SUGGET", b"s", b"a"]),
30695 "*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"
30696 );
30697 assert_eq!(
30698 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
30699 "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
30700 );
30701 assert!(
30703 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
30704 .starts_with("*6\r\n")
30705 );
30706 }
30707
30708 #[test]
30711 fn a_payload_comes_back_beside_the_term_or_a_null_does() {
30712 let mut f = Fixture::new();
30713 f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
30714 assert_eq!(
30715 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30716 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30717 );
30718 f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
30719 assert_eq!(
30720 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30721 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30722 );
30723 f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
30726 assert_eq!(
30727 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30728 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30729 );
30730 f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
30732 assert_eq!(
30733 f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
30734 "*2\r\n$5\r\nother\r\n$-1\r\n"
30735 );
30736 }
30737
30738 #[test]
30742 fn incr_adds_to_the_score_that_is_already_there() {
30743 let mut f = Fixture::new();
30744 for _ in 0..3 {
30745 f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
30746 }
30747 assert_eq!(
30748 f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
30749 "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
30750 );
30751 }
30752
30753 #[test]
30755 fn the_suggestion_errors_are_the_lines_the_module_sends() {
30756 let mut f = Fixture::new();
30757 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30758 assert_eq!(
30759 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
30760 "-ERR invalid score\r\n"
30761 );
30762 assert_eq!(
30764 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
30765 "-Unknown argument `NOPE`\r\n"
30766 );
30767 assert_eq!(
30768 f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
30769 "-Invalid payload: Expected an argument, but none provided\r\n"
30770 );
30771 assert!(
30773 f.run(&[
30774 b"FT.SUGADD",
30775 b"s",
30776 b"t",
30777 b"1",
30778 b"PAYLOAD",
30779 b"a",
30780 b"PAYLOAD",
30781 b"b"
30782 ])
30783 .contains("wrong number of arguments")
30784 );
30785 assert_eq!(
30786 f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
30787 "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
30788 );
30789 for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
30793 assert_eq!(
30794 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30795 "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
30796 "{}",
30797 String::from_utf8_lossy(max)
30798 );
30799 }
30800 for max in [
30801 &b"abc"[..],
30802 b"0.0",
30803 b"00",
30804 b"-0",
30805 b"+0",
30806 b"0.5",
30807 b"-1.5",
30808 b"1e400",
30809 ] {
30810 assert_eq!(
30811 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30812 "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
30813 "{}",
30814 String::from_utf8_lossy(max)
30815 );
30816 }
30817 for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
30818 assert_eq!(
30819 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30820 "*1\r\n$3\r\none\r\n",
30821 "{}",
30822 String::from_utf8_lossy(max)
30823 );
30824 }
30825 assert_eq!(
30826 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
30827 "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
30828 );
30829 assert_eq!(
30832 f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
30833 "-ERR invalid score\r\n"
30834 );
30835 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
30836 }
30837
30838 #[test]
30842 fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
30843 let mut f = Fixture::new();
30844 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30845 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
30846 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
30847 assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
30848 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
30849 assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
30850 assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
30851 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
30852 }
30853
30854 #[test]
30857 fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
30858 let mut f = Fixture::new();
30859 f.run(&[
30860 b"FT.CREATE",
30861 b"ix",
30862 b"PREFIX",
30863 b"1",
30864 b"p:",
30865 b"SCHEMA",
30866 b"n",
30867 b"NUMERIC",
30868 ]);
30869 f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
30870 assert_eq!(held(&f, b"ix"), (0, 0));
30871
30872 let reply = f.run(&[b"FT.INFO", b"ix"]);
30873 assert!(
30874 reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
30875 "{reply}"
30876 );
30877 assert!(reply.contains("hash_indexing_failures"), "{reply}");
30878 }
30879
30880 #[test]
30883 fn an_index_can_only_be_made_on_database_zero() {
30884 let mut f = Fixture::new();
30885 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
30886 f.run(&[b"SELECT", b"1"]);
30887 let refused = "-Cannot create index on db != 0\r\n";
30888 assert_eq!(
30889 f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
30890 refused
30891 );
30892 assert_eq!(
30894 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
30895 refused
30896 );
30897 assert_eq!(
30899 f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
30900 refused
30901 );
30902 assert_eq!(
30904 f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
30905 "+OK\r\n"
30906 );
30907 assert_eq!(f.server.search.lock().len(), 1);
30908 }
30909
30910 #[test]
30916 fn the_scan_is_one_database_and_the_following_is_all_of_them() {
30917 let mut f = Fixture::new();
30918 f.run(&[b"SELECT", b"1"]);
30919 f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
30920 f.run(&[b"SELECT", b"0"]);
30921 f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
30922 f.run(&[
30923 b"FT.CREATE",
30924 b"ix",
30925 b"PREFIX",
30926 b"1",
30927 b"p:",
30928 b"SCHEMA",
30929 b"t",
30930 b"TEXT",
30931 ]);
30932 assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
30933
30934 f.run(&[b"SELECT", b"1"]);
30935 f.run(&[b"HSET", b"p:8", b"t", b"later"]);
30936 assert_eq!(
30937 held(&f, b"ix"),
30938 (2, 2),
30939 "and then it follows every database"
30940 );
30941 }
30942
30943 fn corpus(f: &mut Fixture) {
30946 f.run(&[
30947 b"FT.CREATE",
30948 b"sx",
30949 b"PREFIX",
30950 b"1",
30951 b"d:",
30952 b"SCHEMA",
30953 b"t",
30954 b"TEXT",
30955 b"g",
30956 b"TAG",
30957 b"n",
30958 b"NUMERIC",
30959 ]);
30960 for (key, text, tag, number) in [
30961 (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
30962 (b"d:2", "alpha gamma", "bb", "2"),
30963 (b"d:3", "delta", "cc", "3"),
30964 (b"d:4", "alpha beta gamma", "aa,cc", "4"),
30965 ] {
30966 f.run(&[
30967 b"HSET",
30968 key,
30969 b"t",
30970 text.as_bytes(),
30971 b"g",
30972 tag.as_bytes(),
30973 b"n",
30974 number.as_bytes(),
30975 ]);
30976 }
30977 }
30978
30979 fn sortable(f: &mut Fixture) {
30983 f.run(&[
30984 b"FT.CREATE",
30985 b"sy",
30986 b"PREFIX",
30987 b"1",
30988 b"s:",
30989 b"SCHEMA",
30990 b"t",
30991 b"TEXT",
30992 b"SORTABLE",
30993 b"n",
30994 b"NUMERIC",
30995 b"SORTABLE",
30996 b"body",
30997 b"AS",
30998 b"b",
30999 b"TEXT",
31000 b"SORTABLE",
31001 b"p",
31002 b"TEXT",
31003 ]);
31004 for (key, text, number) in [
31005 (b"s:1".as_slice(), "Banana Split", "2"),
31006 (b"s:2", "apple", "10"),
31007 ] {
31008 f.run(&[
31009 b"HSET",
31010 key,
31011 b"t",
31012 text.as_bytes(),
31013 b"n",
31014 number.as_bytes(),
31015 b"body",
31016 text.as_bytes(),
31017 b"p",
31018 b"alpha",
31019 ]);
31020 }
31021 f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
31024 }
31025
31026 #[test]
31029 fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
31030 let mut f = Fixture::new();
31031 sortable(&mut f);
31032 assert_eq!(
31033 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
31034 "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
31035 );
31036 assert_eq!(
31037 f.run(&[
31038 b"FT.SEARCH",
31039 b"sy",
31040 b"alpha",
31041 b"SORTBY",
31042 b"n",
31043 b"DESC",
31044 b"NOCONTENT"
31045 ]),
31046 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
31047 );
31048 assert_eq!(
31051 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
31052 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
31053 );
31054 }
31055
31056 #[test]
31059 fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
31060 let mut f = Fixture::new();
31061 sortable(&mut f);
31062 f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
31063 assert_eq!(
31064 f.run(&[
31065 b"FT.SEARCH",
31066 b"sy",
31067 b"alpha",
31068 b"SORTBY",
31069 b"p",
31070 b"NOCONTENT",
31071 b"LIMIT",
31072 b"0",
31073 b"2"
31074 ]),
31075 "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
31076 );
31077 assert_eq!(
31080 f.run(&[
31081 b"FT.SEARCH",
31082 b"sy",
31083 b"alpha",
31084 b"SORTBY",
31085 b"p",
31086 b"WITHSORTKEYS",
31087 b"NOCONTENT",
31088 b"LIMIT",
31089 b"2",
31090 b"1"
31091 ]),
31092 "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
31093 );
31094 }
31095
31096 #[test]
31099 fn a_search_can_send_the_value_it_sorted_by_back() {
31100 let mut f = Fixture::new();
31101 sortable(&mut f);
31102 assert_eq!(
31103 f.run(&[
31104 b"FT.SEARCH",
31105 b"sy",
31106 b"alpha",
31107 b"SORTBY",
31108 b"n",
31109 b"WITHSORTKEYS",
31110 b"NOCONTENT"
31111 ]),
31112 concat!(
31113 "*7\r\n:3\r\n",
31114 "$3\r\ns:1\r\n$2\r\n#2\r\n",
31115 "$3\r\ns:2\r\n$3\r\n#10\r\n",
31116 "$3\r\ns:3\r\n$-1\r\n"
31117 )
31118 );
31119 assert_eq!(
31120 f.run(&[
31121 b"FT.SEARCH",
31122 b"sy",
31123 b"alpha",
31124 b"SORTBY",
31125 b"t",
31126 b"WITHSORTKEYS",
31127 b"NOCONTENT"
31128 ]),
31129 concat!(
31130 "*7\r\n:3\r\n",
31131 "$3\r\ns:2\r\n$6\r\n$apple\r\n",
31132 "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
31133 "$3\r\ns:3\r\n$-1\r\n"
31134 )
31135 );
31136 assert_eq!(
31139 f.run(&[
31140 b"FT.SEARCH",
31141 b"sy",
31142 b"banana",
31143 b"WITHSORTKEYS",
31144 b"NOCONTENT"
31145 ]),
31146 "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
31147 );
31148 }
31149
31150 #[test]
31153 fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
31154 let mut f = Fixture::new();
31155 sortable(&mut f);
31156 assert_eq!(
31160 f.run(&[
31161 b"FT.SEARCH",
31162 b"sy",
31163 b"alpha",
31164 b"SORTBY",
31165 b"b",
31166 b"LIMIT",
31167 b"0",
31168 b"1"
31169 ]),
31170 concat!(
31171 "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
31172 "$1\r\nb\r\n$5\r\napple\r\n",
31173 "$1\r\nt\r\n$5\r\napple\r\n",
31174 "$1\r\nn\r\n$2\r\n10\r\n",
31175 "$4\r\nbody\r\n$5\r\napple\r\n",
31176 "$1\r\np\r\n$5\r\nalpha\r\n"
31177 )
31178 );
31179 assert_eq!(
31182 f.run(&[
31183 b"FT.SEARCH",
31184 b"sy",
31185 b"alpha",
31186 b"SORTBY",
31187 b"b",
31188 b"RETURN",
31189 b"2",
31190 b"p",
31191 b"b",
31192 b"LIMIT",
31193 b"0",
31194 b"1"
31195 ]),
31196 concat!(
31197 "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
31198 "$1\r\nb\r\n$5\r\napple\r\n",
31199 "$1\r\np\r\n$5\r\nalpha\r\n"
31200 )
31201 );
31202 }
31203
31204 #[test]
31206 fn a_search_refuses_the_sorts_it_cannot_run() {
31207 let mut f = Fixture::new();
31208 sortable(&mut f);
31209 assert_eq!(
31210 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
31211 "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
31212 );
31213 assert_eq!(
31214 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
31215 "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
31216 );
31217 assert_eq!(
31218 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
31219 "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
31220 );
31221 assert_eq!(
31222 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
31223 "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
31224 );
31225 assert_eq!(
31228 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
31229 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
31230 );
31231 }
31232
31233 fn marking(f: &mut Fixture) {
31237 f.run(&[
31238 b"FT.CREATE",
31239 b"mk",
31240 b"ON",
31241 b"HASH",
31242 b"PREFIX",
31243 b"1",
31244 b"m:",
31245 b"SCHEMA",
31246 b"a",
31247 b"TEXT",
31248 b"b",
31249 b"TEXT",
31250 b"n",
31251 b"NUMERIC",
31252 b"g",
31253 b"TAG",
31254 ]);
31255 f.run(&[
31256 b"HSET",
31257 b"m:1",
31258 b"a",
31259 b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
31260 b"b",
31261 b"t1 t2 t3 t4 t5 t6 t7 t8",
31262 b"n",
31263 b"1",
31264 b"g",
31265 b"red",
31266 ]);
31267 }
31268
31269 #[test]
31272 fn a_summarize_cuts_a_field_down_to_what_matched() {
31273 let mut f = Fixture::new();
31274 marking(&mut f);
31275 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
31276 assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
31277 assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
31279 assert!(got.contains("$1\r\nr\r\n"), "{got}");
31281 }
31282
31283 #[test]
31287 fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
31288 let mut f = Fixture::new();
31289 marking(&mut f);
31290 let got = f.run(&[
31291 b"FT.SEARCH",
31292 b"mk",
31293 b"fox",
31294 b"SUMMARIZE",
31295 b"FRAGS",
31296 b"1",
31297 b"LEN",
31298 b"20",
31299 ]);
31300 assert!(
31301 got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
31302 "{got}"
31303 );
31304 let two = f.run(&[
31307 b"FT.SEARCH",
31308 b"mk",
31309 b"fox",
31310 b"SUMMARIZE",
31311 b"FRAGS",
31312 b"2",
31313 b"LEN",
31314 b"20",
31315 ]);
31316 assert!(
31317 two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
31318 "{two}"
31319 );
31320 }
31321
31322 #[test]
31325 fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
31326 let mut f = Fixture::new();
31327 marking(&mut f);
31328 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
31329 assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
31330 let both = f.run(&[
31331 b"FT.SEARCH",
31332 b"mk",
31333 b"fox",
31334 b"SUMMARIZE",
31335 b"LEN",
31336 b"2",
31337 b"HIGHLIGHT",
31338 ]);
31339 assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
31340 assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
31342 assert!(both.contains("$3\r\nred\r\n"), "{both}");
31343 let split = f.run(&[
31345 b"FT.SEARCH",
31346 b"mk",
31347 b"fox",
31348 b"SUMMARIZE",
31349 b"FIELDS",
31350 b"1",
31351 b"b",
31352 b"LEN",
31353 b"2",
31354 b"HIGHLIGHT",
31355 b"FIELDS",
31356 b"1",
31357 b"a",
31358 ]);
31359 assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
31360 }
31361
31362 #[test]
31364 fn a_highlight_does_not_mark_a_tag() {
31365 let mut f = Fixture::new();
31366 marking(&mut f);
31367 f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
31368 let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
31369 assert!(!got.contains("<b>"), "{got}");
31370 assert!(got.contains("red and blue"), "{got}");
31371 }
31372
31373 #[test]
31376 fn a_search_answers_a_total_and_then_the_rows() {
31377 let mut f = Fixture::new();
31378 corpus(&mut f);
31379 assert_eq!(
31380 f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
31381 "*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"
31382 );
31383 f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
31386 assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
31387 assert_eq!(
31390 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
31391 "*2\r\n:1\r\n$3\r\nd:3\r\n"
31392 );
31393 assert_eq!(
31394 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
31395 "*1\r\n:3\r\n"
31396 );
31397 }
31398
31399 #[test]
31402 fn the_window_is_ten_rows_and_a_million_wide_at_most() {
31403 let mut f = Fixture::new();
31404 corpus(&mut f);
31405 assert_eq!(
31406 f.run(&[
31407 b"FT.SEARCH",
31408 b"sx",
31409 b"alpha",
31410 b"NOCONTENT",
31411 b"LIMIT",
31412 b"1",
31413 b"1"
31414 ]),
31415 "*2\r\n:3\r\n$3\r\nd:2\r\n"
31416 );
31417 assert_eq!(
31418 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
31419 "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
31420 );
31421 assert_eq!(
31422 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
31423 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
31424 );
31425 assert_eq!(
31426 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
31427 "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
31428 );
31429 assert_eq!(
31430 f.run(&[
31431 b"FT.SEARCH",
31432 b"sx",
31433 b"alpha",
31434 b"NOCONTENT",
31435 b"LIMIT",
31436 b"999999",
31437 b"1000000"
31438 ]),
31439 "*1\r\n:3\r\n"
31440 );
31441 }
31442
31443 #[test]
31447 fn a_return_of_nothing_is_not_the_same_as_nocontent() {
31448 let mut f = Fixture::new();
31449 corpus(&mut f);
31450 let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
31451 assert_eq!(
31452 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
31453 bare
31454 );
31455 assert_eq!(
31456 f.run(&[
31457 b"FT.SEARCH",
31458 b"sx",
31459 b"delta",
31460 b"NOCONTENT",
31461 b"RETURN",
31462 b"1",
31463 b"t"
31464 ]),
31465 bare
31466 );
31467 assert_eq!(
31468 f.run(&[
31469 b"FT.SEARCH",
31470 b"sx",
31471 b"delta",
31472 b"RETURN",
31473 b"0",
31474 b"RETURN",
31475 b"1",
31476 b"t"
31477 ]),
31478 "*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"
31479 );
31480 }
31481
31482 #[test]
31485 fn the_count_after_return_counts_words() {
31486 let mut f = Fixture::new();
31487 corpus(&mut f);
31488 assert_eq!(
31491 f.run(&[
31492 b"FT.SEARCH",
31493 b"sx",
31494 b"delta",
31495 b"RETURN",
31496 b"3",
31497 b"t",
31498 b"AS",
31499 b"x"
31500 ]),
31501 "*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"
31502 );
31503 assert_eq!(
31506 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
31507 "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
31508 );
31509 assert_eq!(
31510 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
31511 "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
31512 );
31513 assert_eq!(
31517 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
31518 "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
31519 );
31520 }
31521
31522 #[test]
31525 fn a_filter_is_a_range_written_outside_the_query() {
31526 let mut f = Fixture::new();
31527 corpus(&mut f);
31528 assert_eq!(
31529 f.run(&[
31530 b"FT.SEARCH",
31531 b"sx",
31532 b"alpha",
31533 b"NOCONTENT",
31534 b"FILTER",
31535 b"n",
31536 b"2",
31537 b"4"
31538 ]),
31539 "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
31540 );
31541 assert_eq!(
31542 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
31543 "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
31544 );
31545 assert_eq!(
31546 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
31547 "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
31548 );
31549 assert_eq!(
31550 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
31551 "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
31552 );
31553 for field in [b"g".as_slice(), b"nope"] {
31556 assert_eq!(
31557 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
31558 "*1\r\n:0\r\n"
31559 );
31560 }
31561 }
31562
31563 #[test]
31566 fn the_index_is_found_before_the_arguments_are_read() {
31567 let mut f = Fixture::new();
31568 corpus(&mut f);
31569 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
31570 assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
31571 assert_eq!(
31572 f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
31573 missing
31574 );
31575 assert_eq!(
31578 f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
31579 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
31580 );
31581 }
31582
31583 #[test]
31586 fn inkeys_comes_off_the_total() {
31587 let mut f = Fixture::new();
31588 corpus(&mut f);
31589 assert_eq!(
31590 f.run(&[
31591 b"FT.SEARCH",
31592 b"sx",
31593 b"alpha",
31594 b"NOCONTENT",
31595 b"INKEYS",
31596 b"1",
31597 b"d:1"
31598 ]),
31599 "*2\r\n:1\r\n$3\r\nd:1\r\n"
31600 );
31601 assert_eq!(
31602 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
31603 "*1\r\n:0\r\n"
31604 );
31605 }
31606
31607 #[test]
31614 fn the_fields_are_read_from_the_session_database() {
31615 let mut f = Fixture::new();
31616 corpus(&mut f);
31617 f.run(&[b"SELECT", b"1"]);
31618 f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
31619 assert_eq!(
31622 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
31623 "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
31624 );
31625 assert_eq!(
31626 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
31627 "*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"
31628 );
31629 }
31630
31631 #[test]
31634 fn the_third_protocol_answers_a_map_of_five() {
31635 let mut f = Fixture::new();
31636 corpus(&mut f);
31637 f.out = Out::new(Proto::Resp3);
31638 assert_eq!(
31639 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
31640 concat!(
31641 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
31642 "%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",
31643 "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
31644 )
31645 );
31646 }
31647
31648 #[test]
31652 fn a_window_of_nothing_has_to_start_at_the_top() {
31653 let mut f = Fixture::new();
31654 corpus(&mut f);
31655 let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
31656 assert_eq!(
31657 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31658 refused
31659 );
31660 assert_eq!(
31661 f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31662 refused
31663 );
31664 assert_eq!(
31665 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31666 refused
31667 );
31668 assert_eq!(
31669 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
31670 "*1\r\n:3\r\n"
31671 );
31672 }
31673
31674 #[test]
31677 fn an_aggregation_answers_a_count_and_then_the_properties() {
31678 let mut f = Fixture::new();
31679 corpus(&mut f);
31680 assert_eq!(
31681 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
31682 "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
31683 );
31684 assert_eq!(
31688 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
31689 concat!(
31690 "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
31691 "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
31692 "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
31693 )
31694 );
31695 assert_eq!(
31699 f.run(&[
31700 b"FT.AGGREGATE",
31701 b"sx",
31702 b"alpha",
31703 b"LOAD",
31704 b"1",
31705 b"@n",
31706 b"LIMIT",
31707 b"1",
31708 b"2"
31709 ]),
31710 "*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"
31711 );
31712 assert_eq!(
31714 f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
31715 "*1\r\n:0\r\n"
31716 );
31717 }
31718
31719 #[test]
31723 fn a_load_counts_words_and_can_rename_what_it_reads() {
31724 let mut f = Fixture::new();
31725 corpus(&mut f);
31726 assert_eq!(
31728 f.run(&[
31729 b"FT.AGGREGATE",
31730 b"sx",
31731 b"alpha",
31732 b"LOAD",
31733 b"3",
31734 b"@t",
31735 b"AS",
31736 b"text"
31737 ]),
31738 concat!(
31739 "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
31740 "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
31741 "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
31742 )
31743 );
31744 assert_eq!(
31745 f.run(&[
31746 b"FT.AGGREGATE",
31747 b"sx",
31748 b"alpha",
31749 b"LOAD",
31750 b"*",
31751 b"LIMIT",
31752 b"0",
31753 b"1"
31754 ]),
31755 concat!(
31756 "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
31757 "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
31758 )
31759 );
31760 assert_eq!(
31762 f.run(&[
31763 b"FT.AGGREGATE",
31764 b"sx",
31765 b"alpha",
31766 b"LOAD",
31767 b"2",
31768 b"@n",
31769 b"@nope",
31770 b"LIMIT",
31771 b"0",
31772 b"2"
31773 ]),
31774 "*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"
31775 );
31776 }
31777
31778 #[test]
31781 fn a_load_refuses_a_count_it_cannot_use() {
31782 let mut f = Fixture::new();
31783 corpus(&mut f);
31784 let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
31785 assert_eq!(
31786 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
31787 format!("{head}Expected number of fields or `*`\r\n")
31788 );
31789 assert_eq!(
31790 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
31791 format!("{head}Value is outside acceptable bounds\r\n")
31792 );
31793 assert_eq!(
31794 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
31795 format!("{head}Expected an argument, but none provided\r\n")
31796 );
31797 assert_eq!(
31798 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
31799 format!("{head}Expected an argument, but none provided\r\n")
31800 );
31801 assert_eq!(
31805 f.run(&[
31806 b"FT.AGGREGATE",
31807 b"sx",
31808 b"alpha",
31809 b"LOAD",
31810 b"2",
31811 b"@t",
31812 b"AS"
31813 ]),
31814 "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
31815 );
31816 assert_eq!(
31819 f.run(&[
31820 b"FT.AGGREGATE",
31821 b"sx",
31822 b"alpha",
31823 b"LOAD",
31824 b"2",
31825 b"@t",
31826 b"AS",
31827 b"VERBATIM"
31828 ]),
31829 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
31830 );
31831 assert_eq!(
31835 f.run(&[
31836 b"FT.AGGREGATE",
31837 b"sx",
31838 b"alpha",
31839 b"LOAD",
31840 b"0",
31841 b"LIMIT",
31842 b"0",
31843 b"1"
31844 ]),
31845 "*2\r\n:1\r\n*0\r\n"
31846 );
31847 }
31848
31849 #[test]
31852 fn a_pipeline_step_closes_the_door_on_the_search_words() {
31853 let mut f = Fixture::new();
31854 corpus(&mut f);
31855 assert_eq!(
31856 f.run(&[
31857 b"FT.AGGREGATE",
31858 b"sx",
31859 b"alpha",
31860 b"LOAD",
31861 b"1",
31862 b"@t",
31863 b"VERBATIM"
31864 ]),
31865 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
31866 );
31867 assert_eq!(
31868 f.run(&[
31869 b"FT.AGGREGATE",
31870 b"sx",
31871 b"alpha",
31872 b"LIMIT",
31873 b"0",
31874 b"1",
31875 b"VERBATIM"
31876 ]),
31877 "*2\r\n:1\r\n*0\r\n"
31878 );
31879 for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
31882 let name = core::str::from_utf8(word).expect("the three words are text");
31883 assert_eq!(
31884 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
31885 format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
31886 );
31887 }
31888 }
31889
31890 #[test]
31893 fn addscores_writes_a_shorter_score_than_withscores() {
31894 let mut f = Fixture::new();
31895 corpus(&mut f);
31896 assert_eq!(
31897 f.run(&[
31898 b"FT.AGGREGATE",
31899 b"sx",
31900 b"alpha",
31901 b"ADDSCORES",
31902 b"LOAD",
31903 b"1",
31904 b"@n",
31905 b"LIMIT",
31906 b"0",
31907 b"2"
31908 ]),
31909 concat!(
31910 "*3\r\n:1\r\n",
31911 "*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",
31912 "*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"
31913 )
31914 );
31915 assert_eq!(
31919 f.run(&[
31920 b"FT.AGGREGATE",
31921 b"sx",
31922 b"alpha",
31923 b"NOCONTENT",
31924 b"WITHSCORES",
31925 b"LIMIT",
31926 b"0",
31927 b"2"
31928 ]),
31929 "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
31930 );
31931 assert_eq!(
31932 f.run(&[
31933 b"FT.AGGREGATE",
31934 b"sx",
31935 b"alpha",
31936 b"WITHSORTKEYS",
31937 b"LOAD",
31938 b"1",
31939 b"@n",
31940 b"LIMIT",
31941 b"0",
31942 b"1"
31943 ]),
31944 "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
31945 );
31946 }
31947
31948 #[test]
31951 fn a_normalising_scorer_answers_the_rows_backwards() {
31952 let mut f = Fixture::new();
31953 corpus(&mut f);
31954 assert_eq!(
31955 f.run(&[
31956 b"FT.AGGREGATE",
31957 b"sx",
31958 b"alpha",
31959 b"SCORER",
31960 b"BM25STD.NORM",
31961 b"ADDSCORES",
31962 b"LOAD",
31963 b"1",
31964 b"@n",
31965 b"LIMIT",
31966 b"1",
31967 b"2"
31968 ]),
31969 concat!(
31970 "*3\r\n:3\r\n",
31971 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
31972 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
31973 )
31974 );
31975 assert_eq!(
31978 f.run(&[
31979 b"FT.AGGREGATE",
31980 b"sx",
31981 b"alpha",
31982 b"SCORER",
31983 b"BM25STD.NORM",
31984 b"LOAD",
31985 b"1",
31986 b"@n",
31987 b"LIMIT",
31988 b"1",
31989 b"2"
31990 ]),
31991 "*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"
31992 );
31993 }
31994
31995 #[test]
31998 fn an_aggregation_answers_a_map_of_five_as_well() {
31999 let mut f = Fixture::new();
32000 corpus(&mut f);
32001 f.out = Out::new(Proto::Resp3);
32002 assert_eq!(
32003 f.run(&[
32004 b"FT.AGGREGATE",
32005 b"sx",
32006 b"alpha",
32007 b"ADDSCORES",
32008 b"WITHSCORES",
32009 b"WITHSORTKEYS",
32010 b"LOAD",
32011 b"1",
32012 b"@n",
32013 b"LIMIT",
32014 b"0",
32015 b"1"
32016 ]),
32017 concat!(
32018 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
32019 "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
32020 "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
32021 "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
32022 "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
32023 )
32024 );
32025 assert_eq!(
32028 f.run(&[
32029 b"FT.AGGREGATE",
32030 b"sx",
32031 b"alpha",
32032 b"NOCONTENT",
32033 b"LIMIT",
32034 b"0",
32035 b"1"
32036 ]),
32037 concat!(
32038 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
32039 "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
32040 )
32041 );
32042 }
32043 const INFO_FIELDS: &[&str] = &[
32051 "id",
32052 "addr",
32053 "laddr",
32054 "fd",
32055 "name",
32056 "age",
32057 "idle",
32058 "flags",
32059 "db",
32060 "sub",
32061 "psub",
32062 "ssub",
32063 "multi",
32064 "watch",
32065 "qbuf",
32066 "qbuf-free",
32067 "argv-mem",
32068 "multi-mem",
32069 "rbs",
32070 "rbp",
32071 "obl",
32072 "oll",
32073 "omem",
32074 "omem-shared",
32075 "omem-unshared",
32076 "tot-mem",
32077 "events",
32078 "cmd",
32079 "user",
32080 "redir",
32081 "resp",
32082 "lib-name",
32083 "lib-ver",
32084 "io-thread",
32085 "tot-net-in",
32086 "tot-net-out",
32087 "tot-cmds",
32088 "read-events",
32089 "avg-pipeline-len-sum",
32090 "avg-pipeline-len-cnt",
32091 ];
32092
32093 fn client_info(f: &mut Fixture) -> Vec<(String, String)> {
32096 let reply = f.run(&[b"CLIENT", b"INFO"]);
32097 let body = reply.split_once("\r\n").expect("a bulk header").1;
32098 let line = body.trim_end_matches("\r\n").trim_start_matches("txt:");
32101 assert!(
32102 line.ends_with('\n'),
32103 "the report ends in a newline: {line:?}"
32104 );
32105 line.trim_end()
32106 .split(' ')
32107 .map(|pair| {
32108 let (name, value) = pair.split_once('=').expect("name=value");
32109 (name.to_string(), value.to_string())
32110 })
32111 .collect()
32112 }
32113
32114 fn client_field(f: &mut Fixture, name: &str) -> String {
32116 client_info(f)
32117 .into_iter()
32118 .find(|(n, _)| n == name)
32119 .map(|(_, v)| v)
32120 .unwrap_or_else(|| panic!("no {name} field"))
32121 }
32122
32123 #[test]
32124 fn client_info_names_every_field_a_real_server_names() {
32125 let mut f = Fixture::new();
32126 let got: Vec<String> = client_info(&mut f).into_iter().map(|(n, _)| n).collect();
32127 assert_eq!(got, INFO_FIELDS);
32128 }
32129
32130 #[test]
32133 fn a_connection_with_no_socket_reports_no_address_and_no_descriptor() {
32134 let mut f = Fixture::new();
32135 assert_eq!(client_field(&mut f, "addr"), "");
32136 assert_eq!(client_field(&mut f, "laddr"), "");
32137 assert_eq!(client_field(&mut f, "fd"), "-1");
32138 assert_eq!(client_field(&mut f, "id"), "7");
32139 }
32140
32141 #[test]
32142 fn client_setname_takes_a_name_back_and_refuses_one_with_a_space_in_it() {
32143 let mut f = Fixture::new();
32144 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$-1\r\n");
32145 assert_eq!(f.run(&[b"CLIENT", b"SETNAME", b"worker"]), "+OK\r\n");
32146 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
32147 assert_eq!(client_field(&mut f, "name"), "worker");
32148 assert_eq!(
32149 f.run(&[b"CLIENT", b"SETNAME", b"two words"]),
32150 "-ERR Client names cannot contain spaces, newlines or special characters.\r\n"
32151 );
32152 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
32154 }
32155
32156 #[test]
32159 fn reset_clears_the_name_and_the_switches_and_keeps_the_library() {
32160 let mut f = Fixture::new();
32161 f.run(&[b"CLIENT", b"SETNAME", b"worker"]);
32162 f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME", b"yo-py"]);
32163 f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"1.2.3"]);
32164 f.run(&[b"CLIENT", b"NO-EVICT", b"on"]);
32165 f.run(&[b"CLIENT", b"NO-TOUCH", b"on"]);
32166 assert_eq!(client_field(&mut f, "flags"), "eT");
32167
32168 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
32169 assert_eq!(client_field(&mut f, "name"), "");
32170 assert_eq!(client_field(&mut f, "flags"), "N");
32171 assert_eq!(client_field(&mut f, "lib-name"), "yo-py");
32172 assert_eq!(client_field(&mut f, "lib-ver"), "1.2.3");
32173 }
32174
32175 #[test]
32176 fn client_setinfo_complains_the_way_a_real_server_does() {
32177 let mut f = Fixture::new();
32178 assert_eq!(
32179 f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME"]),
32180 "-ERR wrong number of arguments for 'client|setinfo' command\r\n"
32181 );
32182 assert_eq!(
32183 f.run(&[b"CLIENT", b"SETINFO", b"NOPE", b"x"]),
32184 "-ERR Unrecognized option 'NOPE'\r\n"
32185 );
32186 assert_eq!(
32187 f.run(&[b"CLIENT", b"SETINFO", b"lib-name", b"ok x"]),
32188 "-ERR lib-name cannot contain spaces, newlines or special characters.\r\n"
32189 );
32190 assert_eq!(
32191 f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"has space"]),
32192 "-ERR lib-ver cannot contain spaces, newlines or special characters.\r\n"
32193 );
32194 }
32195
32196 #[test]
32197 fn client_refuses_a_subcommand_it_does_not_have_and_arguments_it_did_not_ask_for() {
32198 let mut f = Fixture::new();
32199 assert_eq!(
32200 f.run(&[b"CLIENT", b"NOPE"]),
32201 "-ERR unknown subcommand 'NOPE'. Try CLIENT HELP.\r\n"
32202 );
32203 assert_eq!(
32204 f.run(&[b"CLIENT", b"GETNAME", b"extra"]),
32205 "-ERR wrong number of arguments for 'client|getname' command\r\n"
32206 );
32207 assert_eq!(
32208 f.run(&[b"CLIENT", b"NO-EVICT", b"maybe"]),
32209 "-ERR syntax error\r\n"
32210 );
32211 assert_eq!(
32212 f.run(&[b"CLIENT", b"REPLY", b"BAD"]),
32213 "-ERR syntax error\r\n"
32214 );
32215 }
32216
32217 #[test]
32221 fn client_info_counts_the_three_subscribe_namespaces_apart() {
32222 let mut f = Fixture::new();
32223 f.run(&[b"HELLO", b"3"]);
32226 f.run(&[b"SUBSCRIBE", b"a", b"b"]);
32227 f.run(&[b"PSUBSCRIBE", b"p*"]);
32228 f.run(&[b"SSUBSCRIBE", b"s"]);
32229 let info = client_info(&mut f);
32230 let at = |name: &str| {
32231 info.iter()
32232 .find(|(n, _)| n == name)
32233 .map(|(_, v)| v.clone())
32234 .unwrap()
32235 };
32236 assert_eq!(at("sub"), "2");
32237 assert_eq!(at("psub"), "1");
32238 assert_eq!(at("ssub"), "1");
32239 assert_eq!(at("flags"), "P");
32240 forget_session(&f.server, &mut f.session);
32241 }
32242
32243 #[test]
32246 fn client_info_reports_itself_as_the_command_running() {
32247 let mut f = Fixture::new();
32248 assert_eq!(client_field(&mut f, "cmd"), "client|info");
32249 f.run(&[b"GET", b"nothing"]);
32250 assert_eq!(client_field(&mut f, "cmd"), "client|info");
32253 }
32254
32255 #[test]
32257 fn the_command_field_is_lower_case_however_the_client_spelled_it() {
32258 let mut f = Fixture::new();
32259 let reply = f.run(&[b"CLIENT", b"Info"]);
32260 assert!(reply.contains("cmd=client|info"), "{reply}");
32261 }
32262
32263 #[test]
32264 fn client_help_lists_the_subcommands_that_are_here() {
32265 let mut f = Fixture::new();
32266 let reply = f.run(&[b"CLIENT", b"HELP"]);
32267 for sub in ["ID", "GETNAME", "SETNAME", "SETINFO", "INFO", "REPLY"] {
32268 assert!(reply.contains(sub), "no {sub} in {reply}");
32269 }
32270 assert!(!reply.contains("TRACKING"), "{reply}");
32273 }
32274
32275 fn commands(stream: &str) -> Vec<Vec<String>> {
32283 let mut out = Vec::new();
32284 let mut rest = stream;
32285 while let Some(tail) = rest.strip_prefix('*') {
32286 let (n, tail) = tail.split_once("\r\n").expect("a header ends");
32287 let mut one = Vec::new();
32288 let mut tail = tail;
32289 for _ in 0..n.parse::<usize>().expect("a count") {
32290 let body = tail.strip_prefix('$').expect("a bulk string");
32291 let (len, body) = body.split_once("\r\n").expect("a length ends");
32292 let len: usize = len.parse().expect("a length");
32293 one.push(body[..len].to_string());
32294 tail = &body[len + 2..];
32295 }
32296 out.push(one);
32297 rest = tail;
32298 }
32299 assert!(rest.is_empty(), "left over: {rest:?}");
32300 out
32301 }
32302
32303 fn only(stream: &str) -> Vec<String> {
32305 let mut each = commands(stream);
32306 assert_eq!(each.len(), 1, "expected one command: {stream:?}");
32307 each.pop().expect("one command")
32308 }
32309
32310 #[test]
32311 fn the_stream_opens_with_a_select_and_says_it_once() {
32312 let mut f = Fixture::replicated();
32313 assert_eq!(
32314 commands(&f.crossed(&[b"SET", b"k", b"v"])),
32315 vec![
32316 vec!["SELECT".to_string(), "0".to_string()],
32317 vec!["SET".to_string(), "k".to_string(), "v".to_string()],
32318 ]
32319 );
32320 assert_eq!(only(&f.crossed(&[b"SET", b"k2", b"v"])), ["SET", "k2", "v"]);
32322 f.run(&[b"SELECT", b"3"]);
32325 assert_eq!(
32326 commands(&f.crossed(&[b"SET", b"k3", b"v"])),
32327 vec![
32328 vec!["SELECT".to_string(), "3".to_string()],
32329 vec!["SET".to_string(), "k3".to_string(), "v".to_string()],
32330 ]
32331 );
32332 }
32333
32334 #[test]
32339 fn a_relative_deadline_crosses_as_the_instant_it_resolved_to() {
32340 let mut f = Fixture::replicated();
32341 f.crossed(&[b"SET", b"seed", b"1"]);
32342 for parts in [
32343 &[b"SET".as_slice(), b"k", b"v", b"EX", b"100"][..],
32344 &[b"SETEX".as_slice(), b"k", b"100", b"v"][..],
32345 ] {
32346 let words = only(&f.crossed(parts));
32347 assert_eq!(&words[..3], ["SET", "k", "v"], "{words:?}");
32348 assert_eq!(words[3], "PXAT", "{words:?}");
32349 let at: i64 = words[4].parse().expect("an instant");
32350 assert!(at > f.server.clock.now_ms() as i64, "{words:?}");
32351 }
32352 for parts in [
32353 &[b"EXPIRE".as_slice(), b"k", b"50"][..],
32354 &[b"PEXPIRE".as_slice(), b"k", b"50000"][..],
32355 &[b"EXPIREAT".as_slice(), b"k", b"99999999999"][..],
32356 &[b"GETEX".as_slice(), b"k", b"EX", b"100"][..],
32357 ] {
32358 let words = only(&f.crossed(parts));
32359 assert_eq!(&words[..2], ["PEXPIREAT", "k"], "{words:?}");
32360 }
32361 assert_eq!(
32362 only(&f.crossed(&[b"GETEX", b"k", b"PERSIST"])),
32363 ["PERSIST", "k"]
32364 );
32365 }
32366
32367 #[test]
32369 fn nothing_crosses_for_a_read_or_for_a_failure() {
32370 let mut f = Fixture::replicated();
32371 f.crossed(&[b"SET", b"k", b"v"]);
32372 for parts in [
32373 &[b"GET".as_slice(), b"k"][..],
32374 &[b"TYPE".as_slice(), b"k"][..],
32375 &[b"STRLEN".as_slice(), b"k"][..],
32376 &[b"EXISTS".as_slice(), b"k"][..],
32377 &[b"PING".as_slice()][..],
32378 &[b"LPUSH".as_slice(), b"k", b"a"][..],
32381 &[b"INCR".as_slice(), b"k"][..],
32382 ] {
32383 assert_eq!(f.crossed(parts), "", "{parts:?}");
32384 }
32385 }
32386
32387 #[test]
32395 fn a_write_that_changed_nothing_still_crosses() {
32396 let mut f = Fixture::replicated();
32397 f.crossed(&[b"SET", b"k", b"v"]);
32398 assert_eq!(
32399 only(&f.crossed(&[b"DEL", b"nosuchkey"])),
32400 ["DEL", "nosuchkey"]
32401 );
32402 assert_eq!(only(&f.crossed(&[b"SET", b"k", b"v"])), ["SET", "k", "v"]);
32403 for parts in [
32405 &[b"SPOP".as_slice(), b"nosuchset"][..],
32406 &[b"EXPIRE".as_slice(), b"nosuchkey", b"100"][..],
32407 &[
32408 b"XADD".as_slice(),
32409 b"nosuchstream",
32410 b"NOMKSTREAM",
32411 b"*",
32412 b"f",
32413 b"v",
32414 ][..],
32415 ] {
32416 assert_eq!(f.crossed(parts), "", "{parts:?}");
32417 }
32418 }
32419
32420 #[test]
32423 fn a_condition_that_held_crosses_without_it() {
32424 let mut f = Fixture::replicated();
32425 f.crossed(&[b"SET", b"seed", b"1"]);
32426 assert_eq!(only(&f.crossed(&[b"SETNX", b"k", b"v"])), ["SET", "k", "v"]);
32427 assert_eq!(
32428 only(&f.crossed(&[b"SET", b"k", b"w", b"XX"])),
32429 ["SET", "k", "w"]
32430 );
32431 }
32432
32433 #[test]
32435 fn a_random_or_derived_write_crosses_as_what_it_did() {
32436 let mut f = Fixture::replicated();
32437 f.crossed(&[b"SET", b"seed", b"1"]);
32438 f.crossed(&[b"SADD", b"s", b"one", b"two"]);
32439 let words = only(&f.crossed(&[b"SPOP", b"s"]));
32440 assert_eq!(&words[..2], ["SREM", "s"], "{words:?}");
32441 assert!(words[2] == "one" || words[2] == "two", "{words:?}");
32442 let words = only(&f.crossed(&[b"SPOP", b"s"]));
32447 assert_eq!(&words[..2], ["SREM", "s"], "{words:?}");
32448 f.crossed(&[b"SADD", b"s", b"one", b"two"]);
32451 assert_eq!(only(&f.crossed(&[b"SPOP", b"s", b"2"])), ["DEL", "s"]);
32452 f.crossed(&[b"SET", b"n", b"1"]);
32453 assert_eq!(
32454 only(&f.crossed(&[b"INCRBYFLOAT", b"n", b"1.5"])),
32455 ["SET", "n", "2.5", "KEEPTTL"]
32456 );
32457 assert_eq!(only(&f.crossed(&[b"GETDEL", b"n"])), ["DEL", "n"]);
32458 let words = only(&f.crossed(&[b"XADD", b"st", b"*", b"f", b"v"]));
32459 assert_eq!(&words[..2], ["XADD", "st"], "{words:?}");
32460 assert_ne!(words[2], "*", "an auto id has to be resolved: {words:?}");
32461 assert_eq!(&words[3..], ["f", "v"], "{words:?}");
32462 }
32463
32464 #[test]
32471 fn an_expiry_a_read_noticed_crosses_as_a_deletion() {
32472 let mut f = Fixture::replicated();
32473 f.crossed(&[b"SET", b"k", b"v", b"PX", b"50"]);
32474 f.advance(100);
32475 assert_eq!(only(&f.crossed(&[b"GET", b"k"])), ["DEL", "k"]);
32476 f.run(&[b"SET", b"k2", b"v", b"PX", b"50"]);
32478 f.crossed(&[b"PING"]);
32479 f.advance(100);
32480 assert_eq!(
32481 commands(&f.crossed(&[b"LPUSH", b"k2", b"a"])),
32482 vec![
32483 vec!["DEL".to_string(), "k2".to_string()],
32484 vec!["LPUSH".to_string(), "k2".to_string(), "a".to_string()],
32485 ]
32486 );
32487 }
32488
32489 #[test]
32494 fn a_blocking_command_that_parked_crosses_nothing() {
32495 let mut f = Fixture::replicated();
32496 f.crossed(&[b"SET", b"seed", b"1"]);
32497 assert_eq!(f.flow(&[b"BLPOP", b"gone", b"0"]).0, Flow::Block);
32498 assert_eq!(f.server.stream_since(f.mark).0, "");
32499 f.run(&[b"XADD", b"st", b"1-1", b"f", b"v"]);
32500 f.run(&[b"XGROUP", b"CREATE", b"st", b"g", b"$"]);
32501 f.crossed(&[b"PING"]);
32502 f.run(&[
32505 b"XREADGROUP",
32506 b"GROUP",
32507 b"g",
32508 b"c",
32509 b"COUNT",
32510 b"1",
32511 b"STREAMS",
32512 b"st",
32513 b">",
32514 ]);
32515 assert_eq!(
32516 only(&f.crossed(&[b"PING"])),
32517 ["XGROUP", "CREATECONSUMER", "st", "g", "c"]
32518 );
32519 }
32520
32521 #[test]
32524 fn every_xgroup_subcommand_that_changed_something_crosses() {
32525 let mut f = Fixture::replicated();
32526 f.crossed(&[b"XADD", b"st", b"1-1", b"f", b"v"]);
32527 assert_eq!(
32530 only(&f.crossed(&[b"XGROUP", b"CREATE", b"st", b"g", b"$"])),
32531 ["XGROUP", "CREATE", "st", "g", "1-1"]
32532 );
32533 assert_eq!(
32534 only(&f.crossed(&[b"XGROUP", b"CREATECONSUMER", b"st", b"g", b"c"])),
32535 ["XGROUP", "CREATECONSUMER", "st", "g", "c"]
32536 );
32537 assert_eq!(
32538 only(&f.crossed(&[b"XGROUP", b"SETID", b"st", b"g", b"0"])),
32539 ["XGROUP", "SETID", "st", "g", "0-0"]
32540 );
32541 assert_eq!(
32542 only(&f.crossed(&[b"XGROUP", b"DELCONSUMER", b"st", b"g", b"c"])),
32543 ["XGROUP", "DELCONSUMER", "st", "g", "c"]
32544 );
32545 assert_eq!(
32546 only(&f.crossed(&[b"XGROUP", b"DESTROY", b"st", b"g"])),
32547 ["XGROUP", "DESTROY", "st", "g"]
32548 );
32549 assert_eq!(f.crossed(&[b"XGROUP", b"DESTROY", b"st", b"g"]), "");
32551 }
32552
32553 #[test]
32558 fn a_publish_crosses_with_nobody_listening() {
32559 let mut f = Fixture::replicated();
32560 f.crossed(&[b"SET", b"seed", b"1"]);
32561 assert_eq!(
32562 only(&f.crossed(&[b"PUBLISH", b"news", b"hello"])),
32563 ["PUBLISH", "news", "hello"]
32564 );
32565 assert_eq!(
32566 only(&f.crossed(&[b"SPUBLISH", b"news", b"hello"])),
32567 ["SPUBLISH", "news", "hello"]
32568 );
32569 }
32570
32571 #[test]
32579 fn a_replica_asking_to_carry_on_is_caught_up_from_the_backlog() {
32580 let mut f = Fixture::replicated();
32581 f.crossed(&[b"SET", b"k", b"v"]);
32582 let id = f.server.repl_id();
32583 let had = f.mark;
32584 f.crossed(&[b"SET", b"k2", b"later"]);
32585 let asked = (had + 1).to_string();
32586 let reply = f.run(&[b"PSYNC", &id, asked.as_bytes()]);
32587 assert!(reply.starts_with("+CONTINUE "), "{reply}");
32588 assert!(reply.contains("later"), "{reply}");
32589 assert_eq!(reply.matches("k2").count(), 1, "{reply}");
32591 }
32592
32593 #[test]
32595 fn a_replica_with_no_history_is_sent_a_snapshot() {
32596 let mut f = Fixture::replicated();
32597 f.crossed(&[b"SET", b"k", b"v"]);
32598 let reply = f.run(&[b"PSYNC", b"?", b"-1"]);
32599 assert!(reply.starts_with("+FULLRESYNC "), "{reply}");
32600 let body = reply.split_once("\r\n").expect("a header ends").1;
32602 assert!(body.starts_with('$'), "{body:?}");
32603 assert!(!body.ends_with("\r\n"), "{body:?}");
32604 }
32605
32606 #[test]
32610 fn a_read_only_replica_refuses_a_write_and_answers_a_read() {
32611 let mut f = Fixture::new();
32612 f.run(&[b"SET", b"k", b"v"]);
32613 f.server.pretend_following("127.0.0.1", 6379, true);
32614 assert_eq!(
32615 f.run(&[b"SET", b"k", b"other"]),
32616 "-READONLY You can't write against a read only replica.\r\n"
32617 );
32618 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
32619 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
32621 }
32622
32623 #[test]
32626 fn a_master_takes_writes_however_the_read_only_setting_is_left() {
32627 let mut f = Fixture::new();
32628 f.server.set_replica_read_only(true);
32629 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32630 f.server.pretend_following("127.0.0.1", 6379, true);
32631 assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32632 f.server.set_replica_read_only(false);
32634 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32635 f.server.set_replica_read_only(true);
32636 f.server.pretend_master();
32639 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32640 }
32641
32642 #[test]
32644 fn the_link_to_the_master_writes_through_the_read_only_refusal() {
32645 let mut f = Fixture::new();
32646 f.server.pretend_following("127.0.0.1", 6379, true);
32647 assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32648 f.session.serve_master(true);
32649 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32650 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
32651 }
32652
32653 #[test]
32656 fn a_transaction_on_a_read_only_replica_is_refused_whole() {
32657 let mut f = Fixture::new();
32658 f.server.pretend_following("127.0.0.1", 6379, true);
32659 f.run(&[b"MULTI"]);
32660 assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32661 assert!(f.run(&[b"EXEC"]).starts_with("-EXECABORT"));
32662 }
32663
32664 #[test]
32666 fn a_replica_says_who_it_follows_in_info_and_in_role() {
32667 let mut f = Fixture::new();
32668 assert!(f.run(&[b"INFO", b"replication"]).contains("role:master"));
32669 f.server.pretend_following("10.0.0.4", 7000, true);
32670 let info = f.run(&[b"INFO", b"replication"]);
32671 assert!(info.contains("role:slave"), "{info}");
32672 assert!(info.contains("master_host:10.0.0.4"), "{info}");
32673 assert!(info.contains("master_port:7000"), "{info}");
32674 assert!(info.contains("master_link_status:up"), "{info}");
32675 assert!(info.contains("slave_read_only:1"), "{info}");
32676 let role = f.run(&[b"ROLE"]);
32679 assert!(role.starts_with("*5\r\n$5\r\nslave\r\n"), "{role}");
32680 assert!(role.contains("10.0.0.4"), "{role}");
32681 assert!(role.contains("connected"), "{role}");
32682 f.server.pretend_following("10.0.0.4", 7000, false);
32684 assert!(
32685 f.run(&[b"INFO", b"replication"])
32686 .contains("master_link_status:down"),
32687 "a link that is not up is down"
32688 );
32689 assert!(f.run(&[b"ROLE"]).contains("connect"));
32690 }
32691
32692 #[test]
32695 fn replicaof_on_an_embedded_server_says_it_is_not_available() {
32696 let mut f = Fixture::new();
32697 let said = f.run(&[b"REPLICAOF", b"127.0.0.1", b"6379"]);
32698 assert!(
32699 said.contains("not available on an embedded server"),
32700 "{said}"
32701 );
32702 assert!(
32705 f.run(&[b"REPLICAOF", b"127.0.0.1"])
32706 .starts_with("-ERR wrong number")
32707 );
32708 assert_eq!(
32709 f.run(&[b"SLAVEOF", b"127.0.0.1", b"abc"]),
32710 "-ERR Invalid master port\r\n"
32711 );
32712 assert_eq!(
32713 f.run(&[b"REPLICAOF", b"127.0.0.1", b"99999"]),
32714 "-ERR Invalid master port\r\n"
32715 );
32716 }
32717
32718 #[test]
32721 fn a_promotion_keeps_the_old_history_as_the_second_id() {
32722 let f = Fixture::new();
32723 let was = f.server.repl_id();
32724 f.server.promote();
32725 assert_ne!(f.server.repl_id(), was);
32726 let info = {
32727 let mut f = f;
32728 f.run(&[b"INFO", b"replication"])
32729 };
32730 let was = String::from_utf8_lossy(&was).into_owned();
32731 assert!(info.contains(&format!("master_replid2:{was}")), "{info}");
32732 }
32733
32734 #[test]
32737 fn change_repl_id_takes_a_new_id_and_forgets_the_old_one() {
32738 let mut f = Fixture::new();
32739 let was = f.server.repl_id();
32740 f.server.promote();
32741 assert_eq!(f.run(&[b"DEBUG", b"CHANGE-REPL-ID"]), "+OK\r\n");
32742 assert_ne!(f.server.repl_id(), was);
32743 let info = f.run(&[b"INFO", b"replication"]);
32744 assert!(
32745 info.contains(&format!("master_replid2:{}", "0".repeat(40))),
32746 "{info}"
32747 );
32748 }
32749
32750 #[test]
32754 fn failover_refuses_in_the_order_the_reference_refuses() {
32755 let mut f = Fixture::new();
32756 assert_eq!(
32758 f.run(&[b"FAILOVER", b"ABORT"]),
32759 "-ERR No failover in progress.\r\n"
32760 );
32761 assert_eq!(
32765 f.run(&[b"FAILOVER", b"TIMEOUT", b"0"]),
32766 "-ERR FAILOVER timeout must be greater than 0\r\n"
32767 );
32768 assert_eq!(
32769 f.run(&[b"FAILOVER", b"TIMEOUT", b"-1"]),
32770 "-ERR FAILOVER timeout must be greater than 0\r\n"
32771 );
32772 assert!(
32773 f.run(&[b"FAILOVER", b"TIMEOUT", b"abc"])
32774 .starts_with("-ERR value is not an integer")
32775 );
32776 assert_eq!(f.run(&[b"FAILOVER", b"bogus"]), "-ERR syntax error\r\n");
32779 assert_eq!(
32780 f.run(&[b"FAILOVER", b"TIMEOUT", b"1", b"TIMEOUT", b"2"]),
32781 "-ERR syntax error\r\n"
32782 );
32783 assert_eq!(
32784 f.run(&[b"FAILOVER", b"FORCE", b"FORCE"]),
32785 "-ERR syntax error\r\n"
32786 );
32787 assert_eq!(f.run(&[b"FAILOVER", b"TO", b"h"]), "-ERR syntax error\r\n");
32790 assert_eq!(
32792 f.run(&[b"FAILOVER", b"ABORT", b"TIMEOUT", b"1"]),
32793 "-ERR syntax error\r\n"
32794 );
32795 assert_eq!(
32798 f.run(&[b"FAILOVER"]),
32799 "-ERR FAILOVER requires connected replicas.\r\n"
32800 );
32801 assert_eq!(
32802 f.run(&[b"FAILOVER", b"FORCE"]),
32803 "-ERR FAILOVER requires connected replicas.\r\n"
32804 );
32805 f.server.pretend_following("10.0.0.4", 7000, true);
32807 assert_eq!(
32808 f.run(&[b"FAILOVER"]),
32809 "-ERR FAILOVER is not valid when server is a replica.\r\n"
32810 );
32811 }
32812
32813 #[test]
32816 fn a_server_that_is_not_failing_over_says_no_failover() {
32817 let mut f = Fixture::new();
32818 assert!(
32819 f.run(&[b"INFO", b"replication"])
32820 .contains("master_failover_state:no-failover"),
32821 "the field is there and says nothing is going on"
32822 );
32823 }
32824
32825 #[test]
32828 fn a_transaction_crosses_as_its_commands() {
32829 let mut f = Fixture::replicated();
32830 f.crossed(&[b"SET", b"seed", b"1"]);
32831 f.run(&[b"MULTI"]);
32832 f.run(&[b"SET", b"a", b"1"]);
32833 f.run(&[b"INCR", b"a"]);
32834 assert_eq!(
32835 commands(&f.crossed(&[b"EXEC"])),
32836 vec![
32837 vec!["SET".to_string(), "a".to_string(), "1".to_string()],
32838 vec!["INCR".to_string(), "a".to_string()],
32839 ]
32840 );
32841 }
32842
32843 const FOO: u16 = 12182;
32849
32850 const BAR: u16 = 5061;
32852
32853 fn clustered() -> Fixture {
32854 let mut server = Server::new();
32855 server.enable_cluster("", 7000);
32856 server.cluster_own_everything();
32857 let other = server.cluster_pretend_node(
32858 "5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f",
32859 "10.0.0.9",
32860 7002,
32861 );
32862 assert_eq!(other, 1, "the made up node is the second one in the table");
32863 Fixture::on(server)
32864 }
32865
32866 #[test]
32870 fn cluster_slots_comes_out_in_slot_order() {
32871 let mut f = clustered();
32872 for slot in 0..100u16 {
32873 f.server.cluster_hand_over(slot, 1);
32874 }
32875 let reply = f.run(&[b"CLUSTER", b"SLOTS"]);
32876 assert!(
32877 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"),
32878 "the other node's run is first because it starts at slot 0: {reply}"
32879 );
32880 assert!(
32881 reply.contains("*3\r\n:100\r\n:16383\r\n"),
32882 "and this node's run is the rest of them: {reply}"
32883 );
32884 }
32885
32886 #[test]
32888 fn a_key_on_another_node_is_moved_there() {
32889 let mut f = clustered();
32890 f.server.cluster_hand_over(BAR, 1);
32891 assert_eq!(
32892 f.run(&[b"GET", b"bar"]),
32893 format!("-MOVED {BAR} 10.0.0.9:7002\r\n")
32894 );
32895 assert_eq!(f.run(&[b"GET", b"foo"]), "$-1\r\n");
32897 }
32898
32899 #[test]
32902 fn a_command_with_no_keys_never_redirects() {
32903 let mut f = clustered();
32904 for slot in 0..16384u16 {
32905 f.server.cluster_hand_over(slot, 1);
32906 }
32907 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
32908 assert_eq!(f.run(&[b"ECHO", b"hi"]), "$2\r\nhi\r\n");
32909 }
32910
32911 #[test]
32914 fn two_slots_in_one_command_is_a_cross_slot() {
32915 let mut f = clustered();
32916 assert_eq!(
32917 f.run(&[b"MGET", b"foo", b"bar"]),
32918 "-CROSSSLOT Keys in request don't hash to the same slot\r\n"
32919 );
32920 assert_eq!(
32922 f.run(&[b"MGET", b"{t}foo", b"{t}bar"]),
32923 "*2\r\n$-1\r\n$-1\r\n"
32924 );
32925 }
32926
32927 #[test]
32930 fn a_hole_is_reported_before_the_cross_slot() {
32931 let mut server = Server::new();
32932 server.enable_cluster("", 7000);
32933 let mut f = Fixture::on(server);
32934 assert_eq!(
32935 f.run(&[b"MGET", b"foo", b"bar"]),
32936 "-CLUSTERDOWN Hash slot not served\r\n"
32937 );
32938 f.server.cluster_own_everything();
32940 assert_eq!(
32941 f.run(&[b"MGET", b"foo", b"bar"]),
32942 "-CROSSSLOT Keys in request don't hash to the same slot\r\n"
32943 );
32944 }
32945
32946 #[test]
32950 fn a_migrating_slot_asks_for_the_keys_that_have_gone() {
32951 let mut f = clustered();
32952 f.run(&[b"SET", b"foo", b"1"]);
32953 f.server.cluster_moving(FOO, Some(1), None);
32954 assert_eq!(f.run(&[b"GET", b"foo"]), "$1\r\n1\r\n");
32956 assert_eq!(
32958 f.run(&[b"GET", b"{foo}gone"]),
32959 format!("-ASK {FOO} 10.0.0.9:7002\r\n")
32960 );
32961 }
32962
32963 #[test]
32966 fn a_half_moved_slot_is_a_try_again() {
32967 let mut f = clustered();
32968 f.run(&[b"SET", b"{t}here", b"1"]);
32969 let slot = cluster::key_slot(b"{t}here");
32970 f.server.cluster_moving(slot, Some(1), None);
32971 assert_eq!(
32972 f.run(&[b"MGET", b"{t}here", b"{t}gone"]),
32973 "-TRYAGAIN Multiple keys request during rehashing of slot\r\n"
32974 );
32975 }
32976
32977 #[test]
32980 fn asking_lets_one_command_into_an_importing_slot() {
32981 let mut f = clustered();
32982 f.server.cluster_hand_over(FOO, 1);
32983 f.server.cluster_moving(FOO, None, Some(1));
32984 let moved = format!("-MOVED {FOO} 10.0.0.9:7002\r\n");
32985 assert_eq!(f.run(&[b"GET", b"foo"]), moved);
32986 assert_eq!(f.run(&[b"ASKING"]), "+OK\r\n");
32987 assert_eq!(f.run(&[b"SET", b"foo", b"1"]), "+OK\r\n");
32988 assert_eq!(f.run(&[b"GET", b"foo"]), moved);
32990 }
32991
32992 #[test]
32997 fn restore_asking_gets_into_an_importing_slot_on_its_own() {
32998 let mut f = clustered();
32999 f.server.cluster_hand_over(FOO, 1);
33000 f.server.cluster_moving(FOO, None, Some(1));
33001 f.run(&[b"SET", b"scratch", b"1"]);
33004 let dumped = f.raw(&[b"DUMP", b"scratch"]);
33005 let payload =
33006 &dumped[dumped.iter().position(|b| *b == b'\n').unwrap() + 1..dumped.len() - 2];
33007 let payload = payload.to_vec();
33008 assert_eq!(
33010 f.run(&[b"RESTORE", b"foo", b"0", &payload]),
33011 format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
33012 );
33013 assert_eq!(
33015 f.run(&[b"RESTORE-ASKING", b"foo", b"0", &payload]),
33016 "+OK\r\n"
33017 );
33018 assert_eq!(
33021 f.run(&[b"GET", b"foo"]),
33022 format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
33023 );
33024 }
33025
33026 #[test]
33030 fn closing_an_import_takes_a_higher_epoch() {
33031 let mut f = clustered();
33032 f.server.cluster_hand_over(FOO, 1);
33033 f.server.cluster_moving(FOO, None, Some(1));
33034 let me = f.run(&[b"CLUSTER", b"MYID"]);
33035 let me = me[me.find("\r\n").unwrap() + 2..me.len() - 2].to_owned();
33036 assert_eq!(
33037 f.run(&[
33038 b"CLUSTER",
33039 b"SETSLOT",
33040 FOO.to_string().as_bytes(),
33041 b"NODE",
33042 me.as_bytes()
33043 ]),
33044 "+OK\r\n"
33045 );
33046 assert_eq!(f.run(&[b"CLUSTER", b"BUMPEPOCH"]), "+STILL 1\r\n");
33049 assert_eq!(f.run(&[b"GET", b"foo"]), "$-1\r\n");
33051 }
33052
33053 #[test]
33056 fn a_plain_hand_over_does_not_touch_the_epoch() {
33057 let mut f = clustered();
33058 let me = f.run(&[b"CLUSTER", b"MYID"]);
33059 let me = me[me.find("\r\n").unwrap() + 2..me.len() - 2].to_owned();
33060 assert_eq!(
33061 f.run(&[
33062 b"CLUSTER",
33063 b"SETSLOT",
33064 FOO.to_string().as_bytes(),
33065 b"NODE",
33066 me.as_bytes()
33067 ]),
33068 "+OK\r\n"
33069 );
33070 assert_eq!(
33071 f.run(&[b"CLUSTER", b"BUMPEPOCH"]),
33072 "+BUMPED 1\r\n",
33073 "the epoch was still zero, so this is the first thing to move it"
33074 );
33075 }
33076
33077 #[test]
33080 fn a_slot_with_keys_in_it_is_not_handed_over() {
33081 let mut f = clustered();
33082 f.run(&[b"SET", b"foo", b"1"]);
33083 let them = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
33084 assert_eq!(
33085 f.run(&[
33086 b"CLUSTER",
33087 b"SETSLOT",
33088 FOO.to_string().as_bytes(),
33089 b"NODE",
33090 them
33091 ]),
33092 format!(
33093 "-ERR Can't assign hashslot {FOO} to a different node while I still hold keys for this hash slot.\r\n"
33094 )
33095 );
33096 f.run(&[b"DEL", b"foo"]);
33098 assert_eq!(
33099 f.run(&[
33100 b"CLUSTER",
33101 b"SETSLOT",
33102 FOO.to_string().as_bytes(),
33103 b"NODE",
33104 them
33105 ]),
33106 "+OK\r\n"
33107 );
33108 assert_eq!(
33109 f.run(&[b"GET", b"foo"]),
33110 format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
33111 );
33112 }
33113
33114 #[test]
33123 fn the_slot_migration_protocol_is_shut_to_a_client() {
33124 let mut f = clustered();
33125 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS"]);
33128 assert_eq!(
33129 reply,
33130 "-ERR wrong number of arguments for 'cluster|syncslots' command\r\n"
33131 );
33132 assert_eq!(flow, Flow::Continue);
33133 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
33134 assert_eq!(
33135 reply,
33136 "-ERR CLUSTER SYNCSLOTS subcommands are only allowed for internal clients\r\n"
33137 );
33138 assert_eq!(flow, Flow::Close, "and the socket goes with it");
33139 }
33140
33141 #[test]
33144 fn the_internal_login_wants_the_cluster_secret() {
33145 let mut f = Fixture::new();
33146 assert_eq!(
33147 f.run(&[b"AUTH", b"internal connection", b"x"]),
33148 "-ERR Cannot authenticate as an internal connection on non-cluster instances\r\n"
33149 );
33150 assert_eq!(
33151 f.run(&[b"DEBUG", b"INTERNAL_SECRET"]),
33152 "-ERR Internal secret is missing\r\n"
33153 );
33154 let mut f = clustered();
33155 assert_eq!(
33156 f.run(&[b"AUTH", b"internal connection", b"x"]),
33157 "-WRONGPASS invalid internal password\r\n"
33158 );
33159 assert_eq!(
33162 f.run(&[b"AUTH", b"INTERNAL CONNECTION", b"x"]),
33163 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
33164 );
33165 let secret = f.server.cluster_secret();
33166 assert_eq!(secret.len(), 40, "forty characters, like a node id");
33167 assert_eq!(
33168 f.run(&[b"DEBUG", b"INTERNAL_SECRET"]),
33169 format!(":{}\r\n", yo_common::crc::crc16(secret.as_bytes())),
33170 "what comes back is a checksum, so a test can see two nodes agree \
33171 and nobody can log in with what they read"
33172 );
33173 assert_eq!(
33174 f.run(&[b"AUTH", b"internal connection", secret.as_bytes()]),
33175 "+OK\r\n"
33176 );
33177 assert_eq!(
33178 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]),
33179 "+OK\r\n"
33180 );
33181 }
33182
33183 #[test]
33186 fn conf_says_ok_after_an_option_it_did_not_know() {
33187 let mut f = clustered();
33188 assert_eq!(f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]), "+OK\r\n");
33189 assert_eq!(
33190 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"zzz", b"1"]),
33191 "-ERR Unknown option zzz\r\n+OK\r\n"
33192 );
33193 assert_eq!(
33196 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"quantum"]),
33197 "+OK\r\n"
33198 );
33199 assert_eq!(
33201 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"node-id", b"abc"]),
33202 "-ERR Invalid node id length 3\r\n"
33203 );
33204 let unknown = b"1111111111111111111111111111111111111111";
33205 assert_eq!(
33206 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"node-id", unknown]),
33207 "-ERR Node 1111111111111111111111111111111111111111 not found in cluster\r\n"
33208 );
33209 assert_eq!(
33210 f.run(&[
33211 b"CLUSTER",
33212 b"SYNCSLOTS",
33213 b"CONF",
33214 b"node-id",
33215 b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f"
33216 ]),
33217 "+OK\r\n"
33218 );
33219 assert_eq!(
33221 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"slot-info", b"5:10:2"]),
33222 "+OK\r\n"
33223 );
33224 for bad in [b"zz".as_slice(), b"16384:0:0", b"5:10:2:3", b"5:-1:0"] {
33225 assert_eq!(
33226 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"slot-info", bad]),
33227 format!(
33228 "-ERR Invalid slot info: {}\r\n",
33229 String::from_utf8_lossy(bad)
33230 )
33231 );
33232 }
33233 assert_eq!(
33236 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"asm-task", b"x"]),
33237 "-ERR CLUSTER SYNCSLOTS CONF ASM-TASK only allowed on replica\r\n"
33238 );
33239 assert_eq!(
33240 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT", b"UNMARK"]),
33241 "+OK\r\n"
33242 );
33243 let (flow, _) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
33244 assert_eq!(flow, Flow::Close, "and the door shuts again");
33245 }
33246
33247 #[test]
33250 fn the_slot_ranges_of_a_sync_are_checked_in_full() {
33251 let mut f = clustered();
33252 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33253 let id = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
33254 fn sync<'a>(id: &'a [u8], slots: &[&'a [u8]]) -> Vec<&'a [u8]> {
33255 let mut parts: Vec<&[u8]> = vec![b"CLUSTER", b"SYNCSLOTS", b"SYNC", id];
33256 parts.extend_from_slice(slots);
33257 parts
33258 }
33259 let bar = BAR.to_string();
33260 let bar = bar.as_bytes();
33261 assert_eq!(
33262 f.run(&sync(id, &[b"5", b"4"])),
33263 "-ERR start slot number 5 is greater than end slot number 4\r\n"
33264 );
33265 assert_eq!(
33266 f.run(&sync(id, &[b"99999", b"2"])),
33267 "-ERR Invalid or out of range slot\r\n"
33268 );
33269 assert_eq!(
33272 f.run(&sync(id, &[b"1", b"2", b"2", b"3"])),
33273 "-ERR Slot 2 specified multiple times\r\n"
33274 );
33275 assert_eq!(
33278 f.run(&sync(id, &[b"1", b"2", b"3", b"4"])),
33279 "+RDBCHANNELSYNCSLOTS\r\n"
33280 );
33281 assert_eq!(
33282 f.run(&[b"CLUSTER", b"MIGRATION", b"CANCEL", b"ALL"]),
33283 ":1\r\n"
33284 );
33285 f.server.cluster_hand_over(BAR, 1);
33287 assert_eq!(
33288 f.run(&sync(id, &[bar])),
33289 "-ERR syntax error\r\n",
33290 "one slot number is not a range, and a shape it does not know is a \
33291 syntax error rather than a count it can complain about"
33292 );
33293 assert_eq!(
33294 f.run(&sync(id, &[bar, bar])),
33295 "-ERR This node is not the owner of the slots\r\n"
33296 );
33297 f.server.cluster_moving(FOO, Some(1), None);
33299 assert_eq!(
33300 f.run(&sync(id, &[b"1", b"2"])),
33301 "-ERR all slot states must be STABLE to start a slot migration task.\r\n"
33302 );
33303 }
33304
33305 #[test]
33313 fn a_sync_and_an_rdbchannel_hand_over_the_slots_asked_for() {
33314 let mut f = clustered();
33315 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33316 f.run(&[b"SET", b"foo", b"in the range"]);
33317 f.run(&[b"SET", b"bar", b"outside it"]);
33318 let id = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
33319 let foo = FOO.to_string();
33320 let foo = foo.as_bytes();
33321
33322 assert_eq!(
33324 f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ALL"]),
33325 "*0\r\n"
33326 );
33327 assert_eq!(
33328 f.run(&[b"CLUSTER", b"MIGRATION", b"CANCEL", b"ALL"]),
33329 ":0\r\n"
33330 );
33331
33332 assert_eq!(
33335 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"RDBCHANNEL", id]),
33336 "-ERR No slot migration task in progress\r\n"
33337 );
33338 assert_eq!(
33339 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"SYNC", id, foo, foo]),
33340 "+RDBCHANNELSYNCSLOTS\r\n"
33341 );
33342 let status = f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ALL"]);
33344 assert!(status.starts_with("*1\r\n"), "{status:?}");
33345 assert!(status.contains("wait-rdbchannel"), "{status:?}");
33346 assert!(status.contains("migrate"), "{status:?}");
33347 assert_eq!(
33349 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"SYNC", id, foo, foo]),
33350 "-ERR Another ASM task is already in progress\r\n"
33351 );
33352
33353 let snapshot = f.raw(&[b"CLUSTER", b"SYNCSLOTS", b"RDBCHANNEL", id]);
33354 let text = String::from_utf8_lossy(&snapshot);
33355 assert!(text.starts_with("+SLOTSSNAPSHOT\r\n"), "{text:?}");
33356 assert!(text.contains("$8\r\nFUNCTION\r\n"), "{text:?}");
33357 assert!(
33358 text.contains("$3\r\nSET\r\n$3\r\nfoo\r\n$12\r\nin the range\r\n"),
33359 "{text:?}"
33360 );
33361 assert!(!text.contains("$3\r\nbar\r\n"), "{text:?}");
33362 assert!(
33363 text.ends_with("$7\r\nCLUSTER\r\n$9\r\nSYNCSLOTS\r\n$12\r\nSNAPSHOT-EOF\r\n"),
33364 "{text:?}"
33365 );
33366 let status = f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ID", id]);
33368 assert!(status.contains("send-stream"), "{status:?}");
33369 assert_eq!(
33370 f.run(&[b"CLUSTER", b"MIGRATION", b"CANCEL", b"ID", id]),
33371 ":1\r\n"
33372 );
33373 let status = f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ID", id]);
33375 assert!(status.contains("canceled"), "{status:?}");
33376 assert!(
33377 status.contains("Cancelled due to user request"),
33378 "{status:?}"
33379 );
33380 }
33381
33382 #[test]
33385 fn a_replica_only_hears_the_settings_and_only_from_its_master() {
33386 let mut server = Server::new();
33387 server.enable_cluster("", 7000);
33388 let of = server.cluster_pretend_node(
33389 "5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f",
33390 "10.0.0.9",
33391 7002,
33392 );
33393 server.cluster_pretend_follower(of);
33394 let mut f = Fixture::on(server);
33395 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33396 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
33397 assert_eq!(
33398 reply,
33399 "-ERR CLUSTER SYNCSLOTS subcommands are only allowed for master\r\n"
33400 );
33401 assert_eq!(flow, Flow::Close);
33402 f.session.serve_master(true);
33406 assert_eq!(
33407 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]),
33408 "+OK\r\n"
33409 );
33410 assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"SNAPSHOT-EOF"]), "");
33411 assert_eq!(
33412 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"asm-task", b"x"]),
33413 "-ERR Failed to handle master task: x\r\n+OK\r\n",
33414 "there is no migration for a replica to follow along with yet, and \
33415 this option is one the reference keeps going past as well"
33416 );
33417 }
33418
33419 #[test]
33422 fn the_one_way_arms_of_the_protocol_say_nothing_back() {
33423 let mut f = clustered();
33424 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33425 assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"ACK", b"x", b"1"]), "");
33426 assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"FAIL", b"boom"]), "");
33427 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"STREAM-EOF"]);
33430 assert_eq!(reply, "");
33431 assert_eq!(flow, Flow::Close);
33432 let mut f = clustered();
33434 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33435 assert_eq!(
33436 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"RDBCHANNEL", b"abc"]),
33437 "-ERR Invalid task id\r\n"
33438 );
33439 assert_eq!(
33440 f.run(&[
33441 b"CLUSTER",
33442 b"SYNCSLOTS",
33443 b"RDBCHANNEL",
33444 b"0000000000000000000000000000000000000000"
33445 ]),
33446 "-ERR No slot migration task in progress\r\n"
33447 );
33448 assert_eq!(
33449 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"NONSENSE"]),
33450 "-ERR syntax error\r\n"
33451 );
33452 }
33453
33454 #[test]
33457 fn a_transaction_is_refused_when_it_is_queued() {
33458 let mut f = clustered();
33459 f.server.cluster_hand_over(BAR, 1);
33460 assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
33461 assert_eq!(
33462 f.run(&[b"GET", b"bar"]),
33463 format!("-MOVED {BAR} 10.0.0.9:7002\r\n")
33464 );
33465 assert_eq!(
33466 f.run(&[b"EXEC"]),
33467 "-EXECABORT Transaction discarded because of previous errors.\r\n"
33468 );
33469 }
33470
33471 #[test]
33474 fn select_and_swapdb_are_not_cluster_commands() {
33475 let mut f = clustered();
33476 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
33477 assert_eq!(
33478 f.run(&[b"SELECT", b"1"]),
33479 "-ERR SELECT is not allowed in cluster mode\r\n"
33480 );
33481 assert_eq!(
33482 f.run(&[b"SWAPDB", b"0", b"1"]),
33483 "-ERR SWAPDB is not allowed in cluster mode\r\n"
33484 );
33485 }
33486
33487 #[test]
33490 fn a_plain_server_has_no_cluster_in_it() {
33491 let mut f = Fixture::new();
33492 for argv in [
33493 &[b"CLUSTER".as_slice(), b"INFO".as_slice()][..],
33494 &[b"CLUSTER", b"MYID"],
33495 &[b"CLUSTER", b"SLOTS"],
33496 &[b"CLUSTER", b"HELP"],
33497 &[b"ASKING"],
33498 &[b"READONLY"],
33499 &[b"READWRITE"],
33500 ] {
33501 assert_eq!(
33502 f.run(argv),
33503 "-ERR This instance has cluster support disabled\r\n",
33504 "{argv:?}"
33505 );
33506 }
33507 assert_eq!(
33509 f.run(&[b"CLUSTER", b"KEYSLOT"]),
33510 "-ERR wrong number of arguments for 'cluster|keyslot' command\r\n"
33511 );
33512 }
33513}