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]]);
8839 let big = int_of(&f.run(&[b"MEMORY", b"USAGE", b"s"]));
8840 assert!(
8841 big - small >= 995 && big - small <= 1005,
8842 "{small} then {big}"
8843 );
8844 f.run(&[b"SADD", b"one", b"a"]);
8847 f.run(&[b"SADD", b"many", b"a"]);
8848 for i in 0..100u32 {
8849 f.run(&[b"SADD", b"many", format!("member:{i}").as_bytes()]);
8850 }
8851 assert!(
8852 int_of(&f.run(&[b"MEMORY", b"USAGE", b"many"]))
8853 > int_of(&f.run(&[b"MEMORY", b"USAGE", b"one"]))
8854 );
8855 assert_eq!(
8858 f.run(&[b"MEMORY", b"USAGE", b"many"]),
8859 f.run(&[b"MEMORY", b"USAGE", b"many"])
8860 );
8861 }
8862
8863 #[test]
8864 fn memory_usage_reads_samples_and_does_not_use_it() {
8865 let mut f = Fixture::new();
8866 f.run(&[b"SET", b"s", b"v"]);
8867 let plain = f.run(&[b"MEMORY", b"USAGE", b"s"]);
8868 for count in [b"0".as_slice(), b"1", b"5", b"1000"] {
8869 assert_eq!(
8870 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", count]),
8871 plain
8872 );
8873 }
8874 assert_eq!(
8877 f.run(&[
8878 b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"1", b"SAMPLES", b"2"
8879 ]),
8880 plain
8881 );
8882 assert_eq!(
8883 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES"]),
8884 "-ERR syntax error\r\n"
8885 );
8886 assert_eq!(
8887 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"-1"]),
8888 "-ERR syntax error\r\n"
8889 );
8890 assert_eq!(
8891 f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"nine"]),
8892 "-ERR value is not an integer or out of range\r\n"
8893 );
8894 assert_eq!(
8895 f.run(&[b"MEMORY", b"USAGE", b"s", b"BAD", b"1"]),
8896 "-ERR syntax error\r\n"
8897 );
8898 assert_eq!(
8899 f.run(&[b"MEMORY", b"USAGE"]),
8900 "-ERR wrong number of arguments for 'memory|usage' command\r\n"
8901 );
8902 }
8903
8904 #[test]
8905 fn memory_stats_grows_a_field_for_every_database_holding_a_key() {
8906 let mut f = Fixture::new();
8907 assert!(
8908 f.run(&[b"MEMORY", b"STATS"]).starts_with("*72\r\n"),
8909 "thirty six pairs on a server nobody has written to"
8910 );
8911 f.run(&[b"SET", b"a", b"1"]);
8912 assert!(f.run(&[b"MEMORY", b"STATS"]).starts_with("*74\r\n"));
8913 f.run(&[b"SELECT", b"7"]);
8914 f.run(&[b"SET", b"b", b"2"]);
8915 let reply = f.run(&[b"MEMORY", b"STATS"]);
8916 assert!(reply.starts_with("*76\r\n"));
8917 assert!(reply.contains("\r\n$4\r\ndb.0\r\n"));
8918 assert!(reply.contains("\r\n$4\r\ndb.7\r\n"));
8919 assert!(reply.contains("overhead.hashtable.main"));
8921 assert!(reply.contains("overhead.hashtable.expires"));
8922 assert!(reply.contains("fragmentation.bytes"));
8923 }
8924
8925 #[test]
8926 fn memory_answers_the_four_that_only_look() {
8927 let mut f = Fixture::new();
8928 assert!(f.run(&[b"MEMORY", b"HELP"]).starts_with("*14\r\n+MEMORY "));
8929 assert_eq!(f.run(&[b"MEMORY", b"PURGE"]), "+OK\r\n");
8930 assert_eq!(
8931 f.run(&[b"MEMORY", b"MALLOC-STATS"]),
8932 "$45\r\nStats not supported for the current allocator\r\n"
8933 );
8934 assert!(
8937 f.run(&[b"MEMORY", b"DOCTOR"])
8938 .contains("my issues detector can't be used in these conditions")
8939 );
8940 assert_eq!(
8941 f.run(&[b"MEMORY", b"NOPE"]),
8942 "-ERR unknown subcommand 'NOPE'. Try MEMORY HELP.\r\n"
8943 );
8944 for sub in [
8945 b"STATS".as_slice(),
8946 b"DOCTOR",
8947 b"PURGE",
8948 b"MALLOC-STATS",
8949 b"HELP",
8950 ] {
8951 let name = String::from_utf8_lossy(sub).to_lowercase();
8952 assert_eq!(
8953 f.run(&[b"MEMORY", sub, b"extra"]),
8954 format!("-ERR wrong number of arguments for 'memory|{name}' command\r\n"),
8955 "the subcommand is named and not the container"
8956 );
8957 }
8958 assert_eq!(
8959 f.run(&[b"MEMORY"]),
8960 "-ERR wrong number of arguments for 'memory' command\r\n"
8961 );
8962 }
8963
8964 #[test]
8965 fn command_getkeys_finds_the_key_memory_usage_names() {
8966 let mut f = Fixture::new();
8967 assert_eq!(
8968 f.run(&[b"COMMAND", b"GETKEYS", b"MEMORY", b"USAGE", b"k"]),
8969 "*1\r\n$1\r\nk\r\n"
8970 );
8971 assert!(
8974 f.run(&[b"COMMAND", b"GETKEYS", b"MEMORY", b"DOCTOR"])
8975 .starts_with("-ERR ")
8976 );
8977 }
8978
8979 #[test]
8980 fn config_moves_the_ladder_and_object_encoding_agrees() {
8981 let mut f = Fixture::new();
8982 assert_eq!(
8983 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
8984 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
8985 "512 and not the 128 everyone remembers, which is what 8.10.1 says"
8986 );
8987 assert_eq!(
8990 f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
8991 "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
8992 );
8993 assert!(
8994 f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
8995 .starts_with("*8\r\n")
8996 );
8997 assert!(
8998 f.run(&[b"CONFIG", b"GET", b"set-max-*"])
8999 .starts_with("*6\r\n")
9000 );
9001
9002 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
9003 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
9004
9005 assert_eq!(
9006 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
9007 "+OK\r\n",
9008 "written under the old name and read back under the new one"
9009 );
9010 assert_eq!(
9011 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
9012 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
9013 );
9014 assert_eq!(
9015 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
9016 "$8\r\nlistpack\r\n",
9017 "the hash that already exists is left exactly where it was"
9018 );
9019 f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
9020 assert_eq!(
9021 f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
9022 "$9\r\nhashtable\r\n",
9023 "and the next one built goes straight to a table"
9024 );
9025
9026 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
9028 f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
9029 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
9030 f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
9031 f.run(&[b"SADD", b"s2", b"abcdefgh"]);
9032 assert_eq!(
9033 f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
9034 "$9\r\nhashtable\r\n"
9035 );
9036 }
9037
9038 #[test]
9039 fn config_set_takes_all_of_the_ladder_or_none_of_it() {
9040 let mut f = Fixture::new();
9041 assert_eq!(
9042 f.run(&[
9043 b"CONFIG",
9044 b"SET",
9045 b"hash-max-listpack-entries",
9046 b"7",
9047 b"set-max-listpack-entries",
9048 b"abc"
9049 ]),
9050 "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
9051 );
9052 assert_eq!(
9053 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
9054 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
9055 "the pair in front of the bad one did not go in"
9056 );
9057 assert_eq!(
9060 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
9061 "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
9062 );
9063 assert_eq!(
9064 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
9065 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
9066 );
9067 assert_eq!(
9070 f.run(&[
9071 b"CONFIG",
9072 b"SET",
9073 b"set-max-intset-entries",
9074 b"99999999999999999999"
9075 ]),
9076 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
9077 );
9078 assert_eq!(
9079 f.run(&[
9080 b"CONFIG",
9081 b"SET",
9082 b"set-max-intset-entries",
9083 b"9223372036854775807"
9084 ]),
9085 "+OK\r\n"
9086 );
9087 }
9088
9089 #[test]
9090 fn a_setting_moved_on_one_database_moved_on_all_of_them() {
9091 let mut f = Fixture::new();
9092 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
9093 f.run(&[b"SELECT", b"3"]);
9094 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9095 assert_eq!(
9096 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
9097 "$9\r\nhashtable\r\n",
9098 "these are one server wide number in Redis, whatever a Keyspace carries"
9099 );
9100 }
9101
9102 #[test]
9103 fn info_reports_the_numbers_it_can_stand_behind() {
9104 let mut f = Fixture::new();
9105 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
9106 let all = f.run(&[b"INFO"]);
9107 assert!(all.contains("redis_version:8.8.0"), "{all}");
9108 assert!(
9109 all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
9110 "{all}"
9111 );
9112 assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
9113 assert!(all.contains("role:master"), "{all}");
9114 let clients = f.run(&[b"INFO", b"clients"]);
9116 assert!(clients.contains("connected_clients:0"), "{clients}");
9117 assert!(!clients.contains("redis_version"), "{clients}");
9118 assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
9119 }
9120
9121 #[test]
9128 fn commandstats_is_asked_for_and_replication_is_not() {
9129 let mut f = Fixture::new();
9130 for arg in ["", "all", "default", "everything"] {
9131 let info = if arg.is_empty() {
9132 f.run(&[b"INFO"])
9133 } else {
9134 f.run(&[b"INFO", arg.as_bytes()])
9135 };
9136 assert!(info.contains("redis_version"), "{arg}: {info}");
9137 assert!(info.contains("used_cpu_user"), "{arg}: {info}");
9138 assert!(info.contains("used_memory"), "{arg}: {info}");
9139 assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
9140 let asked = arg == "all" || arg == "everything";
9141 assert_eq!(
9142 info.contains("rejected_calls"),
9143 asked,
9144 "{arg} should{} carry the command counters: {info}",
9145 if asked { "" } else { " not" }
9146 );
9147 }
9148
9149 let cpu = f.run(&[b"INFO", b"cpu"]);
9150 assert!(cpu.contains("used_cpu_user"), "{cpu}");
9151 assert!(!cpu.contains("used_memory"), "{cpu}");
9152
9153 let stats = f.run(&[b"INFO", b"commandSTATS"]);
9156 assert!(!stats.contains("used_memory"), "{stats}");
9157 assert!(stats.contains("rejected_calls"), "{stats}");
9158
9159 let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
9161 assert!(pair.contains("used_cpu_user"), "{pair}");
9162 assert!(!pair.contains("master_repl_offset"), "{pair}");
9163
9164 let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
9165 assert!(with_all.contains("used_memory"), "{with_all}");
9166 assert!(with_all.contains("master_repl_offset"), "{with_all}");
9167 assert!(with_all.contains("rejected_calls"), "{with_all}");
9168 assert_eq!(
9170 with_all.matches("used_cpu_user_children").count(),
9171 1,
9172 "{with_all}"
9173 );
9174
9175 let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
9176 assert!(with_default.contains("used_memory"), "{with_default}");
9177 assert!(
9178 with_default.contains("master_repl_offset"),
9179 "{with_default}"
9180 );
9181 assert!(!with_default.contains("rejected_calls"), "{with_default}");
9182 assert_eq!(
9183 with_default.matches("used_cpu_user_children").count(),
9184 1,
9185 "{with_default}"
9186 );
9187 }
9188
9189 #[test]
9199 fn the_threads_section_says_where_the_work_landed() {
9200 let mut server = Server::new();
9201 server.set_threads(4);
9202 let mut f = Fixture::on(server);
9203 for _ in 0..3 {
9204 f.run(&[b"PING"]);
9205 }
9206
9207 assert!(!f.run(&[b"INFO"]).contains("# Threads"));
9208 assert!(f.run(&[b"INFO", b"all"]).contains("# Threads"));
9209
9210 let info = f.run(&[b"INFO", b"threads"]);
9211 assert!(info.contains("io_threads:4"), "{info}");
9212 for at in 0..4 {
9213 assert!(info.contains(&format!("thread_{at}:clients=")), "{info}");
9214 }
9215 assert!(!info.contains("thread_4:"), "{info}");
9216
9217 let per = f.server.per_thread();
9218 assert_eq!(per.len(), 4);
9219 assert_eq!(
9220 per.iter().map(|t| t.commands).sum::<u64>(),
9221 f.server.totals().commands
9222 );
9223 assert_eq!(per.iter().filter(|t| t.commands > 0).count(), 1, "{per:?}");
9224 }
9225
9226 #[test]
9235 fn the_thread_count_is_the_same_number_wherever_it_is_asked_for() {
9236 let mut f = Fixture::new();
9237 assert!(f.run(&[b"INFO", b"server"]).contains("io_threads_active:1"));
9238 assert_eq!(
9239 f.run(&[b"CONFIG", b"GET", b"io-threads"]),
9240 "*2\r\n$10\r\nio-threads\r\n$1\r\n1\r\n"
9241 );
9242
9243 let mut server = Server::new();
9244 server.set_threads(4);
9245 let mut f = Fixture::on(server);
9246 let info = f.run(&[b"INFO", b"all"]);
9247 assert!(info.contains("io_threads_active:4"), "{info}");
9248 assert!(info.contains("io_threads:4"), "{info}");
9249 assert_eq!(
9250 f.run(&[b"CONFIG", b"GET", b"io-threads"]),
9251 "*2\r\n$10\r\nio-threads\r\n$1\r\n4\r\n"
9252 );
9253 assert_eq!(f.run(&[b"CONFIG", b"SET", b"io-threads", b"4"]), "+OK\r\n");
9256 assert_eq!(
9257 f.run(&[b"CONFIG", b"SET", b"io-threads", b"1"]),
9258 "-ERR CONFIG SET failed (possibly related to argument 'io-threads') - can't set immutable config\r\n"
9259 );
9260 assert_eq!(
9261 f.run(&[b"CONFIG", b"SET", b"io-threads", b"lots"]),
9262 "-ERR CONFIG SET failed (possibly related to argument 'io-threads') - can't set immutable config\r\n"
9263 );
9264 }
9265
9266 #[test]
9275 fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
9276 let mut f = Fixture::new();
9277 let info = f.run(&[b"INFO", b"memory"]);
9278 for field in [
9279 "total_system_memory:",
9280 "mem_cgroup_limit:",
9281 "mem_limit:",
9282 "mem_budget:",
9283 ] {
9284 assert!(info.contains(field), "no {field} in {info}");
9285 }
9286
9287 let field = |name: &str| -> u64 {
9288 info.lines()
9289 .find_map(|l| l.strip_prefix(name))
9290 .unwrap_or_else(|| panic!("no {name} in {info}"))
9291 .trim()
9292 .parse()
9293 .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
9294 };
9295 let limit = field("mem_limit:");
9296 assert_eq!(field("mem_budget:"), limit / 4, "{info}");
9297 if limit != 0 {
9300 let host = field("total_system_memory:");
9301 let cgroup = field("mem_cgroup_limit:");
9302 assert!(
9303 limit == host || limit == cgroup,
9304 "the limit came from neither number: {info}"
9305 );
9306 }
9307 }
9308
9309 #[test]
9318 fn a_command_counts_what_it_did_separately_from_what_it_refused() {
9319 let mut f = Fixture::new();
9320 f.run(&[b"SET", b"k", b"v"]);
9321 f.run(&[b"SET", b"k", b"w"]);
9322 f.run(&[b"LPUSH", b"k", b"x"]);
9324 f.run(&[b"LPUSH", b"k"]);
9326
9327 let stats = f.run(&[b"INFO", b"commandstats"]);
9328 assert!(
9329 stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
9330 "{stats}"
9331 );
9332 assert!(
9333 stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
9334 "{stats}"
9335 );
9336 assert!(
9337 !stats.contains("cmdstat_zadd"),
9338 "a command nobody has sent has no row: {stats}"
9339 );
9340 }
9341
9342 #[test]
9346 fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
9347 let (dead, live) = if cfg!(miri) {
9354 (375, 125)
9355 } else {
9356 (3_000, 1_000)
9357 };
9358 let mut f = Fixture::new();
9359 for i in 0..dead {
9360 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
9361 }
9362 for i in 0..live {
9363 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
9364 }
9365 let all = format!(":{}\r\n", dead + live);
9366 assert_eq!(f.run(&[b"DBSIZE"]), all);
9367 f.advance(100);
9368 assert_eq!(
9369 f.run(&[b"DBSIZE"]),
9370 all,
9371 "DBSIZE counts records and nothing has read past the dead ones yet"
9372 );
9373
9374 let rest = format!(":{live}\r\n");
9376 let mut spent = 0;
9377 for _ in 0..2_000 {
9378 spent += f.server.expire_step(4096);
9379 if f.run(&[b"DBSIZE"]) == rest {
9380 break;
9381 }
9382 }
9383 assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
9384 assert!(
9385 f.run(&[b"INFO", b"stats"])
9386 .contains(&format!("expired_keys:{dead}"))
9387 );
9388 for i in 0..live {
9389 assert_eq!(
9390 f.run(&[b"GET", format!("k{i}").as_bytes()]),
9391 "$1\r\nv\r\n",
9392 "it took a key that had no deadline"
9393 );
9394 }
9395 }
9396
9397 #[test]
9398 fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
9399 let n = if cfg!(miri) { 200 } else { 2_000 };
9403 let mut f = Fixture::new();
9404 for i in 0..n {
9405 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
9406 }
9407 assert_eq!(f.server.expire_step(4096), 0);
9408 f.run(&[b"SELECT", b"3"]);
9410 f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
9411 f.advance(100);
9412 for _ in 0..64 {
9413 f.server.expire_step(4096);
9414 }
9415 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9416 f.run(&[b"SELECT", b"0"]);
9417 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
9418 assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
9419 }
9420
9421 #[test]
9424 fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
9425 let mut f = Fixture::new();
9426 for i in 0..500u32 {
9427 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
9428 }
9429 f.advance(100);
9430 let at = f.server.striped(0).now_ms();
9431 f.server.set_clock_ms(at);
9432 assert!(f.server.expire_slice(8) > 0, "the first one works");
9436 for _ in 0..1_000 {
9437 assert_eq!(
9438 f.server.expire_slice(8),
9439 0,
9440 "the millisecond has not moved and neither should this"
9441 );
9442 }
9443 assert!(
9444 f.server.striped(0).expires() > 400,
9445 "there is plenty left to take"
9446 );
9447 f.server.set_clock_ms(at + 1);
9448 assert!(f.server.expire_slice(8) > 0, "and then it goes again");
9449 }
9450
9451 #[test]
9454 fn info_keyspace_counts_the_keys_that_have_a_deadline() {
9455 let mut f = Fixture::new();
9456 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
9457 assert!(
9458 f.run(&[b"INFO", b"keyspace"])
9459 .contains("db0:keys=3,expires=0"),
9460 "none of them has one yet"
9461 );
9462 f.run(&[b"EXPIRE", b"a", b"1000"]);
9463 f.run(&[b"EXPIRE", b"b", b"1000"]);
9464 let two = f.run(&[b"INFO", b"keyspace"]);
9465 assert!(two.contains("db0:keys=3,expires=2"), "{two}");
9466 f.run(&[b"PERSIST", b"a"]);
9467 f.run(&[b"DEL", b"b"]);
9468 let none = f.run(&[b"INFO", b"keyspace"]);
9469 assert!(none.contains("db0:keys=2,expires=0"), "{none}");
9470
9471 f.run(&[b"SELECT", b"1"]);
9473 f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
9474 let both = f.run(&[b"INFO", b"keyspace"]);
9475 assert!(both.contains("db0:keys=2,expires=0"), "{both}");
9476 assert!(both.contains("db1:keys=1,expires=1"), "{both}");
9477 }
9478
9479 #[cfg(unix)]
9486 #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
9487 #[test]
9488 fn info_cpu_reports_processor_time_that_was_really_measured() {
9489 let mut f = Fixture::new();
9490 let cpu = f.run(&[b"INFO", b"cpu"]);
9491 assert!(cpu.contains("# CPU"), "{cpu}");
9492 assert!(cpu.contains("used_cpu_user:"), "{cpu}");
9494 assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
9495 assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
9496 assert!(!cpu.contains("redis_version"), "{cpu}");
9497
9498 let before = used_cpu_user(&cpu);
9502 let mut n = 0u64;
9503 let mut rounds = 0;
9504 while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
9505 for i in 0..1_000_000u64 {
9506 n = n.wrapping_add(i.wrapping_mul(i));
9507 }
9508 rounds += 1;
9509 assert!(rounds < 1_000, "cpu time never moved, n is {n}");
9513 }
9514 }
9515
9516 #[cfg(unix)]
9518 fn used_cpu_user(info: &str) -> f64 {
9519 info.lines()
9520 .find_map(|l| l.strip_prefix("used_cpu_user:"))
9521 .expect("no used_cpu_user in the reply")
9522 .trim()
9523 .parse()
9524 .expect("used_cpu_user is not a number")
9525 }
9526
9527 #[test]
9533 fn a_command_that_fails_leaves_nothing_half_written() {
9534 let mut f = Fixture::new();
9535 let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
9536 assert_eq!(reply, "-ERR offset is out of range\r\n");
9537 assert!(!reply.contains(':'), "no integer went out in front of it");
9538 }
9539
9540 #[test]
9541 fn quit_answers_first_and_closes_after() {
9542 let mut f = Fixture::new();
9543 let (flow, reply) = f.flow(&[b"QUIT"]);
9544 assert_eq!(reply, "+OK\r\n");
9545 assert_eq!(flow, Flow::Close);
9546 }
9547
9548 #[test]
9555 fn shutdown_writes_nothing_and_sets_the_flag() {
9556 let mut f = Fixture::new();
9557 assert!(!f.server.stopping(), "nobody has asked yet");
9558
9559 let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
9560 assert_eq!(reply, "");
9561 assert_eq!(flow, Flow::Close);
9562 assert!(f.server.stopping());
9563 }
9564
9565 #[test]
9577 fn shutdown_takes_the_flags_redis_takes() {
9578 let s = Saves::new("shutdown-flags");
9579 for flags in [
9580 &[b"NOSAVE".as_slice()][..],
9581 &[b"SAVE"],
9582 &[b"NOW"],
9583 &[b"FORCE"],
9584 &[b"nosave"],
9585 &[b"NOW", b"NOW"],
9586 &[b"SAVE", b"SAVE"],
9587 &[b"NOSAVE", b"NOW", b"FORCE"],
9588 ] {
9589 let mut f = Fixture::new();
9590 f.server.set_dir(s.dir.clone());
9591 let mut parts = vec![b"SHUTDOWN".as_slice()];
9592 parts.extend_from_slice(flags);
9593 let (flow, reply) = f.flow(&parts);
9594 assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
9595 assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
9596 assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
9597 }
9598
9599 for flags in [
9600 &[b"BOGUS".as_slice()][..],
9601 &[b"SAVE", b"NOSAVE"],
9602 &[b"NOSAVE", b"SAVE"],
9603 &[b"ABORT", b"NOW"],
9604 &[b"NOSAVE", b"ABORT"],
9605 &[b"NOW", b"FORCE", b"ABORT"],
9606 ] {
9607 let mut f = Fixture::new();
9608 let mut parts = vec![b"SHUTDOWN".as_slice()];
9609 parts.extend_from_slice(flags);
9610 assert_eq!(
9611 f.run(&parts),
9612 "-ERR syntax error\r\n",
9613 "SHUTDOWN {flags:?} was accepted"
9614 );
9615 assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
9616 }
9617 }
9618
9619 #[test]
9627 fn shutdown_abort_never_has_anything_to_abort() {
9628 let mut f = Fixture::new();
9629 for parts in [
9630 &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
9631 &[b"SHUTDOWN", b"ABORT", b"ABORT"],
9632 ] {
9633 assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
9634 assert!(!f.server.stopping(), "an abort stopped the server");
9635 }
9636 }
9637
9638 struct Backups {
9646 f: Fixture,
9647 dir: PathBuf,
9648 }
9649
9650 impl Backups {
9651 fn new(name: &str) -> Backups {
9652 let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
9653 let _ = std::fs::remove_dir_all(&dir);
9654 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
9655 let mut f = Fixture::new();
9656 f.server.set_dir(dir.clone());
9657 Backups { f, dir }
9658 }
9659
9660 fn run(&mut self, parts: &[&[u8]]) -> String {
9661 self.f.run(parts)
9662 }
9663
9664 fn files(&self) -> Vec<String> {
9666 let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
9667 Ok(entries) => entries
9668 .filter_map(|e| e.ok())
9669 .map(|e| e.file_name().to_string_lossy().into_owned())
9670 .collect(),
9671 Err(_) => Vec::new(),
9672 };
9673 names.sort();
9674 names
9675 }
9676
9677 fn read(&self, name: &str) -> Vec<u8> {
9678 std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
9679 }
9680 }
9681
9682 impl Drop for Backups {
9683 fn drop(&mut self) {
9684 let _ = std::fs::remove_dir_all(&self.dir);
9685 }
9686 }
9687
9688 #[test]
9691 fn backup_walks_the_states_the_reference_walks() {
9692 let mut b = Backups::new("states");
9693 let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
9694
9695 assert!(status(&mut b).contains("idle"));
9696 assert!(b.files().is_empty(), "an idle server has written a backup");
9697
9698 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
9699 assert!(status(&mut b).contains("incrementing"));
9700 assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
9701
9702 assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
9703 assert!(status(&mut b).contains("sealed"));
9704 assert_eq!(
9705 b.files(),
9706 [
9707 "appendonly.aof.1.base.rdb",
9708 "appendonly.aof.1.incr.aof",
9709 "appendonly.aof.manifest",
9710 ]
9711 );
9712
9713 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
9714 assert!(status(&mut b).contains("idle"));
9715 assert!(b.files().is_empty(), "cleanup left something behind");
9716 }
9717
9718 #[test]
9720 fn backup_refuses_the_moves_the_reference_refuses() {
9721 let mut b = Backups::new("refusals");
9722
9723 assert_eq!(
9724 b.run(&[b"BACKUP", b"SEAL"]),
9725 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
9726 );
9727 assert_eq!(
9728 b.run(&[b"BACKUP", b"ABORT"]),
9729 "-ERR No backup in progress\r\n"
9730 );
9731 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
9734
9735 b.run(&[b"BACKUP", b"START"]);
9736 assert_eq!(
9737 b.run(&[b"BACKUP", b"START"]),
9738 "-ERR A backup is already in progress, ABORT it first\r\n"
9739 );
9740 assert_eq!(
9741 b.run(&[b"BACKUP", b"CLEANUP"]),
9742 "-ERR Backup is in progress\r\n"
9743 );
9744
9745 b.run(&[b"BACKUP", b"SEAL"]);
9746 assert_eq!(
9747 b.run(&[b"BACKUP", b"START"]),
9748 "-ERR A sealed backup exists, CLEANUP it first\r\n"
9749 );
9750 assert_eq!(
9751 b.run(&[b"BACKUP", b"SEAL"]),
9752 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
9753 );
9754 assert_eq!(
9755 b.run(&[b"BACKUP", b"ABORT"]),
9756 "-ERR No backup in progress\r\n"
9757 );
9758 }
9759
9760 #[test]
9767 fn backup_abort_removes_the_file_and_says_who_did_it() {
9768 let mut b = Backups::new("abort");
9769 b.run(&[b"BACKUP", b"START"]);
9770 assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
9771
9772 let status = b.run(&[b"BACKUP", b"STATUS"]);
9773 assert!(status.contains("failed"), "{status}");
9774 assert!(status.contains("aborted by user"), "{status}");
9775 assert!(b.files().is_empty(), "abort left the base file behind");
9776 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
9777
9778 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
9780 assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
9781 let status = b.run(&[b"BACKUP", b"STATUS"]);
9782 assert!(status.contains("incrementing"), "{status}");
9783 assert!(!status.contains("aborted"), "the old error was kept");
9784 }
9785
9786 #[test]
9788 fn backup_list_names_the_files_that_are_pinned_so_far() {
9789 let mut b = Backups::new("list");
9790 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
9791
9792 b.run(&[b"BACKUP", b"START"]);
9793 let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
9794 let base = base.to_string_lossy().into_owned();
9795 assert_eq!(
9796 b.run(&[b"BACKUP", b"LIST"]),
9797 format!("*1\r\n${}\r\n{base}\r\n", base.len())
9798 );
9799
9800 b.run(&[b"BACKUP", b"SEAL"]);
9801 let listed = b.run(&[b"BACKUP", b"LIST"]);
9802 assert!(listed.starts_with("*3\r\n"), "{listed}");
9803 let names: Vec<&str> = listed
9806 .lines()
9807 .filter(|l| l.starts_with('/') || l.contains(":\\"))
9808 .collect();
9809 assert_eq!(names.len(), 3, "{listed}");
9810 assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
9811 assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
9812 assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
9813 }
9814
9815 #[test]
9823 fn a_backup_holds_the_dataset_as_it_was_at_start() {
9824 let mut b = Backups::new("contents");
9825 b.run(&[b"SET", b"bk", b"v1"]);
9826 b.run(&[b"BACKUP", b"START"]);
9827 b.run(&[b"SET", b"bk", b"v2"]);
9828 b.run(&[b"BACKUP", b"SEAL"]);
9829
9830 let base = b.read("appendonly.aof.1.base.rdb");
9831 assert!(base.starts_with(b"REDIS"), "not an RDB file");
9832 assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
9833 assert!(
9834 !base.windows(2).any(|w| w == b"v2"),
9835 "the base file moved on after START"
9836 );
9837 let at = base
9842 .windows(8)
9843 .position(|w| w == b"aof-base")
9844 .expect("no aof-base aux field");
9845 assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
9846
9847 assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
9848 assert_eq!(
9849 String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
9850 "file appendonly.aof.1.base.rdb seq 1 type b\n\
9851 file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
9852 );
9853 }
9854
9855 #[test]
9858 fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
9859 let mut b = Backups::new("status");
9860 b.f.server.set_clock_ms(1_700_000_000_000);
9861
9862 assert_eq!(
9863 b.run(&[b"BACKUP", b"STATUS"]),
9864 "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
9865 $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
9866 );
9867
9868 b.f.out = Out::new(Proto::Resp3);
9869 b.run(&[b"BACKUP", b"START"]);
9870 assert_eq!(
9871 b.run(&[b"BACKUP", b"STATUS"]),
9872 "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
9873 $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
9874 );
9875
9876 b.run(&[b"BACKUP", b"SEAL"]);
9877 let sealed = b.run(&[b"BACKUP", b"STATUS"]);
9878 assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
9879 }
9880
9881 #[test]
9884 fn a_sealed_backup_is_swept_away_after_the_timeout() {
9885 let mut b = Backups::new("ttl");
9886 b.f.server.set_clock_ms(1_000_000);
9887 assert_eq!(
9888 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
9889 "+OK\r\n"
9890 );
9891 b.run(&[b"BACKUP", b"START"]);
9892 b.run(&[b"BACKUP", b"SEAL"]);
9893
9894 b.f.server.set_clock_ms(1_000_000 + 59_000);
9896 b.f.server.backup_expire();
9897 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
9898 assert_eq!(b.files().len(), 3);
9899
9900 b.f.server.set_clock_ms(1_000_000 + 60_000);
9901 b.f.server.backup_expire();
9902 let status = b.run(&[b"BACKUP", b"STATUS"]);
9903 assert!(status.contains("idle"), "{status}");
9904 assert!(b.files().is_empty(), "the timeout left the files behind");
9905
9906 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
9908 b.run(&[b"BACKUP", b"START"]);
9909 b.run(&[b"BACKUP", b"SEAL"]);
9910 b.f.server.set_clock_ms(9_000_000_000);
9911 b.f.server.backup_expire();
9912 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
9913 }
9914
9915 #[test]
9918 fn the_backup_settings_behave_the_way_the_reference_does() {
9919 let mut b = Backups::new("config");
9920 let dir = b.dir.to_string_lossy().into_owned();
9921
9922 assert_eq!(
9923 b.run(&[b"CONFIG", b"GET", b"dir"]),
9924 format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
9925 );
9926 assert_eq!(
9927 b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
9928 "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
9929 );
9930 assert_eq!(
9931 b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
9932 "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
9933 );
9934
9935 assert_eq!(
9938 b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
9939 "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
9940 );
9941 assert_eq!(
9942 b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
9943 "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
9944 );
9945 assert!(
9946 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
9947 .contains("argument couldn't be parsed into an integer")
9948 );
9949 assert!(
9950 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
9951 .contains("argument must be between 0 and 9223372036854775807 inclusive")
9952 );
9953 }
9954
9955 #[test]
9957 fn backup_help_is_the_text_the_reference_sends() {
9958 let mut f = Fixture::new();
9959 let help = f.run(&[b"BACKUP", b"HELP"]);
9960 assert!(help.starts_with("*17\r\n"), "{help}");
9961 assert!(
9962 help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
9963 );
9964 assert!(help.contains("+ Start a new backup into the configured 'backupdirname'.\r\n"));
9965 assert!(help.contains("+ Freeze the current backup (BASE + INCR + manifest).\r\n"));
9966 assert!(help.contains("+ Return this help.\r\n+HELP\r\n+ Print this help.\r\n"));
9967 }
9968
9969 #[test]
9977 fn backup_refuses_what_it_cannot_read() {
9978 let mut f = Fixture::new();
9979 assert_eq!(
9980 f.run(&[b"BACKUP"]),
9981 "-ERR wrong number of arguments for 'backup' command\r\n"
9982 );
9983 assert_eq!(
9984 f.run(&[b"BACKUP", b"START", b"x"]),
9985 "-ERR wrong number of arguments for 'backup' command\r\n"
9986 );
9987 assert_eq!(
9988 f.run(&[b"BACKUP", b"NOPE"]),
9989 "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
9990 );
9991 }
9992
9993 struct Saves {
9999 f: Fixture,
10000 dir: PathBuf,
10001 }
10002
10003 impl Saves {
10004 fn new(name: &str) -> Saves {
10005 let dir = std::env::temp_dir().join(format!("yo-save-{name}-{}", std::process::id()));
10006 let _ = std::fs::remove_dir_all(&dir);
10007 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
10008 let mut f = Fixture::new();
10009 f.server.set_dir(dir.clone());
10010 Saves { f, dir }
10011 }
10012
10013 fn run(&mut self, parts: &[&[u8]]) -> String {
10014 self.f.run(parts)
10015 }
10016
10017 fn files(&self) -> Vec<String> {
10019 let mut names: Vec<String> = match std::fs::read_dir(&self.dir) {
10020 Ok(entries) => entries
10021 .filter_map(|e| e.ok())
10022 .map(|e| e.file_name().to_string_lossy().into_owned())
10023 .collect(),
10024 Err(_) => Vec::new(),
10025 };
10026 names.sort();
10027 names
10028 }
10029
10030 fn image(&self) -> Vec<u8> {
10031 std::fs::read(self.dir.join("dump.rdb")).expect("could not read the file")
10032 }
10033
10034 fn field(&mut self, name: &str) -> String {
10036 let text = self.run(&[b"INFO", b"persistence"]);
10037 let head = format!("\r\n{name}:");
10038 let at = text.find(&head).expect("the field is not in the section");
10039 let rest = &text[at + head.len()..];
10040 rest[..rest.find("\r\n").expect("the field has no end")].to_owned()
10041 }
10042 }
10043
10044 impl Drop for Saves {
10045 fn drop(&mut self) {
10046 let _ = std::fs::remove_dir_all(&self.dir);
10047 }
10048 }
10049
10050 #[test]
10051 fn save_writes_a_file_that_carries_the_dataset() {
10052 let mut s = Saves::new("writes");
10053 s.run(&[b"SET", b"k", b"v"]);
10054 s.run(&[b"RPUSH", b"l", b"a", b"b"]);
10055 assert!(
10056 s.files().is_empty(),
10057 "a server has saved without being asked"
10058 );
10059
10060 assert_eq!(s.run(&[b"SAVE"]), "+OK\r\n");
10061 assert_eq!(s.files(), ["dump.rdb"]);
10062
10063 let image = s.image();
10068 assert!(
10069 image.starts_with(b"REDIS00"),
10070 "the header is not an RDB one"
10071 );
10072 assert!(
10073 image.windows(1).any(|w| w == [0xFF]),
10074 "there is no end marker"
10075 );
10076 assert!(image.len() > 40, "the file is too small to hold anything");
10077 }
10078
10079 #[test]
10080 fn a_save_leaves_no_temporary_file_behind() {
10081 let mut s = Saves::new("temp");
10082 s.run(&[b"SET", b"k", b"v"]);
10083 s.run(&[b"SAVE"]);
10084 s.run(&[b"BGSAVE"]);
10085 assert_eq!(s.files(), ["dump.rdb"]);
10086 }
10087
10088 #[test]
10089 fn a_save_that_cannot_write_says_so_in_one_word() {
10090 let mut s = Saves::new("nowhere");
10091 s.f.server.set_dir(s.dir.join("gone"));
10094 assert_eq!(s.run(&[b"SAVE"]), "-ERR\r\n");
10095 assert_eq!(s.field("rdb_last_bgsave_status"), "err");
10096 assert_eq!(s.field("rdb_saves"), "1");
10098 }
10099
10100 #[test]
10101 fn lastsave_starts_at_the_time_the_server_did_and_moves_on_a_save() {
10102 let mut s = Saves::new("lastsave");
10103 let started = s.run(&[b"LASTSAVE"]);
10104 assert_eq!(started, format!(":{}\r\n", s.f.server.started_ms / 1_000));
10105
10106 s.f.server.set_clock_ms(s.f.server.started_ms + 5_000);
10107 s.run(&[b"SAVE"]);
10108 let after = s.run(&[b"LASTSAVE"]);
10109 assert_eq!(after, format!(":{}\r\n", s.f.server.started_ms / 1_000 + 5));
10110
10111 s.run(&[b"SET", b"k", b"v"]);
10113 assert_eq!(s.run(&[b"LASTSAVE"]), after);
10114 }
10115
10116 #[test]
10117 fn bgsave_takes_the_one_word_it_takes_and_nothing_else() {
10118 let mut s = Saves::new("bgsave");
10119 for parts in [
10120 &[b"BGSAVE".as_slice()][..],
10121 &[b"BGSAVE", b"SCHEDULE"],
10122 &[b"BGSAVE", b"schedule"],
10123 ] {
10124 assert_eq!(s.run(parts), "+Background saving started\r\n");
10125 }
10126 for parts in [
10127 &[b"BGSAVE".as_slice(), b"x"][..],
10128 &[b"BGSAVE", b"SCHEDULE", b"x"],
10129 &[b"BGSAVE", b"SCHEDULE", b"SCHEDULE"],
10130 ] {
10131 assert_eq!(s.run(parts), "-ERR syntax error\r\n");
10132 }
10133 }
10134
10135 #[test]
10136 fn a_save_inside_a_transaction_says_it_was_scheduled() {
10137 let mut s = Saves::new("queued");
10138 assert_eq!(s.run(&[b"MULTI"]), "+OK\r\n");
10140 assert_eq!(
10141 s.run(&[b"SAVE"]),
10142 "-ERR Command not allowed inside a transaction\r\n"
10143 );
10144 assert_eq!(
10145 s.run(&[b"EXEC"]),
10146 "-EXECABORT Transaction discarded because of previous errors.\r\n"
10147 );
10148
10149 assert_eq!(s.run(&[b"MULTI"]), "+OK\r\n");
10150 assert_eq!(s.run(&[b"BGSAVE"]), "+QUEUED\r\n");
10151 assert_eq!(s.run(&[b"BGREWRITEAOF"]), "+QUEUED\r\n");
10152 assert_eq!(
10153 s.run(&[b"EXEC"]),
10154 "*2\r\n+Background saving scheduled\r\n\
10155 +Background append only file rewriting scheduled\r\n"
10156 );
10157 assert_eq!(s.files(), ["dump.rdb"]);
10159 }
10160
10161 #[test]
10162 fn a_rewrite_counts_itself_and_writes_nothing() {
10163 let mut s = Saves::new("rewrite");
10164 assert_eq!(
10165 s.run(&[b"BGREWRITEAOF"]),
10166 "+Background append only file rewriting started\r\n"
10167 );
10168 assert_eq!(s.field("aof_rewrites"), "1");
10169 assert_eq!(s.field("aof_enabled"), "0");
10170 assert!(s.files().is_empty(), "a rewrite has written a file");
10171 }
10172
10173 #[test]
10174 fn role_says_master_with_nothing_following_it() {
10175 let mut f = Fixture::new();
10176 assert_eq!(f.run(&[b"ROLE"]), "*3\r\n$6\r\nmaster\r\n:0\r\n*0\r\n");
10177 assert_eq!(
10178 f.run(&[b"ROLE", b"x"]),
10179 "-ERR wrong number of arguments for 'role' command\r\n"
10180 );
10181 }
10182
10183 #[test]
10184 fn the_persistence_section_counts_the_saves_that_were_asked_for() {
10185 let mut s = Saves::new("counts");
10186 assert_eq!(s.field("rdb_saves"), "0");
10187 assert_eq!(s.field("rdb_last_bgsave_status"), "ok");
10188 s.run(&[b"SAVE"]);
10189 s.run(&[b"BGSAVE"]);
10190 s.run(&[b"BGSAVE", b"SCHEDULE"]);
10191 assert_eq!(s.field("rdb_saves"), "3");
10192 assert_eq!(s.field("rdb_bgsave_in_progress"), "0");
10193 assert_eq!(s.field("loading"), "0");
10194 }
10195
10196 #[test]
10197 fn the_persistence_section_is_in_a_bare_info_and_not_in_another_one() {
10198 let mut f = Fixture::new();
10199 assert!(f.run(&[b"INFO"]).contains("# Persistence"));
10200 assert!(f.run(&[b"INFO", b"persistence"]).contains("# Persistence"));
10201 assert!(f.run(&[b"INFO", b"all"]).contains("# Persistence"));
10202 assert!(!f.run(&[b"INFO", b"clients"]).contains("# Persistence"));
10203 }
10204
10205 #[test]
10206 fn the_file_name_reads_back_and_cannot_be_written() {
10207 let mut f = Fixture::new();
10208 assert_eq!(
10209 f.run(&[b"CONFIG", b"GET", b"dbfilename"]),
10210 "*2\r\n$10\r\ndbfilename\r\n$8\r\ndump.rdb\r\n"
10211 );
10212 assert_eq!(
10213 f.run(&[b"CONFIG", b"SET", b"dbfilename", b"other.rdb"]),
10214 "-ERR CONFIG SET failed (possibly related to argument 'dbfilename') - can't set protected config\r\n"
10215 );
10216 assert_eq!(
10219 f.run(&[b"CONFIG", b"SET", b"dbfilename", b"dump.rdb"]),
10220 "-ERR CONFIG SET failed (possibly related to argument 'dbfilename') - can't set protected config\r\n"
10221 );
10222 }
10223
10224 #[test]
10225 fn shutdown_save_writes_the_file_and_shutdown_on_its_own_does_not() {
10226 let mut s = Saves::new("shutdown");
10227 s.run(&[b"SET", b"k", b"v"]);
10228 s.run(&[b"SHUTDOWN", b"NOSAVE"]);
10229 assert!(s.files().is_empty(), "a nosave shutdown wrote a file");
10230
10231 let mut s = Saves::new("shutdown-save");
10232 s.run(&[b"SET", b"k", b"v"]);
10233 s.run(&[b"SHUTDOWN", b"SAVE"]);
10234 assert_eq!(s.files(), ["dump.rdb"]);
10235 }
10236
10237 #[test]
10243 fn debug_reload_brings_every_type_back_the_way_it_went_in() {
10244 let mut s = Saves::new("reload-types");
10245 s.run(&[b"SET", b"str", b"hello"]);
10246 s.run(&[b"SET", b"num", b"1234"]);
10247 s.run(&[b"RPUSH", b"list", b"a", b"b", b"c"]);
10248 s.run(&[b"SADD", b"set", b"x", b"y"]);
10249 s.run(&[b"SADD", b"ints", b"1", b"2", b"3"]);
10250 s.run(&[b"HSET", b"hash", b"f", b"v", b"g", b"w"]);
10251 s.run(&[b"ZADD", b"zset", b"1.5", b"m", b"2", b"n"]);
10252 s.run(&[b"XADD", b"stream", b"1-1", b"f", b"v"]);
10253 s.run(&[b"PEXPIREAT", b"str", b"4102444800000"]);
10254 let before = s.run(&[b"DBSIZE"]);
10255
10256 assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "+OK\r\n");
10257
10258 assert_eq!(s.run(&[b"DBSIZE"]), before);
10259 assert_eq!(s.run(&[b"GET", b"str"]), "$5\r\nhello\r\n");
10260 assert_eq!(s.run(&[b"GET", b"num"]), "$4\r\n1234\r\n");
10261 assert_eq!(
10262 s.run(&[b"LRANGE", b"list", b"0", b"-1"]),
10263 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10264 );
10265 assert_eq!(s.run(&[b"SCARD", b"set"]), ":2\r\n");
10266 assert_eq!(s.run(&[b"SISMEMBER", b"set", b"y"]), ":1\r\n");
10267 assert_eq!(s.run(&[b"SCARD", b"ints"]), ":3\r\n");
10268 assert_eq!(s.run(&[b"HGET", b"hash", b"g"]), "$1\r\nw\r\n");
10269 assert_eq!(s.run(&[b"ZSCORE", b"zset", b"m"]), "$3\r\n1.5\r\n");
10270 assert_eq!(s.run(&[b"XLEN", b"stream"]), ":1\r\n");
10271 assert_eq!(s.run(&[b"PEXPIRETIME", b"str"]), ":4102444800000\r\n");
10274 assert_eq!(s.run(&[b"PEXPIRETIME", b"num"]), ":-1\r\n");
10275 }
10276
10277 #[test]
10279 fn debug_reload_puts_every_key_back_in_its_own_database() {
10280 let mut s = Saves::new("reload-dbs");
10281 s.run(&[b"SET", b"home", b"zero"]);
10282 s.run(&[b"SELECT", b"9"]);
10283 s.run(&[b"SET", b"away", b"nine"]);
10284 s.run(&[b"SELECT", b"0"]);
10285
10286 assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "+OK\r\n");
10287
10288 assert_eq!(s.run(&[b"GET", b"home"]), "$4\r\nzero\r\n");
10289 assert_eq!(s.run(&[b"EXISTS", b"away"]), ":0\r\n");
10290 s.run(&[b"SELECT", b"9"]);
10291 assert_eq!(s.run(&[b"GET", b"away"]), "$4\r\nnine\r\n");
10292 assert_eq!(s.run(&[b"EXISTS", b"home"]), ":0\r\n");
10293 }
10294
10295 #[test]
10297 fn debug_reload_nosave_reads_the_file_that_is_already_there() {
10298 let mut s = Saves::new("reload-nosave");
10299 s.run(&[b"SET", b"k", b"first"]);
10300 s.run(&[b"SAVE"]);
10301 s.run(&[b"SET", b"k", b"second"]);
10302 s.run(&[b"SET", b"later", b"x"]);
10303
10304 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), "+OK\r\n");
10305
10306 assert_eq!(s.run(&[b"GET", b"k"]), "$5\r\nfirst\r\n");
10308 assert_eq!(s.run(&[b"EXISTS", b"later"]), ":0\r\n");
10309 }
10310
10311 #[test]
10313 fn debug_reload_noflush_keeps_what_the_file_does_not_mention() {
10314 let mut s = Saves::new("reload-noflush");
10315 s.run(&[b"SET", b"k", b"first"]);
10316 s.run(&[b"SAVE"]);
10317 s.run(&[b"SET", b"k", b"second"]);
10318 s.run(&[b"SET", b"later", b"x"]);
10319
10320 assert_eq!(
10321 s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE", b"NOFLUSH"]),
10322 "+OK\r\n"
10323 );
10324
10325 assert_eq!(s.run(&[b"GET", b"k"]), "$5\r\nfirst\r\n");
10329 assert_eq!(s.run(&[b"GET", b"later"]), "$1\r\nx\r\n");
10330 }
10331
10332 #[test]
10334 fn debug_reload_takes_its_three_words_and_no_others() {
10335 let mut s = Saves::new("reload-words");
10336 s.run(&[b"SET", b"k", b"v"]);
10337 for parts in [
10338 &[b"DEBUG".as_slice(), b"RELOAD"][..],
10339 &[b"DEBUG", b"RELOAD", b"NOSAVE"],
10340 &[b"DEBUG", b"RELOAD", b"nosave"],
10341 &[b"DEBUG", b"RELOAD", b"MERGE"],
10342 &[b"DEBUG", b"RELOAD", b"NOFLUSH"],
10343 &[b"DEBUG", b"RELOAD", b"MERGE", b"NOFLUSH", b"NOSAVE"],
10344 &[b"DEBUG", b"RELOAD", b"NOSAVE", b"NOSAVE"],
10346 ] {
10347 assert_eq!(s.run(parts), "+OK\r\n", "{parts:?}");
10348 }
10349 for parts in [
10350 &[b"DEBUG".as_slice(), b"RELOAD", b"BOGUS"][..],
10351 &[b"DEBUG", b"RELOAD", b"NOSAVE", b"BOGUS"],
10352 &[b"DEBUG", b"RELOAD", b""],
10353 ] {
10354 assert_eq!(
10355 s.run(parts),
10356 "-ERR DEBUG RELOAD only supports the MERGE, NOFLUSH and NOSAVE options.\r\n",
10357 "{parts:?}"
10358 );
10359 }
10360 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10362 }
10363
10364 #[test]
10366 fn debug_reload_that_cannot_write_the_file_says_so_in_one_word() {
10367 let mut s = Saves::new("reload-nowhere");
10368 s.run(&[b"SET", b"k", b"v"]);
10369 s.f.server.set_dir(s.dir.join("gone"));
10370 assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "-ERR\r\n");
10371 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10373 }
10374
10375 #[test]
10381 fn debug_reload_that_cannot_read_the_file_says_to_check_the_log() {
10382 let mut s = Saves::new("reload-unreadable");
10383 s.run(&[b"SET", b"k", b"v"]);
10384 let failed = "-ERR Error trying to load the RDB dump, check server logs.\r\n";
10385 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), failed);
10386 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10388
10389 s.run(&[b"SAVE"]);
10390 std::fs::write(s.dir.join("dump.rdb"), b"not an RDB file at all")
10391 .expect("could not write over the file");
10392 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), failed);
10393 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10394 }
10395
10396 #[test]
10403 fn debug_reload_refuses_to_drop_a_key_with_no_rdb_form() {
10404 let mut s = Saves::new("reload-foreign");
10405 s.run(&[b"SET", b"k", b"v"]);
10406 s.run(&[b"TS.CREATE", b"ts"]);
10407 s.run(&[b"TS.ADD", b"ts", b"1000", b"1.5"]);
10408
10409 assert_eq!(
10410 s.run(&[b"DEBUG", b"RELOAD"]),
10411 "-ERR DEBUG RELOAD would drop 1 key with no RDB form, use NOFLUSH to keep it\r\n"
10412 );
10413 assert_eq!(s.run(&[b"EXISTS", b"ts"]), ":1\r\n");
10414
10415 s.run(&[b"TS.CREATE", b"ts2"]);
10416 assert_eq!(
10417 s.run(&[b"DEBUG", b"RELOAD"]),
10418 "-ERR DEBUG RELOAD would drop 2 keys with no RDB form, use NOFLUSH to keep them\r\n"
10419 );
10420
10421 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOFLUSH"]), "+OK\r\n");
10423 assert_eq!(s.run(&[b"EXISTS", b"ts"]), ":1\r\n");
10424 assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10425 assert_eq!(s.run(&[b"TS.GET", b"ts"]), "*2\r\n:1000\r\n+1.5\r\n");
10426 }
10427
10428 #[test]
10430 fn debug_reload_drops_a_key_whose_deadline_went_by() {
10431 let mut s = Saves::new("reload-expired");
10432 s.run(&[b"SET", b"gone", b"v"]);
10433 s.run(&[b"SET", b"stays", b"v"]);
10434 s.run(&[b"PEXPIREAT", b"gone", b"4102444800000"]);
10435 s.run(&[b"SAVE"]);
10436 s.f.server.set_clock_ms(4_102_444_800_001);
10437
10438 assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), "+OK\r\n");
10439
10440 assert_eq!(s.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10441 assert_eq!(s.run(&[b"GET", b"stays"]), "$1\r\nv\r\n");
10442 }
10443
10444 #[test]
10453 fn a_file_one_server_wrote_loads_into_a_server_that_has_never_seen_it() {
10454 let mut wrote = Saves::new("restore-across");
10455 wrote.run(&[b"SET", b"s", b"hello"]);
10456 wrote.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10457 wrote.run(&[b"HSET", b"h", b"f", b"v"]);
10458 wrote.run(&[b"ZADD", b"z", b"1.5", b"m"]);
10459 wrote.run(&[b"SELECT", b"7"]);
10460 wrote.run(&[b"SADD", b"far", b"x"]);
10461 assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10462
10463 let mut fresh = Fixture::new();
10464 let done = fresh
10465 .server
10466 .load_image(&wrote.image(), true)
10467 .expect("the file one server wrote is a file another can read");
10468 assert_eq!(done.keys[0], 4);
10469 assert_eq!(done.keys[7], 1);
10470 assert_eq!(done.total(), 5);
10471 assert_eq!(done.expired, 0);
10472
10473 assert_eq!(fresh.run(&[b"GET", b"s"]), "$5\r\nhello\r\n");
10474 assert_eq!(
10475 fresh.run(&[b"LRANGE", b"l", b"0", b"-1"]),
10476 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10477 );
10478 assert_eq!(fresh.run(&[b"HGET", b"h", b"f"]), "$1\r\nv\r\n");
10479 assert_eq!(fresh.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n1.5\r\n");
10480 fresh.run(&[b"SELECT", b"7"]);
10481 assert_eq!(fresh.run(&[b"SMEMBERS", b"far"]), "*1\r\n$1\r\nx\r\n");
10482 }
10483
10484 #[test]
10487 fn a_load_reports_what_it_kept_and_what_had_already_died() {
10488 let mut wrote = Saves::new("restore-counts");
10489 wrote.run(&[b"SET", b"gone", b"v"]);
10490 wrote.run(&[b"SET", b"stays", b"v"]);
10491 wrote.run(&[b"PEXPIREAT", b"gone", b"4102444800000"]);
10492 assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10493
10494 let mut fresh = Saves::new("restore-counts-into");
10495 fresh.f.server.set_clock_ms(4_102_444_800_001);
10496 let done = fresh
10497 .f
10498 .server
10499 .load_image(&wrote.image(), true)
10500 .expect("a file with a dead key in it is still a good file");
10501 assert_eq!(done.total(), 1);
10502 assert_eq!(done.expired, 1);
10503
10504 assert_eq!(fresh.field("rdb_last_load_keys_loaded"), "1");
10505 assert_eq!(fresh.field("rdb_last_load_keys_expired"), "1");
10506 assert_eq!(fresh.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10507 assert_eq!(fresh.run(&[b"GET", b"stays"]), "$1\r\nv\r\n");
10508 }
10509
10510 #[test]
10513 fn a_server_that_has_loaded_nothing_says_so() {
10514 let mut s = Saves::new("restore-never");
10515 assert_eq!(s.field("rdb_last_load_keys_loaded"), "0");
10516 assert_eq!(s.field("rdb_last_load_keys_expired"), "0");
10517 }
10518
10519 #[test]
10524 fn a_file_that_is_not_an_rdb_is_refused_with_the_dataset_still_there() {
10525 let mut f = Fixture::new();
10526 f.run(&[b"SET", b"k", b"v"]);
10527 let refused = f
10528 .server
10529 .load_image(b"this is not a Redis dump at all, not even close", true)
10530 .expect_err("that is not an RDB");
10531 assert_eq!(refused.to_string(), "the file does not start with REDIS");
10532 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10533 }
10534
10535 #[test]
10539 fn a_damaged_file_is_refused_with_the_dataset_still_there() {
10540 let mut wrote = Saves::new("restore-damaged");
10541 wrote.run(&[b"SET", b"a", b"b"]);
10542 assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10543 let mut image = wrote.image();
10544 let middle = image.len() / 2;
10547 image[middle] ^= 0xff;
10548
10549 let mut f = Fixture::new();
10550 f.run(&[b"SET", b"k", b"v"]);
10551 let refused = f
10552 .server
10553 .load_image(&image, true)
10554 .expect_err("the checksum does not match");
10555 assert_eq!(
10556 refused.to_string(),
10557 "the checksum does not match, so the file is damaged"
10558 );
10559 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10560 }
10561
10562 #[test]
10567 fn debug_object_describes_how_a_value_is_written_down() {
10568 let mut f = Fixture::new();
10569 f.run(&[b"SET", b"s", b"hello"]);
10570
10571 let line = f.run(&[b"DEBUG", b"OBJECT", b"s"]);
10572 let line = line
10573 .strip_prefix('+')
10574 .and_then(|l| l.strip_suffix("\r\n"))
10575 .expect("a simple string");
10576 let mut fields = line.split(' ');
10577 assert_eq!(fields.next(), Some("Value"));
10578 assert!(
10579 fields.next().expect("an address").starts_with("at:0x"),
10580 "{line}"
10581 );
10582 assert_eq!(fields.next(), Some("refcount:1"));
10583 assert_eq!(fields.next(), Some("encoding:embstr"));
10584 assert_eq!(fields.next(), Some("serializedlength:6"));
10588 assert!(
10589 fields.next().expect("a clock").starts_with("lru:"),
10590 "{line}"
10591 );
10592 assert_eq!(fields.next(), Some("lru_seconds_idle:0"));
10593 assert_eq!(fields.next(), None);
10594 }
10595
10596 #[test]
10598 fn debug_object_counts_the_nodes_a_list_broke_into() {
10599 let mut f = Fixture::new();
10600 let member = vec![b'x'; 200];
10603 for _ in 0..100 {
10604 f.run(&[b"RPUSH", b"l", &member]);
10605 }
10606 assert_eq!(
10607 f.run(&[b"OBJECT", b"ENCODING", b"l"]),
10608 "$9\r\nquicklist\r\n"
10609 );
10610
10611 let line = f.run(&[b"DEBUG", b"OBJECT", b"l"]);
10612 let nodes: usize = field(&line, "ql_nodes:").parse().expect("a count");
10613 assert!(nodes > 1, "{line}");
10614 let avg: f64 = field(&line, "ql_avg_node:").parse().expect("an average");
10615 assert!((avg - 100.0 / nodes as f64).abs() < 0.01, "{line}");
10616 assert_eq!(field(&line, "ql_listpack_max:"), "-2");
10617 assert_eq!(field(&line, "ql_compressed:"), "0");
10618 let bytes: usize = field(&line, "ql_uncompressed_size:")
10619 .parse()
10620 .expect("a size");
10621 assert!(bytes > 100 * 200, "{line}");
10622
10623 f.run(&[b"RPUSH", b"small", b"a"]);
10625 let line = f.run(&[b"DEBUG", b"OBJECT", b"small"]);
10626 assert!(!line.contains("ql_nodes"), "{line}");
10627 }
10628
10629 #[test]
10633 fn debug_object_does_not_count_as_using_the_key() {
10634 let mut f = Fixture::new();
10635 f.run(&[b"SET", b"s", b"hello"]);
10636 let was = field(&f.run(&[b"DEBUG", b"OBJECT", b"s"]), "lru:").to_owned();
10637
10638 f.server.set_clock_ms(f.server.clock.now_ms() + 60_000);
10639 let line = f.run(&[b"DEBUG", b"OBJECT", b"s"]);
10640
10641 assert_eq!(field(&line, "lru_seconds_idle:"), "60");
10642 assert_eq!(field(&line, "lru:"), was);
10645 }
10646
10647 #[test]
10650 fn debug_sdslen_measures_the_name_and_the_string_under_it() {
10651 let mut f = Fixture::new();
10652 f.run(&[b"SET", b"name", b"hello"]);
10653
10654 assert_eq!(
10655 f.run(&[b"DEBUG", b"SDSLEN", b"name"]),
10656 "+key_sds_len:4, key_sds_avail:0, key_zmalloc: 4, \
10657 val_sds_len:5, val_sds_avail:0, val_zmalloc: 5\r\n"
10658 );
10659 }
10660
10661 #[test]
10663 fn the_inspecting_subcommands_refuse_what_they_cannot_describe() {
10664 let mut f = Fixture::new();
10665 f.run(&[b"SET", b"s", b"hello"]);
10666 f.run(&[b"SET", b"n", b"12345"]);
10667 f.run(&[b"RPUSH", b"l", b"a"]);
10668
10669 for sub in [
10672 b"OBJECT".as_slice(),
10673 b"SDSLEN".as_slice(),
10674 b"LISTPACK".as_slice(),
10675 b"QUICKLIST".as_slice(),
10676 ] {
10677 assert_eq!(
10678 f.run(&[b"DEBUG", sub, b"nosuch"]),
10679 "-ERR no such key\r\n",
10680 "{}",
10681 String::from_utf8_lossy(sub)
10682 );
10683 }
10684
10685 assert_eq!(
10687 f.run(&[b"DEBUG", b"SDSLEN", b"n"]),
10688 "-ERR Not an sds encoded string.\r\n"
10689 );
10690 assert_eq!(
10691 f.run(&[b"DEBUG", b"SDSLEN", b"l"]),
10692 "-ERR Not an sds encoded string.\r\n"
10693 );
10694
10695 assert_eq!(
10698 f.run(&[b"DEBUG", b"LISTPACK", b"l"]),
10699 "+Listpack structure printed on stdout\r\n"
10700 );
10701 assert_eq!(
10702 f.run(&[b"DEBUG", b"QUICKLIST", b"l"]),
10703 "-ERR Not a quicklist encoded object.\r\n"
10704 );
10705 assert_eq!(
10706 f.run(&[b"DEBUG", b"LISTPACK", b"s"]),
10707 "-ERR Not a listpack encoded object.\r\n"
10708 );
10709 }
10710
10711 #[test]
10714 fn debug_listpack_answers_for_anything_written_as_one() {
10715 let mut f = Fixture::new();
10716 f.run(&[b"RPUSH", b"l", b"a"]);
10717 f.run(&[b"HSET", b"h", b"f", b"v"]);
10718 f.run(&[b"SADD", b"st", b"a"]);
10719 f.run(&[b"ZADD", b"z", b"1", b"m"]);
10720 f.run(&[b"SADD", b"ints", b"1", b"2"]);
10721
10722 for key in [b"l".as_slice(), b"h", b"st", b"z"] {
10723 assert_eq!(
10724 f.run(&[b"DEBUG", b"LISTPACK", key]),
10725 "+Listpack structure printed on stdout\r\n",
10726 "{}",
10727 String::from_utf8_lossy(key)
10728 );
10729 }
10730 assert_eq!(
10731 f.run(&[b"OBJECT", b"ENCODING", b"ints"]),
10732 "$6\r\nintset\r\n"
10733 );
10734 assert_eq!(
10735 f.run(&[b"DEBUG", b"LISTPACK", b"ints"]),
10736 "-ERR Not a listpack encoded object.\r\n"
10737 );
10738 }
10739
10740 #[test]
10743 fn debug_quicklist_takes_a_level_it_does_nothing_with() {
10744 let mut f = Fixture::new();
10745 let member = vec![b'x'; 200];
10746 for _ in 0..100 {
10747 f.run(&[b"RPUSH", b"l", &member]);
10748 }
10749
10750 let said = "+Quicklist structure printed on stdout\r\n";
10751 assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l"]), said);
10752 assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"1"]), said);
10753 assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"abc"]), said);
10756
10757 assert_eq!(
10758 f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"1", b"2"]),
10759 "-ERR unknown subcommand or wrong number of arguments for \
10760 'QUICKLIST'. Try DEBUG HELP.\r\n"
10761 );
10762 assert_eq!(
10763 f.run(&[b"DEBUG", b"LISTPACK", b"l", b"0"]),
10764 "-ERR unknown subcommand or wrong number of arguments for \
10765 'LISTPACK'. Try DEBUG HELP.\r\n"
10766 );
10767 }
10768
10769 #[test]
10774 fn a_value_digest_is_the_number_the_reference_computes() {
10775 let mut f = Fixture::new();
10776 f.run(&[b"SET", b"s", b"hello"]);
10777 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10778 f.run(&[b"SADD", b"t", b"a", b"b", b"c"]);
10779 f.run(&[b"HSET", b"h", b"f", b"v"]);
10780 f.run(&[b"ZADD", b"z", b"1", b"a", b"2.5", b"b"]);
10781 f.run(&[b"XADD", b"x", b"1-1", b"f", b"v"]);
10782
10783 for (key, want) in [
10784 (&b"s"[..], "36b23a1456b2dce2c3ed252c456761301dba8060"),
10785 (b"l", "8bf72d812571eea9b927f3c11beb0c4165a6ff89"),
10786 (b"t", "593c2414786d75446e97f4ea5d4b731f3313da72"),
10787 (b"h", "90c76e9e9f4c62d642a34fc97c7dad503b51f906"),
10788 (b"z", "c45c5b051acd64070e5ed1a949939d5145f806c5"),
10789 (b"x", "2ed9a7a81688084b1f7eae33456ef7727d357031"),
10790 ] {
10791 assert_eq!(
10792 f.run(&[b"DEBUG", b"DIGEST-VALUE", key]),
10793 format!("*1\r\n+{want}\r\n"),
10794 "{}",
10795 String::from_utf8_lossy(key)
10796 );
10797 }
10798 }
10799
10800 #[test]
10803 fn a_deadline_shows_up_without_the_time_left_showing_up() {
10804 let mut f = Fixture::new();
10805 f.run(&[b"SET", b"e", b"value"]);
10806 let bare = "*1\r\n+d59ec93db87f4cd915db3cdf44bb63755bc4a635\r\n";
10807 let dated = "*1\r\n+331b37c26446a68dd4cdd72701d1acee416ae7b6\r\n";
10808 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), bare);
10809
10810 f.run(&[b"EXPIRE", b"e", b"1000"]);
10811 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), dated);
10812 f.run(&[b"EXPIRE", b"e", b"999999"]);
10814 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), dated);
10815 f.run(&[b"PERSIST", b"e"]);
10816 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), bare);
10817
10818 f.run(&[b"HSET", b"he", b"f1", b"v1", b"f2", b"v2"]);
10821 f.run(&[b"HEXPIRE", b"he", b"1000", b"FIELDS", b"1", b"f2"]);
10822 assert_eq!(
10823 f.run(&[b"DEBUG", b"DIGEST-VALUE", b"he"]),
10824 "*1\r\n+8911d6d4d198f5e022f80dfefd5e15d6c0eaabe3\r\n"
10825 );
10826 }
10827
10828 #[test]
10831 fn a_value_digest_does_not_know_what_the_key_is_called() {
10832 let mut f = Fixture::new();
10833 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10834 f.run(&[b"COPY", b"l", b"l2"]);
10835 let want = "*1\r\n+8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n";
10836 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l"]), want);
10837 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l2"]), want);
10838
10839 assert_eq!(
10842 f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l", b"gone", b"l2"]),
10843 format!(
10844 "*3\r\n+8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n+{0}\r\n\
10845 +8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n",
10846 "0".repeat(40)
10847 )
10848 );
10849 assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE"]), "*0\r\n");
10850 }
10851
10852 #[test]
10855 fn the_whole_digest_folds_in_the_names_and_the_database_numbers() {
10856 let mut f = Fixture::new();
10857 let empty = format!("+{}\r\n", "0".repeat(40));
10858 assert_eq!(f.run(&[b"DEBUG", b"DIGEST"]), empty);
10859
10860 f.run(&[b"SET", b"k", b"hello"]);
10861 assert_eq!(
10862 f.run(&[b"DEBUG", b"DIGEST"]),
10863 "+d101db227d1e3b31616b18b0b8700f84c3ffa5e9\r\n"
10864 );
10865
10866 f.run(&[b"SELECT", b"3"]);
10867 f.run(&[b"SET", b"k", b"hello"]);
10868 assert_eq!(
10869 f.run(&[b"DEBUG", b"DIGEST"]),
10870 "+a541f66c15932c1014da1569ff27c15bcde7d1dc\r\n"
10871 );
10872
10873 f.run(&[b"SELECT", b"0"]);
10876 f.run(&[b"FLUSHDB"]);
10877 assert_eq!(
10878 f.run(&[b"DEBUG", b"DIGEST"]),
10879 "+f9b35ab00ad2f456386a2f73d316bf8266013606\r\n"
10880 );
10881
10882 f.run(&[b"FLUSHALL"]);
10883 assert_eq!(f.run(&[b"DEBUG", b"DIGEST"]), empty);
10884 }
10885
10886 #[test]
10889 fn digesting_does_not_count_as_using_anything() {
10890 let mut f = Fixture::new();
10891 f.run(&[b"SET", b"k", b"value"]);
10892 f.run(&[b"CONFIG", b"RESETSTAT"]);
10893 f.run(&[b"DEBUG", b"DIGEST"]);
10894 f.run(&[b"DEBUG", b"DIGEST-VALUE", b"k", b"gone"]);
10895 let stats = f.run(&[b"INFO", b"stats"]);
10896 assert!(stats.contains("keyspace_hits:0\r\n"), "{stats}");
10897 assert!(stats.contains("keyspace_misses:0\r\n"), "{stats}");
10898
10899 f.run(&[b"GET", b"k"]);
10902 f.run(&[b"GET", b"gone"]);
10903 let stats = f.run(&[b"INFO", b"stats"]);
10904 assert!(stats.contains("keyspace_hits:1\r\n"), "{stats}");
10905 assert!(stats.contains("keyspace_misses:1\r\n"), "{stats}");
10906 }
10907
10908 fn field<'a>(line: &'a str, label: &str) -> &'a str {
10910 let at = line
10911 .find(label)
10912 .unwrap_or_else(|| panic!("no {label} in {line}"));
10913 let rest = &line[at + label.len()..];
10914 rest.split([' ', '\r']).next().expect("a value")
10915 }
10916
10917 fn guarded(password: &[u8]) -> Fixture {
10927 let mut f = Fixture::new();
10928 f.server.set_password(password);
10929 f.session.admit(false);
10930 f
10931 }
10932
10933 #[test]
10936 fn a_server_with_a_password_answers_everything_else_with_noauth() {
10937 let mut f = guarded(b"hunter2");
10938 for parts in [
10939 &[b"PING".as_slice()][..],
10940 &[b"GET", b"k"],
10941 &[b"SET", b"k", b"v"],
10942 &[b"COMMAND", b"COUNT"],
10943 &[b"SUBSCRIBE", b"ch"],
10944 &[b"MULTI"],
10945 &[b"INFO"],
10946 ] {
10947 assert_eq!(
10948 f.run(parts),
10949 "-NOAUTH Authentication required.\r\n",
10950 "{parts:?} got through"
10951 );
10952 }
10953 }
10954
10955 #[test]
10962 fn the_four_commands_that_do_not_need_the_password_get_through() {
10963 for name in ["auth", "hello", "reset", "quit"] {
10964 let spec = table::lookup(name.as_bytes()).expect(name);
10965 assert!(spec.flags.contains(&"no_auth"), "{name} is not no_auth");
10966 }
10967 let mut f = guarded(b"hunter2");
10968 assert_eq!(f.run(&[b"QUIT"]), "+OK\r\n");
10969 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
10970 assert_eq!(
10971 f.run(&[b"AUTH", b"wrong"]),
10972 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
10973 );
10974 assert_eq!(f.run(&[b"AUTH", b"hunter2"]), "+OK\r\n");
10975 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10976 }
10977
10978 #[test]
10980 fn auth_takes_the_password_on_its_own_or_behind_the_user_name() {
10981 let mut f = guarded(b"hunter2");
10982 let wrong = "-WRONGPASS invalid username-password pair or user is disabled.\r\n";
10983 assert_eq!(f.run(&[b"AUTH", b"default", b"hunter2"]), "+OK\r\n");
10984 assert_eq!(f.run(&[b"AUTH", b"default", b"wrong"]), wrong);
10985 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10988 assert_eq!(f.run(&[b"AUTH", b"someone", b"hunter2"]), wrong);
10989 assert_eq!(
10990 f.run(&[b"AUTH"]),
10991 "-ERR wrong number of arguments for 'auth' command\r\n"
10992 );
10993 assert_eq!(f.run(&[b"AUTH", b"a", b"b", b"c"]), "-ERR syntax error\r\n");
10994 }
10995
10996 #[test]
11004 fn auth_on_a_server_with_no_password_says_so_at_length() {
11005 let mut f = Fixture::new();
11006 assert_eq!(
11007 f.run(&[b"AUTH", b"anything"]),
11008 "-ERR AUTH <password> called without any password configured for the \
11009 default user. Are you sure your configuration is correct?\r\n"
11010 );
11011 assert_eq!(f.run(&[b"AUTH", b"default", b"anything"]), "+OK\r\n");
11012 assert_eq!(
11013 f.run(&[b"AUTH", b"nobody", b"anything"]),
11014 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
11015 );
11016 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11017 }
11018
11019 #[test]
11028 fn hello_says_which_option_would_have_worked() {
11029 let long = "-NOAUTH HELLO must be called with the client already authenticated, \
11030 otherwise the HELLO <proto> AUTH <user> <pass> option can be used to \
11031 authenticate the client and select the RESP protocol version at the \
11032 same time\r\n";
11033 let mut f = guarded(b"hunter2");
11034 assert_eq!(f.run(&[b"HELLO"]), long);
11035 assert_eq!(f.run(&[b"HELLO", b"3"]), long);
11036 assert_eq!(
11037 f.run(&[b"HELLO", b"9"]),
11038 "-NOPROTO unsupported protocol version\r\n"
11039 );
11040 assert_eq!(
11043 f.run(&[b"HELLO", b"9", b"AUTH", b"default", b"hunter2"]),
11044 "-NOPROTO unsupported protocol version\r\n"
11045 );
11046 assert_eq!(f.run(&[b"PING"]), "-NOAUTH Authentication required.\r\n");
11047 assert_eq!(
11048 f.run(&[b"HELLO", b"2", b"AUTH", b"default", b"wrong"]),
11049 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
11050 );
11051 assert!(
11052 f.run(&[b"HELLO", b"3", b"AUTH", b"default", b"hunter2"])
11053 .starts_with("%7\r\n"),
11054 "the option did not let it in"
11055 );
11056 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11057 }
11058
11059 #[test]
11067 fn exec_without_the_password_is_an_abort_and_says_why() {
11068 let mut f = guarded(b"hunter2");
11069 assert_eq!(f.run(&[b"MULTI"]), "-NOAUTH Authentication required.\r\n");
11070 assert_eq!(
11071 f.run(&[b"EXEC"]),
11072 "-EXECABORT Transaction discarded because of: NOAUTH Authentication \
11073 required.\r\n"
11074 );
11075 }
11076
11077 #[test]
11080 fn reset_gives_the_password_back_to_the_server_to_ask_for_again() {
11081 let mut f = guarded(b"hunter2");
11082 assert_eq!(f.run(&[b"AUTH", b"hunter2"]), "+OK\r\n");
11083 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11084 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
11085 assert_eq!(f.run(&[b"PING"]), "-NOAUTH Authentication required.\r\n");
11086
11087 let mut f = Fixture::new();
11089 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
11090 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11091 }
11092
11093 #[test]
11100 fn a_password_set_under_an_open_connection_leaves_it_alone() {
11101 let mut f = Fixture::new();
11102 f.session.admit(true);
11103 assert_eq!(
11104 f.run(&[b"CONFIG", b"SET", b"requirepass", b"hunter2"]),
11105 "+OK\r\n"
11106 );
11107 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11108 let mut later = Fixture::on(Server::new());
11110 later.server.set_password(b"hunter2");
11111 later.session.admit(false);
11112 assert_eq!(
11113 later.run(&[b"PING"]),
11114 "-NOAUTH Authentication required.\r\n"
11115 );
11116 later.server.set_password(b"");
11117 assert_eq!(later.run(&[b"PING"]), "+PONG\r\n");
11118 }
11119
11120 #[test]
11123 fn requirepass_reads_back_what_was_written_and_an_empty_one_clears_it() {
11124 let mut f = Fixture::new();
11125 f.session.admit(true);
11128 assert_eq!(
11129 f.run(&[b"CONFIG", b"GET", b"requirepass"]),
11130 "*2\r\n$11\r\nrequirepass\r\n$0\r\n\r\n"
11131 );
11132 f.run(&[b"CONFIG", b"SET", b"requirepass", b"hunter2"]);
11133 assert_eq!(
11134 f.run(&[b"CONFIG", b"GET", b"requirepass"]),
11135 "*2\r\n$11\r\nrequirepass\r\n$7\r\nhunter2\r\n"
11136 );
11137 assert!(f.server.guarded());
11138 f.run(&[b"CONFIG", b"SET", b"requirepass", b""]);
11139 assert!(!f.server.guarded(), "an empty password did not clear it");
11140 assert_eq!(
11141 f.run(&[b"CONFIG", b"GET", b"requirepass"]),
11142 "*2\r\n$11\r\nrequirepass\r\n$0\r\n\r\n"
11143 );
11144 }
11145
11146 #[test]
11148 fn debug_protocol_writes_what_the_reference_writes_on_resp2() {
11149 let mut f = Fixture::new();
11150 for (kind, want) in [
11151 ("string", "$11\r\nHello World\r\n"),
11152 ("integer", ":12345\r\n"),
11153 ("double", "$5\r\n3.141\r\n"),
11154 ("bignum", "$37\r\n1234567999999999999999999999999999999\r\n"),
11155 ("null", "$-1\r\n"),
11156 ("array", "*3\r\n:0\r\n:1\r\n:2\r\n"),
11157 ("set", "*3\r\n:0\r\n:1\r\n:2\r\n"),
11158 ("map", "*6\r\n:0\r\n:0\r\n:1\r\n:1\r\n:2\r\n:0\r\n"),
11159 (
11160 "attrib",
11161 "$39\r\nSome real reply following the attribute\r\n",
11162 ),
11163 ("push", "-ERR RESP2 is not supported by this command\r\n"),
11164 ("verbatim", "$25\r\nThis is a verbatim\nstring\r\n"),
11165 ("true", ":1\r\n"),
11166 ("false", ":0\r\n"),
11167 ] {
11168 assert_eq!(
11169 f.run(&[b"DEBUG", b"PROTOCOL", kind.as_bytes()]),
11170 want,
11171 "{kind}"
11172 );
11173 }
11174 }
11175
11176 #[test]
11178 fn debug_protocol_writes_what_the_reference_writes_on_resp3() {
11179 let mut f = Fixture::new();
11180 f.out = Out::new(Proto::Resp3);
11181 for (kind, want) in [
11182 ("string", "$11\r\nHello World\r\n"),
11183 ("integer", ":12345\r\n"),
11184 ("double", ",3.141\r\n"),
11185 ("bignum", "(1234567999999999999999999999999999999\r\n"),
11186 ("null", "_\r\n"),
11187 ("array", "*3\r\n:0\r\n:1\r\n:2\r\n"),
11188 ("set", "~3\r\n:0\r\n:1\r\n:2\r\n"),
11189 ("map", "%3\r\n:0\r\n#f\r\n:1\r\n#t\r\n:2\r\n#f\r\n"),
11190 (
11191 "attrib",
11192 "|1\r\n$14\r\nkey-popularity\r\n*2\r\n$7\r\nkey:123\r\n:90\r\n\
11193 $39\r\nSome real reply following the attribute\r\n",
11194 ),
11195 (
11196 "push",
11197 "$40\r\nSome real reply following the push reply\r\n\
11198 >2\r\n$16\r\nserver-cpu-usage\r\n:42\r\n",
11199 ),
11200 ("verbatim", "=29\r\ntxt:This is a verbatim\nstring\r\n"),
11201 ("true", "#t\r\n"),
11202 ("false", "#f\r\n"),
11203 ] {
11204 assert_eq!(
11205 f.run(&[b"DEBUG", b"PROTOCOL", kind.as_bytes()]),
11206 want,
11207 "{kind}"
11208 );
11209 }
11210 }
11211
11212 #[test]
11214 fn debug_protocol_names_every_type_when_it_is_given_none_of_them() {
11215 let mut f = Fixture::new();
11216 for kind in [&b"bogus"[..], b""] {
11217 assert_eq!(
11218 f.run(&[b"DEBUG", b"PROTOCOL", kind]),
11219 "-ERR Wrong protocol type name. Please use one of the following: \
11220 string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false\r\n"
11221 );
11222 }
11223 }
11224
11225 #[test]
11232 fn debug_says_the_same_thing_about_a_bad_name_and_a_bad_count() {
11233 let mut f = Fixture::new();
11234 for parts in [
11235 &[b"DEBUG".as_slice(), b"NOSUCH"][..],
11236 &[b"DEBUG", b"PROTOCOL"],
11237 &[b"DEBUG", b"PROTOCOL", b"string", b"extra"],
11238 &[b"DEBUG", b"SLEEP"],
11239 &[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"1", b"2"],
11240 &[b"DEBUG", b"HELP", b"me"],
11241 ] {
11242 let got = f.run(parts);
11243 let name = String::from_utf8_lossy(parts[1]).to_string();
11244 assert_eq!(
11245 got,
11246 format!(
11247 "-ERR unknown subcommand or wrong number of arguments for '{name}'. \
11248 Try DEBUG HELP.\r\n"
11249 ),
11250 "{name}"
11251 );
11252 }
11253 assert_eq!(
11254 f.run(&[b"DEBUG"]),
11255 "-ERR wrong number of arguments for 'debug' command\r\n"
11256 );
11257 }
11258
11259 #[test]
11261 fn debug_error_hands_back_whatever_it_was_given() {
11262 let mut f = Fixture::new();
11263 assert_eq!(f.run(&[b"DEBUG", b"ERROR", b"my error"]), "-my error\r\n");
11264 assert_eq!(f.run(&[b"DEBUG", b"ERROR", b""]), "-\r\n");
11265 assert_eq!(
11268 f.run(&[b"DEBUG", b"ERROR", b"-WEIRD thing"]),
11269 "--WEIRD thing\r\n"
11270 );
11271 assert_eq!(
11273 f.run(&[b"DEBUG", b"ERROR", b"two\nlines"]),
11274 "-two lines\r\n"
11275 );
11276 }
11277
11278 #[test]
11280 fn debug_populate_fills_and_skips_what_is_there() {
11281 let mut f = Fixture::new();
11282 assert_eq!(f.run(&[b"SET", b"key:0", b"mine"]), "+OK\r\n");
11283 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"3"]), "+OK\r\n");
11284 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n");
11285 assert_eq!(f.run(&[b"GET", b"key:0"]), "$4\r\nmine\r\n");
11286 assert_eq!(f.run(&[b"GET", b"key:2"]), "$7\r\nvalue:2\r\n");
11287 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"p:"]), "+OK\r\n");
11290 assert_eq!(f.run(&[b"GET", b"p::0"]), "$7\r\nvalue:0\r\n");
11291 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"q", b"9"]), "+OK\r\n");
11293 assert_eq!(f.raw(&[b"GET", b"q:0"]), b"$9\r\nvalue:0\0\0\r\n");
11294 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"r", b"4"]), "+OK\r\n");
11295 assert_eq!(f.run(&[b"GET", b"r:0"]), "$4\r\nvalu\r\n");
11296 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"s", b"0"]), "+OK\r\n");
11298 assert_eq!(f.run(&[b"GET", b"s:0"]), "$7\r\nvalue:0\r\n");
11299 }
11300
11301 #[test]
11303 fn debug_populate_wants_two_numbers_that_are_not_negative() {
11304 let mut f = Fixture::new();
11305 for parts in [
11306 &[b"DEBUG".as_slice(), b"POPULATE", b"abc"][..],
11307 &[b"DEBUG", b"POPULATE", b"-1"],
11308 &[b"DEBUG", b"POPULATE", b"1.5"],
11309 &[b"DEBUG", b"POPULATE", b"1", b"p", b"-1"],
11310 &[b"DEBUG", b"POPULATE", b"1", b"p", b"x"],
11311 ] {
11312 assert_eq!(
11313 f.run(parts),
11314 "-ERR value is out of range, must be positive\r\n"
11315 );
11316 }
11317 assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"0"]), "+OK\r\n");
11318 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11319 }
11320
11321 #[test]
11327 fn debug_quicklist_packed_threshold_takes_what_the_reference_takes() {
11328 let mut f = Fixture::new();
11329 for good in [
11330 &b"1"[..],
11331 b"2",
11332 b"1b",
11333 b"1K",
11334 b"1kb",
11335 b"1G",
11336 b"3gb",
11337 b"0",
11338 b"0b",
11339 ] {
11340 assert_eq!(
11341 f.run(&[b"DEBUG", b"QUICKLIST-PACKED-THRESHOLD", good]),
11342 "+OK\r\n",
11343 "{}",
11344 String::from_utf8_lossy(good)
11345 );
11346 }
11347 for bad in [
11348 &b"4gb"[..],
11349 b"4294967295",
11350 b"4294967296",
11351 b"abc",
11352 b"",
11353 b"+5",
11354 b"1.5",
11355 ] {
11356 assert_eq!(
11357 f.run(&[b"DEBUG", b"QUICKLIST-PACKED-THRESHOLD", bad]),
11358 "-ERR argument must be a memory value bigger than 1 and smaller than 4gb\r\n",
11359 "{}",
11360 String::from_utf8_lossy(bad)
11361 );
11362 }
11363 }
11364
11365 #[test]
11367 fn the_debug_gates_turn_the_things_they_name_off_and_on_again() {
11368 let mut f = Fixture::new();
11369 for (sub, read) in [
11370 (&b"SET-ACTIVE-EXPIRE"[..], 0),
11371 (b"DICT-RESIZING", 1),
11372 (b"PAUSE-CRON", 2),
11373 ] {
11374 let reads: [fn(&Server) -> bool; 3] =
11375 [Server::expiring, Server::resizing, Server::cron_running];
11376 let on = reads[read];
11377 let stop: &[u8] = if read == 2 { b"1" } else { b"0" };
11380 let go: &[u8] = if read == 2 { b"0" } else { b"1" };
11381 assert!(on(&f.server), "{}", String::from_utf8_lossy(sub));
11382 assert_eq!(f.run(&[b"DEBUG", sub, stop]), "+OK\r\n");
11383 assert!(!on(&f.server), "{}", String::from_utf8_lossy(sub));
11384 assert_eq!(f.run(&[b"DEBUG", sub, b"nonsense"]), "+OK\r\n");
11387 assert_eq!(on(&f.server), read == 2);
11388 assert_eq!(f.run(&[b"DEBUG", sub, go]), "+OK\r\n");
11389 assert!(on(&f.server), "{}", String::from_utf8_lossy(sub));
11390 }
11391 }
11392
11393 #[test]
11399 fn the_sweep_stops_when_debug_turns_it_off() {
11400 let mut f = Fixture::new();
11401 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PX", b"10"]), "+OK\r\n");
11402 assert_eq!(f.run(&[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"0"]), "+OK\r\n");
11403 f.advance(50);
11404 assert_eq!(f.server.expire_slice(64), 0);
11405 assert_eq!(f.run(&[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"1"]), "+OK\r\n");
11406 assert_eq!(f.server.expire_slice(64), 1);
11407 }
11408
11409 #[test]
11415 fn command_info_sends_sets_where_the_reference_sends_sets() {
11416 let mut f = Fixture::new();
11417 f.out = Out::new(Proto::Resp3);
11418 assert_eq!(
11419 f.run(&[b"COMMAND", b"INFO", b"get"]),
11420 "*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\
11421 ~3\r\n+@read\r\n+@string\r\n+@fast\r\n~0\r\n~1\r\n%3\r\n\
11422 $5\r\nflags\r\n~2\r\n+RO\r\n+access\r\n\
11423 $12\r\nbegin_search\r\n%2\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
11424 %1\r\n$5\r\nindex\r\n:1\r\n\
11425 $9\r\nfind_keys\r\n%2\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
11426 %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\
11427 ~0\r\n"
11428 );
11429 let mut f = Fixture::new();
11431 assert_eq!(
11432 f.run(&[b"COMMAND", b"INFO", b"get"]),
11433 "*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\
11434 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*1\r\n*6\r\n\
11435 $5\r\nflags\r\n*2\r\n+RO\r\n+access\r\n\
11436 $12\r\nbegin_search\r\n*4\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
11437 *2\r\n$5\r\nindex\r\n:1\r\n\
11438 $9\r\nfind_keys\r\n*4\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
11439 *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\
11440 *0\r\n"
11441 );
11442 }
11443
11444 #[test]
11446 fn debug_is_admin_and_stays_off_a_monitor_feed() {
11447 let spec = table::lookup(b"debug").expect("debug is in the table");
11448 assert!(spec.flags.contains(&"admin"));
11449 assert_eq!(spec.arity, -2);
11450 assert_eq!(spec.acl, ["@admin", "@slow", "@dangerous"]);
11451 }
11452
11453 #[test]
11454 fn the_command_counter_counts_every_command_including_the_bad_ones() {
11455 let mut f = Fixture::new();
11456 f.run(&[b"PING"]);
11457 f.run(&[b"NOPE"]);
11458 f.run(&[b"GET"]);
11459 assert_eq!(f.server.totals().commands, 3);
11460 }
11461
11462 #[test]
11463 fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
11464 let mut server = Server::new();
11465 server.set_threads(2);
11466 server.mine().turn.store(0, Relaxed);
11469 server.locals[1].mark(1 << 9);
11470 server.collect_marks();
11471 assert!(server.mine().wanted(9));
11472 assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
11474 }
11475
11476 #[test]
11477 fn what_two_threads_counted_is_added_up_when_info_asks() {
11478 let mut server = Server::new();
11479 server.set_threads(2);
11480 let ping = lookup(b"PING").expect("PING is a command");
11484 for (at, calls) in [(0, 2), (1, 3)] {
11485 let counters = &server.locals[at];
11486 for _ in 0..calls {
11487 counters.stats.commands.bump();
11488 counters.cmdstats.at(ping).calls.bump();
11489 }
11490 counters.stats.opened();
11491 }
11492 assert_eq!(server.totals().commands, 5);
11493 assert_eq!(server.totals().clients, 2);
11494 assert_eq!(server.totals().connections, 2);
11495 let rows: Vec<_> = server.command_stats().collect();
11496 assert_eq!(rows.len(), 1);
11497 assert_eq!(rows[0].0, "ping");
11498 assert_eq!(rows[0].1.calls, 5);
11499 server.reset_stats();
11502 assert_eq!(server.totals().commands, 0);
11503 assert_eq!(server.totals().connections, 0);
11504 assert_eq!(server.totals().clients, 2);
11505 }
11506
11507 #[test]
11508 fn the_parked_count_says_what_the_waiter_list_says() {
11509 let mut f = Fixture::new();
11510 assert_eq!(f.server.parked(), 0);
11511 for client in 1..=3u64 {
11512 f.session = Session::new(client);
11513 assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
11514 }
11515 assert_eq!(f.server.parked(), 3);
11516 assert_eq!(f.server.waiters().len(), 3);
11517
11518 f.server.forget_waiters(2);
11522 assert_eq!(f.server.parked(), f.server.waiters().len());
11523 f.server.forget_waiters(1);
11524 assert_eq!(f.server.parked(), f.server.waiters().len());
11525 f.run(&[b"RPUSH", b"q", b"v"]);
11526 let mut out = Out::new(Proto::Resp2);
11527 assert!(f.server.serve_waiter(3, 0, &mut out));
11528 f.server.forget_waiters(3);
11529 assert_eq!(f.server.parked(), 0);
11530 assert!(f.server.waiters().is_empty());
11531 }
11532
11533 #[test]
11534 fn a_set_goes_from_bytes_to_bytes() {
11535 let mut f = Fixture::new();
11536 assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
11537 assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
11538 assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
11539 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
11540 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
11541 assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
11542 assert_eq!(
11543 f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
11544 "*3\r\n:1\r\n:0\r\n:1\r\n"
11545 );
11546 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
11547 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
11548 }
11549
11550 #[test]
11551 fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
11552 let mut f = Fixture::new();
11553 assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
11554 assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
11555 assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
11556 assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
11557 assert_eq!(
11558 f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
11559 "*2\r\n:0\r\n:0\r\n"
11560 );
11561 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
11562 }
11563
11564 #[test]
11565 fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
11566 let mut f = Fixture::new();
11570 f.run(&[b"SADD", b"s", b"one"]);
11571 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
11572
11573 f.run(&[b"HELLO", b"3"]);
11574 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
11575 }
11576
11577 #[test]
11578 fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
11579 let mut f = Fixture::new();
11582 f.run(&[b"SADD", b"s", b"42"]);
11583 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
11584 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
11585 assert_eq!(
11586 f.run(&[b"SISMEMBER", b"s", b"042"]),
11587 ":0\r\n",
11588 "the member is the bytes and not the number they parse to"
11589 );
11590 }
11591
11592 #[test]
11593 fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
11594 let mut f = Fixture::new();
11595 f.run(&[b"SET", b"str", b"v"]);
11596 f.run(&[b"SADD", b"set", b"a"]);
11597
11598 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11599 assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
11600 assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
11601 assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
11602 assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
11603 assert_eq!(f.run(&[b"GET", b"set"]), wrong);
11604 assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
11605 assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
11606 assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
11607
11608 assert_eq!(
11611 f.run(&[b"MGET", b"str", b"set", b"nope"]),
11612 "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
11613 );
11614 assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
11616 assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
11617 }
11618
11619 #[test]
11620 fn a_wrongtype_leaves_nothing_half_written() {
11621 let mut f = Fixture::new();
11625 f.run(&[b"SET", b"k", b"v"]);
11626 let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
11627 assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
11628 assert!(!reply.contains('*'), "an array header went out in front");
11629 }
11630
11631 #[test]
11632 fn emptying_a_set_takes_the_key_with_it() {
11633 let mut f = Fixture::new();
11634 f.run(&[b"SADD", b"s", b"a", b"b"]);
11635 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
11636 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
11637 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
11638 assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
11639 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11640 }
11641
11642 fn split_scan(reply: &str) -> (String, Vec<String>) {
11648 let mut lines = reply.split("\r\n");
11649 assert_eq!(lines.next(), Some("*2"), "got {reply}");
11650 lines.next().expect("the cursor header");
11651 let cursor = lines.next().expect("the cursor").to_owned();
11652 let header = lines.next().expect("the member header");
11653 let n: usize = header[1..].parse().expect("a member count");
11654 let mut members = Vec::with_capacity(n);
11655 for _ in 0..n {
11656 lines.next().expect("a member header");
11657 members.push(lines.next().expect("a member").to_owned());
11658 }
11659 (cursor, members)
11660 }
11661
11662 #[test]
11663 fn popping_takes_a_member_off_the_set_and_hands_it_back() {
11664 let mut f = Fixture::new();
11665 f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
11666
11667 let one = f.run(&[b"SPOP", b"s"]);
11668 assert!(
11669 ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
11670 "got {one}"
11671 );
11672 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
11673
11674 let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
11676 assert!(rest.starts_with("*3\r\n"), "got {rest}");
11677 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
11678 assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
11680 assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
11681 }
11682
11683 #[test]
11684 fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
11685 let mut f = Fixture::new();
11690 f.run(&[b"HELLO", b"3"]);
11691 f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
11692
11693 assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
11694 assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
11696
11697 f.run(&[b"SADD", b"one", b"z"]);
11701 assert_eq!(
11702 f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
11703 "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
11704 );
11705 }
11706
11707 #[test]
11708 fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
11709 let mut f = Fixture::new();
11710 f.run(&[b"SADD", b"s", b"only"]);
11711 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
11712 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
11713 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
11714
11715 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
11716 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
11719 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
11720 assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
11722 }
11723
11724 #[test]
11725 fn a_pop_count_that_is_not_a_positive_number_says_so() {
11726 let mut f = Fixture::new();
11727 f.run(&[b"SADD", b"s", b"a"]);
11728 let bad = "-ERR value is out of range, must be positive\r\n";
11729 assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
11730 assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
11731 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
11732 assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
11734 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
11735 }
11736
11737 #[test]
11738 fn a_scan_walks_a_set_of_any_size_exactly_once() {
11739 let mut f = Fixture::new();
11740 let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
11741 let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
11742 .into_iter()
11743 .chain(members.iter().map(Vec::as_slice))
11744 .collect();
11745 f.run(&args);
11746
11747 let mut seen = Vec::new();
11748 let mut cursor = "0".to_owned();
11749 loop {
11750 let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
11751 let (next, got) = split_scan(&reply);
11752 seen.extend(got);
11753 cursor = next;
11754 if cursor == "0" {
11755 break;
11756 }
11757 }
11758 seen.sort();
11759 seen.dedup();
11760 assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
11761
11762 f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
11765 let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
11766 assert_eq!(cursor, "0");
11767 assert_eq!(got.len(), 3);
11768 assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
11770 }
11771
11772 #[test]
11773 fn a_scan_takes_match_and_count_and_refuses_anything_else() {
11774 let mut f = Fixture::new();
11775 f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
11776
11777 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
11778 let mut got = got;
11779 got.sort();
11780 assert_eq!(got, ["aa", "ab"]);
11781
11782 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
11785 let mut got = got;
11786 got.sort();
11787 assert_eq!(got, ["12", "13"]);
11788
11789 assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
11790 assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
11791 assert_eq!(
11792 f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
11793 "-ERR syntax error\r\n"
11794 );
11795 assert_eq!(
11798 f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
11799 "-ERR syntax error\r\n"
11800 );
11801 }
11802
11803 #[test]
11804 fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
11805 let mut f = Fixture::new();
11806 f.run(&[b"SADD", b"src", b"a", b"b"]);
11807 f.run(&[b"SADD", b"dst", b"c"]);
11808
11809 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
11810 assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
11811 assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
11812 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
11814 assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
11815
11816 assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
11819 assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
11820 assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
11821 }
11822
11823 #[test]
11824 fn moving_checks_the_types_in_the_order_redis_checks_them() {
11825 let mut f = Fixture::new();
11829 f.run(&[b"SET", b"str", b"v"]);
11830 f.run(&[b"SADD", b"set", b"a"]);
11831
11832 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11833 assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
11834 assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
11835 assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
11836 assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
11837 assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
11838 assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
11839 assert_eq!(
11840 f.run(&[b"SISMEMBER", b"set", b"a"]),
11841 ":1\r\n",
11842 "and none of that moved anything"
11843 );
11844 }
11845
11846 #[test]
11847 fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
11848 let mut f = Fixture::new();
11851 f.run(&[b"SADD", b"s", b"a"]);
11852 for bad in [
11853 &[b"SSCAN".as_slice(), b"s", b"abc"][..],
11854 &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
11855 &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
11856 ] {
11857 let reply = f.run(bad);
11858 assert!(reply.starts_with("-ERR"), "got {reply}");
11859 assert!(!reply.contains('*'), "an array header went out in front");
11860 }
11861 }
11862
11863 #[test]
11864 fn a_hash_writes_reads_and_deletes_its_fields() {
11865 let mut f = Fixture::new();
11866 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
11867 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
11868 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
11869 assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
11870 assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
11871 assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
11872 assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
11873 assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
11874 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
11875 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
11876
11877 assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
11880
11881 assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
11882 assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
11883 assert_eq!(
11884 f.run(&[b"EXISTS", b"h"]),
11885 ":0\r\n",
11886 "and losing the last field lost the key"
11887 );
11888 }
11889
11890 #[test]
11891 fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
11892 let mut f = Fixture::new();
11893 f.run(&[b"HSET", b"h", b"a", b"1"]);
11894 assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
11895 assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
11896 assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
11897 assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
11898 assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
11899
11900 f.run(&[b"HELLO", b"3"]);
11901 assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
11902 assert_eq!(
11903 f.run(&[b"HGETALL", b"nokey"]),
11904 "%0\r\n",
11905 "a missing key is the empty hash and never a nil"
11906 );
11907 assert_eq!(
11908 f.run(&[b"HKEYS", b"h"]),
11909 "*1\r\n$1\r\na\r\n",
11910 "and the two that answer one side stay arrays"
11911 );
11912 }
11913
11914 #[test]
11915 fn hmget_answers_once_per_field_and_hmset_answers_ok() {
11916 let mut f = Fixture::new();
11917 assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
11918 assert_eq!(
11919 f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
11920 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
11921 "the reply is positional, so b is a nil and not a gap"
11922 );
11923 assert_eq!(
11924 f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
11925 "*2\r\n$-1\r\n$-1\r\n",
11926 "and a missing key is all nils rather than an empty array"
11927 );
11928
11929 assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
11930 assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
11931 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
11932 }
11933
11934 #[test]
11935 fn a_hash_counts_up_and_says_so_when_it_cannot() {
11936 let mut f = Fixture::new();
11937 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
11938 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
11939 assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
11940 assert_eq!(
11941 f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
11942 "$4\r\n10.5\r\n",
11943 "a bulk string and not a double, on both protocols"
11944 );
11945
11946 f.run(&[b"HSET", b"h", b"s", b"words"]);
11947 let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
11948 assert!(
11949 bad.starts_with("-ERR hash value is not an integer"),
11950 "{bad}"
11951 );
11952 let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
11953 assert!(
11954 bad.starts_with("-ERR value is not an integer"),
11955 "a bad argument is not yet a hash value, {bad}"
11956 );
11957 assert_eq!(
11958 f.run(&[b"HGET", b"h", b"s"]),
11959 "$5\r\nwords\r\n",
11960 "and neither of them wrote anything"
11961 );
11962 }
11963
11964 #[test]
11965 fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
11966 let fields = if cfg!(miri) { 96 } else { 500 };
11971 let mut f = Fixture::new();
11972 for i in 0..fields {
11973 let field = format!("field-{i}");
11974 let value = format!("value-{i}");
11975 f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
11976 }
11977
11978 let mut seen: Vec<String> = Vec::new();
11979 let mut cursor = "0".to_owned();
11980 loop {
11981 let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
11982 let (next, items) = scan_reply(&reply);
11983 assert_eq!(items.len() % 2, 0, "a pair went out half written");
11984 for pair in items.chunks(2) {
11985 assert_eq!(
11986 pair[0].strip_prefix("field-"),
11987 pair[1].strip_prefix("value-"),
11988 "a field came back with someone else's value"
11989 );
11990 seen.push(pair[0].clone());
11991 }
11992 cursor = next;
11993 if cursor == "0" {
11994 break;
11995 }
11996 }
11997 seen.sort();
11998 seen.dedup();
11999 assert_eq!(seen.len(), fields, "every field once and only once");
12000
12001 let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
12002 assert!(
12003 items.iter().all(|s| s.starts_with("field-")),
12004 "NOVALUES still sent the values"
12005 );
12006
12007 let last = fields - 1;
12008 let (_, one) = scan_reply(&f.run(&[
12009 b"HSCAN",
12010 b"h",
12011 b"0",
12012 b"MATCH",
12013 format!("field-{last}").as_bytes(),
12014 b"COUNT",
12015 b"1000",
12016 ]));
12017 assert_eq!(
12018 one,
12019 [format!("field-{last}"), format!("value-{last}")],
12020 "MATCH is on the field"
12021 );
12022 }
12023
12024 #[test]
12025 fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
12026 let mut f = Fixture::new();
12027 f.run(&[b"HSET", b"h", b"a", b"1"]);
12028 assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
12029 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
12030 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
12031 assert_eq!(
12032 f.run(&[b"HRANDFIELD", b"h", b"3"]),
12033 "*1\r\n$1\r\na\r\n",
12034 "a positive count is capped at the size of the hash"
12035 );
12036 assert_eq!(
12037 f.run(&[b"HRANDFIELD", b"h", b"-3"]),
12038 "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
12039 "and a negative one repeats itself"
12040 );
12041 assert_eq!(
12042 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
12043 "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
12044 "flat on RESP2"
12045 );
12046
12047 f.run(&[b"HELLO", b"3"]);
12048 assert_eq!(
12049 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
12050 "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
12051 "and nested on RESP3, but still an array and never a map"
12052 );
12053 }
12054
12055 #[test]
12056 fn every_hash_command_says_wrongtype_and_writes_nothing() {
12057 let mut f = Fixture::new();
12058 f.run(&[b"SET", b"str", b"v"]);
12059 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12060
12061 for cmd in [
12062 &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
12063 &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
12064 &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
12065 &[b"HGET".as_slice(), b"str", b"f"][..],
12066 &[b"HMGET".as_slice(), b"str", b"f"][..],
12067 &[b"HDEL".as_slice(), b"str", b"f"][..],
12068 &[b"HLEN".as_slice(), b"str"][..],
12069 &[b"HEXISTS".as_slice(), b"str", b"f"][..],
12070 &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
12071 &[b"HGETALL".as_slice(), b"str"][..],
12072 &[b"HKEYS".as_slice(), b"str"][..],
12073 &[b"HVALS".as_slice(), b"str"][..],
12074 &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
12075 &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
12076 &[b"HRANDFIELD".as_slice(), b"str"][..],
12077 &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
12078 &[b"HSCAN".as_slice(), b"str", b"0"][..],
12079 ] {
12080 let reply = f.run(cmd);
12081 assert_eq!(reply, wrong, "{:?}", cmd[0]);
12082 }
12083 assert_eq!(
12084 f.run(&[b"GET", b"str"]),
12085 "$1\r\nv\r\n",
12086 "and none of them touched the value"
12087 );
12088 }
12089
12090 #[test]
12091 fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
12092 let mut f = Fixture::new();
12093 f.run(&[b"HSET", b"h", b"f", b"v"]);
12094 for bad in [
12095 &[b"HSCAN".as_slice(), b"h", b"abc"][..],
12096 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
12097 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
12098 &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
12099 ] {
12100 let reply = f.run(bad);
12101 assert!(reply.starts_with("-ERR"), "got {reply}");
12102 assert!(!reply.contains('*'), "an array header went out in front");
12103 }
12104 }
12105
12106 #[test]
12107 fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
12108 let mut f = Fixture::new();
12109 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12110 assert_eq!(
12111 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
12112 "*1\r\n:1\r\n"
12113 );
12114 assert_eq!(
12115 f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
12116 "*3\r\n:100\r\n:-1\r\n:-2\r\n",
12117 "one answer per field, and the two sentinels are TTL's own"
12118 );
12119
12120 let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
12123 assert!((99_000..=100_000).contains(&ms), "got {ms}");
12124 let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
12125 let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
12126 assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
12127 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
12128
12129 assert_eq!(
12130 f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
12131 "*3\r\n:1\r\n:-1\r\n:-2\r\n",
12132 "one for the deadline taken off, and it does not say what it was"
12133 );
12134 assert_eq!(
12135 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12136 "*1\r\n:-1\r\n"
12137 );
12138 assert_eq!(
12139 f.run(&[b"HGET", b"h", b"a"]),
12140 "$1\r\n1\r\n",
12141 "and the field is still there with the value it had"
12142 );
12143 }
12144
12145 #[test]
12146 fn a_deadline_that_has_already_gone_deletes_the_field_now() {
12147 let mut f = Fixture::new();
12148 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12149 assert_eq!(
12150 f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
12151 "*1\r\n:2\r\n",
12152 "two, and not one, because nothing was stored"
12153 );
12154 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
12155 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
12156
12157 assert_eq!(
12158 f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
12159 "*1\r\n:2\r\n"
12160 );
12161 assert_eq!(
12162 f.run(&[b"EXISTS", b"h"]),
12163 ":0\r\n",
12164 "and the last field going took the key with it"
12165 );
12166
12167 f.run(&[b"HSET", b"h", b"a", b"1"]);
12170 assert_eq!(
12171 f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
12172 "*1\r\n:2\r\n"
12173 );
12174 }
12175
12176 #[test]
12177 fn a_field_is_gone_once_its_moment_passes() {
12178 let mut f = Fixture::new();
12179 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12180 assert_eq!(
12181 f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
12182 "*1\r\n:1\r\n"
12183 );
12184 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
12185
12186 f.server.advance_clock_ms(60);
12190 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
12191 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
12192 assert_eq!(
12193 f.run(&[b"HGETALL", b"h"]),
12194 "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
12195 "and the walks do not hand back a field that has expired"
12196 );
12197 }
12198
12199 #[test]
12200 fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
12201 let mut f = Fixture::new();
12202 for cmd in [
12203 &[
12204 b"HEXPIRE".as_slice(),
12205 b"nokey",
12206 b"100",
12207 b"FIELDS",
12208 b"2",
12209 b"a",
12210 b"b",
12211 ][..],
12212 &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
12213 &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
12214 &[
12215 b"HEXPIRETIME".as_slice(),
12216 b"nokey",
12217 b"FIELDS",
12218 b"2",
12219 b"a",
12220 b"b",
12221 ][..],
12222 &[
12223 b"HPERSIST".as_slice(),
12224 b"nokey",
12225 b"FIELDS",
12226 b"2",
12227 b"a",
12228 b"b",
12229 ][..],
12230 ] {
12231 assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
12232 }
12233 }
12234
12235 #[test]
12236 fn writing_a_field_clears_the_deadline_that_was_on_it() {
12237 let mut f = Fixture::new();
12238 f.run(&[b"HSET", b"h", b"a", b"1"]);
12239 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
12240 f.run(&[b"HSET", b"h", b"a", b"2"]);
12241 assert_eq!(
12242 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12243 "*1\r\n:-1\r\n",
12244 "Redis has done this since 7.4, and it is why HGETEX exists"
12245 );
12246 }
12247
12248 #[test]
12249 fn the_four_conditions_reach_the_store_the_way_they_were_written() {
12250 let mut f = Fixture::new();
12251 f.run(&[b"HSET", b"h", b"a", b"1"]);
12252 assert_eq!(
12253 f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
12254 "*1\r\n:0\r\n",
12255 "XX on a field with no deadline changes nothing"
12256 );
12257 assert_eq!(
12258 f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
12259 "*1\r\n:1\r\n"
12260 );
12261 assert_eq!(
12262 f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
12263 "*1\r\n:0\r\n",
12264 "and NX will not move one that is already there"
12265 );
12266 assert_eq!(
12267 f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
12268 "*1\r\n:0\r\n"
12269 );
12270 assert_eq!(
12271 f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
12272 "*1\r\n:1\r\n"
12273 );
12274 assert_eq!(
12275 f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
12276 "*1\r\n:1\r\n"
12277 );
12278 assert_eq!(
12279 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12280 "*1\r\n:50\r\n"
12281 );
12282 }
12283
12284 #[test]
12285 fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
12286 let mut f = Fixture::new();
12287 f.run(&[b"HSET", b"h", b"a", b"1"]);
12288 for (bad, want) in [
12289 (
12290 &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
12291 "-ERR invalid expire time, must be >= 0",
12292 ),
12293 (
12294 &[
12295 b"HEXPIRE".as_slice(),
12296 b"h",
12297 b"9999999999999999",
12298 b"FIELDS",
12299 b"1",
12300 b"a",
12301 ][..],
12302 "-ERR invalid expire time in 'hexpire' command",
12303 ),
12304 (
12305 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
12306 "-ERR wrong number of arguments for 'hexpire' command",
12307 ),
12308 (
12309 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
12310 "-ERR Parameter `numFields` should be greater than 0",
12311 ),
12312 (
12313 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
12314 "-ERR wrong number of arguments",
12315 ),
12316 (
12317 &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
12318 "-ERR wrong number of arguments",
12319 ),
12320 ] {
12321 let reply = f.run(bad);
12322 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
12323 assert!(!reply.contains('*'), "an array header went out in front");
12324 }
12325 assert_eq!(
12326 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12327 "*1\r\n:-1\r\n",
12328 "and not one of them put a deadline on anything"
12329 );
12330 }
12331
12332 #[test]
12333 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
12334 let mut f = Fixture::new();
12335 f.run(&[b"SET", b"str", b"v"]);
12336 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12337
12338 for cmd in [
12339 &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
12340 &[
12341 b"HPEXPIRE".as_slice(),
12342 b"str",
12343 b"100",
12344 b"FIELDS",
12345 b"1",
12346 b"f",
12347 ][..],
12348 &[
12349 b"HEXPIREAT".as_slice(),
12350 b"str",
12351 b"9999999999",
12352 b"FIELDS",
12353 b"1",
12354 b"f",
12355 ][..],
12356 &[
12357 b"HPEXPIREAT".as_slice(),
12358 b"str",
12359 b"9999999999999",
12360 b"FIELDS",
12361 b"1",
12362 b"f",
12363 ][..],
12364 &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12365 &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12366 &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12367 &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12368 &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12369 ] {
12370 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12371 }
12372 assert_eq!(
12373 f.run(&[b"GET", b"str"]),
12374 "$1\r\nv\r\n",
12375 "and none of them touched the value"
12376 );
12377 }
12378
12379 #[test]
12380 fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
12381 let mut f = Fixture::new();
12382 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12383 assert_eq!(
12384 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
12385 "*2\r\n$1\r\n1\r\n$-1\r\n",
12386 "positional, so the field that was not there is a nil in its place"
12387 );
12388 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
12389 assert_eq!(
12390 f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
12391 "*1\r\n$-1\r\n"
12392 );
12393 assert_eq!(
12394 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
12395 "*1\r\n$1\r\n2\r\n"
12396 );
12397 assert_eq!(
12398 f.run(&[b"EXISTS", b"h"]),
12399 ":0\r\n",
12400 "and the last field took the key"
12401 );
12402 }
12403
12404 #[test]
12405 fn hgetex_reads_and_moves_the_deadline_in_one_command() {
12406 let mut f = Fixture::new();
12407 f.run(&[b"HSET", b"h", b"a", b"1"]);
12408 assert_eq!(
12409 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
12410 "*1\r\n$1\r\n1\r\n"
12411 );
12412 assert_eq!(
12413 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12414 "*1\r\n:-1\r\n",
12415 "no option means leave it alone, which is the one place this is not GETEX"
12416 );
12417
12418 f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
12419 assert_eq!(
12420 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12421 "*1\r\n:100\r\n"
12422 );
12423 f.run(&[b"HGETEX", b"h", 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 "and a plain read really does leave it alone"
12428 );
12429 assert_eq!(
12430 f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
12431 "*1\r\n$1\r\n1\r\n"
12432 );
12433 assert_eq!(
12434 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12435 "*1\r\n:-1\r\n"
12436 );
12437
12438 assert_eq!(
12439 f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
12440 "*1\r\n$1\r\n1\r\n",
12441 "the value goes out before the deadline that has already gone is applied"
12442 );
12443 assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
12444 assert_eq!(
12445 f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
12446 "*1\r\n$-1\r\n"
12447 );
12448 }
12449
12450 #[test]
12451 fn hsetex_writes_all_of_it_or_none_of_it() {
12452 let mut f = Fixture::new();
12453 assert_eq!(
12454 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
12455 ":1\r\n"
12456 );
12457 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12458 assert_eq!(
12459 f.run(&[
12460 b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
12461 ]),
12462 ":0\r\n",
12463 "FNX wants every field named to be missing"
12464 );
12465 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12466 assert_eq!(
12467 f.run(&[b"HEXISTS", b"h", b"new"]),
12468 ":0\r\n",
12469 "and none of the list was written"
12470 );
12471 assert_eq!(
12472 f.run(&[
12473 b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
12474 ]),
12475 ":0\r\n",
12476 "and FXX wants every one of them to be there"
12477 );
12478 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12479 assert_eq!(
12480 f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
12481 ":1\r\n"
12482 );
12483 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
12484
12485 assert_eq!(
12486 f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
12487 ":0\r\n"
12488 );
12489 assert_eq!(
12490 f.run(&[b"EXISTS", b"gone"]),
12491 ":0\r\n",
12492 "a key with no fields cannot meet FXX and is not created trying"
12493 );
12494 }
12495
12496 #[test]
12497 fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
12498 let mut f = Fixture::new();
12499 f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
12500 assert_eq!(
12501 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12502 "*1\r\n:100\r\n"
12503 );
12504
12505 f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
12506 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
12507 assert_eq!(
12508 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12509 "*1\r\n:100\r\n",
12510 "KEEPTTL put back what the write cleared"
12511 );
12512
12513 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
12514 assert_eq!(
12515 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12516 "*1\r\n:-1\r\n",
12517 "and without it a write clears the deadline the way HSET does"
12518 );
12519
12520 assert_eq!(
12523 f.run(&[
12524 b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
12525 ]),
12526 ":1\r\n"
12527 );
12528 assert_eq!(
12529 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12530 "*1\r\n:100\r\n"
12531 );
12532
12533 assert_eq!(
12534 f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
12535 ":1\r\n",
12536 "written, and not the separate code the HEXPIRE family has for this"
12537 );
12538 assert_eq!(
12539 f.run(&[b"EXISTS", b"h"]),
12540 ":0\r\n",
12541 "and storing it and then removing it emptied the hash"
12542 );
12543 }
12544
12545 #[test]
12546 fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
12547 let mut f = Fixture::new();
12548 f.run(&[b"HSET", b"h", b"a", b"1"]);
12549 for (bad, want) in [
12550 (
12552 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
12553 "-ERR Number of fields must be a positive integer",
12554 ),
12555 (
12556 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
12557 "-ERR The `numfields` parameter must match the number of arguments",
12558 ),
12559 (
12560 &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
12561 "-ERR Mandatory argument FIELDS is missing or not at the right position",
12562 ),
12563 (
12565 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
12566 "-ERR invalid number of fields",
12567 ),
12568 (
12569 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
12570 "-ERR wrong number of arguments",
12571 ),
12572 (
12573 &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
12574 "-ERR unknown argument: FIELD",
12575 ),
12576 (
12577 &[
12578 b"HGETEX".as_slice(),
12579 b"h",
12580 b"KEEPTTL",
12581 b"FIELDS",
12582 b"1",
12583 b"a",
12584 ][..],
12585 "-ERR unknown argument: KEEPTTL",
12586 ),
12587 (
12588 &[
12589 b"HGETEX".as_slice(),
12590 b"h",
12591 b"EX",
12592 b"100",
12593 b"PERSIST",
12594 b"FIELDS",
12595 b"1",
12596 b"a",
12597 ][..],
12598 "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
12599 ),
12600 (
12601 &[
12602 b"HSETEX".as_slice(),
12603 b"h",
12604 b"EX",
12605 b"1",
12606 b"KEEPTTL",
12607 b"FIELDS",
12608 b"1",
12609 b"a",
12610 b"1",
12611 ][..],
12612 "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
12613 ),
12614 (
12615 &[
12616 b"HSETEX".as_slice(),
12617 b"h",
12618 b"FNX",
12619 b"FXX",
12620 b"FIELDS",
12621 b"1",
12622 b"a",
12623 b"1",
12624 ][..],
12625 "-ERR Only one of FXX or FNX arguments can be specified",
12626 ),
12627 (
12628 &[
12629 b"HSETEX".as_slice(),
12630 b"h",
12631 b"FIELDS",
12632 b"2",
12633 b"a",
12634 b"1",
12635 b"b",
12636 ][..],
12637 "-ERR wrong number of arguments",
12638 ),
12639 (
12640 &[
12641 b"HGETEX".as_slice(),
12642 b"h",
12643 b"EX",
12644 b"-1",
12645 b"FIELDS",
12646 b"1",
12647 b"a",
12648 ][..],
12649 "-ERR invalid expire time, must be >= 0",
12650 ),
12651 (
12652 &[
12653 b"HGETEX".as_slice(),
12654 b"h",
12655 b"PXAT",
12656 b"99999999999999",
12657 b"FIELDS",
12658 b"1",
12659 b"a",
12660 ][..],
12661 "-ERR invalid expire time in 'hgetex' command",
12662 ),
12663 (
12664 &[
12665 b"HSETEX".as_slice(),
12666 b"h",
12667 b"EX",
12668 b"abc",
12669 b"FIELDS",
12670 b"1",
12671 b"a",
12672 b"1",
12673 ][..],
12674 "-ERR value is not an integer or out of range",
12675 ),
12676 ] {
12677 let reply = f.run(bad);
12678 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
12679 assert!(!reply.contains('*'), "an array header went out in front");
12680 }
12681 assert_eq!(
12682 f.run(&[b"HGET", b"h", b"a"]),
12683 "$1\r\n1\r\n",
12684 "and not one of them wrote anything"
12685 );
12686 assert_eq!(
12687 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12688 "*1\r\n:-1\r\n"
12689 );
12690 }
12691
12692 #[test]
12693 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
12694 let mut f = Fixture::new();
12695 f.run(&[b"SET", b"str", b"v"]);
12696 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12697 for cmd in [
12698 &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12699 &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12700 &[
12701 b"HGETEX".as_slice(),
12702 b"str",
12703 b"EX",
12704 b"100",
12705 b"FIELDS",
12706 b"1",
12707 b"f",
12708 ][..],
12709 &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
12710 ] {
12711 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12712 }
12713 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
12714 }
12715
12716 #[test]
12724 fn himport_writes_declared_values_into_sorted_fields() {
12725 let mut f = Fixture::new();
12726 assert_eq!(
12727 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
12728 "+OK\r\n"
12729 );
12730 assert_eq!(
12731 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
12732 "+OK\r\n"
12733 );
12734 assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
12735 assert_eq!(
12736 f.run(&[b"HGETALL", b"k"]),
12737 bulks(&["a", "3", "b", "1", "aa", "2"])
12738 );
12739 }
12740
12741 #[test]
12744 fn himport_set_replaces_the_whole_key() {
12745 let mut f = Fixture::new();
12746 f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
12747 f.run(&[b"EXPIRE", b"k", b"100"]);
12748 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12749 assert_eq!(
12750 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
12751 "+OK\r\n"
12752 );
12753 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
12754 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
12755 }
12756
12757 #[test]
12760 fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
12761 let mut f = Fixture::new();
12762 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
12763 f.run(&[b"SELECT", b"1"]);
12764 assert_eq!(
12765 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
12766 "+OK\r\n"
12767 );
12768 f.run(&[b"SELECT", b"0"]);
12769 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
12770 assert_eq!(
12771 f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
12772 "-ERR no such fieldset\r\n"
12773 );
12774 }
12775
12776 #[test]
12783 fn himport_complains_in_the_order_a_real_server_does() {
12784 let mut f = Fixture::new();
12785 f.run(&[b"SET", b"str", b"v"]);
12786 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12787 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12788 assert_eq!(
12789 f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
12790 wrong,
12791 "the type beats a missing fieldset"
12792 );
12793 assert_eq!(
12794 f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
12795 wrong,
12796 "and it beats a value count that does not fit"
12797 );
12798 assert_eq!(
12799 f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
12800 "-ERR no such fieldset\r\n"
12801 );
12802 for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
12804 let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
12805 line.extend_from_slice(values);
12806 assert_eq!(
12807 f.run(&line),
12808 "-ERR value count does not match fieldset field count\r\n",
12809 "{} values into two fields",
12810 values.len()
12811 );
12812 }
12813 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
12814 }
12815
12816 #[test]
12818 fn himport_checks_each_subcommand_count_under_its_own_name() {
12819 let mut f = Fixture::new();
12820 assert_eq!(
12821 f.run(&[b"HIMPORT"]),
12822 "-ERR wrong number of arguments for 'himport' command\r\n"
12823 );
12824 for (rest, name) in [
12825 (&["PREPARE"][..], "prepare"),
12826 (&["PREPARE", "fs"][..], "prepare"),
12827 (&["SET"][..], "set"),
12828 (&["SET", "k"][..], "set"),
12829 (&["SET", "k", "fs"][..], "set"),
12830 (&["DISCARD"][..], "discard"),
12831 (&["DISCARD", "a", "b"][..], "discard"),
12832 (&["DISCARDALL", "x"][..], "discardall"),
12833 ] {
12834 let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
12835 line.extend(rest.iter().map(|a| a.as_bytes()));
12836 assert_eq!(
12837 f.run(&line),
12838 format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
12839 "HIMPORT {}",
12840 rest.join(" ")
12841 );
12842 }
12843 assert_eq!(
12844 f.run(&[b"HIMPORT", b"NOPE", b"x"]),
12845 "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
12846 );
12847 }
12848
12849 #[test]
12852 fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
12853 let mut f = Fixture::new();
12854 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12855 assert_eq!(
12856 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
12857 "-ERR duplicate field name in fieldset\r\n"
12858 );
12859 assert_eq!(
12860 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
12861 "+OK\r\n"
12862 );
12863 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
12864 }
12865
12866 #[test]
12869 fn himport_prepare_replaces_and_the_discards_count() {
12870 let mut f = Fixture::new();
12871 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12872 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
12873 assert_eq!(
12874 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
12875 "+OK\r\n"
12876 );
12877 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
12878
12879 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
12880 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
12881 f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
12882 f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
12883 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
12884 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
12885 }
12886
12887 fn int(reply: &str) -> i64 {
12893 let body = reply
12894 .strip_prefix(':')
12895 .and_then(|s| s.strip_suffix("\r\n"))
12896 .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
12897 body.parse().expect("an integer")
12898 }
12899
12900 fn int_reply(reply: &str) -> i64 {
12901 let body = reply
12902 .strip_prefix("*1\r\n:")
12903 .and_then(|s| s.strip_suffix("\r\n"))
12904 .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
12905 body.parse().expect("an integer")
12906 }
12907
12908 fn scan_reply(reply: &str) -> (String, Vec<String>) {
12910 let mut lines = reply.split("\r\n");
12911 assert_eq!(lines.next(), Some("*2"), "got {reply}");
12912 lines.next().expect("the cursor header");
12913 let cursor = lines.next().expect("a cursor").to_owned();
12914 let header = lines.next().expect("an item count");
12915 let n: usize = header[1..].parse().expect("a count");
12916 let mut items = Vec::with_capacity(n);
12917 for _ in 0..n {
12918 lines.next().expect("an item header");
12919 items.push(lines.next().expect("an item").to_owned());
12920 }
12921 (cursor, items)
12922 }
12923
12924 fn sorted(reply: &str) -> Vec<String> {
12927 let mut lines = reply.split("\r\n");
12928 let header = lines.next().expect("a header");
12929 assert!(
12930 header.starts_with('*') || header.starts_with('~'),
12931 "got {reply}"
12932 );
12933 let n: usize = header[1..].parse().expect("a member count");
12934 let mut got = Vec::with_capacity(n);
12935 for _ in 0..n {
12936 lines.next().expect("a member header");
12937 got.push(lines.next().expect("a member").to_owned());
12938 }
12939 got.sort();
12940 got
12941 }
12942
12943 #[test]
12944 fn the_algebra_answers_what_the_sets_share_and_do_not() {
12945 let mut f = Fixture::new();
12946 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
12947 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
12948 f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
12949
12950 assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
12951 assert_eq!(
12952 sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
12953 ["1", "2", "3", "4", "5"]
12954 );
12955 assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
12956 assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
12957
12958 assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
12961 assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
12962 assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
12963 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
12964 }
12965
12966 #[test]
12967 fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
12968 let mut f = Fixture::new();
12969 f.run(&[b"SADD", b"a", b"x"]);
12970 assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
12971 assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
12972 assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
12973
12974 f.run(&[b"HELLO", b"3"]);
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 assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
12979 }
12980
12981 #[test]
12982 fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
12983 let mut f = Fixture::new();
12984 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
12985 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
12986
12987 assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
12988 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
12989 assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
12990 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
12991 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
12992 assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
12993
12994 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
12997 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
12998 assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
12999 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
13000
13001 f.run(&[b"SET", b"str", b"v"]);
13004 assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
13005 assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
13006 }
13007
13008 #[test]
13009 fn sintercard_counts_without_building_and_stops_at_a_limit() {
13010 let mut f = Fixture::new();
13011 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
13012 f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
13013
13014 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
13015 assert_eq!(
13016 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
13017 ":2\r\n"
13018 );
13019 assert_eq!(
13020 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
13021 ":3\r\n",
13022 "a limit of zero is no limit"
13023 );
13024 assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
13025 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
13026
13027 assert_eq!(
13029 f.run(&[b"SINTERCARD", b"0", b"a"]),
13030 "-ERR numkeys should be greater than 0\r\n"
13031 );
13032 assert_eq!(
13033 f.run(&[b"SINTERCARD", b"abc", b"a"]),
13034 "-ERR numkeys should be greater than 0\r\n"
13035 );
13036 assert_eq!(
13037 f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
13038 "-ERR Number of keys can't be greater than number of args\r\n"
13039 );
13040 assert_eq!(
13041 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
13042 "-ERR LIMIT can't be negative\r\n"
13043 );
13044 assert_eq!(
13045 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
13046 "-ERR syntax error\r\n"
13047 );
13048 f.run(&[b"SADD", b"LIMIT", b"2"]);
13050 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
13051 }
13052
13053 #[test]
13056 fn sunioncard_and_sdiffcard_count_without_building() {
13057 let mut f = Fixture::new();
13058 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
13059 f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
13060
13061 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
13062 assert_eq!(
13063 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
13064 ":2\r\n"
13065 );
13066 assert_eq!(
13067 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
13068 ":6\r\n",
13069 "a limit of zero is no limit"
13070 );
13071 assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
13072 assert_eq!(
13073 f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
13074 ":4\r\n",
13075 "a missing key adds nothing to a union"
13076 );
13077
13078 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
13079 assert_eq!(
13080 f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
13081 ":1\r\n"
13082 );
13083 assert_eq!(
13084 f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
13085 ":2\r\n",
13086 "a difference is not symmetric"
13087 );
13088 assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
13089 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
13090 assert_eq!(
13091 f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
13092 ":0\r\n",
13093 "nothing taken away from nothing"
13094 );
13095
13096 for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
13099 assert_eq!(
13100 f.run(&[name, b"0", b"a"]),
13101 "-ERR numkeys should be greater than 0\r\n"
13102 );
13103 assert_eq!(
13104 f.run(&[name, b"abc", b"a"]),
13105 "-ERR numkeys should be greater than 0\r\n"
13106 );
13107 assert_eq!(
13108 f.run(&[name, b"-1", b"a"]),
13109 "-ERR numkeys should be greater than 0\r\n"
13110 );
13111 assert_eq!(
13112 f.run(&[name, b"3", b"a", b"b"]),
13113 "-ERR Number of keys can't be greater than number of args\r\n"
13114 );
13115 assert_eq!(
13116 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
13117 "-ERR LIMIT can't be negative\r\n"
13118 );
13119 assert_eq!(
13120 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
13121 "-ERR LIMIT can't be negative\r\n",
13122 "a LIMIT that is not a number gets the negative message too"
13123 );
13124 assert_eq!(
13125 f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
13126 "-ERR syntax error\r\n"
13127 );
13128 assert_eq!(
13129 f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
13130 "-ERR syntax error\r\n"
13131 );
13132 assert_eq!(
13133 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
13134 "-ERR syntax error\r\n"
13135 );
13136 }
13137
13138 f.run(&[b"SADD", b"LIMIT", b"2"]);
13140 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
13141 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
13142 }
13143
13144 #[test]
13145 fn the_algebra_answers_wrongtype_before_it_writes_anything() {
13146 let mut f = Fixture::new();
13147 f.run(&[b"SADD", b"a", b"1"]);
13148 f.run(&[b"SADD", b"d", b"old"]);
13149 f.run(&[b"SET", b"str", b"v"]);
13150
13151 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13152 for bad in [
13153 &[b"SINTER".as_slice(), b"a", b"str"][..],
13154 &[b"SUNION".as_slice(), b"str"][..],
13155 &[b"SDIFF".as_slice(), b"a", b"str"][..],
13156 &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
13157 &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
13158 &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
13159 &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
13160 ] {
13161 let reply = f.run(bad);
13162 assert_eq!(reply, wrong, "for {:?}", bad[0]);
13163 }
13164 assert_eq!(
13165 f.run(&[b"SMEMBERS", b"d"]),
13166 "*1\r\n$3\r\nold\r\n",
13167 "and the destination was left alone every time"
13168 );
13169 }
13170
13171 #[cfg_attr(miri, ignore = "the volume is the claim")]
13183 #[test]
13184 fn churning_sets_does_not_grow_the_server() {
13185 let mut f = Fixture::new();
13186 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
13187 let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
13188 .chain(std::iter::once(&b"s"[..]))
13189 .chain(members.iter().map(Vec::as_slice))
13190 .collect();
13191
13192 f.run(&args);
13193 f.run(&[b"DEL", b"s"]);
13194 f.server.compact_step();
13195 let after_first = f.server.memory_bytes();
13196
13197 for _ in 0..200 {
13198 f.run(&args);
13199 f.run(&[b"DEL", b"s"]);
13200 f.server.compact_step();
13201 }
13202 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13203 assert!(
13204 f.server.memory_bytes() <= after_first * 2,
13205 "held {} after two hundred passes against {after_first} after one",
13206 f.server.memory_bytes()
13207 );
13208 }
13209
13210 #[test]
13218 fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
13219 let mut f = Fixture::new();
13220 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
13221 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
13222 assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
13223 assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
13224 assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
13225 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
13226
13227 assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
13229 assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
13230 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
13231
13232 f.run(&[b"SET", b"num", b"12345"]);
13233 assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
13234 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
13235 assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
13236 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
13237 assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
13238 }
13239
13240 #[test]
13245 fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
13246 let mut f = Fixture::new();
13247 f.run(&[b"SET", b"mykey", b"foobar"]);
13248 assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
13249 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
13250 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
13251 assert_eq!(
13252 f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
13253 ":6\r\n"
13254 );
13255 assert_eq!(
13256 f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
13257 ":25\r\n"
13258 );
13259 assert_eq!(
13260 f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
13261 ":17\r\n"
13262 );
13263 assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
13264
13265 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
13268
13269 assert_eq!(
13271 f.run(&[b"BITCOUNT", b"mykey", b"0"]),
13272 "-ERR syntax error\r\n"
13273 );
13274 assert_eq!(
13275 f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
13276 "-ERR syntax error\r\n"
13277 );
13278 }
13279
13280 #[test]
13286 fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
13287 let mut f = Fixture::new();
13288 f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
13289 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
13290 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
13291 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
13292 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
13293 assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
13294
13295 f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
13296 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
13297 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
13298 assert_eq!(
13299 f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
13300 ":8\r\n"
13301 );
13302
13303 assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
13306 assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
13307 }
13308
13309 #[test]
13311 fn the_eight_combinations_write_what_a_real_server_writes() {
13312 let mut f = Fixture::new();
13313 f.run(&[b"SET", b"a", b"abc"]);
13314 f.run(&[b"SET", b"b", b"abd"]);
13315 let cases: &[(&[u8], &str)] = &[
13316 (b"AND", "ab`"),
13317 (b"OR", "abg"),
13318 (b"XOR", "\u{0}\u{0}\u{7}"),
13319 (b"DIFF", "\u{0}\u{0}\u{3}"),
13320 (b"DIFF1", "\u{0}\u{0}\u{4}"),
13321 (b"ANDOR", "ab`"),
13322 (b"ONE", "\u{0}\u{0}\u{7}"),
13323 ];
13324 for (op, want) in cases {
13325 assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
13326 assert_eq!(
13327 f.run(&[b"GET", b"d"]),
13328 format!("$3\r\n{want}\r\n"),
13329 "{op:?}"
13330 );
13331 }
13332 assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
13334 assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
13335
13336 assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
13339 assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
13340
13341 f.run(&[b"SET", b"dest", b"x"]);
13344 assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
13345 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
13346 }
13347
13348 #[test]
13350 fn bitop_names_the_operation_in_its_own_complaints() {
13351 let mut f = Fixture::new();
13352 f.run(&[b"SET", b"a", b"abc"]);
13353 assert_eq!(
13354 f.run(&[b"BITOP", b"nope", b"d", b"a"]),
13355 "-ERR syntax error\r\n"
13356 );
13357 assert_eq!(
13358 f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
13359 "-ERR BITOP NOT must be called with a single source key.\r\n"
13360 );
13361 for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
13362 assert_eq!(
13363 f.run(&[b"BITOP", op, b"d", b"a"]),
13364 format!(
13365 "-ERR BITOP {} must be called with at least two source keys.\r\n",
13366 String::from_utf8_lossy(op)
13367 )
13368 );
13369 }
13370 f.run(&[b"LPUSH", b"l", b"x"]);
13371 assert_eq!(
13372 f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
13373 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13374 );
13375 }
13376
13377 #[test]
13379 fn bitfield_reads_and_writes_packed_fields() {
13380 let mut f = Fixture::new();
13381 assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
13382 assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
13383
13384 assert_eq!(
13385 f.run(&[
13386 b"BITFIELD",
13387 b"bf",
13388 b"INCRBY",
13389 b"u2",
13390 b"100",
13391 b"1",
13392 b"GET",
13393 b"u4",
13394 b"0"
13395 ]),
13396 "*2\r\n:1\r\n:0\r\n"
13397 );
13398 assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
13401
13402 assert_eq!(
13404 f.run(&[
13405 b"BITFIELD",
13406 b"bf",
13407 b"SET",
13408 b"u8",
13409 b"#0",
13410 b"255",
13411 b"GET",
13412 b"u8",
13413 b"#0"
13414 ]),
13415 "*2\r\n:0\r\n:255\r\n"
13416 );
13417
13418 assert_eq!(
13419 f.run(&[
13420 b"BITFIELD",
13421 b"bf",
13422 b"OVERFLOW",
13423 b"SAT",
13424 b"INCRBY",
13425 b"i8",
13426 b"0",
13427 b"120",
13428 b"INCRBY",
13429 b"i8",
13430 b"0",
13431 b"120"
13432 ]),
13433 "*2\r\n:119\r\n:127\r\n"
13434 );
13435 assert_eq!(
13436 f.run(&[
13437 b"BITFIELD",
13438 b"bf2",
13439 b"OVERFLOW",
13440 b"FAIL",
13441 b"INCRBY",
13442 b"u2",
13443 b"0",
13444 b"5"
13445 ]),
13446 "*1\r\n$-1\r\n"
13447 );
13448 assert_eq!(
13449 f.run(&[
13450 b"BITFIELD",
13451 b"bf3",
13452 b"OVERFLOW",
13453 b"WRAP",
13454 b"INCRBY",
13455 b"u2",
13456 b"0",
13457 b"5"
13458 ]),
13459 "*1\r\n:1\r\n"
13460 );
13461 assert_eq!(
13462 f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
13463 "*1\r\n:4611686018427387904\r\n"
13464 );
13465 }
13466
13467 #[test]
13473 fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
13474 let mut f = Fixture::new();
13475 let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
13476 assert_eq!(
13477 f.run(&[
13478 b"BITFIELD",
13479 b"bad",
13480 b"SET",
13481 b"u8",
13482 b"0",
13483 b"1",
13484 b"GET",
13485 b"u99",
13486 b"0"
13487 ]),
13488 bad_type
13489 );
13490 assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
13491 assert_eq!(
13492 f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
13493 bad_type
13494 );
13495 assert_eq!(
13496 f.run(&[b"BITFIELD", b"bad", b"GET"]),
13497 "-ERR syntax error\r\n"
13498 );
13499 assert_eq!(
13500 f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
13501 "-ERR syntax error\r\n"
13502 );
13503 assert_eq!(
13504 f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
13505 "-ERR syntax error\r\n"
13506 );
13507 assert_eq!(
13508 f.run(&[
13509 b"BITFIELD",
13510 b"bad",
13511 b"OVERFLOW",
13512 b"NOPE",
13513 b"GET",
13514 b"u8",
13515 b"0"
13516 ]),
13517 "-ERR Invalid OVERFLOW type specified\r\n"
13518 );
13519 assert_eq!(
13520 f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
13521 "-ERR value is not an integer or out of range\r\n"
13522 );
13523 for at in [&b"#-1"[..], b"abc"] {
13524 assert_eq!(
13525 f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
13526 "-ERR bit offset is not an integer or out of range\r\n"
13527 );
13528 }
13529 }
13530
13531 #[test]
13533 fn bitfield_ro_answers_gets_and_refuses_the_rest() {
13534 let mut f = Fixture::new();
13535 f.run(&[b"SET", b"n", b"123"]);
13536 assert_eq!(
13537 f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
13538 "*1\r\n:49\r\n"
13539 );
13540 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
13542
13543 assert_eq!(
13545 f.run(&[
13546 b"BITFIELD_RO",
13547 b"n",
13548 b"OVERFLOW",
13549 b"SAT",
13550 b"GET",
13551 b"u8",
13552 b"0"
13553 ]),
13554 "*1\r\n:49\r\n"
13555 );
13556 for sub in [&b"SET"[..], b"INCRBY"] {
13557 assert_eq!(
13558 f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
13559 "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
13560 );
13561 }
13562
13563 assert_eq!(
13564 f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
13565 "*1\r\n:0\r\n"
13566 );
13567 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13568 }
13569
13570 #[test]
13572 fn an_offset_off_the_end_of_the_world_is_refused() {
13573 let mut f = Fixture::new();
13574 let bad = "-ERR bit offset is not an integer or out of range\r\n";
13575 for arg in [&b"abc"[..], b"-1", b"4294967296"] {
13576 assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
13577 assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
13578 }
13579 for arg in [&b"2"[..], b"-1"] {
13580 assert_eq!(
13581 f.run(&[b"BITPOS", b"k", arg]),
13582 "-ERR The bit argument must be 1 or 0.\r\n"
13583 );
13584 }
13585 assert_eq!(
13586 f.run(&[b"BITPOS", b"k", b"abc"]),
13587 "-ERR value is not an integer or out of range\r\n"
13588 );
13589 assert_eq!(
13590 f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
13591 "-ERR value is not an integer or out of range\r\n"
13592 );
13593 let bad_bit = "-ERR bit is not an integer or out of range\r\n";
13594 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
13595 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
13596 }
13597
13598 #[test]
13600 fn every_bitmap_command_says_wrongtype() {
13601 let mut f = Fixture::new();
13602 f.run(&[b"LPUSH", b"l", b"x"]);
13603 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13604 let cases: &[&[&[u8]]] = &[
13605 &[b"SETBIT", b"l", b"0", b"1"],
13606 &[b"GETBIT", b"l", b"0"],
13607 &[b"BITCOUNT", b"l"],
13608 &[b"BITPOS", b"l", b"1"],
13609 &[b"BITOP", b"AND", b"d", b"l"],
13610 &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
13611 &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
13612 ];
13613 for case in cases {
13614 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
13615 }
13616 }
13617
13618 #[test]
13621 fn a_sketch_is_added_to_and_counted() {
13622 let mut f = Fixture::new();
13623 assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
13625 assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
13626 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
13627 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
13628 assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
13631 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
13632
13633 assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
13634 assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
13635 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
13636 }
13637
13638 #[test]
13639 fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
13640 let mut f = Fixture::new();
13641 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13642 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";
13644 let mut reply = b"$27\r\n".to_vec();
13645 reply.extend_from_slice(want);
13646 reply.extend_from_slice(b"\r\n");
13647 assert_eq!(f.raw(&[b"GET", b"h"]), reply);
13648 }
13649
13650 #[test]
13651 fn counting_several_keys_counts_their_union() {
13652 let mut f = Fixture::new();
13653 f.run(&[b"PFADD", b"a", b"x", b"y"]);
13654 f.run(&[b"PFADD", b"b", b"y", b"z"]);
13655 assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
13656 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
13657 assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
13660 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
13661 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13662 }
13663
13664 #[test]
13665 fn a_merge_keeps_what_the_destination_had() {
13666 let mut f = Fixture::new();
13667 f.run(&[b"PFADD", b"a", b"x", b"y"]);
13668 f.run(&[b"PFADD", b"b", b"z"]);
13669 assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
13670 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
13671 f.run(&[b"PFADD", b"c", b"w"]);
13673 assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
13674 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
13675 assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
13678 assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
13679 }
13680
13681 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
13690 #[test]
13691 fn the_debug_forms_answer_four_different_shapes() {
13692 let mut f = Fixture::new();
13693 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13694 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
13695 assert_eq!(
13696 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
13697 "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
13698 );
13699 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
13700 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
13701 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
13702 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
13703 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
13704 assert_eq!(
13706 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
13707 "-ERR HLL encoding is not sparse\r\n"
13708 );
13709
13710 let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
13712 assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
13713 assert_eq!(reply.matches(":0\r\n").count(), 16381);
13714 assert_eq!(reply.matches(":1\r\n").count(), 2);
13715 assert_eq!(reply.matches(":2\r\n").count(), 1);
13716
13717 assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
13718 }
13719
13720 #[test]
13721 fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
13722 let mut f = Fixture::new();
13723 f.run(&[b"SET", b"plain", b"not a sketch"]);
13724 let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
13725 assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
13726 assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
13727 assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
13728 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
13729
13730 f.run(&[b"RPUSH", b"l", b"x"]);
13733 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13734 assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
13735 assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
13736 assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
13737 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
13738 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
13739 }
13740
13741 #[test]
13742 fn pfdebug_has_its_own_complaints() {
13743 let mut f = Fixture::new();
13744 f.run(&[b"PFADD", b"h", b"a"]);
13745 assert_eq!(
13748 f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
13749 "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
13750 );
13751 let gone = "-ERR The specified key does not exist\r\n";
13753 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
13754 assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
13755 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
13756 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
13757 assert_eq!(
13758 f.run(&[b"PFDEBUG"]),
13759 "-ERR wrong number of arguments for 'pfdebug' command\r\n"
13760 );
13761 assert_eq!(
13762 f.run(&[b"PFSELFTEST", b"x"]),
13763 "-ERR wrong number of arguments for 'pfselftest' command\r\n"
13764 );
13765 }
13766
13767 #[test]
13768 fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
13769 let mut f = Fixture::new();
13770 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13771 let reply = f.raw(&[b"GET", b"h"]);
13774 let short = reply[5..reply.len() - 3].to_vec();
13775 f.run(&[b"SET", b"h", &short]);
13776 assert_eq!(
13777 f.run(&[b"PFCOUNT", b"h"]),
13778 "-INVALIDOBJ Corrupted HLL object detected\r\n"
13779 );
13780 }
13781
13782 #[test]
13783 fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
13784 let mut f = Fixture::new();
13785 f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
13788 if cfg!(miri) {
13795 f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
13796 f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
13797 } else {
13798 for i in 0..10_000u32 {
13799 let ele = format!("e{i}");
13800 f.run(&[b"PFADD", b"big", ele.as_bytes()]);
13801 }
13802 }
13803 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
13804 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
13805
13806 for key in [&b"small"[..], b"big"] {
13807 let mut copy = key.to_vec();
13808 copy.push(b'2');
13809 let bytes = payload(&f.raw(&[b"DUMP", key]));
13810 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
13811 assert_eq!(f.raw(&[b"GET", ©]), f.raw(&[b"GET", key]));
13814 assert_eq!(
13815 f.run(&[b"PFDEBUG", b"ENCODING", ©]),
13816 f.run(&[b"PFDEBUG", b"ENCODING", key])
13817 );
13818 assert_eq!(f.run(&[b"PFCOUNT", ©]), f.run(&[b"PFCOUNT", key]));
13819 }
13820 assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
13821 assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
13822 }
13823
13824 fn bulk(s: &str) -> String {
13828 format!("${}\r\n{s}\r\n", s.len())
13829 }
13830
13831 fn bulks(parts: &[&str]) -> String {
13834 let mut s = format!("*{}\r\n", parts.len());
13835 for p in parts {
13836 s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
13837 }
13838 s
13839 }
13840
13841 #[test]
13842 fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
13843 let mut f = Fixture::new();
13844 assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
13848 assert_eq!(
13849 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13850 bulks(&["c", "b", "a"])
13851 );
13852 assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
13853 assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
13854 assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
13855 assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
13856 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
13857 assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
13858 }
13859
13860 #[test]
13861 fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
13862 let mut f = Fixture::new();
13863 assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
13864 assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
13865 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
13866 f.run(&[b"RPUSH", b"k", b"a"]);
13867 assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
13868 assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
13869 assert_eq!(
13870 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13871 bulks(&["z", "a", "y"])
13872 );
13873 }
13874
13875 #[test]
13878 fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
13879 let mut f = Fixture::new();
13880 assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
13881 assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
13882 assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
13883 assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
13884 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13885 assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
13888 assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
13889 assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
13891 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
13892 }
13893
13894 #[test]
13895 fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
13896 let mut f = Fixture::new();
13897 f.run(&[b"RPUSH", b"k", b"a"]);
13898 let range = "-ERR value is out of range, must be positive\r\n";
13899 assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
13900 assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
13901 assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
13902 assert_eq!(
13905 f.run(&[b"LPOP", b"k", b"1", b"2"]),
13906 "-ERR wrong number of arguments for 'lpop' command\r\n"
13907 );
13908 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
13909 }
13910
13911 #[test]
13912 fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
13913 let mut f = Fixture::new();
13914 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13915 assert_eq!(
13916 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13917 bulks(&["a", "b", "c"])
13918 );
13919 assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
13920 assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
13921 assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
13922 assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
13923 assert_eq!(
13924 f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
13925 bulks(&["a", "b", "c"])
13926 );
13927 assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
13930 assert_eq!(
13931 f.run(&[b"LRANGE", b"k", b"a", b"b"]),
13932 "-ERR value is not an integer or out of range\r\n"
13933 );
13934 }
13935
13936 #[test]
13937 fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
13938 let mut f = Fixture::new();
13939 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13940 assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
13941 assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
13942 assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
13943 assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
13944 assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
13945 assert_eq!(
13946 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13947 bulks(&["a", "b", "z"])
13948 );
13949 assert_eq!(
13952 f.run(&[b"LSET", b"k", b"99", b"z"]),
13953 "-ERR index out of range\r\n"
13954 );
13955 assert_eq!(
13956 f.run(&[b"LSET", b"nope", b"0", b"z"]),
13957 "-ERR no such key\r\n"
13958 );
13959 }
13960
13961 #[test]
13962 fn linsert_says_three_things_with_one_signed_number() {
13963 let mut f = Fixture::new();
13964 assert_eq!(
13967 f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
13968 ":0\r\n"
13969 );
13970 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
13971 assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
13972 assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
13973 assert_eq!(
13974 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13975 bulks(&["X", "a", "b", "Y"])
13976 );
13977 assert_eq!(
13978 f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
13979 ":-1\r\n"
13980 );
13981 assert_eq!(
13982 f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
13983 "-ERR syntax error\r\n"
13984 );
13985 }
13986
13987 #[test]
13988 fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
13989 let mut f = Fixture::new();
13990 f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
13991 assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
13992 assert_eq!(
13993 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13994 bulks(&["b", "c", "a"])
13995 );
13996 assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
13997 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
13998 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
13999 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
14000 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
14001 assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
14002 }
14003
14004 #[test]
14005 fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
14006 let mut f = Fixture::new();
14007 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
14008 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
14009 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
14010 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
14013 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
14014 assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
14015 }
14016
14017 #[test]
14018 fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
14019 let mut f = Fixture::new();
14020 f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
14021 assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
14022 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
14023 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
14024 assert_eq!(
14025 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
14026 "*2\r\n:0\r\n:3\r\n"
14027 );
14028 assert_eq!(
14029 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
14030 "*3\r\n:6\r\n:3\r\n:0\r\n"
14031 );
14032 assert_eq!(
14035 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
14036 "*1\r\n:0\r\n"
14037 );
14038 assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
14041 assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
14042 assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
14043 assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
14044 }
14045
14046 #[test]
14047 fn lpos_words_its_three_mistakes_the_way_redis_does() {
14048 let mut f = Fixture::new();
14049 f.run(&[b"RPUSH", b"p", b"a"]);
14050 assert_eq!(
14053 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
14054 "-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"
14055 );
14056 assert_eq!(
14057 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
14058 "-ERR COUNT can't be negative\r\n"
14059 );
14060 assert_eq!(
14061 f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
14062 "-ERR MAXLEN can't be negative\r\n"
14063 );
14064 assert_eq!(
14065 f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
14066 "-ERR syntax error\r\n"
14067 );
14068 assert_eq!(
14069 f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
14070 "-ERR syntax error\r\n"
14071 );
14072 }
14073
14074 #[test]
14075 fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
14076 let mut f = Fixture::new();
14077 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
14078 assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
14079 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
14080 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
14081 assert_eq!(
14082 f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
14083 "$1\r\na\r\n"
14084 );
14085 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
14086 f.run(&[b"DEL", b"r"]);
14089 f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
14090 assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
14091 assert_eq!(
14092 f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
14093 bulks(&["3", "1", "2"])
14094 );
14095 assert_eq!(
14096 f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
14097 "$-1\r\n"
14098 );
14099 assert_eq!(
14100 f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
14101 "-ERR syntax error\r\n"
14102 );
14103 }
14104
14105 #[test]
14106 fn a_move_checks_the_destination_before_it_takes_anything() {
14107 let mut f = Fixture::new();
14108 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
14109 f.run(&[b"SET", b"str", b"v"]);
14110 assert_eq!(
14111 f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
14112 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
14113 );
14114 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
14116 }
14117
14118 #[test]
14119 fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
14120 for (from, to, order, want) in [
14124 ("LEFT", "RIGHT", "OBO", ["a", "b"]),
14125 ("LEFT", "RIGHT", "BULK", ["a", "b"]),
14126 ("LEFT", "LEFT", "OBO", ["b", "a"]),
14127 ("LEFT", "LEFT", "BULK", ["a", "b"]),
14128 ("RIGHT", "LEFT", "OBO", ["d", "e"]),
14129 ("RIGHT", "LEFT", "BULK", ["d", "e"]),
14130 ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
14131 ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
14132 ] {
14133 let mut f = Fixture::new();
14134 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
14135 let how = format!("{from} {to} {order}");
14136 let reply = f.run(&[
14137 b"LMOVEM",
14138 b"s",
14139 b"d",
14140 from.as_bytes(),
14141 to.as_bytes(),
14142 b"COUNT",
14143 b"2",
14144 order.as_bytes(),
14145 ]);
14146 assert_eq!(reply, bulks(&want), "the reply for {how}");
14147 assert_eq!(
14148 f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
14149 bulks(&want),
14150 "the destination for {how}"
14151 );
14152 }
14153 }
14154
14155 #[test]
14156 fn a_block_move_of_one_needs_no_count_at_all() {
14157 let mut f = Fixture::new();
14158 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
14159 assert_eq!(
14160 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
14161 bulks(&["a"])
14162 );
14163 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
14164 assert_eq!(
14167 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
14168 "-ERR syntax error\r\n"
14169 );
14170 assert_eq!(
14171 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
14172 "-ERR syntax error\r\n"
14173 );
14174 }
14175
14176 #[test]
14177 fn a_block_move_with_exactly_takes_all_of_them_or_none() {
14178 let mut f = Fixture::new();
14179 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
14180 assert_eq!(
14184 f.run(&[
14185 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
14186 ]),
14187 "*-1\r\n"
14188 );
14189 assert_eq!(
14190 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
14191 bulks(&["a", "b", "c"])
14192 );
14193 assert_eq!(
14195 f.run(&[
14196 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
14197 ]),
14198 bulks(&["a", "b", "c"])
14199 );
14200 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
14201 assert_eq!(
14202 f.run(&[
14203 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
14204 ]),
14205 "*-1\r\n"
14206 );
14207 }
14208
14209 #[test]
14210 fn a_block_move_onto_itself_rotates_by_the_count() {
14211 let mut f = Fixture::new();
14212 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
14213 assert_eq!(
14214 f.run(&[
14215 b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
14216 ]),
14217 bulks(&["a", "b"])
14218 );
14219 assert_eq!(
14220 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
14221 bulks(&["c", "a", "b"])
14222 );
14223 }
14224
14225 #[test]
14226 fn a_block_move_reads_the_count_before_the_ordering_word() {
14227 let mut f = Fixture::new();
14228 f.run(&[b"RPUSH", b"s", b"a", b"b"]);
14229 f.run(&[b"SET", b"str", b"v"]);
14230 let count = "-ERR count should be greater than 0\r\n";
14231 assert_eq!(
14232 f.run(&[
14233 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
14234 ]),
14235 count
14236 );
14237 assert_eq!(
14238 f.run(&[
14239 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
14240 ]),
14241 count
14242 );
14243 assert_eq!(
14244 f.run(&[
14245 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
14246 ]),
14247 "-ERR syntax error\r\n"
14248 );
14249 assert_eq!(
14250 f.run(&[
14251 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
14252 ]),
14253 "-ERR syntax error\r\n"
14254 );
14255 assert_eq!(
14258 f.run(&[
14259 b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
14260 ]),
14261 count
14262 );
14263 assert_eq!(
14264 f.run(&[
14265 b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
14266 ]),
14267 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
14268 );
14269 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
14270 }
14271
14272 #[test]
14273 fn lmpop_answers_from_the_first_key_that_has_anything() {
14274 let mut f = Fixture::new();
14275 f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
14276 assert_eq!(
14279 f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
14280 "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
14281 );
14282 assert_eq!(
14283 f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
14284 "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
14285 );
14286 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
14287 assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
14290 }
14291
14292 #[test]
14293 fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
14294 let mut f = Fixture::new();
14295 f.run(&[b"RPUSH", b"k", b"a"]);
14296 assert_eq!(
14297 f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
14298 "-ERR numkeys should be greater than 0\r\n"
14299 );
14300 assert_eq!(
14301 f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
14302 "-ERR numkeys should be greater than 0\r\n"
14303 );
14304 assert_eq!(
14305 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
14306 "-ERR count should be greater than 0\r\n"
14307 );
14308 assert_eq!(
14311 f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
14312 "-ERR syntax error\r\n"
14313 );
14314 assert_eq!(
14315 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
14316 "-ERR syntax error\r\n"
14317 );
14318 assert_eq!(
14319 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
14320 "-ERR syntax error\r\n"
14321 );
14322 assert_eq!(
14323 f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
14324 "-ERR syntax error\r\n"
14325 );
14326 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
14327 }
14328
14329 #[test]
14330 fn every_list_command_says_wrongtype_and_writes_nothing() {
14331 let mut f = Fixture::new();
14332 f.run(&[b"SET", b"str", b"v"]);
14333 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14334 for cmd in [
14335 &[b"LPUSH".as_slice(), b"str", b"a"][..],
14336 &[b"RPUSH", b"str", b"a"],
14337 &[b"LPUSHX", b"str", b"a"],
14338 &[b"RPUSHX", b"str", b"a"],
14339 &[b"LPOP", b"str"],
14340 &[b"LPOP", b"str", b"2"],
14341 &[b"RPOP", b"str"],
14342 &[b"LLEN", b"str"],
14343 &[b"LRANGE", b"str", b"0", b"-1"],
14344 &[b"LINDEX", b"str", b"0"],
14345 &[b"LSET", b"str", b"0", b"a"],
14346 &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
14347 &[b"LREM", b"str", b"0", b"a"],
14348 &[b"LTRIM", b"str", b"0", b"-1"],
14349 &[b"LPOS", b"str", b"a"],
14350 &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
14351 &[b"RPOPLPUSH", b"str", b"d"],
14352 &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
14353 &[b"LMPOP", b"1", b"str", b"LEFT"],
14354 ] {
14355 assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
14356 }
14357 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
14358 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
14359 }
14360
14361 #[test]
14365 fn a_timeout_has_three_ways_of_being_wrong() {
14366 let mut f = Fixture::new();
14367 let not_float = "-ERR timeout is not a float or out of range\r\n";
14368 let range = "-ERR timeout is out of range\r\n";
14369 for (bad, want) in [
14370 (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
14371 (&[b"BLPOP", b"k", b"nan"], not_float),
14372 (&[b"BLPOP", b"k", b""], not_float),
14373 (&[b"BLPOP", b"k", b" 1"], not_float),
14376 (&[b"BLPOP", b"k", b"1 "], not_float),
14377 (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
14378 (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
14379 (&[b"BLPOP", b"k", b"1e400"], range),
14382 (&[b"BLPOP", b"k", b"inf"], range),
14383 (&[b"BLPOP", b"k", b"9999999999999999"], range),
14384 (&[b"BRPOP", b"k", b"abc"], not_float),
14385 (
14386 &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
14387 not_float,
14388 ),
14389 (
14390 &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
14391 "-ERR timeout is negative\r\n",
14392 ),
14393 (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
14394 ] {
14395 assert_eq!(f.run(bad), want, "for {bad:?}");
14396 }
14397 }
14398
14399 #[test]
14402 fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
14403 let mut f = Fixture::new();
14404 for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
14405 let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
14406 assert_eq!(flow, Flow::Block, "for {timeout:?}");
14407 assert!(out.is_empty(), "for {timeout:?}");
14408 }
14409 let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
14413 assert_eq!(flow, Flow::Block);
14414 assert!(out.is_empty());
14415 }
14416
14417 #[test]
14418 fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
14419 let mut f = Fixture::new();
14420 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
14421
14422 assert_eq!(
14425 f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
14426 (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
14427 );
14428 assert_eq!(
14429 f.run(&[b"BRPOP", b"L", b"0"]),
14430 "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
14431 );
14432 assert_eq!(
14433 f.run(&[
14434 b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
14435 ]),
14436 "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14437 );
14438 assert_eq!(
14439 f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
14440 "$1\r\nd\r\n"
14441 );
14442 assert_eq!(
14443 f.run(&[b"EXISTS", b"L"]),
14444 ":0\r\n",
14445 "and the key went with it"
14446 );
14447 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
14448 f.run(&[b"RPUSH", b"D", b"x"]);
14451 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
14452 assert_eq!(
14453 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
14454 "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
14455 );
14456 }
14457
14458 #[test]
14459 fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
14460 let mut f = Fixture::new();
14461 f.run(&[b"RPUSH", b"k", b"a"]);
14462 for (bad, want) in [
14463 (
14464 &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
14465 "-ERR numkeys should be greater than 0\r\n",
14466 ),
14467 (
14468 &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
14469 "-ERR numkeys should be greater than 0\r\n",
14470 ),
14471 (
14474 &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
14475 "-ERR syntax error\r\n",
14476 ),
14477 (
14478 &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
14479 "-ERR syntax error\r\n",
14480 ),
14481 (
14482 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
14483 "-ERR syntax error\r\n",
14484 ),
14485 (
14486 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
14487 "-ERR syntax error\r\n",
14488 ),
14489 (
14492 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
14493 "-ERR count should be greater than 0\r\n",
14494 ),
14495 (
14496 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
14497 "-ERR count should be greater than 0\r\n",
14498 ),
14499 ] {
14500 assert_eq!(f.run(bad), want, "for {bad:?}");
14501 }
14502 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
14503 }
14504
14505 #[test]
14506 fn a_blocking_move_reads_its_directions_before_its_timeout() {
14507 let mut f = Fixture::new();
14508 assert_eq!(
14511 f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
14512 "-ERR syntax error\r\n"
14513 );
14514 assert_eq!(
14515 f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
14516 "-ERR syntax error\r\n"
14517 );
14518 }
14519
14520 #[test]
14524 fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
14525 let mut f = Fixture::new();
14526 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
14527 assert_eq!(
14528 f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
14529 (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
14530 );
14531 assert_eq!(
14532 f.run(&[
14533 b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
14534 ]),
14535 bulks(&["e", "d"])
14536 );
14537 assert_eq!(
14538 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
14539 bulks(&["a", "e", "d"])
14540 );
14541 assert_eq!(
14543 f.run(&[
14544 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
14545 ]),
14546 bulks(&["b", "c"])
14547 );
14548 assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
14549 }
14550
14551 #[test]
14555 fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
14556 let mut f = Fixture::new();
14557 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
14558 assert_eq!(
14560 f.flow(&[
14561 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
14562 ]),
14563 (Flow::Continue, bulks(&["a", "b"]))
14564 );
14565
14566 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
14567 assert_eq!(
14570 f.flow(&[
14571 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
14572 ])
14573 .0,
14574 Flow::Block
14575 );
14576 assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
14577 }
14578
14579 #[test]
14580 fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
14581 let mut f = Fixture::new();
14582 let syntax = "-ERR syntax error\r\n";
14583 assert_eq!(
14585 f.run(&[
14586 b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
14587 ]),
14588 syntax
14589 );
14590 assert_eq!(
14592 f.run(&[
14593 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
14594 ]),
14595 "-ERR timeout is not a float or out of range\r\n"
14596 );
14597 assert_eq!(
14598 f.run(&[
14599 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
14600 ]),
14601 "-ERR timeout is negative\r\n"
14602 );
14603 assert_eq!(
14605 f.run(&[
14606 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
14607 ]),
14608 "-ERR count should be greater than 0\r\n"
14609 );
14610 assert_eq!(
14611 f.run(&[
14612 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
14613 ]),
14614 syntax
14615 );
14616 assert_eq!(
14619 f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
14620 syntax
14621 );
14622 assert_eq!(
14623 f.run(&[
14624 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
14625 ]),
14626 syntax
14627 );
14628 }
14629
14630 #[test]
14633 fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
14634 let mut f = Fixture::new();
14635 f.run(&[b"SET", b"S", b"v"]);
14636 f.run(&[b"RPUSH", b"D", b"x"]);
14637 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14638
14639 assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
14640 assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
14643 assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
14644 assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
14645 assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
14646 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
14649 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
14650 assert_eq!(
14651 f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
14652 wrong
14653 );
14654 assert_eq!(
14655 f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
14656 wrong
14657 );
14658
14659 assert_eq!(
14663 f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
14664 .0,
14665 Flow::Block
14666 );
14667 assert_eq!(
14671 f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
14672 .0,
14673 Flow::Block
14674 );
14675 f.run(&[b"RPUSH", b"E", b"1", b"2"]);
14676 assert_eq!(
14677 f.flow(&[
14678 b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
14679 ])
14680 .0,
14681 Flow::Block
14682 );
14683 }
14684
14685 #[cfg_attr(miri, ignore = "the volume is the claim")]
14690 #[test]
14691 fn churning_lists_does_not_grow_the_server() {
14692 let mut f = Fixture::new();
14693 let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
14694 let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
14695 .into_iter()
14696 .chain(vals.iter().map(Vec::as_slice))
14697 .collect();
14698
14699 f.run(&args);
14700 f.run(&[b"DEL", b"k"]);
14701 f.server.compact_step();
14702 let after_first = f.server.memory_bytes();
14703
14704 for _ in 0..200 {
14705 f.run(&args);
14706 f.run(&[b"LTRIM", b"k", b"1", b"0"]);
14707 f.server.compact_step();
14708 }
14709 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14710 assert!(
14711 f.server.memory_bytes() <= after_first * 2,
14712 "held {} after two hundred passes against {after_first} after one",
14713 f.server.memory_bytes()
14714 );
14715 }
14716
14717 #[test]
14720 fn a_sorted_set_takes_scores_and_gives_them_back() {
14721 let mut f = Fixture::new();
14722 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
14723 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
14724 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
14725 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
14726 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
14727 assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
14728 assert_eq!(
14729 f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
14730 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
14731 );
14732 assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
14733 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
14734 assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
14736 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
14737 }
14738
14739 #[test]
14740 fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
14741 let mut f = Fixture::new();
14742 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
14743 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
14744 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
14745 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
14746
14747 f.out = Out::new(Proto::Resp3);
14748 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
14749 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
14750 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
14751 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
14752 }
14753
14754 #[test]
14755 fn the_zadd_options_gate_what_gets_written() {
14756 let mut f = Fixture::new();
14757 f.run(&[b"ZADD", b"z", b"5", b"a"]);
14758 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
14760 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
14761 assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
14762 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
14763 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
14765 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
14766 assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
14767 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
14769 assert_eq!(
14770 f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
14771 ":2\r\n"
14772 );
14773 }
14774
14775 #[test]
14776 fn zadd_incr_answers_a_score_or_nothing_at_all() {
14777 let mut f = Fixture::new();
14778 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
14779 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
14780 assert_eq!(
14783 f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
14784 "$-1\r\n"
14785 );
14786 assert_eq!(
14787 f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
14788 "$-1\r\n"
14789 );
14790 assert_eq!(
14791 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
14792 "$-1\r\n"
14793 );
14794 assert_eq!(
14795 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
14796 "$1\r\n8\r\n"
14797 );
14798 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
14799 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
14800 }
14801
14802 #[test]
14803 fn the_two_infinities_will_not_be_added_together() {
14804 let mut f = Fixture::new();
14805 f.run(&[b"ZADD", b"z", b"inf", b"m"]);
14806 let nan = "-ERR resulting score is not a number (NaN)\r\n";
14807 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
14808 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
14809 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
14810 assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
14812 }
14813
14814 #[test]
14815 fn zadd_says_its_mistakes_the_way_redis_says_them() {
14816 let mut f = Fixture::new();
14817 assert_eq!(
14820 f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
14821 "-ERR syntax error\r\n"
14822 );
14823 assert_eq!(
14824 f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
14825 "-ERR XX and NX options at the same time are not compatible\r\n"
14826 );
14827 let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
14828 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
14829 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
14830 assert_eq!(
14831 f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
14832 "-ERR INCR option supports a single increment-element pair\r\n"
14833 );
14834 assert_eq!(
14836 f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
14837 "-ERR syntax error\r\n"
14838 );
14839 assert_eq!(
14841 f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
14842 "-ERR value is not a valid float\r\n"
14843 );
14844 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
14845 }
14846
14847 #[test]
14848 fn a_rank_says_where_a_member_sits_from_either_end() {
14849 let mut f = Fixture::new();
14850 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14851 assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
14852 assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
14853 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
14854 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
14855 assert_eq!(
14857 f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
14858 "*2\r\n:1\r\n$1\r\n2\r\n"
14859 );
14860 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
14861 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
14862 assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
14863 assert_eq!(
14866 f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
14867 "-ERR syntax error\r\n"
14868 );
14869 assert_eq!(
14870 f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
14871 "-ERR wrong number of arguments for 'zrevrank' command\r\n"
14872 );
14873 }
14874
14875 #[test]
14876 fn the_two_counts_read_their_two_kinds_of_bound() {
14877 let mut f = Fixture::new();
14878 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14879 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
14880 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
14881 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
14882 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
14883 assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
14884 assert_eq!(
14885 f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
14886 "-ERR min or max is not a float\r\n"
14887 );
14888
14889 f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
14890 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
14891 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
14892 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
14893 assert_eq!(
14896 f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
14897 "-ERR min or max not valid string range item\r\n"
14898 );
14899 }
14900
14901 #[test]
14907 fn one_range_command_selects_by_rank_or_score_or_name() {
14908 let mut f = Fixture::new();
14909 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14910 assert_eq!(
14911 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
14912 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
14913 );
14914 assert_eq!(
14915 f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
14916 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14917 );
14918 assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
14919 assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
14920 assert_eq!(
14923 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
14924 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14925 );
14926 assert_eq!(
14927 f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
14928 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14929 );
14930 assert_eq!(
14933 f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
14934 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
14935 );
14936 assert_eq!(
14937 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
14938 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
14939 );
14940 assert_eq!(
14941 f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
14942 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14943 );
14944 }
14945
14946 #[test]
14949 fn the_older_range_spellings_name_their_high_end_first() {
14950 let mut f = Fixture::new();
14951 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14952 assert_eq!(
14953 f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
14954 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14955 );
14956 assert_eq!(
14957 f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
14958 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
14959 );
14960 assert_eq!(
14961 f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
14962 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14963 );
14964 assert_eq!(
14965 f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
14966 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
14967 );
14968 assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
14972 assert_eq!(
14973 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
14974 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
14975 );
14976 assert_eq!(
14977 f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
14978 "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
14979 );
14980 for cmd in [
14983 &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
14984 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
14985 &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
14986 ] {
14987 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
14988 }
14989 }
14990
14991 #[test]
14994 fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
14995 let mut f = Fixture::new();
14996 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14997 assert_eq!(
14998 f.run(&[
14999 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
15000 ]),
15001 "*1\r\n$1\r\nb\r\n"
15002 );
15003 assert_eq!(
15005 f.run(&[
15006 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
15007 ]),
15008 "*0\r\n"
15009 );
15010 assert_eq!(
15011 f.run(&[
15012 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
15013 ]),
15014 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
15015 );
15016 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";
15018 assert_eq!(
15019 f.run(&[
15020 b"ZRANGEBYSCORE",
15021 b"z",
15022 b"1",
15023 b"3",
15024 b"WITHSCORES",
15025 b"LIMIT",
15026 b"0",
15027 b"2"
15028 ]),
15029 both
15030 );
15031 assert_eq!(
15032 f.run(&[
15033 b"ZRANGEBYSCORE",
15034 b"z",
15035 b"1",
15036 b"3",
15037 b"LIMIT",
15038 b"0",
15039 b"2",
15040 b"WITHSCORES"
15041 ]),
15042 both
15043 );
15044 let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
15047 assert_eq!(
15048 f.run(&[
15049 b"ZREVRANGE",
15050 b"z",
15051 b"0",
15052 b"-1",
15053 b"WITHSCORES",
15054 b"LIMIT",
15055 b"0",
15056 b"1"
15057 ]),
15058 needs_by
15059 );
15060 assert_eq!(
15061 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
15062 needs_by
15063 );
15064 let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
15065 assert_eq!(
15066 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
15067 not_bylex
15068 );
15069 assert_eq!(
15070 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
15071 not_bylex
15072 );
15073 for cmd in [
15076 &[
15077 b"ZRANGE".as_slice(),
15078 b"z",
15079 b"0",
15080 b"-1",
15081 b"BYSCORE",
15082 b"BYLEX",
15083 ][..],
15084 &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
15085 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
15086 ] {
15087 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15088 }
15089 assert_eq!(
15090 f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
15091 "-ERR min or max is not a float\r\n"
15092 );
15093 assert_eq!(
15094 f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
15095 "-ERR min or max not valid string range item\r\n"
15096 );
15097 assert_eq!(
15098 f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
15099 "-ERR value is not an integer or out of range\r\n"
15100 );
15101 }
15102
15103 #[test]
15106 fn withscores_nests_on_resp3_and_flattens_on_resp2() {
15107 let mut f = Fixture::new();
15108 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15109 assert_eq!(
15110 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
15111 "*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"
15112 );
15113 f.out = Out::new(Proto::Resp3);
15114 assert_eq!(
15115 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
15116 "*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"
15117 );
15118 assert_eq!(
15119 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
15120 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
15121 );
15122 }
15123
15124 #[test]
15126 fn a_range_store_writes_the_window_into_another_key() {
15127 let mut f = Fixture::new();
15128 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15129 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
15130 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
15133 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
15134 assert_eq!(
15135 f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
15136 ":2\r\n"
15137 );
15138 assert_eq!(
15139 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
15140 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15141 );
15142 assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
15145 assert_eq!(
15146 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
15147 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15148 );
15149 assert_eq!(
15152 f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
15153 "-ERR syntax error\r\n"
15154 );
15155 }
15156
15157 #[test]
15160 fn the_three_removals_share_their_window_with_the_reads() {
15161 let mut f = Fixture::new();
15162 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15163 assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
15164 assert_eq!(
15165 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
15166 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
15167 );
15168 assert_eq!(
15169 f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
15170 ":1\r\n"
15171 );
15172 assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
15173 assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
15175 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15176 assert_eq!(
15177 f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
15178 ":0\r\n"
15179 );
15180 assert_eq!(
15181 f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
15182 "-ERR value is not an integer or out of range\r\n"
15183 );
15184 }
15185
15186 #[test]
15188 fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
15189 let mut f = Fixture::new();
15190 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15191 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
15192 assert_eq!(
15193 f.run(&[b"ZUNION", b"2", b"z", b"y"]),
15194 "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
15195 );
15196 assert_eq!(
15199 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
15200 "*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"
15201 );
15202 assert_eq!(
15203 f.run(&[
15204 b"ZUNION",
15205 b"2",
15206 b"z",
15207 b"y",
15208 b"WEIGHTS",
15209 b"2",
15210 b"3",
15211 b"WITHSCORES"
15212 ]),
15213 "*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"
15214 );
15215 assert_eq!(
15216 f.run(&[
15217 b"ZUNION",
15218 b"2",
15219 b"z",
15220 b"y",
15221 b"AGGREGATE",
15222 b"MIN",
15223 b"WITHSCORES"
15224 ]),
15225 "*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"
15226 );
15227 assert_eq!(
15228 f.run(&[
15229 b"ZUNION",
15230 b"2",
15231 b"z",
15232 b"y",
15233 b"AGGREGATE",
15234 b"MAX",
15235 b"WITHSCORES"
15236 ]),
15237 "*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"
15238 );
15239 assert_eq!(
15240 f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
15241 "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
15242 );
15243 assert_eq!(
15244 f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
15245 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
15246 );
15247 assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
15248 f.run(&[b"SADD", b"p", b"a", b"d"]);
15251 assert_eq!(
15252 f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
15253 "*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"
15254 );
15255 for cmd in [
15258 &[
15259 b"ZDIFF".as_slice(),
15260 b"2",
15261 b"z",
15262 b"y",
15263 b"WEIGHTS",
15264 b"1",
15265 b"1",
15266 ][..],
15267 &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
15268 ] {
15269 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15270 }
15271 }
15272
15273 #[test]
15275 fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
15276 let mut f = Fixture::new();
15277 f.run(&[b"ZADD", b"z", b"1", b"a"]);
15278 f.run(&[b"ZADD", b"y", b"2", b"b"]);
15279 assert_eq!(
15281 f.run(&[b"ZUNION", b"0", b"z"]),
15282 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
15283 );
15284 assert_eq!(
15285 f.run(&[b"ZUNION", b"-1", b"z"]),
15286 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
15287 );
15288 assert_eq!(
15289 f.run(&[b"ZINTERCARD", b"0", b"z"]),
15290 "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
15291 );
15292 assert_eq!(
15295 f.run(&[b"ZUNION", b"3", b"z", b"y"]),
15296 "-ERR syntax error\r\n"
15297 );
15298 assert_eq!(
15299 f.run(&[b"ZUNION", b"x", b"z"]),
15300 "-ERR value is not an integer or out of range\r\n"
15301 );
15302 assert_eq!(
15305 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
15306 "-ERR syntax error\r\n"
15307 );
15308 assert_eq!(
15309 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
15310 "-ERR weight value is not a float\r\n"
15311 );
15312 assert_eq!(
15313 f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
15314 "-ERR syntax error\r\n"
15315 );
15316 }
15317
15318 #[test]
15320 fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
15321 let mut f = Fixture::new();
15322 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15323 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
15324 assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
15325 assert_eq!(
15326 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
15327 "*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"
15328 );
15329 assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
15330 assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
15331 assert_eq!(
15334 f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
15335 ":0\r\n"
15336 );
15337 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
15338 assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
15340 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
15341 for cmd in [
15342 &[
15343 b"ZUNIONSTORE".as_slice(),
15344 b"d",
15345 b"2",
15346 b"z",
15347 b"y",
15348 b"WITHSCORES",
15349 ][..],
15350 &[
15351 b"ZDIFFSTORE",
15352 b"d",
15353 b"2",
15354 b"z",
15355 b"y",
15356 b"WEIGHTS",
15357 b"1",
15358 b"1",
15359 ],
15360 ] {
15361 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15362 }
15363 }
15364
15365 #[test]
15367 fn intercard_counts_and_stops_at_its_limit() {
15368 let mut f = Fixture::new();
15369 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15370 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
15371 assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
15372 assert_eq!(
15374 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
15375 ":2\r\n"
15376 );
15377 assert_eq!(
15378 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
15379 ":1\r\n"
15380 );
15381 let bad = "-ERR LIMIT can't be negative\r\n";
15384 assert_eq!(
15385 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
15386 bad
15387 );
15388 assert_eq!(
15389 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
15390 bad
15391 );
15392 for cmd in [
15393 &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
15394 &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
15395 &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
15396 ] {
15397 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15398 }
15399 }
15400
15401 #[test]
15403 fn a_draw_answers_one_member_or_an_array_of_them() {
15404 let mut f = Fixture::new();
15405 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15406 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
15409 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
15410 assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
15411 assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
15412 let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
15415 assert!(all.starts_with("*3\r\n"), "{all}");
15416 for m in ["a", "b", "c"] {
15417 assert!(all.contains(m), "{all}");
15418 }
15419 assert!(
15422 f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
15423 "five draws with replacement"
15424 );
15425 assert!(
15426 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
15427 .starts_with("*4\r\n"),
15428 "two pairs, flat on RESP2"
15429 );
15430 f.out = Out::new(Proto::Resp3);
15431 let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
15432 assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
15433 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
15434 f.out = Out::new(Proto::Resp2);
15435 assert_eq!(
15436 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
15437 "-ERR syntax error\r\n"
15438 );
15439 assert_eq!(
15440 f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
15441 "-ERR value is not an integer or out of range\r\n"
15442 );
15443 }
15444
15445 #[test]
15447 fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
15448 let mut f = Fixture::new();
15449 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15450 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";
15451 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
15452 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
15453 assert_eq!(
15454 f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
15455 "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
15456 );
15457 assert_eq!(
15458 f.run(&[b"ZSCAN", b"nokey", b"0"]),
15459 "*2\r\n$1\r\n0\r\n*0\r\n"
15460 );
15461 f.out = Out::new(Proto::Resp3);
15464 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
15465 f.out = Out::new(Proto::Resp2);
15466 assert_eq!(
15467 f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
15468 "-ERR NOVALUES option can only be used in HSCAN\r\n"
15469 );
15470 assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
15471 assert_eq!(
15472 f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
15473 "-ERR syntax error\r\n"
15474 );
15475 }
15476
15477 #[test]
15479 fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
15480 let mut f = Fixture::new();
15481 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15482 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
15484 assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
15485 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
15486 assert_eq!(
15488 f.run(&[b"ZPOPMIN", b"z", b"2"]),
15489 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
15490 );
15491 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
15494 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
15495 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
15496 assert_eq!(
15498 f.run(&[b"ZPOPMIN", b"z", b"9"]),
15499 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15500 );
15501 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15502
15503 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
15504 f.out = Out::new(Proto::Resp3);
15505 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
15506 assert_eq!(
15507 f.run(&[b"ZPOPMIN", b"z", b"1"]),
15508 "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
15509 );
15510 f.out = Out::new(Proto::Resp2);
15511 let bad = "-ERR value is out of range, must be positive\r\n";
15514 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
15515 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
15516 assert_eq!(
15517 f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
15518 "-ERR syntax error\r\n"
15519 );
15520 }
15521
15522 #[test]
15524 fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
15525 let mut f = Fixture::new();
15526 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15527 assert_eq!(
15528 f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
15529 "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
15530 );
15531 assert_eq!(
15534 f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
15535 "*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"
15536 );
15537 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
15539 f.out = Out::new(Proto::Resp3);
15540 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
15541 f.out = Out::new(Proto::Resp2);
15542 let numkeys = "-ERR numkeys should be greater than 0\r\n";
15543 for bad in [
15544 &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
15545 &[b"ZMPOP", b"-1", b"z", b"MIN"],
15546 &[b"ZMPOP", b"x", b"z", b"MIN"],
15547 ] {
15548 assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
15549 }
15550 let count = "-ERR count should be greater than 0\r\n";
15551 for bad in [
15552 &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
15553 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
15554 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
15555 ] {
15556 assert_eq!(f.run(bad), count, "{:?}", bad[5]);
15557 }
15558 let syntax = "-ERR syntax error\r\n";
15559 for bad in [
15560 &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
15563 &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
15564 &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
15565 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
15566 ] {
15567 assert_eq!(f.run(bad), syntax, "{bad:?}");
15568 }
15569 }
15570
15571 #[test]
15574 fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
15575 let mut f = Fixture::new();
15576 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15577 assert_eq!(
15578 f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
15579 (
15580 Flow::Continue,
15581 "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
15582 )
15583 );
15584 assert_eq!(
15585 f.run(&[b"BZPOPMAX", b"z", b"0"]),
15586 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
15587 );
15588 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
15589 assert_eq!(
15590 f.run(&[
15591 b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
15592 ]),
15593 "*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"
15594 );
15595 f.out = Out::new(Proto::Resp3);
15596 assert_eq!(
15597 f.run(&[b"BZPOPMIN", b"z", b"0"]),
15598 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
15599 );
15600 f.out = Out::new(Proto::Resp2);
15601 assert_eq!(
15603 f.flow(&[b"BZPOPMIN", b"z", b"0"]),
15604 (Flow::Block, String::new())
15605 );
15606 assert_eq!(
15607 f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
15608 (Flow::Block, String::new())
15609 );
15610 assert_eq!(
15613 f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
15614 "-ERR timeout is not a float or out of range\r\n"
15615 );
15616 assert_eq!(
15617 f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
15618 "-ERR numkeys should be greater than 0\r\n"
15619 );
15620 assert_eq!(
15621 f.run(&[b"BZPOPMIN", b"z", b"-1"]),
15622 "-ERR timeout is negative\r\n"
15623 );
15624 }
15625
15626 #[test]
15630 fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
15631 let mut f = Fixture::new();
15632 assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
15633 assert_eq!(f.server.parked(), 1);
15634 f.run(&[b"SET", b"z", b"v"]);
15637 let mut out = Out::new(Proto::Resp2);
15638 assert!(!f.server.serve_waiter(7, 0, &mut out));
15639 assert!(out.as_slice().is_empty());
15640 f.run(&[b"DEL", b"z"]);
15641 f.run(&[b"ZADD", b"z", b"5", b"m"]);
15642 assert!(f.server.serve_waiter(7, 0, &mut out));
15643 assert_eq!(
15644 core::str::from_utf8(out.as_slice()).expect("ascii"),
15645 "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
15646 );
15647 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15650 }
15651
15652 #[test]
15653 fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
15654 let mut f = Fixture::new();
15655 f.run(&[b"SET", b"s", b"v"]);
15656 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
15657 for cmd in [
15658 &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
15659 &[b"ZINCRBY", b"s", b"1", b"a"],
15660 &[b"ZCARD", b"s"],
15661 &[b"ZSCORE", b"s", b"a"],
15662 &[b"ZMSCORE", b"s", b"a"],
15663 &[b"ZREM", b"s", b"a"],
15664 &[b"ZRANK", b"s", b"a"],
15665 &[b"ZREVRANK", b"s", b"a"],
15666 &[b"ZCOUNT", b"s", b"1", b"2"],
15667 &[b"ZLEXCOUNT", b"s", b"-", b"+"],
15668 &[b"ZRANGE", b"s", b"0", b"-1"],
15669 &[b"ZREVRANGE", b"s", b"0", b"-1"],
15670 &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
15671 &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
15672 &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
15673 &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
15674 &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
15675 &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
15676 &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
15677 &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
15678 &[b"ZUNION", b"1", b"s"],
15679 &[b"ZINTER", b"1", b"s"],
15680 &[b"ZDIFF", b"1", b"s"],
15681 &[b"ZUNIONSTORE", b"d", b"1", b"s"],
15682 &[b"ZINTERSTORE", b"d", b"1", b"s"],
15683 &[b"ZDIFFSTORE", b"d", b"1", b"s"],
15684 &[b"ZINTERCARD", b"1", b"s"],
15685 &[b"ZRANDMEMBER", b"s"],
15686 &[b"ZSCAN", b"s", b"0"],
15687 &[b"ZPOPMIN", b"s"],
15688 &[b"ZPOPMAX", b"s", b"2"],
15689 &[b"ZMPOP", b"1", b"s", b"MIN"],
15690 &[b"BZPOPMIN", b"s", b"0"],
15691 &[b"BZPOPMAX", b"s", b"0"],
15692 &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
15693 ] {
15694 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
15695 }
15696 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
15697 }
15698
15699 #[cfg_attr(miri, ignore = "the volume is the claim")]
15704 #[test]
15705 fn churning_sorted_sets_does_not_grow_the_server() {
15706 let mut f = Fixture::new();
15707 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
15708 let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
15709 let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
15710 for i in 0..200 {
15711 args.push(&scores[i]);
15712 args.push(&members[i]);
15713 }
15714
15715 f.run(&args);
15716 f.run(&[b"DEL", b"z"]);
15717 f.server.compact_step();
15718 let after_first = f.server.memory_bytes();
15719
15720 for _ in 0..200 {
15721 f.run(&args);
15722 f.run(&[b"DEL", b"z"]);
15723 f.server.compact_step();
15724 }
15725 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
15726 assert!(
15727 f.server.memory_bytes() <= after_first * 2,
15728 "held {} after two hundred passes against {after_first} after one",
15729 f.server.memory_bytes()
15730 );
15731 }
15732
15733 fn sicily(f: &mut Fixture) {
15741 f.run(&[
15742 b"GEOADD",
15743 b"Sicily",
15744 b"13.361389",
15745 b"38.115556",
15746 b"Palermo",
15747 b"15.087269",
15748 b"37.502669",
15749 b"Catania",
15750 ]);
15751 f.run(&[
15752 b"GEOADD",
15753 b"Sicily",
15754 b"13.583333",
15755 b"37.316667",
15756 b"Agrigento",
15757 ]);
15758 }
15759
15760 #[test]
15761 fn places_go_in_as_scores_and_come_back_as_positions() {
15762 let mut f = Fixture::new();
15763 assert_eq!(
15764 f.run(&[
15765 b"GEOADD",
15766 b"Sicily",
15767 b"13.361389",
15768 b"38.115556",
15769 b"Palermo",
15770 b"15.087269",
15771 b"37.502669",
15772 b"Catania"
15773 ]),
15774 ":2\r\n"
15775 );
15776 assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
15780 assert_eq!(
15781 f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
15782 "$16\r\n3479099956230698\r\n"
15783 );
15784 assert_eq!(
15785 f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
15786 "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
15787 );
15788 assert_eq!(
15789 f.run(&[
15790 b"GEOHASH",
15791 b"Sicily",
15792 b"Palermo",
15793 b"Catania",
15794 b"NonExisting"
15795 ]),
15796 "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
15797 );
15798 assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
15802 assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
15803 }
15804
15805 #[test]
15806 fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
15807 let mut f = Fixture::new();
15808 sicily(&mut f);
15809 assert_eq!(
15810 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
15811 "$11\r\n166274.1516\r\n"
15812 );
15813 assert_eq!(
15814 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
15815 "$8\r\n166.2742\r\n"
15816 );
15817 assert_eq!(
15818 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
15819 "$8\r\n103.3182\r\n"
15820 );
15821 assert_eq!(
15825 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
15826 "$-1\r\n"
15827 );
15828 assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
15829 assert_eq!(
15830 f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
15831 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
15832 );
15833 assert_eq!(
15834 f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
15835 "-ERR syntax error\r\n"
15836 );
15837 }
15838
15839 #[test]
15840 fn a_search_finds_what_is_inside_it_nearest_first() {
15841 let mut f = Fixture::new();
15842 sicily(&mut f);
15843 let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
15844 assert_eq!(
15845 f.run(&[
15846 b"GEOSEARCH",
15847 b"Sicily",
15848 b"FROMLONLAT",
15849 b"15",
15850 b"37",
15851 b"BYRADIUS",
15852 b"200",
15853 b"km",
15854 b"ASC"
15855 ]),
15856 all
15857 );
15858 assert_eq!(
15861 f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
15862 all
15863 );
15864 assert_eq!(
15865 f.run(&[
15866 b"GEORADIUS_RO",
15867 b"Sicily",
15868 b"15",
15869 b"37",
15870 b"200",
15871 b"km",
15872 b"ASC"
15873 ]),
15874 all
15875 );
15876 assert_eq!(
15879 f.run(&[
15880 b"GEORADIUS",
15881 b"Sicily",
15882 b"15",
15883 b"37",
15884 b"200",
15885 b"km",
15886 b"DESC",
15887 b"COUNT",
15888 b"1"
15889 ]),
15890 "*1\r\n$7\r\nPalermo\r\n"
15891 );
15892 assert_eq!(
15893 f.run(&[
15894 b"GEORADIUS",
15895 b"Sicily",
15896 b"15",
15897 b"37",
15898 b"200",
15899 b"km",
15900 b"COUNT",
15901 b"1"
15902 ]),
15903 "*1\r\n$7\r\nCatania\r\n"
15904 );
15905 let empty = "*0\r\n";
15908 assert_eq!(
15909 f.run(&[
15910 b"GEOSEARCH",
15911 b"Sicily",
15912 b"FROMLONLAT",
15913 b"15",
15914 b"37",
15915 b"BYRADIUS",
15916 b"1",
15917 b"km"
15918 ]),
15919 empty
15920 );
15921 assert_eq!(
15922 f.run(&[
15923 b"GEOSEARCH",
15924 b"nokey",
15925 b"FROMLONLAT",
15926 b"15",
15927 b"37",
15928 b"BYRADIUS",
15929 b"1",
15930 b"km"
15931 ]),
15932 empty
15933 );
15934 assert_eq!(
15935 f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
15936 empty
15937 );
15938 }
15939
15940 #[test]
15941 fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
15942 let mut f = Fixture::new();
15943 sicily(&mut f);
15944 assert_eq!(
15945 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
15946 "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
15947 );
15948 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";
15951 assert_eq!(
15952 f.run(&[
15953 b"GEORADIUSBYMEMBER_RO",
15954 b"Sicily",
15955 b"Agrigento",
15956 b"100",
15957 b"km",
15958 b"WITHDIST"
15959 ]),
15960 with_dist
15961 );
15962 assert_eq!(
15963 f.run(&[
15964 b"GEOSEARCH",
15965 b"Sicily",
15966 b"FROMMEMBER",
15967 b"Agrigento",
15968 b"BYRADIUS",
15969 b"100",
15970 b"km",
15971 b"ASC",
15972 b"WITHDIST"
15973 ]),
15974 with_dist
15975 );
15976 assert_eq!(
15977 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
15978 "-ERR could not decode requested zset member\r\n"
15979 );
15980 }
15981
15982 #[test]
15983 fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
15984 let mut f = Fixture::new();
15985 sicily(&mut f);
15986 assert_eq!(
15990 f.run(&[
15991 b"GEOSEARCH",
15992 b"Sicily",
15993 b"FROMLONLAT",
15994 b"15",
15995 b"37",
15996 b"BYBOX",
15997 b"400",
15998 b"400",
15999 b"km",
16000 b"ASC",
16001 b"WITHCOORD",
16002 b"WITHDIST",
16003 b"WITHHASH"
16004 ]),
16005 "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
16006 $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
16007 *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
16008 $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
16009 *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
16010 $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
16011 );
16012 }
16013
16014 #[test]
16015 fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
16016 let mut f = Fixture::new();
16017 sicily(&mut f);
16018 let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
16019 $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
16020 $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
16021 assert_eq!(
16022 f.run(&[
16023 b"GEOSEARCHSTORE",
16024 b"dst",
16025 b"Sicily",
16026 b"FROMLONLAT",
16027 b"15",
16028 b"37",
16029 b"BYRADIUS",
16030 b"200",
16031 b"km",
16032 b"ASC"
16033 ]),
16034 ":3\r\n"
16035 );
16036 assert_eq!(
16037 f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
16038 hashes
16039 );
16040 assert_eq!(
16043 f.run(&[
16044 b"GEORADIUS",
16045 b"Sicily",
16046 b"15",
16047 b"37",
16048 b"200",
16049 b"km",
16050 b"STORE",
16051 b"dst3"
16052 ]),
16053 ":3\r\n"
16054 );
16055 assert_eq!(
16056 f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
16057 hashes
16058 );
16059 assert_eq!(
16068 f.run(&[
16069 b"GEOSEARCHSTORE",
16070 b"dst2",
16071 b"Sicily",
16072 b"FROMLONLAT",
16073 b"15",
16074 b"37",
16075 b"BYRADIUS",
16076 b"200",
16077 b"km",
16078 b"ASC",
16079 b"STOREDIST"
16080 ]),
16081 ":3\r\n"
16082 );
16083 for (member, want) in [
16084 ("Catania", 56.441_257_870_158_19),
16085 ("Agrigento", 130.423_487_067_147_14),
16086 ("Palermo", 190.442_429_847_757_92),
16087 ] {
16088 let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
16089 let got: f64 = reply
16090 .trim_start_matches(|c: char| c != '\n')
16091 .trim()
16092 .parse()
16093 .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
16094 assert!(
16095 (got - want).abs() < 1e-9,
16096 "{member} scored {got} not {want}"
16097 );
16098 }
16099 assert_eq!(
16102 f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
16103 "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
16104 );
16105 assert_eq!(
16109 f.run(&[
16110 b"GEOSEARCHSTORE",
16111 b"dst",
16112 b"nokey",
16113 b"FROMLONLAT",
16114 b"15",
16115 b"37",
16116 b"BYRADIUS",
16117 b"200",
16118 b"km"
16119 ]),
16120 ":0\r\n"
16121 );
16122 assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
16123 }
16124
16125 #[test]
16126 fn the_gates_on_geoadd_are_the_ones_zadd_has() {
16127 let mut f = Fixture::new();
16128 sicily(&mut f);
16129 assert_eq!(
16132 f.run(&[
16133 b"GEOADD",
16134 b"Sicily",
16135 b"XX",
16136 b"CH",
16137 b"13.361389",
16138 b"38.115556",
16139 b"Palermo"
16140 ]),
16141 ":0\r\n"
16142 );
16143 assert_eq!(
16144 f.run(&[
16145 b"GEOADD",
16146 b"Sicily",
16147 b"NX",
16148 b"13.361389",
16149 b"38.9",
16150 b"Palermo"
16151 ]),
16152 ":0\r\n"
16153 );
16154 assert_eq!(
16155 f.run(&[
16156 b"GEOADD",
16157 b"Sicily",
16158 b"CH",
16159 b"13.361389",
16160 b"38.9",
16161 b"Palermo"
16162 ]),
16163 ":1\r\n"
16164 );
16165 assert_eq!(
16168 f.run(&[
16169 b"GEOADD",
16170 b"new",
16171 b"13.361389",
16172 b"38.115556",
16173 b"here",
16174 b"181",
16175 b"38",
16176 b"there"
16177 ]),
16178 "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
16179 );
16180 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
16181 assert_eq!(
16182 f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
16183 "-ERR value is not a valid float\r\n"
16184 );
16185 assert_eq!(
16188 f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
16189 "-ERR syntax error\r\n"
16190 );
16191 assert_eq!(
16192 f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
16193 "-ERR syntax error\r\n"
16194 );
16195 assert_eq!(
16196 f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
16197 "-ERR syntax error\r\n"
16198 );
16199 assert_eq!(
16200 f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
16201 "-ERR wrong number of arguments for 'geoadd' command\r\n"
16202 );
16203 }
16204
16205 #[test]
16208 fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
16209 let mut f = Fixture::new();
16210 sicily(&mut f);
16211 let cases: &[(&[&[u8]], &str)] = &[
16212 (
16213 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
16214 "-ERR need numeric radius\r\n",
16215 ),
16216 (
16217 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
16218 "-ERR radius cannot be negative\r\n",
16219 ),
16220 (
16221 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
16222 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
16223 ),
16224 (
16225 &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
16226 "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
16227 ),
16228 (
16229 &[
16230 b"GEOSEARCH",
16231 b"Sicily",
16232 b"FROMLONLAT",
16233 b"15",
16234 b"37",
16235 b"BYBOX",
16236 b"x",
16237 b"1",
16238 b"km",
16239 ],
16240 "-ERR need numeric width\r\n",
16241 ),
16242 (
16243 &[
16244 b"GEOSEARCH",
16245 b"Sicily",
16246 b"FROMLONLAT",
16247 b"15",
16248 b"37",
16249 b"BYBOX",
16250 b"1",
16251 b"y",
16252 b"km",
16253 ],
16254 "-ERR need numeric height\r\n",
16255 ),
16256 (
16257 &[
16258 b"GEOSEARCH",
16259 b"Sicily",
16260 b"FROMLONLAT",
16261 b"15",
16262 b"37",
16263 b"BYBOX",
16264 b"-1",
16265 b"1",
16266 b"km",
16267 ],
16268 "-ERR height or width cannot be negative\r\n",
16269 ),
16270 (
16271 &[
16272 b"GEOSEARCH",
16273 b"Sicily",
16274 b"FROMLONLAT",
16275 b"15",
16276 b"37",
16277 b"BYRADIUS",
16278 b"1",
16279 b"km",
16280 b"ANY",
16281 ],
16282 "-ERR the ANY argument requires COUNT argument\r\n",
16283 ),
16284 (
16285 &[
16286 b"GEOSEARCH",
16287 b"Sicily",
16288 b"FROMLONLAT",
16289 b"15",
16290 b"37",
16291 b"BYRADIUS",
16292 b"1",
16293 b"km",
16294 b"COUNT",
16295 b"0",
16296 ],
16297 "-ERR COUNT must be > 0\r\n",
16298 ),
16299 (
16300 &[
16301 b"GEOSEARCH",
16302 b"Sicily",
16303 b"BYRADIUS",
16304 b"1",
16305 b"km",
16306 b"BYBOX",
16307 b"1",
16308 b"1",
16309 b"km",
16310 ],
16311 "-ERR syntax error\r\n",
16312 ),
16313 (
16314 &[
16315 b"GEOSEARCH",
16316 b"Sicily",
16317 b"FROMMEMBER",
16318 b"Palermo",
16319 b"FROMLONLAT",
16320 b"1",
16321 b"2",
16322 b"BYRADIUS",
16323 b"1",
16324 b"km",
16325 ],
16326 "-ERR syntax error\r\n",
16327 ),
16328 (
16331 &[
16332 b"geosearch",
16333 b"Sicily",
16334 b"BYRADIUS",
16335 b"1",
16336 b"km",
16337 b"ASC",
16338 b"WITHDIST",
16339 ],
16340 "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
16341 ),
16342 (
16343 &[
16344 b"GEOSEARCH",
16345 b"Sicily",
16346 b"FROMLONLAT",
16347 b"15",
16348 b"37",
16349 b"ASC",
16350 b"WITHDIST",
16351 ],
16352 "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
16353 ),
16354 (
16357 &[
16358 b"GEOSEARCHSTORE",
16359 b"d",
16360 b"Sicily",
16361 b"FROMLONLAT",
16362 b"15",
16363 b"37",
16364 b"BYRADIUS",
16365 b"1",
16366 b"km",
16367 b"WITHCOORD",
16368 ],
16369 "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
16370 ),
16371 (
16372 &[
16373 b"GEORADIUS",
16374 b"Sicily",
16375 b"15",
16376 b"37",
16377 b"1",
16378 b"km",
16379 b"WITHDIST",
16380 b"STORE",
16381 b"d",
16382 ],
16383 "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
16384 ),
16385 (
16388 &[
16389 b"GEORADIUS_RO",
16390 b"Sicily",
16391 b"15",
16392 b"37",
16393 b"1",
16394 b"km",
16395 b"STORE",
16396 b"d",
16397 ],
16398 "-ERR syntax error\r\n",
16399 ),
16400 (
16401 &[
16402 b"GEOSEARCH",
16403 b"Sicily",
16404 b"FROMLONLAT",
16405 b"15",
16406 b"37",
16407 b"BYRADIUS",
16408 b"1",
16409 b"km",
16410 b"STOREDIST",
16411 ],
16412 "-ERR syntax error\r\n",
16413 ),
16414 ];
16415 for (parts, want) in cases {
16416 assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
16417 }
16418 }
16419
16420 #[test]
16423 fn every_geo_command_says_wrongtype() {
16424 let mut f = Fixture::new();
16425 f.run(&[b"SET", b"s", b"v"]);
16426 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16427 let cases: &[&[&[u8]]] = &[
16428 &[b"GEOADD", b"s", b"13", b"38", b"m"],
16429 &[b"GEOPOS", b"s", b"m"],
16430 &[b"GEOHASH", b"s", b"m"],
16431 &[b"GEODIST", b"s", b"a", b"b"],
16432 &[
16433 b"GEOSEARCH",
16434 b"s",
16435 b"FROMLONLAT",
16436 b"15",
16437 b"37",
16438 b"BYRADIUS",
16439 b"1",
16440 b"km",
16441 ],
16442 &[
16443 b"GEOSEARCHSTORE",
16444 b"d",
16445 b"s",
16446 b"FROMLONLAT",
16447 b"15",
16448 b"37",
16449 b"BYRADIUS",
16450 b"1",
16451 b"km",
16452 ],
16453 &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
16454 &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
16455 &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
16456 &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
16457 ];
16458 for case in cases {
16459 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
16460 }
16461 assert_eq!(
16464 f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
16465 wrong
16466 );
16467 }
16468
16469 #[test]
16472 fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
16473 let mut f = Fixture::new();
16474 assert_eq!(
16477 f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
16478 ":3\r\n"
16479 );
16480 assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
16481 assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
16482 assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
16483 assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
16485 assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
16486 assert_eq!(
16487 f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
16488 "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
16489 );
16490 assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
16492 assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
16493 }
16494
16495 #[test]
16498 fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
16499 let mut f = Fixture::new();
16500 assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
16501 assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
16502 f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
16503 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
16504 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
16505 assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
16507 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
16508 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
16509
16510 f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
16514 assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
16515 assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
16516 assert_eq!(
16519 f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
16520 "-ERR array index overflow\r\n"
16521 );
16522 assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
16523 }
16524
16525 #[test]
16528 fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
16529 let mut f = Fixture::new();
16530 f.run(&[b"ARSET", b"a", b"1", b"x"]);
16531 assert_eq!(
16532 f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
16533 "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
16534 );
16535 assert_eq!(
16538 f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
16539 "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
16540 );
16541 assert_eq!(
16543 f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
16544 "*2\r\n$-1\r\n$-1\r\n"
16545 );
16546 assert_eq!(
16550 f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
16551 "-ERR range exceeds maximum of 1000000 items\r\n"
16552 );
16553 }
16554
16555 #[test]
16558 fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
16559 let mut f = Fixture::new();
16560 assert_eq!(
16561 f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
16562 "-ERR invalid array index\r\n"
16563 );
16564 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
16565 f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
16566 assert_eq!(
16567 f.run(&[b"ARDEL", b"a", b"0", b"01"]),
16568 "-ERR invalid array index\r\n"
16569 );
16570 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
16571 assert_eq!(
16574 f.run(&[b"ARGET", b"a", b"-1"]),
16575 "-ERR invalid array index\r\n"
16576 );
16577 assert_eq!(
16580 f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
16581 "-ERR wrong number of arguments for 'armset' command\r\n"
16582 );
16583 assert_eq!(
16584 f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
16585 "-ERR wrong number of arguments for 'ardelrange' command\r\n"
16586 );
16587 }
16588
16589 #[test]
16590 fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
16591 let mut f = Fixture::new();
16592 f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
16593 assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
16594 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
16595 assert_eq!(
16598 f.run(&[
16599 b"ARDELRANGE",
16600 b"a",
16601 b"100",
16602 b"200",
16603 b"0",
16604 b"18446744073709551614"
16605 ]),
16606 ":2\r\n"
16607 );
16608 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
16609 assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
16610 assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
16611 }
16612
16613 #[test]
16616 fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
16617 let mut f = Fixture::new();
16618 let long = vec![b'v'; 200];
16619 f.run(&[
16620 b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
16621 b"short", b"5", &long, b"6", b"-0",
16622 ]);
16623 assert_eq!(
16627 f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
16628 format!(
16629 "*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",
16630 String::from_utf8_lossy(&long)
16631 )
16632 );
16633 }
16634
16635 #[test]
16636 fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
16637 let mut f = Fixture::new();
16638 f.run(&[b"ARSET", b"a", b"0", b"x"]);
16639 assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
16640 assert_eq!(
16641 f.run(&[b"OBJECT", b"ENCODING", b"a"]),
16642 "$12\r\nsliced-array\r\n"
16643 );
16644 assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
16646 assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
16647 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
16648 assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
16649 assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
16650 assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
16651 }
16652
16653 #[test]
16654 fn every_array_command_refuses_a_key_holding_something_else() {
16655 let mut f = Fixture::new();
16656 f.run(&[b"SET", b"s", b"v"]);
16657 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16658 for cmd in [
16659 &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
16660 &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
16661 &[b"ARGET".as_ref(), b"s", b"0"][..],
16662 &[b"ARMGET".as_ref(), b"s", b"0"][..],
16663 &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
16664 &[b"ARLEN".as_ref(), b"s"][..],
16665 &[b"ARCOUNT".as_ref(), b"s"][..],
16666 &[b"ARDEL".as_ref(), b"s", b"0"][..],
16667 &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
16668 &[b"ARINSERT".as_ref(), b"s", b"x"][..],
16669 &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
16670 &[b"ARNEXT".as_ref(), b"s"][..],
16671 &[b"ARSEEK".as_ref(), b"s", b"1"][..],
16672 &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
16673 &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
16674 &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
16675 &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
16676 &[b"ARINFO".as_ref(), b"s"][..],
16677 ] {
16678 assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
16679 }
16680 }
16681
16682 #[test]
16686 fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
16687 let mut f = Fixture::new();
16688 f.run(&[b"SET", b"s", b"v"]);
16689 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16690 let bad = "-ERR invalid array index\r\n";
16691 assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
16692 assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
16693 assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
16694 assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
16695 assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
16696 assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
16697 f.run(&[b"ARSET", b"a", b"0", b"x"]);
16699 assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
16700 assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
16701 }
16702
16703 #[test]
16704 fn an_append_follows_a_cursor_the_client_can_move() {
16705 let mut f = Fixture::new();
16706 assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
16707 assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
16708 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
16709 assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
16710 assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
16711
16712 assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
16715 assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
16716 assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
16717 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
16718 assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
16719 assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
16720 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
16721
16722 assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
16725 assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
16726 assert_eq!(
16727 f.run(&[b"ARINSERT", b"a", b"x"]),
16728 "-ERR insert index overflow\r\n"
16729 );
16730 assert_eq!(
16731 f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
16732 "-ERR invalid array index\r\n"
16733 );
16734 }
16735
16736 #[test]
16737 fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
16738 let mut f = Fixture::new();
16739 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
16740 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
16741 assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
16742 assert_eq!(
16743 f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
16744 "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
16745 );
16746 assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
16749 assert_eq!(
16750 f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
16751 "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
16752 );
16753 assert_eq!(
16756 f.run(&[b"ARRING", b"r", b"0", b"x"]),
16757 "-ERR size must be positive\r\n"
16758 );
16759 assert_eq!(
16760 f.run(&[b"ARRING", b"r", b"big", b"x"]),
16761 "-ERR invalid size\r\n"
16762 );
16763 }
16764
16765 #[test]
16766 fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
16767 let mut f = Fixture::new();
16768 assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
16769 f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
16770 assert_eq!(
16771 f.run(&[b"ARLASTITEMS", b"r", b"3"]),
16772 "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
16773 );
16774 assert_eq!(
16775 f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
16776 "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
16777 );
16778 assert_eq!(
16779 f.run(&[b"ARLASTITEMS", b"r", b"99"]),
16780 "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
16781 "more than there is gets what there is"
16782 );
16783 assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
16786 assert_eq!(
16787 f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
16788 "-ERR syntax error\r\n"
16789 );
16790 assert_eq!(
16791 f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
16792 "-ERR invalid COUNT\r\n"
16793 );
16794
16795 f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
16798 assert_eq!(
16799 f.run(&[b"ARLASTITEMS", b"h", b"5"]),
16800 "*2\r\n$-1\r\n$1\r\nz\r\n"
16801 );
16802 }
16803
16804 #[test]
16805 fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
16806 let mut f = Fixture::new();
16807 assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
16808 f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
16809 assert_eq!(
16812 f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
16813 "*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"
16814 );
16815 assert_eq!(
16816 f.run(&[
16817 b"ARSCAN",
16818 b"a",
16819 b"18446744073709551614",
16820 b"0",
16821 b"LIMIT",
16822 b"1"
16823 ]),
16824 "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
16825 );
16826 assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
16827 assert_eq!(
16828 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
16829 "-ERR LIMIT must be positive\r\n"
16830 );
16831 assert_eq!(
16832 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
16833 "-ERR syntax error\r\n"
16834 );
16835 assert_eq!(
16836 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
16837 "-ERR wrong number of arguments for 'arscan' command\r\n"
16838 );
16839 }
16840
16841 #[test]
16842 fn a_grep_answers_the_indexes_whose_elements_match() {
16843 let mut f = Fixture::new();
16844 assert_eq!(
16845 f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
16846 "*0\r\n"
16847 );
16848 f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
16849
16850 assert_eq!(
16853 f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
16854 "*3\r\n:0\r\n:1\r\n:2\r\n"
16855 );
16856 assert_eq!(
16857 f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
16858 "*3\r\n:2\r\n:1\r\n:0\r\n"
16859 );
16860 assert_eq!(
16861 f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
16862 "*2\r\n:1\r\n:2\r\n"
16863 );
16864
16865 assert_eq!(
16868 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
16869 "*1\r\n:0\r\n"
16870 );
16871 assert_eq!(
16872 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
16873 "*2\r\n:0\r\n:3\r\n"
16874 );
16875 assert_eq!(
16876 f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
16877 "*1\r\n:2\r\n"
16878 );
16879 assert_eq!(
16880 f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
16881 "*2\r\n:1\r\n:2\r\n"
16882 );
16883
16884 let both: &[&[u8]] = &[
16887 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
16888 ];
16889 assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
16890 assert_eq!(
16891 f.run(&[
16892 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
16893 ]),
16894 "*0\r\n"
16895 );
16896 assert_eq!(
16897 f.run(&[
16898 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
16899 ]),
16900 "*2\r\n:0\r\n:1\r\n"
16901 );
16902
16903 assert_eq!(
16906 f.run(&[
16907 b"ARGREP",
16908 b"a",
16909 b"-",
16910 b"+",
16911 b"MATCH",
16912 b"a",
16913 b"WITHVALUES",
16914 b"LIMIT",
16915 b"2"
16916 ]),
16917 "*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"
16918 );
16919 assert_eq!(
16920 f.run(&[
16921 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
16922 ]),
16923 "*1\r\n:3\r\n"
16924 );
16925 }
16926
16927 #[test]
16929 fn a_grep_reports_a_broken_command_the_way_redis_does() {
16930 let mut f = Fixture::new();
16931 f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
16932 let syntax = "-ERR syntax error\r\n";
16933
16934 assert_eq!(
16937 f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
16938 "-ERR invalid array index\r\n"
16939 );
16940 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
16941 assert_eq!(
16943 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
16944 syntax
16945 );
16946 assert_eq!(
16947 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
16948 syntax
16949 );
16950 assert_eq!(
16951 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
16952 syntax,
16953 "a command with no predicate in it at all"
16954 );
16955 assert_eq!(
16956 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
16957 "-ERR LIMIT must be positive\r\n"
16958 );
16959 assert_eq!(
16960 f.run(&[
16961 b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
16962 ]),
16963 "-ERR value is not an integer or out of range\r\n"
16964 );
16965 assert_eq!(
16966 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
16967 "-ERR regular expression is empty\r\n"
16968 );
16969 assert_eq!(
16970 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
16971 "-ERR invalid regular expression: Missing ')'\r\n"
16972 );
16973 assert_eq!(
16974 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
16975 "-ERR regular expression backreferences are not supported\r\n"
16976 );
16977 let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
16980 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
16981 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
16982 }
16983
16984 #[test]
16985 fn an_op_reduces_a_range_to_one_number() {
16986 let mut f = Fixture::new();
16987 f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
16988 assert_eq!(
16989 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
16990 "$4\r\n-0.5\r\n"
16991 );
16992 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
16993 assert_eq!(
16994 f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
16995 "$3\r\n2.5\r\n"
16996 );
16997 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
16998 assert_eq!(
16999 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
17000 ":1\r\n"
17001 );
17002 f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
17005 assert_eq!(
17006 f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
17007 "$19\r\n0.30000000000000004\r\n"
17008 );
17009 assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
17010 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
17011
17012 f.run(&[b"ARSET", b"w", b"0", b"word"]);
17015 assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
17016 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
17017 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
17018
17019 assert_eq!(
17020 f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
17021 "-ERR unknown operation\r\n"
17022 );
17023 assert_eq!(
17024 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
17025 "-ERR MATCH requires a value argument\r\n"
17026 );
17027 assert_eq!(
17028 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
17029 "-ERR wrong number of arguments for 'arop' command\r\n"
17030 );
17031 }
17032
17033 #[test]
17034 fn the_info_is_a_map_and_a_missing_key_is_an_error() {
17035 let mut f = Fixture::new();
17036 assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
17037 f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
17038 let short = f.run(&[b"ARINFO", b"a"]);
17039 assert!(
17040 short.starts_with("*14\r\n"),
17041 "seven pairs on RESP2: {short}"
17042 );
17043 assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
17044 assert!(
17045 short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
17046 "{short}"
17047 );
17048 assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
17049 let full = f.run(&[b"ARINFO", b"a", b"full"]);
17050 assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
17051 assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
17054 assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
17055 assert!(
17056 full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
17057 "{full}"
17058 );
17059 assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
17060
17061 let mut g = Fixture::new();
17063 g.run(&[b"HELLO", b"3"]);
17064 g.run(&[b"ARINSERT", b"a", b"x"]);
17065 let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
17066 assert!(map.starts_with("%12\r\n"), "{map}");
17067 assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
17068 assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
17069 }
17070
17071 #[test]
17072 fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
17073 let mut f = Fixture::new();
17074 for (score, want) in [
17077 ("3", "3"),
17078 ("3.5", "3.5"),
17079 ("0.3", "0.3"),
17080 ("1e30", "1e+30"),
17081 ("1e19", "1e+19"),
17082 ("1e-7", "1e-7"),
17083 ("0.000001", "0.000001"),
17084 ("4611686018427387904", "4611686018427387904"),
17085 ("-0", "-0"),
17086 ] {
17087 f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
17088 assert_eq!(
17089 f.run(&[b"ZSCORE", b"z", b"m"]),
17090 format!("${}\r\n{want}\r\n", want.len()),
17091 "score {score}"
17092 );
17093 }
17094
17095 let mut g = Fixture::new();
17098 g.run(&[b"HELLO", b"3"]);
17099 g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
17100 assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
17101 assert_eq!(
17106 g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
17107 "$31\r\n1000000000000000000000000000000\r\n"
17108 );
17109 assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
17110 assert_eq!(
17111 g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
17112 "$20\r\n10000000000000000000\r\n"
17113 );
17114 }
17115
17116 #[test]
17119 fn a_node_comes_back_with_the_fields_it_went_in_with() {
17120 let mut f = Fixture::new();
17121 assert_eq!(
17122 f.run(&[
17123 b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
17124 ]),
17125 ":1\r\n"
17126 );
17127 assert_eq!(
17133 f.run(&[b"G.NGET", b"social", b"ada"]),
17134 "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
17135 );
17136 assert_eq!(
17139 f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
17140 ":0\r\n"
17141 );
17142 assert_eq!(
17143 f.run(&[b"G.NGET", b"social", b"ada"]),
17144 "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
17145 );
17146 assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
17149 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
17150 assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
17151 assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
17152
17153 assert_eq!(
17156 f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
17157 "-ERR syntax error\r\n"
17158 );
17159 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
17160
17161 let mut g = Fixture::new();
17163 g.run(&[b"HELLO", b"3"]);
17164 g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
17165 assert_eq!(
17166 g.run(&[b"G.NGET", b"social", b"ada"]),
17167 "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
17168 );
17169 }
17170
17171 #[test]
17172 fn an_edge_creates_the_ends_it_needs() {
17173 let mut f = Fixture::new();
17174 assert_eq!(
17175 f.run(&[
17176 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
17177 ]),
17178 ":1\r\n"
17179 );
17180 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
17182 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
17183 assert_eq!(
17184 f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
17185 "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
17186 );
17187 assert_eq!(
17188 f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
17189 "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
17190 );
17191 assert_eq!(
17194 f.run(&[
17195 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
17196 ]),
17197 ":0\r\n"
17198 );
17199 assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
17200 assert_eq!(
17202 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
17203 ":1\r\n"
17204 );
17205 assert_eq!(
17206 f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
17207 ":1\r\n"
17208 );
17209
17210 assert_eq!(
17211 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
17212 ":1\r\n"
17213 );
17214 assert_eq!(
17215 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
17216 ":0\r\n"
17217 );
17218 assert_eq!(
17221 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
17222 ":0\r\n"
17223 );
17224 assert_eq!(
17225 f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
17226 ":0\r\n"
17227 );
17228 assert_eq!(
17229 f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
17230 ":0\r\n"
17231 );
17232 }
17233
17234 #[test]
17237 fn a_hop_answers_a_cursor_and_a_page() {
17238 let mut f = Fixture::new();
17239 for i in 0..25u32 {
17240 let dst = format!("n{i}");
17241 f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
17242 }
17243 let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
17245 assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
17246
17247 let mut seen = 0;
17248 let mut cursor = String::from("0");
17249 loop {
17250 let page = f.run(&[
17251 b"G.OUT",
17252 b"social",
17253 b"hub",
17254 b"FOLLOWS",
17255 b"COUNT",
17256 b"7",
17257 b"CURSOR",
17258 cursor.as_bytes(),
17259 ]);
17260 let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
17261 cursor = head
17262 .rsplit("\r\n")
17263 .next()
17264 .expect("the cursor line")
17265 .to_string();
17266 seen += rest
17267 .split_once("\r\n")
17268 .expect("the page length")
17269 .0
17270 .parse::<usize>()
17271 .expect("a length");
17272 if cursor == "0" {
17273 break;
17274 }
17275 }
17276 assert_eq!(seen, 25, "every neighbour once across the pages");
17277
17278 assert_eq!(
17281 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
17282 "*2\r\n$1\r\n0\r\n*0\r\n"
17283 );
17284 assert_eq!(
17285 f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
17286 "*2\r\n$1\r\n0\r\n*0\r\n"
17287 );
17288 assert_eq!(
17289 f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
17290 "*2\r\n$1\r\n0\r\n*0\r\n"
17291 );
17292 assert_eq!(
17293 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
17294 "-ERR COUNT must be a positive integer\r\n"
17295 );
17296 assert_eq!(
17297 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
17298 "-ERR syntax error\r\n"
17299 );
17300 }
17301
17302 #[test]
17303 fn a_degree_counts_one_way_or_both() {
17304 let mut f = Fixture::new();
17305 f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
17306 f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
17307 f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
17308 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
17309 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
17310 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
17311 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
17312 assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
17313 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
17314 assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
17315 assert_eq!(
17316 f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
17317 "-ERR syntax error\r\n"
17318 );
17319 }
17320
17321 #[test]
17324 fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
17325 let mut f = Fixture::new();
17326 for (src, dst) in [
17327 ("ada", "grace"),
17328 ("ada", "alan"),
17329 ("grace", "edsger"),
17330 ("alan", "edsger"),
17331 ("edsger", "barbara"),
17332 ] {
17333 f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
17334 }
17335 assert_eq!(
17338 f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
17339 "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
17340 );
17341 assert_eq!(
17342 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
17343 "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
17344 );
17345 let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
17346 assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
17347 assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
17348 assert_eq!(
17350 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
17351 "*1\r\n$5\r\ngrace\r\n"
17352 );
17353 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
17355 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
17356 assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
17357 assert_eq!(
17358 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
17359 "-ERR DEPTH must be a positive integer\r\n"
17360 );
17361 assert_eq!(
17362 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
17363 "-ERR syntax error\r\n"
17364 );
17365 }
17366
17367 #[test]
17370 fn a_path_is_the_shortest_one_and_goes_over_any_label() {
17371 let mut f = Fixture::new();
17372 for i in 0..6u32 {
17375 let src = format!("n{i}");
17376 let dst = format!("n{}", i + 1);
17377 f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
17378 }
17379 assert_eq!(
17380 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
17381 "*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"
17382 );
17383 f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
17384 assert_eq!(
17385 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
17386 "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
17387 );
17388 assert_eq!(
17391 f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
17392 "*1\r\n$2\r\nn2\r\n"
17393 );
17394 assert_eq!(
17395 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
17396 "*0\r\n"
17397 );
17398 assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
17400 f.run(&[b"G.NADD", b"road", b"island"]);
17403 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
17404 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
17405 assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
17406 assert_eq!(
17407 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
17408 "-ERR syntax error\r\n"
17409 );
17410 }
17411
17412 #[test]
17416 fn the_keyspace_sees_a_graph_key_like_any_other() {
17417 let mut f = Fixture::new();
17418 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
17419 assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
17420 assert_eq!(
17421 f.run(&[b"OBJECT", b"ENCODING", b"social"]),
17422 "$9\r\nadjacency\r\n"
17423 );
17424 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
17425 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
17426 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
17427 let held = f.server.memory_bytes();
17431 for i in 0..200u32 {
17432 let dst = format!("n{i}");
17433 f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
17434 }
17435 assert!(
17436 f.server.memory_bytes() > held,
17437 "two hundred edges cost something: {held} then {}",
17438 f.server.memory_bytes()
17439 );
17440 f.run(&[b"DEL", b"big"]);
17441
17442 assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
17445 assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
17446 assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
17447 assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
17448 assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
17449 f.run(&[b"SELECT", b"1"]);
17450 assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
17451
17452 assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
17453 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17454 f.run(&[b"G.NADD", b"g", b"n"]);
17455 assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
17456 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17457 }
17458
17459 #[test]
17462 fn a_graph_cannot_be_copied_or_dumped() {
17463 let mut f = Fixture::new();
17464 f.run(&[b"G.NADD", b"social", b"ada"]);
17465 assert_eq!(
17466 f.run(&[b"COPY", b"social", b"other"]),
17467 "-ERR COPY is not supported for a graph\r\n"
17468 );
17469 assert_eq!(
17470 f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
17471 "-ERR COPY is not supported for a graph\r\n"
17472 );
17473 assert_eq!(
17474 f.run(&[b"DUMP", b"social"]),
17475 "-ERR DUMP is not supported for a graph\r\n"
17476 );
17477 assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
17479 }
17480
17481 #[test]
17484 fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
17485 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
17486 let mut f = Fixture::new();
17487 f.run(&[b"G.NADD", b"social", b"ada"]);
17488 assert_eq!(f.run(&[b"GET", b"social"]), wrong);
17489 assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
17490 assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
17491
17492 f.run(&[b"SET", b"str", b"v"]);
17493 for cmd in [
17494 vec![b"G.NADD".as_ref(), b"str", b"n"],
17495 vec![b"G.NGET".as_ref(), b"str", b"n"],
17496 vec![b"G.NDEL".as_ref(), b"str", b"n"],
17497 vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
17498 vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
17499 vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
17500 vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
17501 vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
17502 vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
17503 vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
17504 ] {
17505 assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
17506 }
17507 }
17508
17509 #[test]
17512 fn a_graph_goes_when_its_last_node_does() {
17513 let mut f = Fixture::new();
17514 f.run(&[
17515 b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
17516 ]);
17517 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
17518 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
17520 assert_eq!(
17521 f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
17522 ":0\r\n"
17523 );
17524 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
17525 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
17526
17527 assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
17528 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
17529 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17530 assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
17531
17532 f.run(&[b"G.NADD", b"social", b"first"]);
17535 f.run(&[b"G.NADD", b"social", b"second"]);
17536 f.run(&[b"G.NDEL", b"social", b"first"]);
17537 f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
17538 assert_eq!(
17539 f.run(&[b"G.OUT", b"social", b"third", b"F"]),
17540 "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
17541 );
17542 }
17543
17544 #[test]
17549 fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
17550 let mut f = Fixture::new();
17551 let doc = br#"{"a":1,"b":{"c":true}}"#;
17552 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
17553 assert_eq!(
17556 f.run(&[b"JSON.GET", b"doc"]),
17557 bulk(r#"{"a":1,"b":{"c":true}}"#)
17558 );
17559 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
17560 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
17561 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
17562 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
17565 assert_eq!(
17566 f.run(&[b"JSON.GET", b"doc", b".nope"]),
17567 "-ERR Path does not exist\r\n"
17568 );
17569 assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
17570 assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
17573 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
17574 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
17575 assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
17576 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
17577 }
17578
17579 #[test]
17585 fn the_two_json_errors_that_carry_no_prefix() {
17586 let mut f = Fixture::new();
17587 f.run(&[b"SET", b"plain", b"x"]);
17588 let wrong = "-Existing key has wrong Redis type\r\n";
17589 assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
17590 assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
17591 assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
17592 assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
17593 assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
17594
17595 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
17596 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
17600 assert_eq!(
17601 f.run(&[b"JSON.GET", b"doc"]),
17602 bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
17603 );
17604 assert_eq!(
17605 f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
17606 "-Err wrong static path\r\n"
17607 );
17608 }
17609
17610 #[test]
17612 fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
17613 let mut f = Fixture::new();
17614 assert_eq!(
17616 f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
17617 "-ERR new objects must be created at the root\r\n"
17618 );
17619 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17620 assert_eq!(
17623 f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
17624 "-ERR new objects must be created at the root\r\n"
17625 );
17626 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
17627 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
17628
17629 f.run(&[
17630 b"JSON.SET",
17631 b"doc",
17632 b"$",
17633 br#"{"o":{},"arr":[1,2],"s":"x"}"#,
17634 ]);
17635 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
17637 assert_eq!(
17640 f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
17641 "$-1\r\n"
17642 );
17643 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
17644 assert_eq!(
17646 f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
17647 "-ERR array index out of range\r\n"
17648 );
17649 assert_eq!(
17650 f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
17651 "-ERR array index out of range\r\n"
17652 );
17653 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
17654 assert_eq!(
17657 f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
17658 "$-1\r\n"
17659 );
17660 assert_eq!(
17661 f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
17662 "$-1\r\n"
17663 );
17664 assert_eq!(
17665 f.run(&[b"JSON.GET", b"doc"]),
17666 bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
17667 );
17668 assert!(
17672 f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
17673 .starts_with("-this is not the start of a value")
17674 );
17675 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
17676 }
17677
17678 #[test]
17681 fn the_json_commands_that_do_not_answer_text() {
17682 let mut f = Fixture::new();
17683 let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
17684 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
17685
17686 assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
17687 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
17688 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
17689 assert_eq!(
17690 f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
17691 format!("*1\r\n{}", bulk("integer"))
17692 );
17693 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
17696 assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
17697
17698 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
17701 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
17702 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
17705 assert_eq!(
17706 f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
17707 "-ERR Path does not exist or not a bool\r\n"
17708 );
17709 assert_eq!(
17710 f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
17711 "-ERR Path does not exist or not a bool\r\n"
17712 );
17713 assert_eq!(
17714 f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
17715 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17716 );
17717
17718 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
17721 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
17722 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
17723 assert_eq!(
17724 f.run(&[b"JSON.GET", b"doc"]),
17725 bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
17726 );
17727
17728 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
17731 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
17732 assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
17737 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
17738 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
17739 assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
17740 assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
17741 assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
17742 assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
17743 assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
17744 }
17745
17746 #[test]
17753 fn json_get_lays_out_the_wrapper_it_builds() {
17754 let mut f = Fixture::new();
17755 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
17756
17757 assert_eq!(
17758 f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
17759 bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
17760 );
17761 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 let fmt: &[&[u8]] = &[b"INDENT", b" ", b"NEWLINE", b"\n", b"SPACE", b" "];
17767 let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
17768 one.extend_from_slice(fmt);
17769 one.push(b"$.b");
17770 assert_eq!(
17771 f.run(&one),
17772 bulk("[\n [\n 1,\n {\n \"c\": 2\n }\n ]\n]")
17773 );
17774 let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
17775 two.extend_from_slice(fmt);
17776 two.push(b"$.a");
17777 two.push(b"$.nope");
17778 assert_eq!(
17779 f.run(&two),
17780 bulk("{\n \"$.a\": [\n 1\n ],\n \"$.nope\": []\n}")
17781 );
17782 let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
17785 root.push(b".a");
17786 assert_eq!(f.run(&root), bulk("1"));
17787 }
17788
17789 #[test]
17792 fn json_mget_answers_once_per_key_whatever_is_under_them() {
17793 let mut f = Fixture::new();
17794 f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
17795 f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
17796 f.run(&[b"SET", b"plain", b"x"]);
17797 assert_eq!(
17798 f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
17799 format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
17800 );
17801 assert_eq!(
17804 f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
17805 format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
17806 );
17807 assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
17810 }
17811
17812 #[test]
17820 fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
17821 let mut f = Fixture::new();
17822 let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
17823 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
17824
17825 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
17826 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
17827 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
17828 assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
17829 assert_eq!(
17830 f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
17831 format!("*2\r\n{}{}", bulk("x"), bulk("y"))
17832 );
17833 assert_eq!(
17836 f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
17837 "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
17838 );
17839
17840 assert_eq!(
17843 f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
17844 "-ERR Path does not exist\r\n"
17845 );
17846 assert_eq!(
17847 f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
17848 "-ERR Path does not exist\r\n"
17849 );
17850 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
17851 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
17854 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
17857
17858 assert_eq!(
17862 f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
17863 "-ERR Path does not exist or not an array\r\n"
17864 );
17865 assert_eq!(
17866 f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
17867 "-ERR Path does not exist or not an object\r\n"
17868 );
17869 assert_eq!(
17870 f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
17871 "-WRONGTYPE wrong type of path value - expected object\r\n"
17872 );
17873 assert_eq!(
17874 f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
17875 "-WRONGTYPE wrong type of path value - expected string\r\n"
17876 );
17877
17878 assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
17881 assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
17882 assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
17883 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
17884 assert_eq!(
17885 f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
17886 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17887 );
17888 assert_eq!(
17890 f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
17891 "-ERR Path does not exist or not an object\r\n"
17892 );
17893 }
17894
17895 #[test]
17901 fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
17902 let mut f = Fixture::new();
17903 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
17904
17905 assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
17906 assert_eq!(
17907 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
17908 "*1\r\n:6\r\n"
17909 );
17910 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
17911
17912 assert_eq!(
17915 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
17916 ":7\r\n"
17917 );
17918 assert_eq!(
17919 f.run(&[b"JSON.GET", b"doc", b".a"]),
17920 bulk("[1,2,3,4,5,0,6]")
17921 );
17922 assert_eq!(
17923 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
17924 ":8\r\n"
17925 );
17926 assert_eq!(
17928 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
17929 "-ERR index out of bounds\r\n"
17930 );
17931 assert_eq!(
17932 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
17933 "-ERR index out of bounds\r\n"
17934 );
17935
17936 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
17939 assert_eq!(
17940 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
17941 ":3\r\n"
17942 );
17943 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
17944 assert_eq!(
17945 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
17946 ":2\r\n"
17947 );
17948 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
17949 assert_eq!(
17950 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
17951 ":0\r\n"
17952 );
17953 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
17954
17955 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
17958 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
17959 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
17960 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
17961 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
17962
17963 for call in [
17966 &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
17967 &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
17968 &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
17969 &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
17970 ] {
17971 for path in [&b".n"[..], &b".nope"[..]] {
17972 let args: Vec<&[u8]> = call
17973 .iter()
17974 .map(|a| if *a == b"PATH" { path } else { *a })
17975 .collect();
17976 assert_eq!(
17977 f.run(&args),
17978 "-ERR Path does not exist or not an array\r\n",
17979 "{} {}",
17980 String::from_utf8_lossy(call[0]),
17981 String::from_utf8_lossy(path)
17982 );
17983 }
17984 }
17985
17986 assert_eq!(
17989 f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
17990 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17991 );
17992 assert_eq!(
17993 f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
17994 "-ERR could not perform this operation on a key that doesn't exist\r\n"
17995 );
17996
17997 assert!(
18002 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
18003 .starts_with("-this is not the start of a value")
18004 );
18005 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
18006 }
18007
18008 #[test]
18016 fn json_arrinsert_is_all_or_nothing_across_the_matches() {
18017 let mut f = Fixture::new();
18018 let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
18019 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18020 assert_eq!(
18021 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
18022 "-ERR index out of bounds\r\n"
18023 );
18024 assert_eq!(
18025 f.run(&[b"JSON.GET", b"doc"]),
18026 bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
18027 );
18028 assert_eq!(
18030 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
18031 "*3\r\n:4\r\n:3\r\n:2\r\n"
18032 );
18033 assert_eq!(
18034 f.run(&[b"JSON.GET", b"doc"]),
18035 bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
18036 );
18037 }
18038
18039 #[test]
18047 fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
18048 let mut f = Fixture::new();
18049 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
18050
18051 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
18052 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
18053 assert_eq!(
18054 f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
18055 "*1\r\n:1\r\n"
18056 );
18057
18058 assert_eq!(
18061 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
18062 ":3\r\n"
18063 );
18064 assert_eq!(
18067 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
18068 ":-1\r\n"
18069 );
18070
18071 for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
18074 assert_eq!(
18075 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
18076 ":3\r\n",
18077 "{}",
18078 String::from_utf8_lossy(start)
18079 );
18080 }
18081 assert_eq!(
18082 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
18083 ":0\r\n"
18084 );
18085 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
18088 assert_eq!(
18089 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
18090 ":-1\r\n"
18091 );
18092
18093 f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
18097 assert_eq!(
18098 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
18099 ":0\r\n"
18100 );
18101 assert_eq!(
18102 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
18103 ":1\r\n"
18104 );
18105 assert_eq!(
18106 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
18107 ":-1\r\n"
18108 );
18109
18110 assert_eq!(
18114 f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
18115 "-ERR Path does not exist\r\n"
18116 );
18117 assert_eq!(
18118 f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
18119 "-WRONGTYPE wrong type of path value - expected array\r\n"
18120 );
18121 assert_eq!(
18122 f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
18123 "-ERR Path does not exist\r\n"
18124 );
18125 assert_eq!(
18126 f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
18127 "-ERR Path does not exist\r\n"
18128 );
18129 }
18130
18131 #[test]
18134 fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
18135 let mut f = Fixture::new();
18136 let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
18137 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18138
18139 assert_eq!(
18142 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
18143 bulk("9").as_str()
18144 );
18145 assert_eq!(
18147 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
18148 bulk("[11]").as_str()
18149 );
18150 assert_eq!(
18153 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
18154 bulk("13.0").as_str()
18155 );
18156 assert_eq!(
18157 f.run(&[b"JSON.TYPE", b"doc", b".i"]),
18158 bulk("number").as_str()
18159 );
18160 assert_eq!(
18161 f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
18162 bulk("3.0").as_str()
18163 );
18164 assert_eq!(
18165 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
18166 bulk("-8").as_str()
18167 );
18168 f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
18171 assert_eq!(
18172 f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
18173 bulk("1.224744871391589").as_str()
18174 );
18175 assert_eq!(
18176 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
18177 "-ERR result is not a number\r\n"
18178 );
18179 f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
18183 assert_eq!(
18184 f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
18185 "-ERR numeric overflow\r\n"
18186 );
18187 f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
18188 assert_eq!(
18189 f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
18190 "-ERR numeric overflow\r\n"
18191 );
18192 f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
18194 assert_eq!(
18195 f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
18196 "-ERR result is not a number\r\n"
18197 );
18198
18199 assert_eq!(
18203 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
18204 bulk("[null]").as_str()
18205 );
18206 assert_eq!(
18207 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
18208 bulk("[]").as_str()
18209 );
18210 assert_eq!(
18211 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
18212 "-ERR Path does not exist or does not contains a number\r\n"
18213 );
18214 assert_eq!(
18215 f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
18216 "-ERR Path does not exist or does not contains a number\r\n"
18217 );
18218 assert_eq!(
18221 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
18222 "-bad input number\r\n"
18223 );
18224 assert_eq!(
18225 f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
18226 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18227 );
18228 assert_eq!(
18229 f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
18230 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18231 );
18232 }
18233
18234 #[test]
18237 fn json_strappend_reads_its_shape_off_the_argument_count() {
18238 let mut f = Fixture::new();
18239 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
18240
18241 assert_eq!(
18242 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
18243 ":3\r\n"
18244 );
18245 assert_eq!(
18246 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
18247 "*1\r\n:4\r\n"
18248 );
18249 assert_eq!(
18252 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
18253 ":6\r\n"
18254 );
18255 f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
18258 assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
18259 assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
18260
18261 assert_eq!(
18265 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
18266 "-WRONGTYPE wrong type of path value - expected string\r\n"
18267 );
18268 assert_eq!(
18269 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
18270 "*1\r\n$-1\r\n"
18271 );
18272 assert_eq!(
18273 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
18274 "-ERR Path does not exist or not a string\r\n"
18275 );
18276 assert_eq!(
18277 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
18278 "*0\r\n"
18279 );
18280 assert_eq!(
18281 f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
18282 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18283 );
18284 }
18285
18286 #[test]
18289 fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
18290 let mut f = Fixture::new();
18291 let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
18294
18295 f.run(&[b"JSON.SET", b"doc", b"$", three]);
18296 assert_eq!(
18297 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
18298 ":4\r\n"
18299 );
18300 f.run(&[b"JSON.SET", b"doc", b"$", three]);
18301 assert_eq!(
18302 f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
18303 ":2\r\n"
18304 );
18305 f.run(&[b"JSON.SET", b"doc", b"$", three]);
18306 assert_eq!(
18307 f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
18308 ":1\r\n"
18309 );
18310 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
18311 assert_eq!(
18312 f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
18313 bulk("1").as_str()
18314 );
18315 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
18316 assert_eq!(
18317 f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
18318 bulk("13").as_str()
18319 );
18320 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
18321 assert_eq!(
18322 f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
18323 ":4\r\n"
18324 );
18325 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
18326 assert_eq!(
18327 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
18328 bulk("false").as_str()
18329 );
18330 assert_eq!(
18333 f.run(&[b"JSON.GET", b"doc", b".a"]),
18334 bulk("[false,true,false]").as_str()
18335 );
18336
18337 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
18340 assert_eq!(
18341 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
18342 ":3\r\n"
18343 );
18344 assert_eq!(
18345 f.run(&[b"JSON.GET", b"doc", b".a"]),
18346 bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
18347 );
18348 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
18351 assert_eq!(
18352 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
18353 "-ERR Path does not exist or not an array\r\n"
18354 );
18355 assert_eq!(
18356 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
18357 "-ERR Path does not exist or not a bool\r\n"
18358 );
18359 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
18363 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
18364 assert_eq!(
18365 f.run(&[b"JSON.GET", b"doc", b".a"]),
18366 bulk("[[],[2]]").as_str()
18367 );
18368 }
18369
18370 #[test]
18373 fn a_write_reaches_a_match_that_sits_inside_another_match() {
18374 let mut f = Fixture::new();
18375 let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
18376
18377 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
18378 assert_eq!(
18379 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
18380 "*3\r\n:3\r\n:2\r\n:3\r\n"
18381 );
18382 assert_eq!(
18383 f.run(&[b"JSON.GET", b"doc", b"$"]),
18384 bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
18385 );
18386
18387 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
18390 assert_eq!(
18391 f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
18392 "*3\r\n:1\r\n:1\r\n:1\r\n"
18393 );
18394 assert_eq!(
18395 f.run(&[b"JSON.GET", b"doc", b"$"]),
18396 bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
18397 );
18398
18399 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
18402 assert_eq!(
18403 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
18404 bulk("[null,8,8]").as_str()
18405 );
18406 }
18407
18408 #[test]
18411 fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
18412 let mut f = Fixture::new();
18413 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
18414
18415 assert_eq!(
18418 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
18419 bulk("[null]").as_str()
18420 );
18421 assert_eq!(
18422 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
18423 bulk("[null]").as_str()
18424 );
18425 assert_eq!(
18426 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
18427 bulk("[]").as_str()
18428 );
18429 assert_eq!(
18430 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
18431 "-ERR Path does not exist or does not contains a number\r\n"
18432 );
18433 assert_eq!(
18435 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
18436 "-bad input number\r\n"
18437 );
18438
18439 assert_eq!(
18441 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
18442 "*1\r\n$-1\r\n"
18443 );
18444 assert_eq!(
18445 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
18446 "-ERR Path does not exist or not a string\r\n"
18447 );
18448 assert_eq!(
18449 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
18450 "-WRONGTYPE wrong type of path value - expected string\r\n"
18451 );
18452
18453 assert_eq!(
18455 f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
18456 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18457 );
18458 assert_eq!(
18459 f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
18460 "-ERR could not perform this operation on a key that doesn't exist\r\n"
18461 );
18462 }
18463
18464 #[test]
18467 fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
18468 let mut f = Fixture::new();
18469
18470 assert_eq!(
18474 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
18475 "+OK\r\n"
18476 );
18477 assert_eq!(
18478 f.run(&[b"JSON.GET", b"doc", b"$"]),
18479 bulk(r#"[{"x":null,"y":1}]"#).as_str()
18480 );
18481
18482 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
18485 assert_eq!(
18486 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
18487 "+OK\r\n"
18488 );
18489 assert_eq!(
18490 f.run(&[b"JSON.GET", b"doc", b"$"]),
18491 bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
18492 );
18493
18494 assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
18496 assert_eq!(
18497 f.run(&[b"JSON.GET", b"doc", b"$"]),
18498 bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
18499 );
18500
18501 assert_eq!(
18505 f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
18506 "+OK\r\n"
18507 );
18508 assert_eq!(
18509 f.run(&[b"JSON.GET", b"doc", b"$"]),
18510 bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
18511 );
18512
18513 assert_eq!(
18517 f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
18518 "+OK\r\n"
18519 );
18520 assert_eq!(
18521 f.run(&[b"JSON.GET", b"doc", b"$.new"]),
18522 bulk(r#"[{"z":null}]"#).as_str()
18523 );
18524 assert_eq!(
18525 f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
18526 "$-1\r\n"
18527 );
18528 assert_eq!(
18529 f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
18530 "-Err wrong static path\r\n"
18531 );
18532
18533 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
18535 assert_eq!(
18536 f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
18537 "+OK\r\n"
18538 );
18539 assert_eq!(
18540 f.run(&[b"JSON.GET", b"doc", b"$"]),
18541 bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
18542 );
18543
18544 assert_eq!(
18546 f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
18547 "-ERR syntax error\r\n"
18548 );
18549 assert_eq!(
18550 f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
18551 "-ERR new objects must be created at the root\r\n"
18552 );
18553 f.run(&[b"SET", b"str", b"x"]);
18554 assert_eq!(
18555 f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
18556 "-Existing key has wrong Redis type\r\n"
18557 );
18558 }
18559
18560 #[test]
18563 fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
18564 let mut f = Fixture::new();
18565 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
18566 assert_eq!(
18567 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
18568 "+OK\r\n"
18569 );
18570 assert_eq!(
18574 f.run(&[b"JSON.GET", b"doc", b"$"]),
18575 bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
18576 );
18577
18578 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
18581 assert_eq!(
18582 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
18583 "+OK\r\n"
18584 );
18585 assert_eq!(
18586 f.run(&[b"JSON.GET", b"doc", b"$"]),
18587 bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
18588 );
18589 }
18590
18591 #[test]
18594 fn a_filter_path_reads_and_writes_the_members_it_keeps() {
18595 let mut f = Fixture::new();
18596 let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
18597 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18598
18599 assert_eq!(
18600 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
18601 bulk(r#"["a","c"]"#).as_str()
18602 );
18603 assert_eq!(
18606 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
18607 bulk(r#"["a","c"]"#).as_str()
18608 );
18609 assert_eq!(
18611 f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
18612 bulk(r#""a""#).as_str()
18613 );
18614 assert_eq!(
18615 f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
18616 "*1\r\n$6\r\nobject\r\n"
18617 );
18618
18619 assert_eq!(
18623 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
18624 bulk("[9,10]").as_str()
18625 );
18626 assert_eq!(
18627 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
18628 "+OK\r\n"
18629 );
18630 assert_eq!(
18631 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
18632 "-Err wrong static path\r\n"
18633 );
18634 assert_eq!(
18635 f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
18636 ":2\r\n"
18637 );
18638 assert_eq!(
18639 f.run(&[b"JSON.GET", b"doc", b"$"]),
18640 bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
18641 );
18642
18643 assert!(
18646 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
18647 .starts_with("-ERR")
18648 );
18649 assert!(
18650 f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
18651 .starts_with("-ERR")
18652 );
18653 }
18654
18655 #[test]
18658 fn a_filter_takes_the_membership_operators_and_the_methods_too() {
18659 let mut f = Fixture::new();
18660 let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
18661 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18662
18663 for (path, want) in [
18664 (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
18665 (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
18666 (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
18667 (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
18668 (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
18669 (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
18670 (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
18671 (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
18672 (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
18673 (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
18674 (b"$.box[?(@.n~)].t", "[]"),
18675 (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
18676 (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
18677 (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
18678 (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
18679 ] {
18680 assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
18681 }
18682
18683 assert_eq!(
18686 f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
18687 "+OK\r\n"
18688 );
18689 assert_eq!(
18690 f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
18691 bulk(r#"["b"]"#).as_str()
18692 );
18693 }
18694
18695 #[test]
18698 fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
18699 let mut f = Fixture::new();
18700 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
18701 assert_eq!(
18702 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
18703 "+OK\r\n"
18704 );
18705 assert_eq!(
18706 f.run(&[b"JSON.GET", b"doc", b"$"]),
18707 bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
18708 );
18709 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
18712 assert_eq!(
18713 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
18714 "+OK\r\n"
18715 );
18716 assert_eq!(
18717 f.run(&[b"JSON.GET", b"doc", b"$"]),
18718 bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
18719 );
18720 }
18721
18722 #[test]
18725 fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
18726 let mut f = Fixture::new();
18727 assert_eq!(
18728 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
18729 "+OK\r\n"
18730 );
18731 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
18732 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
18733
18734 assert_eq!(
18736 f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
18737 "+OK\r\n"
18738 );
18739 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
18740
18741 f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
18746 assert_eq!(
18747 f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
18748 "$-1\r\n"
18749 );
18750 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
18751 assert_eq!(
18752 f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
18753 "$-1\r\n"
18754 );
18755 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
18756
18757 f.run(&[b"SET", b"str", b"x"]);
18762 assert_eq!(
18763 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
18764 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
18765 );
18766 assert_eq!(
18767 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
18768 "-Existing key has wrong Redis type\r\n"
18769 );
18770 assert_eq!(
18771 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
18772 "-ERR new objects must be created at the root\r\n"
18773 );
18774 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
18775
18776 assert_eq!(
18781 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
18782 "-Err wrong static path\r\n"
18783 );
18784 assert_eq!(
18785 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
18786 "-ERR array index out of range\r\n"
18787 );
18788 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
18789
18790 f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
18794 assert_eq!(
18795 f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
18796 "+OK\r\n"
18797 );
18798 assert_eq!(
18799 f.run(&[b"JSON.GET", b"c", b"$"]),
18800 bulk(r#"[{"n":3}]"#).as_str()
18801 );
18802
18803 assert_eq!(
18806 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
18807 "-ERR wrong number of arguments for 'json.mset' command\r\n"
18808 );
18809 }
18810
18811 #[test]
18814 fn json_resp_answers_the_document_as_resp_types() {
18815 let mut f = Fixture::new();
18816 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
18817 assert_eq!(
18818 f.run(&[b"JSON.RESP", b"doc"]),
18819 "*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"
18820 );
18821 assert_eq!(
18823 f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
18824 "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
18825 );
18826
18827 f.run(&[
18828 b"JSON.SET",
18829 b"doc",
18830 b"$",
18831 br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
18832 ]);
18833 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
18834 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
18835 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
18838 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
18839 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
18840
18841 assert_eq!(
18844 f.run(&[b"JSON.RESP", b"doc", b".nope"]),
18845 "-ERR Path does not exist\r\n"
18846 );
18847 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
18848 assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
18849 assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
18850 }
18851
18852 #[test]
18856 fn json_debug_answers_a_byte_count_and_its_own_help() {
18857 let mut f = Fixture::new();
18858 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
18859 let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
18860 assert!(one.starts_with(':'), "{one}");
18861 assert_eq!(
18862 f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
18863 format!("*1\r\n{one}")
18864 );
18865 let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
18866 assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
18867
18868 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
18872 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
18873 assert_eq!(
18874 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
18875 "-ERR Path does not exist\r\n"
18876 );
18877 assert_eq!(
18878 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
18879 "*0\r\n"
18880 );
18881
18882 assert_eq!(
18883 f.run(&[b"JSON.DEBUG", b"HELP"]),
18884 "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
18885 $34\r\nHELP - this message\r\n"
18886 );
18887 assert_eq!(
18888 f.run(&[b"JSON.DEBUG", b"NOPE"]),
18889 "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
18890 );
18891 assert_eq!(
18892 f.run(&[b"JSON.DEBUG", b"MEMORY"]),
18893 "-ERR wrong number of arguments for 'json.debug' command\r\n"
18894 );
18895 }
18896
18897 #[test]
18902 fn the_first_vadd_decides_how_wide_the_set_is() {
18903 let mut f = Fixture::new();
18904 assert_eq!(
18905 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
18906 ":1\r\n"
18907 );
18908 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
18909 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
18910 assert_eq!(
18913 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
18914 ":0\r\n"
18915 );
18916 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
18917 assert_eq!(
18920 f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
18921 "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
18922 );
18923 assert_eq!(
18926 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
18927 ":1\r\n"
18928 );
18929 assert_eq!(
18930 f.run(&[b"VEMB", b"v", b"nowhere"]),
18931 "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
18932 );
18933 assert_eq!(
18937 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
18938 "-ERR asked quantization mismatch with existing vector set\r\n"
18939 );
18940 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
18943 assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
18944 assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
18945 }
18946
18947 #[test]
18950 fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
18951 let mut f = Fixture::new();
18952 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
18953 assert_eq!(
18959 f.run(&[b"VEMB", b"v", b"a"]),
18960 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
18961 );
18962 f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
18964 assert_eq!(
18965 f.run(&[b"VEMB", b"n", b"a"]),
18966 "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
18967 );
18968 f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
18971 assert_eq!(
18972 f.run(&[b"VEMB", b"b", b"a"]),
18973 "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
18974 );
18975 assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
18976 assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
18977
18978 let mut f = Fixture::new();
18982 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
18983 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
18984 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
18985 assert_eq!(
18986 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
18987 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
18988 $8\r\nopposite\r\n$1\r\n0\r\n"
18989 );
18990 assert_eq!(
18993 f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
18994 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
18995 );
18996 assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
18999 assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
19000 assert_eq!(
19003 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
19004 "*1\r\n$6\r\nacross\r\n"
19005 );
19006 assert_eq!(
19007 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
19008 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
19009 );
19010 assert_eq!(
19014 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
19015 "*1\r\n$6\r\nacross\r\n"
19016 );
19017
19018 let mut g = Fixture::new();
19021 g.run(&[b"HELLO", b"3"]);
19022 g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19023 assert_eq!(
19024 g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
19025 "%1\r\n$4\r\neast\r\n,1\r\n"
19026 );
19027 }
19028
19029 #[test]
19031 fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
19032 let mut f = Fixture::new();
19033 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19034 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
19035 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
19036 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
19037 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
19040 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
19041 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
19043 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
19044 assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
19047 assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
19048 assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
19049 assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
19052 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
19053 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
19054 assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
19055
19056 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
19058 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
19059 assert_eq!(
19060 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
19061 "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
19062 );
19063 }
19064
19065 #[test]
19068 fn vrem_takes_the_attribute_with_it() {
19069 let mut f = Fixture::new();
19070 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19071 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
19072 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
19073 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
19074 assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
19075 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
19078
19079 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19082 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
19083 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
19084 f.run(&[b"VREM", b"v", b"east"]);
19085 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
19086 assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
19087 }
19088
19089 #[test]
19092 fn vinfo_says_partition_first() {
19093 let mut f = Fixture::new();
19094 f.run(&[
19095 b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
19096 ]);
19097 f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
19098 let info = f.run(&[b"VINFO", b"v"]);
19099 assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
19100 assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
19103 assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
19104 assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
19105 assert!(
19108 info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
19109 "{info}"
19110 );
19111 let mut f = Fixture::new();
19112 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
19113 assert!(
19114 f.run(&[b"VINFO", b"v"])
19115 .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
19116 );
19117 assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
19118 }
19119
19120 fn named() -> Fixture {
19122 let mut f = Fixture::new();
19123 for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
19124 .iter()
19125 .enumerate()
19126 {
19127 let x = (i + 1).to_string();
19128 f.run(&[
19129 b"VADD",
19130 b"r",
19131 b"VALUES",
19132 b"2",
19133 x.as_bytes(),
19134 b"1",
19135 name.as_bytes(),
19136 ]);
19137 }
19138 f
19139 }
19140
19141 #[test]
19144 fn vrange_walks_the_names_and_not_the_vectors() {
19145 let mut f = named();
19146 assert_eq!(
19147 f.run(&[b"VRANGE", b"r", b"-", b"+"]),
19148 "*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"
19149 );
19150 assert_eq!(
19151 f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
19152 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
19153 "the high end is a name and not a prefix, so delta is past it"
19154 );
19155 assert_eq!(
19156 f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
19157 "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
19158 );
19159 assert_eq!(
19160 f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
19161 "*1\r\n$4\r\nbeta\r\n"
19162 );
19163 assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
19164 f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
19167 assert_eq!(
19168 f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
19169 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
19170 );
19171 assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
19172 }
19173
19174 #[test]
19177 fn a_vrange_count_of_zero_asks_for_nothing() {
19178 let mut f = named();
19179 assert_eq!(
19180 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
19181 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
19182 );
19183 assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
19184 assert!(
19185 f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
19186 .starts_with("*5\r\n"),
19187 "a negative count is no limit at all"
19188 );
19189 }
19190
19191 #[test]
19194 fn vrange_says_which_end_it_could_not_read() {
19195 let mut f = named();
19196 assert_eq!(
19197 f.run(&[b"VRANGE", b"r", b"x", b"y"]),
19198 "-ERR invalid start range format\r\n"
19199 );
19200 assert_eq!(
19201 f.run(&[b"VRANGE", b"r", b"+", b"x"]),
19202 "-ERR invalid end range format\r\n",
19203 "the high end is spelled wrong, which is worth saying before the \
19204 low end being on the wrong side"
19205 );
19206 assert_eq!(
19207 f.run(&[b"VRANGE", b"r", b"+", b"-"]),
19208 "-ERR '-' can only be used as first argument, '+' only as second\r\n"
19209 );
19210 assert_eq!(
19213 f.run(&[b"VRANGE", b"r", b"[", b"+"]),
19214 "-ERR invalid start range format\r\n"
19215 );
19216 assert_eq!(
19217 f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
19218 "-ERR invalid COUNT value\r\n"
19219 );
19220 assert_eq!(
19221 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
19222 "-ERR wrong number of arguments for 'VRANGE' command\r\n"
19223 );
19224 f.run(&[b"SET", b"s", b"x"]);
19225 assert!(
19226 f.run(&[b"VRANGE", b"s", b"-", b"+"])
19227 .starts_with("-WRONGTYPE")
19228 );
19229 }
19230
19231 #[test]
19234 fn reduce_is_refused_and_not_ignored() {
19235 let mut f = Fixture::new();
19236 let reduce = f.run(&[
19237 b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
19238 ]);
19239 assert!(
19240 reduce.starts_with("-ERR REDUCE is not supported."),
19241 "{reduce}"
19242 );
19243 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
19244 }
19245
19246 #[test]
19249 fn vsim_filter_reads_the_attributes() {
19250 let mut f = Fixture::new();
19251 for (name, x, y, attr) in [
19252 ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
19253 ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
19254 ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
19255 ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
19256 ] {
19257 f.run(&[
19258 b"VADD",
19259 b"v",
19260 b"VALUES",
19261 b"2",
19262 x.as_bytes(),
19263 y.as_bytes(),
19264 name.as_bytes(),
19265 b"SETATTR",
19266 attr.as_bytes(),
19267 ]);
19268 }
19269 assert_eq!(
19272 f.run(&[
19273 b"VSIM",
19274 b"v",
19275 b"VALUES",
19276 b"2",
19277 b"9",
19278 b"1",
19279 b"COUNT",
19280 b"2",
19281 b"FILTER",
19282 b".lang == \"en\"",
19283 ]),
19284 "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
19285 );
19286 assert_eq!(
19289 f.run(&[
19290 b"VSIM",
19291 b"v",
19292 b"VALUES",
19293 b"2",
19294 b"9",
19295 b"1",
19296 b"FILTER",
19297 b".lang == 'en' and .year > 1980",
19298 ]),
19299 "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
19300 );
19301 assert_eq!(
19303 f.run(&[
19304 b"VSIM",
19305 b"v",
19306 b"VALUES",
19307 b"2",
19308 b"9",
19309 b"1",
19310 b"FILTER",
19311 b".lang in ['fr', 'de']",
19312 ]),
19313 "*1\r\n$1\r\nb\r\n"
19314 );
19315 assert_eq!(
19316 f.run(&[
19317 b"VSIM",
19318 b"v",
19319 b"VALUES",
19320 b"2",
19321 b"9",
19322 b"1",
19323 b"FILTER",
19324 b".rating > 3"
19325 ]),
19326 "*0\r\n"
19327 );
19328 assert_eq!(
19331 f.run(&[
19332 b"VSIM",
19333 b"v",
19334 b"VALUES",
19335 b"2",
19336 b"9",
19337 b"1",
19338 b"TRUTH",
19339 b"FILTER",
19340 b".year < 1980",
19341 ]),
19342 "*1\r\n$1\r\nc\r\n"
19343 );
19344 f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
19347 assert_eq!(
19348 f.run(&[
19349 b"VSIM",
19350 b"v",
19351 b"VALUES",
19352 b"2",
19353 b"9",
19354 b"1",
19355 b"COUNT",
19356 b"1",
19357 b"FILTER",
19358 b".lang == \"en\"",
19359 ]),
19360 "*1\r\n$1\r\nb\r\n"
19361 );
19362 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
19365 assert_eq!(
19366 f.run(&[
19367 b"VSIM",
19368 b"v",
19369 b"VALUES",
19370 b"2",
19371 b"9",
19372 b"1",
19373 b"COUNT",
19374 b"1",
19375 b"FILTER",
19376 b".lang == \"en\"",
19377 ]),
19378 "*1\r\n$1\r\nb\r\n"
19379 );
19380
19381 let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
19384 assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
19385 assert_eq!(
19386 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
19387 "-ERR invalid FILTER expression\r\n"
19388 );
19389 assert_eq!(
19392 f.run(&[
19393 b"VSIM",
19394 b"v",
19395 b"VALUES",
19396 b"2",
19397 b"9",
19398 b"1",
19399 b"COUNT",
19400 b"1",
19401 b"FILTER-EF",
19402 b"500",
19403 b"FILTER",
19404 b".lang == 'en'",
19405 ]),
19406 "*1\r\n$1\r\nb\r\n"
19407 );
19408 assert_eq!(
19409 f.run(&[
19410 b"VSIM",
19411 b"v",
19412 b"VALUES",
19413 b"2",
19414 b"9",
19415 b"1",
19416 b"COUNT",
19417 b"1",
19418 b"FILTER-EF",
19419 b"0"
19420 ]),
19421 "*1\r\n$1\r\nb\r\n"
19422 );
19423 assert_eq!(
19424 f.run(&[
19425 b"VSIM",
19426 b"v",
19427 b"VALUES",
19428 b"2",
19429 b"9",
19430 b"1",
19431 b"FILTER-EF",
19432 b"lots"
19433 ]),
19434 "-ERR EF must be a positive integer\r\n"
19435 );
19436 }
19437
19438 #[test]
19441 fn the_keyspace_sees_a_vector_set_key_like_any_other() {
19442 let mut f = Fixture::new();
19443 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19444 assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
19445 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
19446 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
19447 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
19448 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
19449 assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
19450 assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
19451 assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
19452 assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
19453 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
19454
19455 f.run(&[b"SET", b"s", b"1"]);
19457 assert_eq!(
19458 f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
19459 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19460 );
19461 assert_eq!(
19462 f.run(&[b"VCARD", b"s"]),
19463 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19464 );
19465 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19466 assert_eq!(
19467 f.run(&[b"GET", b"v"]),
19468 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19469 );
19470 f.run(&[b"G.NADD", b"social", b"ada"]);
19474 assert_eq!(
19475 f.run(&[b"VCARD", b"social"]),
19476 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19477 );
19478 assert_eq!(
19479 f.run(&[b"G.NGET", b"v", b"ada"]),
19480 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19481 );
19482 }
19483
19484 #[test]
19487 fn vrandmember_has_the_two_shapes_srandmember_has() {
19488 let mut f = Fixture::new();
19489 for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
19490 let x = (i + 1).to_string();
19491 f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
19492 }
19493 let one = f.run(&[b"VRANDMEMBER", b"v"]);
19495 assert!(one.starts_with("$1\r\n"), "{one}");
19496 let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
19498 assert!(all.starts_with("*3\r\n"), "{all}");
19499 for name in ["a", "b", "c"] {
19500 assert!(all.contains(name), "{all} is missing {name}");
19501 }
19502 all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
19503 assert!(all.starts_with("*2\r\n"), "{all}");
19504 let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
19506 assert!(many.starts_with("*5\r\n"), "{many}");
19507 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
19509 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
19510 }
19511
19512 #[test]
19515 fn vlinks_reports_one_layer_of_partition_neighbours() {
19516 let mut f = Fixture::new();
19517 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19518 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
19519 assert_eq!(
19522 f.run(&[b"VLINKS", b"v", b"east"]),
19523 "*1\r\n*1\r\n$5\r\nnorth\r\n"
19524 );
19525 assert_eq!(
19526 f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
19527 "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
19528 );
19529 assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
19530 assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
19531 }
19532
19533 #[test]
19536 fn fp32_and_values_are_the_same_vector() {
19537 let mut f = Fixture::new();
19538 let mut blob = Vec::new();
19539 for x in [3.0f32, 4.0] {
19540 blob.extend_from_slice(&x.to_le_bytes());
19541 }
19542 assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
19543 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
19544 assert_eq!(
19545 f.run(&[b"VEMB", b"v", b"a"]),
19546 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
19547 );
19548 assert_eq!(
19554 f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
19555 "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
19556 );
19557 assert_eq!(
19559 f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
19560 "-ERR invalid vector specification\r\n"
19561 );
19562 assert_eq!(
19564 f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
19565 "-ERR syntax error\r\n"
19566 );
19567 assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
19568 }
19569
19570 #[test]
19575 fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
19576 let mut f = Fixture::new();
19577 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
19578 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
19579 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
19580 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
19581 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
19582 assert_eq!(
19585 f.run(&[b"BF.INFO", b"b"]),
19586 "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
19587 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
19588 +Expansion rate\r\n:2\r\n"
19589 );
19590 assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
19591 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
19592 assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
19595 assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
19596 }
19597
19598 #[test]
19604 fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
19605 let mut f = Fixture::new();
19606 f.run(&[b"SET", b"s", b"text"]);
19607 assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
19608 assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
19609 for cmd in [
19610 vec![&b"BF.ADD"[..], b"s", b"x"],
19611 vec![&b"BF.MADD"[..], b"s", b"x"],
19612 vec![&b"BF.CARD"[..], b"s"],
19613 vec![&b"BF.INFO"[..], b"s"],
19614 vec![&b"BF.DEBUG"[..], b"s"],
19615 vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
19616 ] {
19617 let name = String::from_utf8_lossy(cmd[0]).into_owned();
19618 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
19619 }
19620 assert_eq!(
19623 f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
19624 "-ERR bad error rate\r\n"
19625 );
19626 assert!(
19627 f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
19628 .starts_with("-WRONGTYPE")
19629 );
19630 }
19631
19632 #[test]
19635 fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
19636 let mut f = Fixture::new();
19637 assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
19638 for i in 0..10u32 {
19639 assert_eq!(
19640 f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
19641 ":1\r\n"
19642 );
19643 }
19644 assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
19645 assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
19646 assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
19647 assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
19650 assert_eq!(
19651 f.run(&[b"BF.DEBUG", b"g"]),
19652 "*3\r\n$7\r\nsize:11\r\n\
19653 $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
19654 $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
19655 );
19656
19657 assert_eq!(
19659 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
19660 "+OK\r\n"
19661 );
19662 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
19663 assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
19664 assert_eq!(
19665 f.run(&[b"BF.ADD", b"n", b"c"]),
19666 "-ERR non scaling filter is full\r\n"
19667 );
19668 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
19671 assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
19674 f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
19675 assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
19676 assert_eq!(
19679 f.run(&[
19680 b"BF.RESERVE",
19681 b"q",
19682 b"0.01",
19683 b"2",
19684 b"NONSCALING",
19685 b"EXPANSION",
19686 b"2"
19687 ]),
19688 "-Nonscaling filters cannot expand\r\n"
19689 );
19690 }
19691
19692 #[test]
19695 fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
19696 let mut f = Fixture::new();
19697 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
19698 assert_eq!(
19699 f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
19700 "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
19701 );
19702 assert_eq!(
19703 f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
19704 "*2\r\n:1\r\n:0\r\n"
19705 );
19706 }
19707
19708 #[test]
19711 fn insert_is_a_reserve_and_a_madd_with_different_errors() {
19712 let mut f = Fixture::new();
19713 assert_eq!(
19714 f.run(&[
19715 b"BF.INSERT",
19716 b"i",
19717 b"CAPACITY",
19718 b"50",
19719 b"ERROR",
19720 b"0.001",
19721 b"ITEMS",
19722 b"a",
19723 b"b"
19724 ]),
19725 "*2\r\n:1\r\n:1\r\n"
19726 );
19727 assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
19728 assert_eq!(
19730 f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
19731 "-ERR not found\r\n"
19732 );
19733 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
19734 assert_eq!(
19737 f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
19738 "-Bad capacity\r\n"
19739 );
19740 assert_eq!(
19741 f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
19742 "-Bad error rate\r\n"
19743 );
19744 assert_eq!(
19745 f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
19746 "-Bad expansion\r\n"
19747 );
19748 assert_eq!(
19753 f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
19754 "*1\r\n:1\r\n"
19755 );
19756 assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
19757 assert_eq!(
19761 f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
19762 "*1\r\n:1\r\n"
19763 );
19764 assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
19765 assert_eq!(
19766 f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
19767 "*1\r\n:1\r\n"
19768 );
19769 assert_eq!(
19770 f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
19771 "-ERR not found\r\n"
19772 );
19773 assert_eq!(
19775 f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
19776 "-Unknown argument received\r\n"
19777 );
19778 assert_eq!(
19780 f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
19781 "*1\r\n:1\r\n"
19782 );
19783 assert!(
19785 f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
19786 .contains("wrong number of arguments")
19787 );
19788 }
19789
19790 #[test]
19793 fn a_dump_replays_into_a_filter_that_answers_the_same() {
19794 let mut f = Fixture::new();
19795 f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
19796 for i in 0..25u32 {
19797 f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
19798 }
19799 assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
19800
19801 let mut iter = b"0".to_vec();
19804 let mut chunks = 0;
19805 loop {
19806 let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
19807 let text = String::from_utf8_lossy(&raw).into_owned();
19808 let next = text
19809 .split("\r\n")
19810 .nth(1)
19811 .and_then(|n| n.strip_prefix(':'))
19812 .expect("a two element reply of an iterator and a chunk")
19813 .to_owned();
19814 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
19815 let data = &body[body
19816 .windows(2)
19817 .position(|w| w == b"\r\n")
19818 .expect("a length line")
19819 + 2..body.len() - 2];
19820 if next == "0" {
19821 assert!(data.is_empty(), "the last chunk is empty");
19822 break;
19823 }
19824 let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
19825 assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
19826 iter = next.into_bytes();
19827 chunks += 1;
19828 }
19829 assert_eq!(chunks, 3, "a header and one chunk per link");
19830
19831 assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
19832 assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
19833 for i in 0..25u32 {
19834 assert_eq!(
19835 f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
19836 ":1\r\n"
19837 );
19838 }
19839
19840 assert_eq!(
19843 f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
19844 "-ERR received bad data\r\n"
19845 );
19846 assert_eq!(
19847 f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
19848 "-ERR received bad data\r\n"
19849 );
19850 assert_eq!(
19852 f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
19853 "-ERR invalid offset - no link found\r\n"
19854 );
19855 assert_eq!(
19856 f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
19857 "-ERR Second argument must be numeric\r\n"
19858 );
19859 assert_eq!(
19862 f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
19863 "-Second argument must be numeric\r\n"
19864 );
19865 }
19866
19867 #[test]
19870 fn reserve_reads_its_numbers_the_way_string2ll_does() {
19871 let mut f = Fixture::new();
19872 for (args, want) in [
19873 (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
19874 (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
19875 (
19876 vec![&b"0"[..], b"10"],
19877 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19878 ),
19879 (
19880 vec![&b"1"[..], b"10"],
19881 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19882 ),
19883 (
19884 vec![&b"inf"[..], b"10"],
19885 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19886 ),
19887 (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
19888 (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
19889 (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
19890 (
19891 vec![&b"0.01"[..], b"0"],
19892 "-ERR capacity must be in the range [1, 1073741824]\r\n",
19893 ),
19894 (
19895 vec![&b"0.01"[..], b"1073741825"],
19896 "-ERR capacity must be in the range [1, 1073741824]\r\n",
19897 ),
19898 ] {
19899 let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
19900 cmd.extend(args.iter().copied());
19901 assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
19902 }
19903 assert_eq!(
19904 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
19905 "-ERR no expansion\r\n"
19906 );
19907 assert_eq!(
19908 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
19909 "-ERR bad expansion\r\n"
19910 );
19911 assert_eq!(
19912 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
19913 "-ERR expansion must be in the range [0, 32768]\r\n"
19914 );
19915 assert_eq!(
19917 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
19918 "+OK\r\n"
19919 );
19920 assert_eq!(
19921 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
19922 "-ERR item exists\r\n"
19923 );
19924 assert_eq!(
19925 f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
19926 "-Invalid information value\r\n"
19927 );
19928 assert!(
19929 f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
19930 .contains("wrong number of arguments")
19931 );
19932 }
19933
19934 #[test]
19936 fn the_bloom_family_answers_in_resp3_spelling_too() {
19937 let mut f = Fixture::new();
19938 f.out.set_proto(Proto::Resp3);
19939 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
19940 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
19941 assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
19942 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
19943 assert_eq!(
19944 f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
19945 "*2\r\n#t\r\n#f\r\n"
19946 );
19947 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
19949 assert_eq!(
19950 f.run(&[b"BF.INFO", b"b"]),
19951 "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
19952 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
19953 +Expansion rate\r\n:2\r\n"
19954 );
19955 assert_eq!(
19958 f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
19959 "%1\r\n+Capacity\r\n:100\r\n"
19960 );
19961 }
19962
19963 fn cf_header(
19972 items: u64,
19973 buckets: u64,
19974 deletes: u64,
19975 filters: u64,
19976 geometry: [u16; 3],
19977 ) -> Vec<u8> {
19978 let mut out = Vec::with_capacity(38);
19979 for n in [items, buckets, deletes, filters] {
19980 out.extend_from_slice(&n.to_le_bytes());
19981 }
19982 for n in geometry {
19983 out.extend_from_slice(&n.to_le_bytes());
19984 }
19985 out
19986 }
19987
19988 #[test]
19992 fn cf_add_makes_the_filter_and_counts_the_copies() {
19993 let mut f = Fixture::new();
19994 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
19995 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
19996 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
19997 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
20000 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
20001 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
20002 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
20003 assert_eq!(
20004 f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
20005 "*2\r\n:1\r\n:0\r\n"
20006 );
20007 assert_eq!(
20010 f.run(&[b"CF.INFO", b"d"]),
20011 "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
20012 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
20013 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
20014 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
20015 );
20016 assert_eq!(
20017 f.run(&[b"CF.DEBUG", b"d"]),
20018 "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
20019 max_iterations:20 expansion:1\r\n"
20020 );
20021 assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
20022 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
20023
20024 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
20026 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
20027 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
20028 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
20029 assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
20030
20031 assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
20034 assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
20035 assert_eq!(
20036 f.run(&[b"CF.COMPACT", b"gone"]),
20037 "-Cuckoo filter was not found\r\n"
20038 );
20039 assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
20040 assert!(
20044 f.run(&[b"CF.COMPACT", b"a", b"b"])
20045 .contains("wrong number of arguments")
20046 );
20047 }
20048
20049 #[test]
20052 fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
20053 let mut f = Fixture::new();
20054 f.run(&[b"SET", b"s", b"text"]);
20055 assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
20056 assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
20057 assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
20058 assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
20062 assert_eq!(
20063 f.run(&[b"CF.COMPACT", b"s"]),
20064 "-Cuckoo filter was not found\r\n"
20065 );
20066 for cmd in [
20067 vec![&b"CF.ADD"[..], b"s", b"x"],
20068 vec![&b"CF.ADDNX"[..], b"s", b"x"],
20069 vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
20070 vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
20071 vec![&b"CF.INFO"[..], b"s"],
20072 vec![&b"CF.DEBUG"[..], b"s"],
20073 vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
20074 vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
20075 vec![&b"CF.RESERVE"[..], b"s", b"64"],
20076 ] {
20077 let name = String::from_utf8_lossy(cmd[0]).into_owned();
20078 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
20079 }
20080 }
20081
20082 #[test]
20085 fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
20086 let mut f = Fixture::new();
20087 assert_eq!(
20088 f.run(&[
20089 b"CF.RESERVE",
20090 b"r",
20091 b"64",
20092 b"BUCKETSIZE",
20093 b"1",
20094 b"MAXITERATIONS",
20095 b"7",
20096 b"EXPANSION",
20097 b"4"
20098 ]),
20099 "+OK\r\n"
20100 );
20101 assert_eq!(
20102 f.run(&[b"CF.DEBUG", b"r"]),
20103 "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
20104 max_iterations:7 expansion:4\r\n"
20105 );
20106 assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
20107
20108 assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
20109 assert_eq!(
20110 f.run(&[b"CF.RESERVE", b"q", b"1"]),
20111 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
20112 );
20113 assert_eq!(
20116 f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
20117 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
20118 );
20119 assert_eq!(
20120 f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
20121 "+OK\r\n"
20122 );
20123
20124 assert_eq!(
20129 f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
20130 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
20131 );
20132 assert_eq!(
20133 f.run(&[
20134 b"CF.RESERVE",
20135 b"q2",
20136 b"64",
20137 b"EXPANSION",
20138 b"xx",
20139 b"BUCKETSIZE",
20140 b"0"
20141 ]),
20142 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
20143 );
20144 assert_eq!(
20145 f.run(&[
20146 b"CF.RESERVE",
20147 b"q2",
20148 b"64",
20149 b"MAXITERATIONS",
20150 b"0",
20151 b"BUCKETSIZE",
20152 b"0"
20153 ]),
20154 "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
20155 );
20156 assert_eq!(
20160 f.run(&[
20161 b"CF.RESERVE",
20162 b"q3",
20163 b"64",
20164 b"BUCKETSIZE",
20165 b"1",
20166 b"BUCKETSIZE",
20167 b"0"
20168 ]),
20169 "+OK\r\n"
20170 );
20171 assert_eq!(
20174 f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
20175 "+OK\r\n"
20176 );
20177 assert_eq!(
20178 f.run(&[b"CF.DEBUG", b"q4"]),
20179 "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
20180 max_iterations:20 expansion:1\r\n"
20181 );
20182 assert!(
20185 f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
20186 .contains("wrong number of arguments")
20187 );
20188 }
20189
20190 #[test]
20193 fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
20194 let mut f = Fixture::new();
20195 assert_eq!(
20196 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
20197 "*2\r\n:1\r\n:1\r\n"
20198 );
20199 assert_eq!(
20200 f.run(&[b"CF.DEBUG", b"i"]),
20201 "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
20202 max_iterations:20 expansion:1\r\n"
20203 );
20204 assert_eq!(
20207 f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
20208 "*2\r\n:0\r\n:1\r\n"
20209 );
20210 assert_eq!(
20211 f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
20212 "-ERR not found\r\n"
20213 );
20214 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20215
20216 assert_eq!(
20217 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
20218 "-Bad capacity\r\n"
20219 );
20220 assert_eq!(
20223 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
20224 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
20225 );
20226 assert_eq!(
20230 f.run(&[
20231 b"CF.INSERT",
20232 b"i",
20233 b"CAPACITY",
20234 b"8",
20235 b"CAPACITY",
20236 b"2",
20237 b"ITEMS",
20238 b"a"
20239 ]),
20240 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
20241 );
20242 assert_eq!(
20246 f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
20247 "*1\r\n:1\r\n"
20248 );
20249 assert_eq!(
20250 f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
20251 "*1\r\n:1\r\n"
20252 );
20253 assert_eq!(
20254 f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
20255 "-Unknown argument received\r\n"
20256 );
20257 assert_eq!(
20259 f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
20260 "*1\r\n:1\r\n"
20261 );
20262 assert!(
20264 f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
20265 .contains("wrong number of arguments")
20266 );
20267 assert!(
20268 f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
20269 .contains("wrong number of arguments")
20270 );
20271 }
20272
20273 #[test]
20276 fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
20277 let mut f = Fixture::new();
20278 f.run(&[
20279 b"CF.RESERVE",
20280 b"s",
20281 b"4",
20282 b"BUCKETSIZE",
20283 b"1",
20284 b"EXPANSION",
20285 b"0",
20286 ]);
20287 for i in 0..4u32 {
20288 assert_eq!(
20289 f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
20290 ":1\r\n"
20291 );
20292 }
20293 assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
20294 assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
20295 assert_eq!(
20298 f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
20299 "*2\r\n:-1\r\n:-1\r\n"
20300 );
20301 assert_eq!(
20302 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
20303 "*2\r\n:0\r\n:-1\r\n"
20304 );
20305
20306 let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
20312 assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
20313 assert_eq!(
20314 f.run(&[b"CF.ADD", b"g", b"q"]),
20315 "-Maximum expansions reached\r\n"
20316 );
20317 assert_eq!(
20318 f.run(&[b"CF.INFO", b"g"]),
20319 "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
20320 +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
20321 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
20322 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
20323 );
20324 }
20325
20326 #[test]
20330 fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
20331 let mut f = Fixture::new();
20332 f.run(&[
20333 b"CF.RESERVE",
20334 b"src",
20335 b"8",
20336 b"BUCKETSIZE",
20337 b"2",
20338 b"EXPANSION",
20339 b"2",
20340 ]);
20341 for i in 0..40u32 {
20342 f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
20343 }
20344 let mut pos = b"0".to_vec();
20348 let mut chunks = 0;
20349 loop {
20350 let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
20351 let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
20352 let next = head
20353 .split("\r\n")
20354 .nth(1)
20355 .and_then(|n| n.strip_prefix(':'))
20356 .expect("a two element reply of a position and a chunk")
20357 .to_owned();
20358 if next == "0" {
20359 assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
20360 break;
20361 }
20362 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
20363 let at = body
20364 .windows(2)
20365 .position(|w| w == b"\r\n")
20366 .expect("a length line")
20367 + 2;
20368 let data = &body[at..body.len() - 2];
20369 assert_eq!(
20370 f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
20371 "+OK\r\n",
20372 "loading chunk {chunks}"
20373 );
20374 pos = next.into_bytes();
20375 chunks += 1;
20376 }
20377 assert!(chunks >= 2, "a header and at least one chunk");
20378
20379 assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
20380 assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
20381 for i in 0..40u32 {
20382 assert_eq!(
20383 f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
20384 ":1\r\n"
20385 );
20386 }
20387
20388 f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
20391 assert_eq!(
20392 f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
20393 "*2\r\n:0\r\n$-1\r\n"
20394 );
20395
20396 assert_eq!(
20400 f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
20401 "-Invalid position\r\n"
20402 );
20403 assert_eq!(
20404 f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
20405 "-Invalid position\r\n"
20406 );
20407 assert_eq!(
20408 f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
20409 "-Invalid position\r\n"
20410 );
20411 assert_eq!(
20412 f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
20413 "-Couldn't load chunk!\r\n"
20414 );
20415 let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
20417 assert_eq!(
20418 f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
20419 "-ERR item exists\r\n"
20420 );
20421 assert_eq!(
20425 f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
20426 "-Invalid header\r\n"
20427 );
20428 for (why, bad) in [
20429 ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
20430 ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
20431 (
20432 "a bucket count that is not a power of two",
20433 cf_header(0, 3, 0, 1, [2, 20, 1]),
20434 ),
20435 ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
20436 ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
20437 (
20438 "a growth nobody could reach",
20439 cf_header(0, 8, 0, 1, [2, 20, 32769]),
20440 ),
20441 (
20442 "a chain that cannot grow and did",
20443 cf_header(0, 8, 0, 2, [2, 20, 0]),
20444 ),
20445 (
20448 "a filter count that wraps",
20449 cf_header(0, 8, 0, 65_536, [2, 20, 1]),
20450 ),
20451 ] {
20452 assert_eq!(
20453 f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
20454 "-Couldn't create filter!\r\n",
20455 "{why}"
20456 );
20457 }
20458 }
20459
20460 #[test]
20463 fn the_cuckoo_family_answers_in_resp3_spelling_too() {
20464 let mut f = Fixture::new();
20465 f.out.set_proto(Proto::Resp3);
20466 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
20467 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
20468 assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
20469 assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
20470 assert_eq!(
20471 f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
20472 "*2\r\n#t\r\n#f\r\n"
20473 );
20474 assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
20475 assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
20476 assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
20478 assert_eq!(
20479 f.run(&[b"CF.INFO", b"c"]),
20480 "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
20481 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
20482 +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
20483 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
20484 );
20485
20486 f.run(&[
20491 b"CF.RESERVE",
20492 b"s",
20493 b"4",
20494 b"BUCKETSIZE",
20495 b"1",
20496 b"EXPANSION",
20497 b"0",
20498 ]);
20499 assert_eq!(
20500 f.run(&[
20501 b"CF.INSERT",
20502 b"s",
20503 b"ITEMS",
20504 b"a",
20505 b"b",
20506 b"c",
20507 b"d",
20508 b"e",
20509 b"f"
20510 ]),
20511 "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
20512 );
20513 assert_eq!(
20514 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
20515 "*2\r\n:0\r\n:-1\r\n"
20516 );
20517 assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
20518 assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
20521 }
20522
20523 #[test]
20528 fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
20529 let mut f = Fixture::new();
20530 assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
20531 assert_eq!(
20532 f.run(&[b"CMS.INFO", b"d"]),
20533 "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
20534 );
20535 assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
20536 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
20537 assert_eq!(
20540 f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
20541 "+OK\r\n"
20542 );
20543 assert_eq!(
20544 f.run(&[b"CMS.INFO", b"p"]),
20545 "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
20546 );
20547 assert_eq!(
20550 f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
20551 "-CMS: key already exists\r\n"
20552 );
20553 assert_eq!(
20554 f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
20555 "-CMS: invalid width\r\n"
20556 );
20557 assert_eq!(
20558 f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
20559 "-CMS: invalid depth\r\n"
20560 );
20561 assert_eq!(
20562 f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
20563 "-CMS: invalid overestimation value\r\n"
20564 );
20565 assert_eq!(
20566 f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
20567 "-CMS: invalid prob value\r\n"
20568 );
20569 assert_eq!(
20573 f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
20574 "-CMS: invalid init arguments\r\n"
20575 );
20576 assert_eq!(
20580 f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
20581 "-CMS: Insufficient memory to create the key\r\n"
20582 );
20583 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
20584 }
20585
20586 #[test]
20589 fn increments_are_parsed_whole_and_the_counters_saturate() {
20590 let mut f = Fixture::new();
20591 f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
20592 assert_eq!(
20593 f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
20594 "*2\r\n:3\r\n:4\r\n"
20595 );
20596 assert_eq!(
20599 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
20600 "*2\r\n:4\r\n:5\r\n"
20601 );
20602 assert_eq!(
20604 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
20605 "-CMS: Cannot parse number\r\n"
20606 );
20607 assert_eq!(
20608 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
20609 "-CMS: Number cannot be negative\r\n"
20610 );
20611 assert_eq!(
20612 f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
20613 "*2\r\n:5\r\n:4\r\n"
20614 );
20615 f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
20618 assert_eq!(
20619 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
20620 "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
20621 );
20622 assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
20623 f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
20626 f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
20627 f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
20628 assert_eq!(
20629 f.run(&[b"CMS.INFO", b"w"]),
20630 "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
20631 );
20632 assert!(
20635 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
20636 .contains("wrong number of arguments")
20637 );
20638 assert_eq!(
20639 f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
20640 "-CMS: key does not exist\r\n"
20641 );
20642 assert_eq!(
20643 f.run(&[b"CMS.QUERY", b"nope", b"a"]),
20644 "-CMS: key does not exist\r\n"
20645 );
20646 }
20647
20648 #[test]
20651 fn a_merge_lands_whole_or_not_at_all() {
20652 let mut f = Fixture::new();
20653 for name in [&b"m1"[..], b"m2", b"dst"] {
20654 f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
20655 }
20656 f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
20657 f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
20658 assert_eq!(
20659 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
20660 "+OK\r\n"
20661 );
20662 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
20663 assert_eq!(
20666 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
20667 "+OK\r\n"
20668 );
20669 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
20670 assert_eq!(
20671 f.run(&[
20672 b"CMS.MERGE",
20673 b"dst",
20674 b"2",
20675 b"m1",
20676 b"m2",
20677 b"WEIGHTS",
20678 b"2",
20679 b"3"
20680 ]),
20681 "+OK\r\n"
20682 );
20683 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
20684 assert_eq!(
20687 f.run(&[
20688 b"CMS.MERGE",
20689 b"dst",
20690 b"1",
20691 b"m1",
20692 b"WEIGHTS",
20693 b"4611686018427387904"
20694 ]),
20695 "-CMS: MERGE overflow\r\n"
20696 );
20697 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
20698 f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
20701 assert_eq!(
20702 f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
20703 "-CMS: key does not exist\r\n"
20704 );
20705 assert_eq!(
20706 f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
20707 "-CMS: Number of keys must be positive\r\n"
20708 );
20709 assert_eq!(
20710 f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
20711 "-CMS: wrong number of keys\r\n"
20712 );
20713 assert_eq!(
20714 f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
20715 "-CMS: wrong number of keys/weights\r\n"
20716 );
20717 assert_eq!(
20718 f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
20719 "-CMS: width/depth is not equal\r\n"
20720 );
20721 assert_eq!(
20722 f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
20723 "-CMS: key does not exist\r\n"
20724 );
20725 }
20726
20727 #[test]
20730 fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
20731 let mut f = Fixture::new();
20732 f.run(&[b"SET", b"s", b"text"]);
20733 for cmd in [
20734 vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
20735 vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
20736 vec![&b"CMS.QUERY"[..], b"s", b"a"],
20737 vec![&b"CMS.INFO"[..], b"s"],
20738 vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
20739 ] {
20740 let name = String::from_utf8_lossy(cmd[0]).into_owned();
20741 let reply = f.run(&cmd);
20742 assert!(
20745 reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
20746 "{name}: {reply}"
20747 );
20748 }
20749 f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
20750 assert_eq!(
20754 f.run(&[b"COPY", b"c", b"c2"]),
20755 "-ERR not supported for this module key\r\n"
20756 );
20757 assert_eq!(
20758 f.run(&[b"DUMP", b"c"]),
20759 "-ERR DUMP is not supported for this module key\r\n"
20760 );
20761 f.run(&[b"G.NADD", b"g", b"a"]);
20763 assert_eq!(
20764 f.run(&[b"COPY", b"g", b"g2"]),
20765 "-ERR COPY is not supported for a graph\r\n"
20766 );
20767 assert_eq!(
20768 f.run(&[b"DUMP", b"g"]),
20769 "-ERR DUMP is not supported for a graph\r\n"
20770 );
20771 assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
20774 assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
20775 assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
20776 assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
20777 assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
20778 }
20779
20780 #[test]
20785 fn a_reserve_takes_three_arguments_or_six() {
20786 let mut f = Fixture::new();
20787 assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
20788 assert_eq!(
20789 f.run(&[b"TOPK.INFO", b"t"]),
20790 "*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"
20791 );
20792 for cmd in [
20795 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
20796 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
20797 ] {
20798 assert!(f.run(&cmd).contains("wrong number of arguments"));
20799 }
20800 assert_eq!(
20801 f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
20802 "+OK\r\n"
20803 );
20804 assert_eq!(
20807 f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
20808 "-TopK: key already exists\r\n"
20809 );
20810 assert_eq!(
20811 f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
20812 "-TopK: invalid k\r\n"
20813 );
20814 assert_eq!(
20815 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
20816 "-TopK: invalid width\r\n"
20817 );
20818 assert_eq!(
20819 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
20820 "-TopK: invalid depth\r\n"
20821 );
20822 assert_eq!(
20824 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
20825 "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
20826 );
20827 assert_eq!(
20828 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
20829 "+OK\r\n"
20830 );
20831 assert_eq!(
20833 f.run(&[
20834 b"TOPK.RESERVE",
20835 b"w",
20836 b"1",
20837 b"4294967295",
20838 b"4294967295",
20839 b"0.9"
20840 ]),
20841 "-ERR Insufficient memory to create topk data structure\r\n"
20842 );
20843 }
20844
20845 #[test]
20847 fn the_kept_set_is_what_query_and_list_answer_from() {
20848 let mut f = Fixture::new();
20849 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
20850 assert_eq!(
20853 f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
20854 "*2\r\n$-1\r\n$-1\r\n"
20855 );
20856 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
20857 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
20860 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
20861 assert_eq!(
20862 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
20863 "*3\r\n:1\r\n:0\r\n:1\r\n"
20864 );
20865 assert_eq!(
20867 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
20868 "*3\r\n:11\r\n:1\r\n:6\r\n"
20869 );
20870 assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
20871 assert_eq!(
20872 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
20873 "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
20874 );
20875 assert_eq!(
20878 f.run(&[b"TOPK.LIST", b"t", b"w"]),
20879 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
20880 );
20881 assert_eq!(
20882 f.run(&[b"TOPK.LIST", b"t", b""]),
20883 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
20884 );
20885 assert_eq!(
20886 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
20887 "-WITHCOUNT keyword expected\r\n"
20888 );
20889 assert_eq!(
20892 f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
20893 "-WITHCOUNT keyword expected\r\n"
20894 );
20895 assert_eq!(
20896 f.run(&[b"TOPK.LIST", b"missing"]),
20897 "-TopK: key does not exist\r\n"
20898 );
20899 f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
20901 assert_eq!(
20902 f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
20903 "*1\r\n$-1\r\n"
20904 );
20905 assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
20906 assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
20907 }
20908
20909 #[test]
20912 fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
20913 let mut f = Fixture::new();
20914 f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
20915 assert_eq!(
20919 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
20920 format!(
20921 "*2\r\n$-1\r\n-{}\r\n",
20922 "TopK: increment must be an integer greater or equal to 0 and smaller or equal to 100,000"
20923 )
20924 );
20925 assert_eq!(
20926 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
20927 "*3\r\n:3\r\n:0\r\n:0\r\n"
20928 );
20929 assert_eq!(
20931 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
20932 "*1\r\n$-1\r\n"
20933 );
20934 assert!(
20935 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
20936 .contains("smaller or equal to 100,000")
20937 );
20938 assert!(
20940 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
20941 .contains("wrong number of arguments")
20942 );
20943 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
20944 }
20945
20946 #[test]
20948 fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
20949 let mut f = Fixture::new();
20950 f.run(&[b"HELLO", b"3"]);
20951 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
20952 f.run(&[b"TOPK.ADD", b"t", b"a"]);
20953 assert_eq!(
20954 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
20955 "*2\r\n#t\r\n#f\r\n"
20956 );
20957 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
20959 assert_eq!(
20960 f.run(&[b"TOPK.INFO", b"t"]),
20961 "%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"
20962 );
20963 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
20964 }
20965
20966 #[test]
20969 fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
20970 let mut f = Fixture::new();
20971 f.run(&[b"SET", b"s", b"text"]);
20972 for cmd in [
20973 vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
20974 vec![&b"TOPK.ADD"[..], b"s", b"a"],
20975 vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
20976 vec![&b"TOPK.QUERY"[..], b"s", b"a"],
20977 vec![&b"TOPK.COUNT"[..], b"s", b"a"],
20978 vec![&b"TOPK.LIST"[..], b"s"],
20979 vec![&b"TOPK.INFO"[..], b"s"],
20980 ] {
20981 let name = String::from_utf8_lossy(cmd[0]).into_owned();
20982 let reply = f.run(&cmd);
20983 assert!(
20984 reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
20985 "{name}: {reply}"
20986 );
20987 }
20988 f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
20989 assert_eq!(
20990 f.run(&[b"COPY", b"t", b"t2"]),
20991 "-ERR not supported for this module key\r\n"
20992 );
20993 assert_eq!(
20994 f.run(&[b"DUMP", b"t"]),
20995 "-ERR DUMP is not supported for this module key\r\n"
20996 );
20997 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
20998 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
20999 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
21000 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
21001 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
21002 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
21003 assert_eq!(
21006 f.run(&[b"TOPK.INFO", b"t3"]),
21007 "-TopK: key does not exist\r\n"
21008 );
21009 }
21010
21011 #[test]
21016 fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
21017 let mut f = Fixture::new();
21018 assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
21019 assert_eq!(
21021 f.run(&[b"TDIGEST.INFO", b"t"]),
21022 "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
21023 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
21024 +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
21025 );
21026 assert_eq!(
21027 f.run(&[b"TDIGEST.CREATE", b"t"]),
21028 "-ERR T-Digest: key already exists\r\n"
21029 );
21030 assert!(
21032 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
21033 .contains("wrong number of arguments")
21034 );
21035 assert_eq!(
21036 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
21037 "+OK\r\n"
21038 );
21039 assert_eq!(
21040 f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
21041 "+OK\r\n"
21042 );
21043 assert_eq!(
21047 f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
21048 "-ERR T-Digest: error parsing compression parameter\r\n"
21049 );
21050 assert_eq!(
21051 f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
21052 "-ERR T-Digest: wrong keyword\r\n"
21053 );
21054 assert_eq!(
21055 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
21056 "-ERR T-Digest: error parsing compression parameter\r\n"
21057 );
21058 assert_eq!(
21059 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
21060 "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
21061 );
21062 assert_eq!(
21065 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
21066 "-ERR T-Digest: allocation failed\r\n"
21067 );
21068 assert_eq!(
21070 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
21071 "-ERR T-Digest: allocation failed\r\n"
21072 );
21073 assert_eq!(
21076 f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
21077 "-ERR T-Digest: key already exists\r\n"
21078 );
21079 }
21080
21081 #[test]
21084 fn the_quantile_family_answers_what_the_module_answers() {
21085 let mut f = Fixture::new();
21086 f.run(&[b"TDIGEST.CREATE", b"s"]);
21087 assert_eq!(
21088 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
21089 "+OK\r\n"
21090 );
21091 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
21092 assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
21093 assert_eq!(
21095 f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
21096 "*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"
21097 );
21098 assert_eq!(
21099 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
21100 "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
21101 );
21102 assert_eq!(
21105 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
21106 "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
21107 );
21108 assert_eq!(
21109 f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
21110 "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
21111 );
21112 assert_eq!(
21113 f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
21114 "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
21115 );
21116 assert_eq!(
21117 f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
21118 "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
21119 );
21120 assert_eq!(
21121 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
21122 "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
21123 );
21124 assert_eq!(
21125 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
21126 "$3\r\n2.5\r\n"
21127 );
21128 assert_eq!(
21129 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
21130 "$3\r\n2.5\r\n"
21131 );
21132 assert_eq!(
21134 f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
21135 "-ERR T-Digest: quantile should be in [0,1]\r\n"
21136 );
21137 assert_eq!(
21138 f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
21139 "-ERR T-Digest: error parsing quantile\r\n"
21140 );
21141 assert_eq!(
21142 f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
21143 "-ERR T-Digest: error parsing cdf\r\n"
21144 );
21145 assert_eq!(
21146 f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
21147 "-ERR T-Digest: error parsing value\r\n"
21148 );
21149 assert_eq!(
21150 f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
21151 "-ERR T-Digest: rank needs to be non negative\r\n"
21152 );
21153 assert_eq!(
21154 f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
21155 "-ERR T-Digest: error parsing rank\r\n"
21156 );
21157 assert_eq!(
21160 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
21161 "-ERR T-Digest: error parsing low_cut_percentile\r\n"
21162 );
21163 assert_eq!(
21164 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
21165 "-ERR T-Digest: error parsing high_cut_percentile\r\n"
21166 );
21167 assert_eq!(
21168 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
21169 "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
21170 );
21171 assert_eq!(
21172 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
21173 "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
21174 );
21175 }
21176
21177 #[test]
21180 fn an_empty_digest_has_an_answer_for_everything() {
21181 let mut f = Fixture::new();
21182 f.run(&[b"TDIGEST.CREATE", b"e"]);
21183 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
21184 assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
21185 assert_eq!(
21186 f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
21187 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
21188 );
21189 assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
21190 assert_eq!(
21191 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
21192 "$3\r\nnan\r\n"
21193 );
21194 assert_eq!(
21197 f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
21198 "*2\r\n:-2\r\n:-2\r\n"
21199 );
21200 assert_eq!(
21201 f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
21202 "*2\r\n:-2\r\n:-2\r\n"
21203 );
21204 assert_eq!(
21205 f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
21206 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
21207 );
21208 f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
21210 assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
21211 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
21212 f.run(&[b"TDIGEST.CREATE", b"e2"]);
21215 assert_eq!(
21216 f.run(&[b"TDIGEST.INFO", b"e"]),
21217 f.run(&[b"TDIGEST.INFO", b"e2"])
21218 );
21219 }
21220
21221 #[test]
21224 fn a_sample_is_read_the_way_redis_reads_a_double() {
21225 let mut f = Fixture::new();
21226 f.run(&[b"TDIGEST.CREATE", b"a"]);
21227 for bad in [
21230 &b"nan"[..],
21231 b"1e400",
21232 b"-1e400",
21233 b"1e309",
21234 b"1e-400",
21235 b"",
21236 b" 1",
21237 b"1 ",
21238 b"1e",
21239 b"--1",
21240 ] {
21241 assert_eq!(
21242 f.run(&[b"TDIGEST.ADD", b"a", bad]),
21243 "-ERR T-Digest: error parsing val parameter\r\n",
21244 "{}",
21245 String::from_utf8_lossy(bad)
21246 );
21247 }
21248 for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
21251 assert_eq!(
21252 f.run(&[b"TDIGEST.ADD", b"a", word]),
21253 "-ERR T-Digest: val parameter needs to be a finite number\r\n",
21254 "{}",
21255 String::from_utf8_lossy(word)
21256 );
21257 }
21258 for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
21261 assert_eq!(
21262 f.run(&[b"TDIGEST.ADD", b"a", good]),
21263 "+OK\r\n",
21264 "{}",
21265 String::from_utf8_lossy(good)
21266 );
21267 }
21268 assert!(
21270 f.run(&[b"TDIGEST.INFO", b"a"])
21271 .contains("Observations\r\n:6\r\n")
21272 );
21273 assert_eq!(
21276 f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
21277 "-ERR T-Digest: error parsing val parameter\r\n"
21278 );
21279 assert!(
21280 f.run(&[b"TDIGEST.INFO", b"a"])
21281 .contains("Observations\r\n:6\r\n")
21282 );
21283 }
21284
21285 #[test]
21288 fn a_merge_sweeps_the_destination_between_its_inputs() {
21289 let mut f = Fixture::new();
21290 f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
21291 f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
21292 f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
21293 f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
21294 assert_eq!(
21295 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
21296 "+OK\r\n"
21297 );
21298 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
21303 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
21304 assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
21305 assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
21306 assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
21307 assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
21308 assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
21309 assert!(
21312 f.run(&[b"TDIGEST.INFO", b"m1"])
21313 .contains("Merged nodes\r\n:3\r\n")
21314 );
21315 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
21318 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
21319 assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
21320 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
21321 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
21324 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
21325 assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
21326 assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
21327 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
21329 assert!(
21330 f.run(&[b"TDIGEST.INFO", b"d"])
21331 .contains("Compression\r\n:500\r\n")
21332 );
21333 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
21335 assert!(
21336 f.run(&[b"TDIGEST.INFO", b"d"])
21337 .contains("Observations\r\n:12\r\n")
21338 );
21339 assert_eq!(
21341 f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
21342 "-ERR T-Digest: error parsing numkeys\r\n"
21343 );
21344 assert_eq!(
21345 f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
21346 "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
21347 );
21348 assert!(
21349 f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
21350 .contains("wrong number of arguments")
21351 );
21352 assert!(
21353 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
21354 .contains("wrong number of arguments")
21355 );
21356 assert_eq!(
21357 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
21358 "-ERR T-Digest: wrong keyword\r\n"
21359 );
21360 assert_eq!(
21363 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
21364 "-ERR T-Digest: key does not exist\r\n"
21365 );
21366 assert!(
21367 f.run(&[b"TDIGEST.INFO", b"d"])
21368 .contains("Observations\r\n:12\r\n")
21369 );
21370 assert_eq!(
21373 f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
21374 "-ERR T-Digest: key does not exist\r\n"
21375 );
21376 }
21377
21378 #[test]
21380 fn a_digest_answers_doubles_and_a_map_on_resp3() {
21381 let mut f = Fixture::new();
21382 f.run(&[b"HELLO", b"3"]);
21383 f.run(&[b"TDIGEST.CREATE", b"s"]);
21384 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
21385 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
21386 assert_eq!(
21387 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
21388 "*2\r\n,1\r\n,4\r\n"
21389 );
21390 assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
21391 assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
21393 assert_eq!(
21394 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
21395 "*1\r\n,-inf\r\n"
21396 );
21397 f.run(&[b"TDIGEST.CREATE", b"e"]);
21398 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
21399 assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
21401 assert_eq!(
21404 f.run(&[b"TDIGEST.INFO", b"s"]),
21405 "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
21406 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
21407 +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
21408 );
21409 }
21410
21411 #[test]
21414 fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
21415 let mut f = Fixture::new();
21416 f.run(&[b"SET", b"s", b"text"]);
21417 for cmd in [
21418 vec![&b"TDIGEST.CREATE"[..], b"s"],
21419 vec![&b"TDIGEST.RESET"[..], b"s"],
21420 vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
21421 vec![&b"TDIGEST.MIN"[..], b"s"],
21422 vec![&b"TDIGEST.MAX"[..], b"s"],
21423 vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
21424 vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
21425 vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
21426 vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
21427 vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
21428 vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
21429 vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
21430 vec![&b"TDIGEST.INFO"[..], b"s"],
21431 ] {
21432 let name = String::from_utf8_lossy(cmd[0]).into_owned();
21433 let reply = f.run(&cmd);
21434 assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
21435 }
21436 f.run(&[b"TDIGEST.CREATE", b"t"]);
21438 assert!(
21439 f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
21440 .starts_with("-WRONGTYPE")
21441 );
21442 assert!(
21443 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
21444 .starts_with("-WRONGTYPE")
21445 );
21446 assert_eq!(
21447 f.run(&[b"COPY", b"t", b"t2"]),
21448 "-ERR not supported for this module key\r\n"
21449 );
21450 assert_eq!(
21451 f.run(&[b"DUMP", b"t"]),
21452 "-ERR DUMP is not supported for this module key\r\n"
21453 );
21454 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
21455 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
21456 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
21457 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
21458 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
21459 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
21460 assert_eq!(
21463 f.run(&[b"TDIGEST.INFO", b"t3"]),
21464 "-ERR T-Digest: key does not exist\r\n"
21465 );
21466 assert_eq!(
21469 f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
21470 "-ERR T-Digest: key does not exist\r\n"
21471 );
21472 }
21473
21474 fn without_memory(reply: &str) -> String {
21484 let head = "+memoryUsage\r\n:";
21485 let at = reply.find(head).expect("every TS.INFO reports memory");
21486 let rest = &reply[at + head.len()..];
21487 let end = rest.find("\r\n").expect("and it is a whole number");
21488 format!("{}{}", &reply[..at + head.len()], &rest[end..])
21489 }
21490
21491 #[test]
21494 fn a_series_is_made_empty_and_reports_on_itself() {
21495 let mut f = Fixture::new();
21496 assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
21497 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
21498 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
21499 assert_eq!(
21503 without_memory(&f.run(&[b"TS.INFO", b"t"])),
21504 "*28\r\n\
21505 +totalSamples\r\n:0\r\n\
21506 +memoryUsage\r\n:\r\n\
21507 +firstTimestamp\r\n:0\r\n\
21508 +lastTimestamp\r\n:0\r\n\
21509 +retentionTime\r\n:0\r\n\
21510 +chunkCount\r\n:1\r\n\
21511 +chunkSize\r\n:4096\r\n\
21512 +chunkType\r\n+compressed\r\n\
21513 +duplicatePolicy\r\n+block\r\n\
21514 +labels\r\n*0\r\n\
21515 +sourceKey\r\n$-1\r\n\
21516 +rules\r\n*0\r\n\
21517 +ignoreMaxTimeDiff\r\n:0\r\n\
21518 +ignoreMaxValDiff\r\n$1\r\n0\r\n"
21519 );
21520 assert_eq!(
21523 f.run(&[b"TS.CREATE", b"t"]),
21524 "-ERR TSDB: key already exists\r\n"
21525 );
21526 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21527 assert_eq!(
21528 f.run(&[b"TS.CREATE", b"str"]),
21529 "-ERR TSDB: key already exists\r\n"
21530 );
21531 assert_eq!(
21534 f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
21535 "-ERR TSDB: Couldn't parse RETENTION\r\n"
21536 );
21537 let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
21543 assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
21544 assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
21545 assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
21546 assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
21547 assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
21548 assert_eq!(
21549 f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
21550 "-ERR TSDB: the key is not a TSDB key\r\n"
21551 );
21552 assert_eq!(
21554 f.run(&[b"TS.INFO", b"nope"]),
21555 "-ERR TSDB: the key does not exist\r\n"
21556 );
21557 assert_eq!(
21558 f.run(&[b"TS.GET", b"nope"]),
21559 "-ERR TSDB: the key does not exist\r\n"
21560 );
21561 assert_eq!(
21562 f.run(&[b"TS.ALTER", b"nope"]),
21563 "-ERR TSDB: the key does not exist\r\n"
21564 );
21565 assert_eq!(
21566 f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
21567 "-ERR TSDB: the key does not exist\r\n"
21568 );
21569 }
21570
21571 #[test]
21574 fn the_options_are_a_keyword_scan_and_not_a_grammar() {
21575 let mut f = Fixture::new();
21576 assert_eq!(
21577 f.run(&[
21578 b"TS.CREATE",
21579 b"t",
21580 b"RETENTION",
21581 b"5000",
21582 b"ENCODING",
21583 b"UNCOMPRESSED",
21584 b"CHUNK_SIZE",
21585 b"128",
21586 b"DUPLICATE_POLICY",
21587 b"LAST",
21588 b"IGNORE",
21589 b"10",
21590 b"0.5",
21591 b"LABELS",
21592 b"room",
21593 b"kitchen"
21594 ]),
21595 "+OK\r\n"
21596 );
21597 let info = f.run(&[b"TS.INFO", b"t"]);
21598 assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
21599 assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
21600 assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
21601 assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
21602 assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
21603 assert!(
21606 info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
21607 "{info}"
21608 );
21609 assert!(
21610 info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
21611 "{info}"
21612 );
21613
21614 assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
21616 assert_eq!(
21620 f.run(&[
21621 b"TS.CREATE",
21622 b"g",
21623 b"LABELS",
21624 b"a",
21625 b"b",
21626 b"RETENTION",
21627 b"5"
21628 ]),
21629 "+OK\r\n"
21630 );
21631 let greedy = f.run(&[b"TS.INFO", b"g"]);
21632 assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
21633 assert!(
21634 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"),
21635 "{greedy}"
21636 );
21637
21638 assert_eq!(
21640 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
21641 "-ERR TSDB: Couldn't parse LABELS\r\n"
21642 );
21643 assert_eq!(
21644 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
21645 "-ERR TSDB: Couldn't parse LABELS\r\n"
21646 );
21647 assert_eq!(
21648 f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
21649 "-ERR TSDB: Couldn't parse RETENTION\r\n"
21650 );
21651 assert_eq!(
21654 f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
21655 "-TSDB: Couldn't parse RETENTION\r\n"
21656 );
21657 assert_eq!(
21658 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
21659 "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
21660 );
21661 assert_eq!(
21662 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
21663 "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
21664 );
21665 assert_eq!(
21666 f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
21667 "-ERR TSDB: unknown ENCODING parameter\r\n"
21668 );
21669 assert!(
21672 f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
21673 .contains("wrong number of arguments for 'ts.create' command")
21674 );
21675 assert_eq!(
21676 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
21677 "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
21678 );
21679 assert_eq!(
21680 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
21681 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
21682 );
21683 assert_eq!(
21684 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
21685 "-ERR TSDB: Couldn't parse IGNORE\r\n"
21686 );
21687 assert_eq!(
21688 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
21689 "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
21690 );
21691 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
21692
21693 assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
21696 let after = f.run(&[b"TS.INFO", b"t"]);
21697 assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
21698 assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
21699 assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
21700 assert_eq!(
21701 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
21702 "-ERR TSDB: unknown ENCODING parameter\r\n"
21703 );
21704 assert_eq!(
21706 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
21707 "+OK\r\n"
21708 );
21709 assert!(
21710 f.run(&[b"TS.INFO", b"t"])
21711 .contains("+chunkType\r\n+uncompressed\r\n")
21712 );
21713 }
21714
21715 #[test]
21718 fn samples_land_where_they_are_put_and_the_newest_comes_back() {
21719 let mut f = Fixture::new();
21720 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
21721 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
21723 assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
21724 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
21727 assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
21728 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
21729 assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
21732 assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
21733
21734 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21737 assert_eq!(
21738 f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
21739 "-ERR TSDB: invalid value\r\n"
21740 );
21741 for bad in [
21745 &b".5"[..],
21746 b"1.",
21747 b"+1",
21748 b" 1",
21749 b"0x10",
21750 b"inf",
21751 b"1e400",
21752 b"--1",
21753 b"1e",
21754 ] {
21755 assert_eq!(
21756 f.run(&[b"TS.ADD", b"v", b"1", bad]),
21757 "-ERR TSDB: invalid value\r\n",
21758 "{}",
21759 String::from_utf8_lossy(bad)
21760 );
21761 }
21762 assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
21764
21765 assert_eq!(
21768 f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
21769 "-ERR TSDB: invalid timestamp\r\n"
21770 );
21771 assert_eq!(
21772 f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
21773 "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
21774 );
21775
21776 assert_eq!(
21779 f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
21780 "-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"
21781 );
21782 assert_eq!(
21783 f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
21784 ":300\r\n"
21785 );
21786 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
21787 assert_eq!(
21790 f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
21791 ":1\r\n"
21792 );
21793 assert_eq!(
21794 f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
21795 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
21796 );
21797
21798 assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
21801 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
21802 assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
21803 assert_eq!(
21804 f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
21805 "-ERR TSDB: Timestamp is older than retention\r\n"
21806 );
21807 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
21809 assert!(
21810 f.run(&[b"TS.INFO", b"r"])
21811 .contains("+totalSamples\r\n:1\r\n")
21812 );
21813
21814 assert_eq!(
21817 f.run(&[
21818 b"TS.CREATE",
21819 b"i",
21820 b"DUPLICATE_POLICY",
21821 b"LAST",
21822 b"IGNORE",
21823 b"10",
21824 b"0.5"
21825 ]),
21826 "+OK\r\n"
21827 );
21828 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
21829 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
21830 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
21831 }
21832
21833 #[test]
21836 fn a_madd_answers_each_triple_and_creates_nothing() {
21837 let mut f = Fixture::new();
21838 assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
21839 assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
21840 assert_eq!(
21841 f.run(&[
21842 b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
21843 ]),
21844 "*3\r\n:100\r\n:100\r\n:200\r\n"
21845 );
21846 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21849 assert_eq!(
21850 f.run(&[
21851 b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
21852 ]),
21853 "*3\r\n\
21854 -ERR TSDB: the key is not a TSDB key\r\n\
21855 -ERR TSDB: the key is not a TSDB key\r\n\
21856 :300\r\n"
21857 );
21858 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
21859 assert_eq!(
21861 f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
21862 "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
21863 );
21864 assert!(
21866 f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
21867 .contains("wrong number of arguments for 'ts.madd' command")
21868 );
21869 }
21870
21871 #[test]
21873 fn an_increment_walks_the_newest_value_up_and_down() {
21874 let mut f = Fixture::new();
21875 assert_eq!(
21876 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
21877 ":100\r\n"
21878 );
21879 assert_eq!(
21880 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
21881 ":100\r\n"
21882 );
21883 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
21886 assert_eq!(
21887 f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
21888 ":200\r\n"
21889 );
21890 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
21891 assert_eq!(
21894 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
21895 "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
21896 );
21897 assert_eq!(
21901 f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
21902 ":1\r\n"
21903 );
21904 assert_eq!(
21905 f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
21906 ":1\r\n"
21907 );
21908 assert_eq!(
21909 f.run(&[b"TS.INCRBY", b"t", b"nan"]),
21910 "-ERR TSDB: invalid increase/decrease value\r\n"
21911 );
21912 assert_eq!(
21913 f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
21914 "-ERR TSDB: invalid increase/decrease value\r\n"
21915 );
21916 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21919 assert_eq!(
21920 f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
21921 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21922 );
21923 assert_eq!(
21928 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
21929 "-ERR TSDB: invalid timestamp\r\n"
21930 );
21931 assert_eq!(
21934 f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
21935 format!(":{}\r\n", f.server.now_ms())
21936 );
21937 assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
21939 assert_eq!(
21940 f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
21941 "-ERR TSDB: cannot increment/decrement NaN value\r\n"
21942 );
21943 }
21944
21945 #[test]
21947 fn deleting_takes_out_a_span_and_answers_how_many_went() {
21948 let mut f = Fixture::new();
21949 for at in [b"100".as_slice(), b"200", b"300", b"400"] {
21950 f.run(&[b"TS.ADD", b"t", at, b"1"]);
21951 }
21952 assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
21953 assert!(
21954 f.run(&[b"TS.INFO", b"t"])
21955 .contains("+totalSamples\r\n:2\r\n")
21956 );
21957 assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
21959 assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
21961 let empty = f.run(&[b"TS.INFO", b"t"]);
21964 assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
21965 assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
21966 assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
21967 assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
21968 assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
21969 assert_eq!(
21971 f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
21972 "-ERR TSDB: wrong fromTimestamp\r\n"
21973 );
21974 assert_eq!(
21975 f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
21976 "-ERR TSDB: wrong toTimestamp\r\n"
21977 );
21978 assert_eq!(
21979 f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
21980 "-ERR TSDB: wrong fromTimestamp\r\n"
21981 );
21982 }
21983
21984 #[test]
21987 fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
21988 let mut f = Fixture::new();
21989 f.out = Out::new(Proto::Resp3);
21990 assert_eq!(
21991 f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
21992 "+OK\r\n"
21993 );
21994 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
21995 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
21997 assert_eq!(
21998 without_memory(&f.run(&[b"TS.INFO", b"t"])),
21999 "%14\r\n\
22000 +totalSamples\r\n:1\r\n\
22001 +memoryUsage\r\n:\r\n\
22002 +firstTimestamp\r\n:100\r\n\
22003 +lastTimestamp\r\n:100\r\n\
22004 +retentionTime\r\n:0\r\n\
22005 +chunkCount\r\n:1\r\n\
22006 +chunkSize\r\n:4096\r\n\
22007 +chunkType\r\n+compressed\r\n\
22008 +duplicatePolicy\r\n+block\r\n\
22009 +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
22010 +sourceKey\r\n_\r\n\
22011 +rules\r\n%0\r\n\
22012 +ignoreMaxTimeDiff\r\n:0\r\n\
22013 +ignoreMaxValDiff\r\n,0\r\n"
22014 );
22015 }
22016
22017 #[test]
22020 fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
22021 let mut f = Fixture::new();
22022 for (at, v) in [
22023 (b"100".as_slice(), b"1".as_slice()),
22024 (b"200", b"2"),
22025 (b"300", b"3"),
22026 (b"400", b"4"),
22027 ] {
22028 f.run(&[b"TS.ADD", b"t", at, v]);
22029 }
22030 assert_eq!(
22031 f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
22032 "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
22033 *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
22034 );
22035 assert_eq!(
22037 f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
22038 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
22039 );
22040 assert_eq!(
22043 f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
22044 "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
22045 );
22046 assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
22048 assert_eq!(
22050 f.run(&[
22051 b"TS.RANGE",
22052 b"t",
22053 b"-",
22054 b"+",
22055 b"FILTER_BY_VALUE",
22056 b"2",
22057 b"3"
22058 ]),
22059 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
22060 );
22061 assert_eq!(
22062 f.run(&[
22063 b"TS.RANGE",
22064 b"t",
22065 b"-",
22066 b"+",
22067 b"FILTER_BY_TS",
22068 b"100",
22069 b"400"
22070 ]),
22071 "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
22072 );
22073 assert_eq!(
22075 f.run(&[
22076 b"TS.RANGE",
22077 b"t",
22078 b"-",
22079 b"+",
22080 b"ZZZ",
22081 b"FILTER_BY_TS",
22082 b"400"
22083 ]),
22084 "*1\r\n*2\r\n:400\r\n+4\r\n"
22085 );
22086 assert_eq!(
22088 f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
22089 "*1\r\n*2\r\n:100\r\n+1\r\n"
22090 );
22091 }
22092
22093 #[test]
22095 fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
22096 let mut f = Fixture::new();
22097 for (at, v) in [
22098 (b"100".as_slice(), b"1".as_slice()),
22099 (b"200", b"2"),
22100 (b"300", b"3"),
22101 (b"400", b"4"),
22102 ] {
22103 f.run(&[b"TS.ADD", b"t", at, v]);
22104 }
22105 assert_eq!(
22106 f.run(&[
22107 b"TS.RANGE",
22108 b"t",
22109 b"-",
22110 b"+",
22111 b"AGGREGATION",
22112 b"avg",
22113 b"200"
22114 ]),
22115 "*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"
22116 );
22117 assert_eq!(
22120 f.run(&[
22121 b"TS.RANGE",
22122 b"t",
22123 b"-",
22124 b"+",
22125 b"AGGREGATION",
22126 b"min,max,count",
22127 b"200"
22128 ]),
22129 "*3\r\n\
22130 *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
22131 *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
22132 *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
22133 );
22134 assert_eq!(
22136 f.run(&[
22137 b"TS.RANGE",
22138 b"t",
22139 b"-",
22140 b"+",
22141 b"AGGREGATION",
22142 b"avg",
22143 b"200",
22144 b"BUCKETTIMESTAMP",
22145 b"+"
22146 ]),
22147 "*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"
22148 );
22149 assert_eq!(
22151 f.run(&[
22152 b"TS.RANGE",
22153 b"t",
22154 b"100",
22155 b"400",
22156 b"ALIGN",
22157 b"100",
22158 b"AGGREGATION",
22159 b"sum",
22160 b"200"
22161 ]),
22162 "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
22163 );
22164 assert_eq!(
22167 f.run(&[
22168 b"TS.RANGE",
22169 b"t",
22170 b"-",
22171 b"+",
22172 b"AGGREGATION",
22173 b"count",
22174 b"200"
22175 ]),
22176 "*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"
22177 );
22178 assert_eq!(
22179 f.run(&[
22180 b"TS.RANGE",
22181 b"t",
22182 b"-",
22183 b"+",
22184 b"AGGREGATION",
22185 b"count",
22186 b"200",
22187 b"COUNT",
22188 b"1"
22189 ]),
22190 "*1\r\n*2\r\n:0\r\n+1\r\n"
22191 );
22192 }
22193
22194 #[test]
22197 fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
22198 let mut f = Fixture::new();
22199 for (at, v) in [
22200 (b"0".as_slice(), b"1".as_slice()),
22201 (b"100", b"2"),
22202 (b"500", b"nan"),
22203 (b"600", b"3"),
22204 ] {
22205 f.run(&[b"TS.ADD", b"g", at, v]);
22206 }
22207 assert_eq!(
22210 f.run(&[
22211 b"TS.RANGE",
22212 b"g",
22213 b"-",
22214 b"+",
22215 b"AGGREGATION",
22216 b"avg",
22217 b"100"
22218 ]),
22219 "*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"
22220 );
22221 assert_eq!(
22223 f.run(&[
22224 b"TS.RANGE",
22225 b"g",
22226 b"-",
22227 b"+",
22228 b"AGGREGATION",
22229 b"sum",
22230 b"100",
22231 b"EMPTY"
22232 ]),
22233 "*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\
22234 *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\
22235 *2\r\n:600\r\n+3\r\n"
22236 );
22237 assert_eq!(
22242 f.run(&[
22243 b"TS.RANGE",
22244 b"g",
22245 b"-",
22246 b"+",
22247 b"AGGREGATION",
22248 b"last",
22249 b"100",
22250 b"EMPTY"
22251 ]),
22252 "*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\
22253 *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\
22254 *2\r\n:600\r\n+3\r\n"
22255 );
22256 assert_eq!(
22257 f.run(&[
22258 b"TS.REVRANGE",
22259 b"g",
22260 b"-",
22261 b"+",
22262 b"AGGREGATION",
22263 b"last",
22264 b"100",
22265 b"EMPTY"
22266 ]),
22267 "*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\
22268 *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\
22269 *2\r\n:0\r\n+1\r\n"
22270 );
22271 assert_eq!(
22274 f.run(&[
22275 b"TS.RANGE",
22276 b"g",
22277 b"500",
22278 b"600",
22279 b"AGGREGATION",
22280 b"last",
22281 b"100",
22282 b"EMPTY"
22283 ]),
22284 "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
22285 );
22286 }
22287
22288 #[test]
22291 fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
22292 let mut f = Fixture::new();
22293 f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
22294 f.run(&[b"SET", b"str", b"x"]);
22295 let cases: &[(&[&[u8]], &str)] = &[
22296 (
22297 &[b"TS.RANGE", b"t"],
22298 "-ERR wrong number of arguments for 'ts.range' command\r\n",
22299 ),
22300 (
22302 &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
22303 "-ERR TSDB: the key does not exist\r\n",
22304 ),
22305 (
22306 &[b"TS.RANGE", b"str", b"-", b"+"],
22307 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
22308 ),
22309 (
22310 &[b"TS.RANGE", b"t", b"abc", b"+"],
22311 "-ERR TSDB: wrong fromTimestamp\r\n",
22312 ),
22313 (
22314 &[b"TS.RANGE", b"t", b"-", b"abc"],
22315 "-ERR TSDB: wrong toTimestamp\r\n",
22316 ),
22317 (
22318 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
22319 "-ERR TSDB: COUNT argument is missing\r\n",
22320 ),
22321 (
22322 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
22323 "-ERR TSDB: Couldn't parse COUNT\r\n",
22324 ),
22325 (
22326 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
22327 "-ERR TSDB: Invalid COUNT value\r\n",
22328 ),
22329 (
22330 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
22331 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22332 ),
22333 (
22334 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
22335 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22336 ),
22337 (
22338 &[
22339 b"TS.RANGE",
22340 b"t",
22341 b"-",
22342 b"+",
22343 b"AGGREGATION",
22344 b"nope",
22345 b"100",
22346 ],
22347 "-ERR TSDB: Unknown aggregation type\r\n",
22348 ),
22349 (
22350 &[
22351 b"TS.RANGE",
22352 b"t",
22353 b"-",
22354 b"+",
22355 b"AGGREGATION",
22356 b"avg,,min",
22357 b"100",
22358 ],
22359 "-ERR TSDB: Empty aggregation type in list\r\n",
22360 ),
22361 (
22363 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
22364 "-ERR TSDB: Unknown aggregation type\r\n",
22365 ),
22366 (
22367 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
22368 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
22369 ),
22370 (
22371 &[
22372 b"TS.RANGE",
22373 b"t",
22374 b"-",
22375 b"+",
22376 b"AGGREGATION",
22377 b"avg",
22378 b"100",
22379 b"X",
22380 b"EMPTY",
22381 ],
22382 "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
22383 ),
22384 (
22385 &[
22386 b"TS.RANGE",
22387 b"t",
22388 b"-",
22389 b"+",
22390 b"AGGREGATION",
22391 b"avg",
22392 b"100",
22393 b"BUCKETTIMESTAMP",
22394 b"z",
22395 ],
22396 "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
22397 ),
22398 (
22399 &[
22400 b"TS.RANGE",
22401 b"t",
22402 b"-",
22403 b"+",
22404 b"AGGREGATION",
22405 b"avg",
22406 b"100",
22407 b"X",
22408 b"Y",
22409 b"BUCKETTIMESTAMP",
22410 b"-",
22411 ],
22412 "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
22413 AGGREGATION flag\r\n",
22414 ),
22415 (
22416 &[
22417 b"TS.RANGE",
22418 b"t",
22419 b"-",
22420 b"+",
22421 b"ALIGN",
22422 b"z",
22423 b"AGGREGATION",
22424 b"avg",
22425 b"100",
22426 ],
22427 "-ERR TSDB: unknown ALIGN parameter\r\n",
22428 ),
22429 (
22430 &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
22431 "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
22432 ),
22433 (
22434 &[
22435 b"TS.RANGE",
22436 b"t",
22437 b"-",
22438 b"+",
22439 b"ALIGN",
22440 b"-",
22441 b"AGGREGATION",
22442 b"avg",
22443 b"100",
22444 ],
22445 "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
22446 ),
22447 (
22448 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
22449 "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
22450 ),
22451 (
22452 &[
22453 b"TS.RANGE",
22454 b"t",
22455 b"-",
22456 b"+",
22457 b"FILTER_BY_VALUE",
22458 b"x",
22459 b"2",
22460 ],
22461 "-ERR TSDB: Couldn't parse MIN\r\n",
22462 ),
22463 (
22464 &[
22465 b"TS.RANGE",
22466 b"t",
22467 b"-",
22468 b"+",
22469 b"FILTER_BY_VALUE",
22470 b"1",
22471 b"y",
22472 ],
22473 "-ERR TSDB: Couldn't parse MAX\r\n",
22474 ),
22475 (
22476 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
22477 "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
22478 ),
22479 ];
22480 for (argv, want) in cases {
22481 let got = f.run(argv);
22482 assert_eq!(&got, want, "{:?}", argv.last());
22483 }
22484 f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
22488 f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
22489 assert_eq!(
22490 f.run(&[
22491 b"TS.RANGE",
22492 b"wide",
22493 b"-",
22494 b"+",
22495 b"AGGREGATION",
22496 b"avg",
22497 b"1",
22498 b"EMPTY"
22499 ]),
22500 "-ERR TSDB: the requested range holds too many empty buckets\r\n"
22501 );
22502 }
22503
22504 #[test]
22506 fn resp3_writes_a_read_value_as_a_double() {
22507 let mut f = Fixture::new();
22508 f.out = Out::new(Proto::Resp3);
22509 for (at, v) in [
22510 (b"0".as_slice(), b"1".as_slice()),
22511 (b"100", b"2"),
22512 (b"500", b"nan"),
22513 (b"600", b"3"),
22514 ] {
22515 f.run(&[b"TS.ADD", b"g", at, v]);
22516 }
22517 assert_eq!(
22518 f.run(&[
22519 b"TS.RANGE",
22520 b"g",
22521 b"0",
22522 b"100",
22523 b"AGGREGATION",
22524 b"avg,min",
22525 b"200"
22526 ]),
22527 "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
22528 );
22529 assert_eq!(
22530 f.run(&[
22531 b"TS.RANGE",
22532 b"g",
22533 b"500",
22534 b"600",
22535 b"AGGREGATION",
22536 b"last",
22537 b"100",
22538 b"EMPTY"
22539 ]),
22540 "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
22541 );
22542 }
22543
22544 fn joined() -> Fixture {
22547 let mut f = Fixture::new();
22548 f.run(&[b"TS.CREATE", b"z"]);
22549 for (at, v) in [
22550 (b"10".as_slice(), b"1".as_slice()),
22551 (b"20", b"2"),
22552 (b"40", b"4"),
22553 (b"50", b"5"),
22554 ] {
22555 f.run(&[b"TS.ADD", b"x", at, v]);
22556 }
22557 for (at, v) in [
22558 (b"20".as_slice(), b"20".as_slice()),
22559 (b"30", b"30"),
22560 (b"50", b"50"),
22561 (b"60", b"60"),
22562 ] {
22563 f.run(&[b"TS.ADD", b"y", at, v]);
22564 }
22565 f
22566 }
22567
22568 #[test]
22572 fn an_nrange_joins_its_keys_on_the_timestamp() {
22573 let mut f = joined();
22574 assert_eq!(
22576 f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
22577 "*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\
22578 *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
22579 );
22580 assert_eq!(
22582 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
22583 "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
22584 *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
22585 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
22586 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
22587 *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
22588 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22589 );
22590 assert_eq!(
22593 f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
22594 "*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"
22595 );
22596 assert_eq!(
22597 f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
22598 "*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"
22599 );
22600 assert_eq!(
22603 f.run(&[
22604 b"TS.NREVRANGE",
22605 b"2",
22606 b"x",
22607 b"y",
22608 b"-",
22609 b"+",
22610 b"COUNT",
22611 b"1"
22612 ]),
22613 "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22614 );
22615 assert_eq!(
22616 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
22617 "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
22618 );
22619 assert_eq!(
22621 f.run(&[
22622 b"TS.NRANGE",
22623 b"2",
22624 b"x",
22625 b"y",
22626 b"-",
22627 b"+",
22628 b"FILTER_BY_VALUE",
22629 b"2",
22630 b"30"
22631 ]),
22632 "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
22633 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
22634 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
22635 *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
22636 );
22637 }
22638
22639 #[test]
22643 fn an_nrange_aggregation_names_one_reduction_a_key() {
22644 let mut f = joined();
22645 assert_eq!(
22646 f.run(&[
22647 b"TS.NRANGE",
22648 b"2",
22649 b"x",
22650 b"y",
22651 b"-",
22652 b"+",
22653 b"AGGREGATION",
22654 b"sum",
22655 b"sum",
22656 b"20"
22657 ]),
22658 "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
22659 *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
22660 *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
22661 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22662 );
22663 assert_eq!(
22665 f.run(&[
22666 b"TS.NRANGE",
22667 b"2",
22668 b"x",
22669 b"y",
22670 b"-",
22671 b"+",
22672 b"AGGREGATION",
22673 b"sum,count",
22674 b"avg",
22675 b"20"
22676 ]),
22677 "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
22678 *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
22679 *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
22680 *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
22681 );
22682 assert_eq!(
22685 f.run(&[
22686 b"TS.NRANGE",
22687 b"2",
22688 b"x",
22689 b"y",
22690 b"-",
22691 b"+",
22692 b"AGGREGATION",
22693 b"avg",
22694 b"sum",
22695 b"100",
22696 b"EMPTY",
22697 b"BUCKETTIMESTAMP",
22698 b"end"
22699 ]),
22700 "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
22701 );
22702 assert_eq!(
22705 f.run(&[
22706 b"TS.NRANGE",
22707 b"2",
22708 b"x",
22709 b"y",
22710 b"-",
22711 b"+",
22712 b"AGGREGATION",
22713 b"avg",
22714 b"COUNT",
22715 b"100"
22716 ]),
22717 "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
22718 );
22719 }
22720
22721 #[test]
22724 fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
22725 let mut f = joined();
22726 f.run(&[b"SET", b"str", b"hi"]);
22727 let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
22728 let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
22729 must be equal to numkeys\r\n";
22730 let cases: &[(&[&[u8]], &str)] = &[
22731 (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
22732 (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
22733 (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
22734 (
22737 &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
22738 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
22739 ),
22740 (
22741 &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
22742 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
22743 ),
22744 (
22747 &[
22748 b"TS.NRANGE",
22749 b"2",
22750 b"x",
22751 b"y",
22752 b"abc",
22753 b"+",
22754 b"AGGREGATION",
22755 b"nope",
22756 b"sum",
22757 b"100",
22758 ],
22759 "-ERR TSDB: Unknown aggregation type\r\n",
22760 ),
22761 (
22762 &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
22763 "-ERR TSDB: wrong fromTimestamp\r\n",
22764 ),
22765 (
22766 &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
22767 "-ERR TSDB: wrong toTimestamp\r\n",
22768 ),
22769 (
22773 &[
22774 b"TS.NRANGE",
22775 b"2",
22776 b"x",
22777 b"y",
22778 b"-",
22779 b"+",
22780 b"AGGREGATION",
22781 b"avg",
22782 ],
22783 numkeys,
22784 ),
22785 (
22786 &[
22787 b"TS.NRANGE",
22788 b"2",
22789 b"x",
22790 b"y",
22791 b"-",
22792 b"+",
22793 b"AGGREGATION",
22794 b"100",
22795 b"sum",
22796 b"100",
22797 ],
22798 numkeys,
22799 ),
22800 (
22801 &[
22802 b"TS.NRANGE",
22803 b"2",
22804 b"x",
22805 b"y",
22806 b"-",
22807 b"+",
22808 b"AGGREGATION",
22809 b"avg",
22810 b"sum",
22811 b"sum",
22812 b"100",
22813 ],
22814 numkeys,
22815 ),
22816 (
22817 &[
22818 b"TS.NRANGE",
22819 b"2",
22820 b"x",
22821 b"y",
22822 b"-",
22823 b"+",
22824 b"AGGREGATION",
22825 b"avg",
22826 b"sum",
22827 b"abc",
22828 ],
22829 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22830 ),
22831 (
22832 &[
22833 b"TS.NRANGE",
22834 b"2",
22835 b"x",
22836 b"y",
22837 b"-",
22838 b"+",
22839 b"AGGREGATION",
22840 b"avg",
22841 b"sum",
22842 b"0",
22843 ],
22844 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
22845 ),
22846 (
22849 &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
22850 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22851 ),
22852 (
22853 &[
22854 b"TS.NRANGE",
22855 b"1",
22856 b"x",
22857 b"-",
22858 b"+",
22859 b"AGGREGATION",
22860 b"100",
22861 b"200",
22862 ],
22863 "-ERR TSDB: Unknown aggregation type\r\n",
22864 ),
22865 (
22867 &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
22868 "-ERR TSDB: the key does not exist\r\n",
22869 ),
22870 (
22871 &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
22872 "-ERR WRONGTYPE Operation against a key \
22873 holding the wrong kind of value\r\n",
22874 ),
22875 ];
22876 for (argv, want) in cases {
22877 let got = f.run(argv);
22878 assert_eq!(&got, want, "{argv:?}");
22879 }
22880 }
22881
22882 #[test]
22884 fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
22885 let mut f = joined();
22886 assert_eq!(
22887 f.run(&[b"TS.READ", b"x", b"-"]),
22888 "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
22889 *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
22890 );
22891 assert_eq!(
22894 f.run(&[b"TS.READ", b"x", b"+"]),
22895 "*1\r\n*2\r\n:50\r\n+5\r\n"
22896 );
22897 assert_eq!(
22898 f.run(&[b"TS.READ", b"x", b"25"]),
22899 "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
22900 );
22901 assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
22904 assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
22905 assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
22906 assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
22907 assert_eq!(
22911 f.run(&[b"TS.READ", b"x", b"abc"]),
22912 "-TSDB: invalid timestamp\r\n"
22913 );
22914 assert_eq!(
22915 f.run(&[b"TS.READ", b"x", b"-1"]),
22916 "-TSDB: invalid timestamp\r\n"
22917 );
22918 f.run(&[b"SET", b"str", b"hi"]);
22919 assert_eq!(
22920 f.run(&[b"TS.READ", b"str", b"-"]),
22921 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22922 );
22923 assert_eq!(
22926 f.run(&[b"TS.READ", b"x"]),
22927 "-ERR wrong number of arguments for 'ts.read' command\r\n"
22928 );
22929 assert_eq!(
22930 f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
22931 "-ERR wrong number of arguments for 'ts.read' command\r\n"
22932 );
22933 }
22934
22935 #[test]
22938 fn getkeys_reads_the_count_of_a_joined_read() {
22939 let mut f = Fixture::new();
22940 assert_eq!(
22941 f.run(&[
22942 b"COMMAND",
22943 b"GETKEYS",
22944 b"TS.NRANGE",
22945 b"2",
22946 b"a",
22947 b"b",
22948 b"-",
22949 b"+"
22950 ]),
22951 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
22952 );
22953 assert_eq!(
22954 f.run(&[
22955 b"COMMAND",
22956 b"GETKEYS",
22957 b"TS.NREVRANGE",
22958 b"1",
22959 b"a",
22960 b"-",
22961 b"+"
22962 ]),
22963 "*1\r\n$1\r\na\r\n"
22964 );
22965 for n in [b"0".as_slice(), b"9", b"abc"] {
22968 assert_eq!(
22969 f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
22970 "-ERR Invalid arguments specified for command\r\n"
22971 );
22972 }
22973 }
22974
22975 fn labelled() -> Fixture {
22977 let mut f = Fixture::new();
22978 f.run(&[
22979 b"TS.CREATE",
22980 b"a",
22981 b"LABELS",
22982 b"room",
22983 b"kitchen",
22984 b"x",
22985 b"1",
22986 ]);
22987 f.run(&[
22988 b"TS.CREATE",
22989 b"b",
22990 b"LABELS",
22991 b"room",
22992 b"bedroom",
22993 b"x",
22994 b"2",
22995 ]);
22996 f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
22997 f.run(&[b"TS.CREATE", b"d"]);
22998 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
22999 f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
23000 f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
23001 f
23002 }
23003
23004 #[test]
23007 fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
23008 let mut f = labelled();
23009 let cases: &[(&[&[u8]], &str)] = &[
23010 (
23013 &[b"TS.QUERYINDEX", b"room=kitchen"],
23014 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
23015 ),
23016 (
23017 &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
23018 "*1\r\n$1\r\na\r\n",
23019 ),
23020 (
23021 &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
23022 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
23023 ),
23024 (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
23027 (
23029 &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
23030 "*1\r\n$1\r\nc\r\n",
23031 ),
23032 (
23033 &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
23034 "*1\r\n$1\r\na\r\n",
23035 ),
23036 (
23037 &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
23038 "-ERR TSDB: please provide at least one matcher\r\n",
23039 ),
23040 (
23043 &[b"TS.QUERYINDEX", b"room==kitchen"],
23044 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
23045 ),
23046 (
23047 &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
23048 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
23049 ),
23050 (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
23051 (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
23054 (
23055 &[b"TS.QUERYINDEX", b"=(1)"],
23056 "-ERR TSDB: failed parsing labels\r\n",
23057 ),
23058 (
23059 &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
23060 "-ERR TSDB: failed parsing labels\r\n",
23061 ),
23062 (
23063 &[b"TS.QUERYINDEX", b"room=(kitchen"],
23064 "-ERR TSDB: failed parsing labels\r\n",
23065 ),
23066 (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
23067 (
23068 &[b"TS.QUERYINDEX", b"nonsense"],
23069 "-ERR TSDB: failed parsing labels\r\n",
23070 ),
23071 (
23073 &[b"TS.QUERYINDEX", b"room!=kitchen"],
23074 "-ERR TSDB: please provide at least one matcher\r\n",
23075 ),
23076 (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
23078 (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
23079 (
23080 &[b"TS.QUERYINDEX"],
23081 "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
23082 ),
23083 ];
23084 for (argv, want) in cases {
23085 let got = f.run(argv);
23086 assert_eq!(&got, want, "{:?}", argv.last());
23087 }
23088 }
23089
23090 #[test]
23092 fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
23093 let mut f = labelled();
23094 let cases: &[(&[&[u8]], &str)] = &[
23095 (
23096 &[b"TS.QUERYLABELS", b"LABELS"],
23097 "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
23098 ),
23099 (
23100 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
23101 "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
23102 ),
23103 (
23104 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
23105 "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
23106 ),
23107 (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
23110 (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
23111 (
23112 &[b"TS.QUERYLABELS", b"VALUES"],
23113 "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
23114 ),
23115 (
23116 &[b"TS.QUERYLABELS", b"ZZZ"],
23117 "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
23118 ),
23119 (
23120 &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
23121 "-ERR TSDB: unknown argument, expected FILTER\r\n",
23122 ),
23123 (
23124 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
23125 "-ERR TSDB: FILTER given with no filter expressions\r\n",
23126 ),
23127 (
23131 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
23132 "-ERR TSDB: please provide at least one matcher\r\n",
23133 ),
23134 (
23135 &[
23136 b"TS.QUERYLABELS",
23137 b"LABELS",
23138 b"FILTER",
23139 b"room=kitchen",
23140 b"x=",
23141 ],
23142 "*1\r\n$4\r\nroom\r\n",
23143 ),
23144 ];
23145 for (argv, want) in cases {
23146 let got = f.run(argv);
23147 assert_eq!(&got, want, "{:?}", argv.last());
23148 }
23149 }
23150
23151 #[test]
23154 fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
23155 let mut f = labelled();
23156 let cases: &[(&[&[u8]], &str)] = &[
23157 (
23160 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
23161 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
23162 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
23163 ),
23164 (
23165 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
23166 "*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\
23167 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
23168 *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",
23169 ),
23170 (
23172 &[
23173 b"TS.MGET",
23174 b"SELECTED_LABELS",
23175 b"x",
23176 b"FILTER",
23177 b"room=kitchen",
23178 ],
23179 "*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\
23180 *2\r\n:100\r\n+1.5\r\n\
23181 *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",
23182 ),
23183 (
23186 &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
23187 "*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",
23188 ),
23189 (
23190 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
23191 "*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\
23192 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
23193 ),
23194 (
23197 &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
23198 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
23199 ),
23200 (
23201 &[b"TS.MGET", b"a", b"b", b"c"],
23202 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
23203 ),
23204 (
23205 &[b"TS.MGET", b"FILTER"],
23206 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
23207 ),
23208 (
23211 &[
23212 b"TS.MGET",
23213 b"WITHLABELS",
23214 b"SELECTED_LABELS",
23215 b"x",
23216 b"FILTER",
23217 b"bad",
23218 ],
23219 "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
23220 ),
23221 (
23222 &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
23223 "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
23224 ),
23225 ];
23226 for (argv, want) in cases {
23227 let got = f.run(argv);
23228 assert_eq!(&got, want, "{:?}", argv.last());
23229 }
23230 }
23231
23232 #[test]
23235 fn resp3_writes_the_label_surface_as_sets_and_maps() {
23236 let mut f = labelled();
23237 f.out = Out::new(Proto::Resp3);
23238 let cases: &[(&[&[u8]], &str)] = &[
23239 (
23240 &[b"TS.QUERYINDEX", b"room=kitchen"],
23241 "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
23242 ),
23243 (
23244 &[b"TS.QUERYLABELS", b"LABELS"],
23245 "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
23246 ),
23247 (
23248 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
23249 "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
23250 ),
23251 (
23254 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
23255 "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
23256 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
23257 ),
23258 (
23259 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
23260 "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
23261 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
23262 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
23263 ),
23264 (
23265 &[
23266 b"TS.MGET",
23267 b"SELECTED_LABELS",
23268 b"x",
23269 b"FILTER",
23270 b"room=kitchen",
23271 ],
23272 "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
23273 *2\r\n:100\r\n,1.5\r\n\
23274 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
23275 ),
23276 (
23279 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
23280 "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
23281 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
23282 ),
23283 ];
23284 for (argv, want) in cases {
23285 let got = f.run(argv);
23286 assert_eq!(&got, want, "{:?}", argv.last());
23287 }
23288 }
23289
23290 fn spanned() -> Fixture {
23293 let mut f = labelled();
23294 f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
23295 f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
23296 f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
23297 f
23298 }
23299
23300 #[test]
23303 fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
23304 let mut f = spanned();
23305 let cases: &[(&[&[u8]], &str)] = &[
23306 (
23307 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
23308 "*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\
23309 *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",
23310 ),
23311 (
23314 &[
23315 b"TS.MREVRANGE",
23316 b"-",
23317 b"+",
23318 b"WITHLABELS",
23319 b"FILTER",
23320 b"room=kitchen",
23321 ],
23322 "*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\
23323 *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\
23324 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
23325 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
23326 ),
23327 (
23330 &[
23331 b"TS.MRANGE",
23332 b"-",
23333 b"+",
23334 b"SELECTED_LABELS",
23335 b"x",
23336 b"FILTER",
23337 b"room=kitchen",
23338 ],
23339 "*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\
23340 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
23341 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
23342 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
23343 ),
23344 (
23347 &[
23348 b"TS.MRANGE",
23349 b"-",
23350 b"+",
23351 b"FILTER",
23352 b"room=kitchen",
23353 b"GROUPBY",
23354 b"room",
23355 b"REDUCE",
23356 b"sum",
23357 ],
23358 "*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\
23359 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
23360 ),
23361 (
23364 &[
23365 b"TS.MRANGE",
23366 b"-",
23367 b"+",
23368 b"WITHLABELS",
23369 b"FILTER",
23370 b"room=kitchen",
23371 b"GROUPBY",
23372 b"room",
23373 b"REDUCE",
23374 b"max",
23375 ],
23376 "*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\
23377 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
23378 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
23379 *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",
23380 ),
23381 (
23383 &[
23384 b"TS.MREVRANGE",
23385 b"-",
23386 b"+",
23387 b"COUNT",
23388 b"1",
23389 b"FILTER",
23390 b"room=kitchen",
23391 b"GROUPBY",
23392 b"room",
23393 b"REDUCE",
23394 b"count",
23395 ],
23396 "*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",
23397 ),
23398 (
23400 &[
23401 b"TS.MRANGE",
23402 b"-",
23403 b"+",
23404 b"FILTER",
23405 b"room=kitchen",
23406 b"GROUPBY",
23407 b"nope",
23408 b"REDUCE",
23409 b"sum",
23410 ],
23411 "*0\r\n",
23412 ),
23413 (
23414 &[
23415 b"TS.MRANGE",
23416 b"-",
23417 b"+",
23418 b"AGGREGATION",
23419 b"sum,avg",
23420 b"100",
23421 b"FILTER",
23422 b"room=bedroom",
23423 ],
23424 "*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",
23425 ),
23426 (
23428 &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
23429 "-ERR TSDB: missing FILTER argument\r\n",
23430 ),
23431 (
23432 &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
23433 "-ERR TSDB: missing labels for filter argument\r\n",
23434 ),
23435 (
23436 &[
23437 b"TS.MRANGE",
23438 b"-",
23439 b"+",
23440 b"GROUPBY",
23441 b"room",
23442 b"REDUCE",
23443 b"sum",
23444 b"FILTER",
23445 b"room=kitchen",
23446 ],
23447 "-ERR TSDB: GROUPBY should always come after filter\r\n",
23448 ),
23449 (
23452 &[
23453 b"TS.MRANGE",
23454 b"-",
23455 b"+",
23456 b"FILTER",
23457 b"room=kitchen",
23458 b"GROUPBY",
23459 b"room",
23460 b"REDUCE",
23461 b"sum",
23462 b"x",
23463 ],
23464 "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
23465 ),
23466 (
23468 &[
23469 b"TS.MRANGE",
23470 b"-",
23471 b"+",
23472 b"FILTER",
23473 b"nope",
23474 b"GROUPBY",
23475 b"room",
23476 b"REDUCE",
23477 b"sum",
23478 b"x",
23479 ],
23480 "-ERR TSDB: failed parsing labels\r\n",
23481 ),
23482 (
23483 &[
23484 b"TS.MRANGE",
23485 b"-",
23486 b"+",
23487 b"FILTER",
23488 b"room=kitchen",
23489 b"GROUPBY",
23490 b"room",
23491 b"REDUCE",
23492 b"twa",
23493 ],
23494 "-ERR TSDB: Invalid reducer type\r\n",
23495 ),
23496 (
23497 &[
23498 b"TS.MRANGE",
23499 b"-",
23500 b"+",
23501 b"AGGREGATION",
23502 b"sum,avg",
23503 b"100",
23504 b"FILTER",
23505 b"room=kitchen",
23506 b"GROUPBY",
23507 b"room",
23508 b"REDUCE",
23509 b"sum",
23510 ],
23511 "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
23512 ),
23513 (
23516 &[
23517 b"TS.MRANGE",
23518 b"-",
23519 b"+",
23520 b"SELECTED_LABELS",
23521 b"COUNT",
23522 b"FILTER",
23523 b"room=kitchen",
23524 ],
23525 "-ERR TSDB: Couldn't parse COUNT\r\n",
23526 ),
23527 ];
23528 for (argv, want) in cases {
23529 let got = f.run(argv);
23530 assert_eq!(&got, want, "{argv:?}");
23531 }
23532 }
23533
23534 #[test]
23537 fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
23538 let mut f = spanned();
23539 f.out = Out::new(Proto::Resp3);
23540 let cases: &[(&[&[u8]], &str)] = &[
23541 (
23542 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
23543 "%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\
23544 *1\r\n*2\r\n:200\r\n,2\r\n",
23545 ),
23546 (
23549 &[
23550 b"TS.MRANGE",
23551 b"-",
23552 b"+",
23553 b"AGGREGATION",
23554 b"sum,avg",
23555 b"100",
23556 b"FILTER",
23557 b"room=bedroom",
23558 ],
23559 "%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\
23560 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
23561 ),
23562 (
23563 &[
23564 b"TS.MRANGE",
23565 b"-",
23566 b"+",
23567 b"FILTER",
23568 b"room=kitchen",
23569 b"GROUPBY",
23570 b"room",
23571 b"REDUCE",
23572 b"sum",
23573 ],
23574 "%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\
23575 $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\
23576 *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",
23577 ),
23578 (
23581 &[
23582 b"TS.MRANGE",
23583 b"-",
23584 b"+",
23585 b"WITHLABELS",
23586 b"FILTER",
23587 b"room=kitchen",
23588 b"GROUPBY",
23589 b"room",
23590 b"REDUCE",
23591 b"max",
23592 ],
23593 "%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\
23594 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
23595 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
23596 *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",
23597 ),
23598 (
23599 &[
23600 b"TS.MRANGE",
23601 b"-",
23602 b"+",
23603 b"FILTER",
23604 b"room=kitchen",
23605 b"GROUPBY",
23606 b"nope",
23607 b"REDUCE",
23608 b"sum",
23609 ],
23610 "%0\r\n",
23611 ),
23612 ];
23613 for (argv, want) in cases {
23614 let got = f.run(argv);
23615 assert_eq!(&got, want, "{argv:?}");
23616 }
23617 }
23618
23619 #[test]
23621 fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
23622 let mut f = Fixture::new();
23623 f.run(&[b"TS.CREATE", b"src"]);
23624 f.run(&[b"TS.CREATE", b"dst"]);
23625 f.run(&[b"SET", b"plain", b"v"]);
23626 let cases: &[(&[&[u8]], &str)] = &[
23627 (
23631 &[
23632 b"TS.CREATERULE",
23633 b"src",
23634 b"dst",
23635 b"AGGREGATION",
23636 b"nope",
23637 b"x",
23638 ],
23639 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
23640 ),
23641 (
23642 &[
23643 b"TS.CREATERULE",
23644 b"src",
23645 b"dst",
23646 b"AGGREGATION",
23647 b"nope",
23648 b"10",
23649 ],
23650 "-ERR TSDB: Unknown aggregation type\r\n",
23651 ),
23652 (
23653 &[
23654 b"TS.CREATERULE",
23655 b"src",
23656 b"dst",
23657 b"AGGREGATION",
23658 b"avg",
23659 b"0",
23660 ],
23661 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
23662 ),
23663 (
23664 &[
23665 b"TS.CREATERULE",
23666 b"src",
23667 b"dst",
23668 b"AGGREGATION",
23669 b"avg",
23670 b"10",
23671 b"x",
23672 ],
23673 "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
23674 ),
23675 (
23676 &[
23677 b"TS.CREATERULE",
23678 b"src",
23679 b"src",
23680 b"AGGREGATION",
23681 b"avg",
23682 b"10",
23683 ],
23684 "-ERR TSDB: the source key and destination key should be different\r\n",
23685 ),
23686 (
23690 &[
23691 b"TS.CREATERULE",
23692 b"nope",
23693 b"plain",
23694 b"AGGREGATION",
23695 b"avg",
23696 b"10",
23697 ],
23698 "-ERR TSDB: the key does not exist\r\n",
23699 ),
23700 (
23701 &[
23702 b"TS.CREATERULE",
23703 b"src",
23704 b"nope",
23705 b"AGGREGATION",
23706 b"avg",
23707 b"10",
23708 ],
23709 "-ERR TSDB: the key does not exist\r\n",
23710 ),
23711 (
23714 &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
23715 "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
23716 ),
23717 (
23718 &[
23719 b"TS.CREATERULE",
23720 b"src",
23721 b"dst",
23722 b"AGGREGATION",
23723 b"avg",
23724 b"10",
23725 ],
23726 "+OK\r\n",
23727 ),
23728 (
23731 &[
23732 b"TS.CREATERULE",
23733 b"src",
23734 b"dst",
23735 b"AGGREGATION",
23736 b"avg",
23737 b"10",
23738 ],
23739 "-ERR TSDB: the destination key already has a src rule\r\n",
23740 ),
23741 (
23744 &[
23745 b"TS.CREATERULE",
23746 b"dst",
23747 b"src",
23748 b"AGGREGATION",
23749 b"avg",
23750 b"10",
23751 ],
23752 "-ERR TSDB: the source key already has a source rule\r\n",
23753 ),
23754 (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
23755 (
23756 &[b"TS.DELETERULE", b"src", b"dst"],
23757 "-ERR TSDB: compaction rule does not exist\r\n",
23758 ),
23759 (
23763 &[b"TS.DELETERULE", b"src", b"nope"],
23764 "-ERR TSDB: compaction rule does not exist\r\n",
23765 ),
23766 (
23767 &[b"TS.DELETERULE", b"nope", b"dst"],
23768 "-ERR TSDB: the key does not exist\r\n",
23769 ),
23770 ];
23771 for (argv, want) in cases {
23772 let got = f.run(argv);
23773 assert_eq!(&got, want, "{argv:?}");
23774 }
23775 }
23776
23777 #[test]
23779 fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
23780 let mut f = Fixture::new();
23781 f.run(&[b"TS.CREATE", b"src"]);
23782 f.run(&[b"TS.CREATE", b"dst"]);
23783 f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
23786 f.run(&[
23787 b"TS.CREATERULE",
23788 b"src",
23789 b"dst",
23790 b"AGGREGATION",
23791 b"sum",
23792 b"100",
23793 ]);
23794 f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
23795 assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
23796 assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
23799 f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
23800 assert_eq!(
23801 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23802 "*1\r\n*2\r\n:0\r\n+2\r\n"
23803 );
23804 f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
23807 assert_eq!(
23808 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23809 "*1\r\n*2\r\n:0\r\n+11\r\n"
23810 );
23811 assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
23814 assert_eq!(
23815 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23816 "*1\r\n*2\r\n:0\r\n+8\r\n"
23817 );
23818 assert_eq!(
23819 f.run(&[b"TS.GET", b"dst", b"LATEST"]),
23820 "*2\r\n:100\r\n+4\r\n"
23821 );
23822 assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
23824 f.run(&[b"DEL", b"dst"]);
23825 assert_eq!(
23826 f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
23827 "-ERR TSDB: compaction rule does not exist\r\n"
23828 );
23829 }
23830
23831 #[test]
23834 fn xadd_ids_only_ever_go_up() {
23835 let mut f = Fixture::new();
23836 assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
23838 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
23840 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
23841 assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
23842 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
23843
23844 assert!(
23845 f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
23846 .contains("equal or smaller")
23847 );
23848 assert!(
23849 f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
23850 .contains("must be greater than 0-0")
23851 );
23852 assert!(
23853 f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
23854 .contains("Invalid stream ID")
23855 );
23856 assert!(
23859 f.run(&[b"XADD", b"s", b"*", b"a"])
23860 .contains("wrong number of arguments")
23861 );
23862
23863 assert_eq!(
23866 f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
23867 "$-1\r\n"
23868 );
23869 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
23870 assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
23871 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
23872 }
23873
23874 #[test]
23877 fn trimming_reads_its_options_the_way_redis_does() {
23878 let mut f = Fixture::new();
23879 for i in 1..=10u32 {
23880 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
23881 }
23882 assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
23883 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
23884 assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
23885 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
23886
23887 assert!(
23891 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
23892 .contains("not an integer")
23893 );
23894 assert!(
23895 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
23896 .contains("MAXLEN argument must be >= 0")
23897 );
23898 assert!(
23901 f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
23902 .contains("without specifying a trimming strategy")
23903 );
23904 assert!(
23905 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
23906 .contains("without the special ~ option")
23907 );
23908 assert!(
23909 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
23910 .contains("at the same time are not compatible")
23911 );
23912 assert!(
23914 f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
23915 .contains("syntax error")
23916 );
23917 assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
23918 }
23919
23920 #[test]
23922 fn xrange_looks_the_key_up_before_it_reads_the_count() {
23923 let mut f = Fixture::new();
23924 f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
23925 f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
23926
23927 assert_eq!(
23928 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
23929 "*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\
23930 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
23931 );
23932 assert_eq!(
23933 f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
23934 "*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"
23935 );
23936 assert_eq!(
23940 f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
23941 "*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\
23942 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
23943 );
23944 assert_eq!(
23945 f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
23946 "*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"
23947 );
23948 assert!(
23949 f.run(&[b"XRANGE", b"s", b"(-", b"+"])
23950 .contains("Invalid stream ID")
23951 );
23952
23953 assert_eq!(
23957 f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
23958 "*0\r\n"
23959 );
23960 assert_eq!(
23961 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
23962 "*-1\r\n"
23963 );
23964 f.run(&[b"SET", b"str", b"v"]);
23965 assert!(
23966 f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
23967 .starts_with("-WRONGTYPE")
23968 );
23969 assert_eq!(
23971 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
23972 "*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"
23973 );
23974 }
23975
23976 #[test]
23978 fn a_bad_id_late_in_the_list_stops_the_whole_command() {
23979 let mut f = Fixture::new();
23980 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
23981 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
23982 assert!(
23983 f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
23984 .contains("Invalid stream ID")
23985 );
23986 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
23987 assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
23988 assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
23989 assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
23990 assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
23991 }
23992
23993 #[test]
23995 fn xgroup_has_an_arity_per_subcommand() {
23996 let mut f = Fixture::new();
23997 assert!(
23998 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
23999 .contains("requires the key")
24000 );
24001 assert_eq!(
24002 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
24003 "+OK\r\n"
24004 );
24005 assert!(
24008 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
24009 .starts_with("-BUSYGROUP")
24010 );
24011 assert_eq!(
24012 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
24013 ":1\r\n"
24014 );
24015 assert_eq!(
24016 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
24017 ":0\r\n"
24018 );
24019 assert_eq!(
24020 f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
24021 ":0\r\n"
24022 );
24023
24024 let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
24026 assert!(
24027 short.contains("wrong number of arguments for 'xgroup|destroy' command"),
24028 "{short}"
24029 );
24030 let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
24032 assert!(
24033 odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
24034 "{odd}"
24035 );
24036 assert!(
24037 f.run(&[b"XGROUP", b"NOSUCH", b"s"])
24038 .contains("Try XGROUP HELP")
24039 );
24040
24041 assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
24042 assert!(
24043 f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
24044 .starts_with("-NOGROUP")
24045 );
24046 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
24047 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
24048 assert!(
24049 f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
24050 .contains("requires the key")
24051 );
24052 }
24053
24054 #[test]
24056 fn xreadgroup_hands_out_and_xack_takes_back() {
24057 let mut f = Fixture::new();
24058 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24059 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24060 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24061
24062 let first = f.run(&[
24063 b"XREADGROUP",
24064 b"GROUP",
24065 b"g",
24066 b"c1",
24067 b"COUNT",
24068 b"1",
24069 b"STREAMS",
24070 b"s",
24071 b">",
24072 ]);
24073 assert_eq!(
24074 first,
24075 "*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"
24076 );
24077 assert_eq!(
24080 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
24081 "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
24082 );
24083 assert_eq!(
24084 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
24085 "*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"
24086 );
24087
24088 assert_eq!(
24089 f.run(&[b"XPENDING", b"s", b"g"]),
24090 "*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"
24091 );
24092 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
24093 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
24094 assert_eq!(
24096 f.run(&[b"XPENDING", b"s", b"g"]),
24097 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
24098 );
24099
24100 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24103 f.run(&[b"XDEL", b"s", b"2-1"]);
24104 assert_eq!(
24105 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
24106 "*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"
24107 );
24108
24109 assert!(
24112 f.run(&[
24113 b"XREADGROUP",
24114 b"GROUP",
24115 b"nope",
24116 b"c",
24117 b"STREAMS",
24118 b"s",
24119 b"+"
24120 ])
24121 .starts_with("-NOGROUP")
24122 );
24123 assert!(
24124 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
24125 .contains("meaningless in the context of XREADGROUP")
24126 );
24127 assert!(
24128 f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
24129 .contains("only supported by XREADGROUP")
24130 );
24131 assert!(
24132 f.run(&[
24133 b"XREADGROUP",
24134 b"GROUP",
24135 b"g",
24136 b"c",
24137 b"STREAMS",
24138 b"s",
24139 b"a",
24140 b"b"
24141 ])
24142 .contains("Unbalanced 'xreadgroup' list of streams")
24143 );
24144 }
24145
24146 #[test]
24149 fn xread_with_no_block_writes_the_null_itself() {
24150 let mut f = Fixture::new();
24151 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24152 assert_eq!(
24153 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
24154 "*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"
24155 );
24156 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
24159 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
24160 f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
24161 assert_eq!(
24162 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
24163 "*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"
24164 );
24165 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
24167 assert_eq!(
24169 f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
24170 "*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"
24171 );
24172 assert_eq!(
24175 f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
24176 "*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"
24177 );
24178 assert!(
24180 f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
24181 .contains("not an integer")
24182 );
24183 assert!(
24184 f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
24185 .contains("timeout is negative")
24186 );
24187 assert!(
24188 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
24189 .contains("Unbalanced 'xread' list of streams")
24190 );
24191 }
24192
24193 #[test]
24195 fn a_blocked_xread_wakes_on_the_next_entry() {
24196 let mut f = Fixture::new();
24197 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24198 let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
24199 assert_eq!(flow, Flow::Block);
24200 assert!(reply.is_empty());
24201
24202 f.session = Session::new(8);
24207 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
24208 assert_eq!(flow, Flow::Block);
24209 assert_eq!(f.server.parked(), 2);
24210
24211 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24212 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";
24213 for client in [7, 8] {
24214 let mut out = Out::new(Proto::Resp2);
24215 assert!(f.server.serve_waiter(client, 0, &mut out));
24216 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
24217 }
24218
24219 f.server.forget_waiters(7);
24222 f.server.forget_waiters(8);
24223 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
24224 assert_eq!(flow, Flow::Block);
24225 let mut out = Out::new(Proto::Resp2);
24226 assert!(!f.server.serve_waiter(8, 0, &mut out));
24227 assert!(out.as_slice().is_empty());
24228 assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
24229 assert_eq!(
24230 core::str::from_utf8(out.as_slice()).expect("ascii"),
24231 "*-1\r\n"
24232 );
24233 }
24234
24235 #[test]
24237 fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
24238 let mut f = Fixture::new();
24239 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24240 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
24241 let (flow, _) = f.flow(&[
24242 b"XREADGROUP",
24243 b"GROUP",
24244 b"g",
24245 b"c",
24246 b"BLOCK",
24247 b"0",
24248 b"STREAMS",
24249 b"s",
24250 b">",
24251 ]);
24252 assert_eq!(flow, Flow::Block);
24253
24254 f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
24255 let mut out = Out::new(Proto::Resp2);
24256 assert!(f.server.serve_waiter(7, 0, &mut out));
24257 assert_eq!(
24260 core::str::from_utf8(out.as_slice()).expect("ascii"),
24261 "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
24262 );
24263 }
24264
24265 #[test]
24267 fn xclaim_reads_ids_until_one_will_not_parse() {
24268 let mut f = Fixture::new();
24269 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24270 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24271 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24272 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24273
24274 assert!(
24277 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
24278 .contains("Unrecognized XCLAIM option '-'")
24279 );
24280 assert_eq!(
24281 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
24282 "*1\r\n$3\r\n1-1\r\n"
24283 );
24284 f.run(&[b"XDEL", b"s", b"2-1"]);
24287 assert_eq!(
24288 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
24289 "*0\r\n"
24290 );
24291 assert!(
24292 f.run(&[b"XPENDING", b"s", b"g"])
24293 .starts_with("*4\r\n:1\r\n")
24294 );
24295 assert!(
24296 f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
24297 .starts_with("-NOGROUP")
24298 );
24299 assert!(
24300 f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
24301 .contains("Invalid min-idle-time argument for XCLAIM")
24302 );
24303 }
24304
24305 #[test]
24307 fn xautoclaim_reports_what_it_dropped() {
24308 let mut f = Fixture::new();
24309 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24310 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24311 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24312 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24313 f.run(&[b"XDEL", b"s", b"1-1"]);
24314
24315 assert_eq!(
24318 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
24319 "*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"
24320 );
24321 assert!(
24322 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
24323 .contains("COUNT must be > 0")
24324 );
24325 assert!(
24326 f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
24327 .starts_with("-NOGROUP")
24328 );
24329 }
24330
24331 #[test]
24333 fn xdelex_answers_one_integer_an_id() {
24334 let mut f = Fixture::new();
24335 for i in 1..=4 {
24336 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
24337 }
24338 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24339 f.run(&[
24340 b"XREADGROUP",
24341 b"GROUP",
24342 b"g",
24343 b"c",
24344 b"COUNT",
24345 b"2",
24346 b"STREAMS",
24347 b"s",
24348 b">",
24349 ]);
24350
24351 assert_eq!(
24353 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
24354 "*2\r\n:1\r\n:-1\r\n"
24355 );
24356 assert!(
24359 f.run(&[b"XPENDING", b"s", b"g"])
24360 .starts_with("*4\r\n:2\r\n")
24361 );
24362 assert_eq!(
24364 f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
24365 "*1\r\n:1\r\n"
24366 );
24367 assert_eq!(
24369 f.run(&[b"XPENDING", b"s", b"g"]),
24370 "*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"
24371 );
24372
24373 assert_eq!(
24377 f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
24378 "*2\r\n:2\r\n:2\r\n"
24379 );
24380
24381 assert_eq!(
24383 f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
24384 "*2\r\n:-1\r\n:-1\r\n"
24385 );
24386 assert!(
24388 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
24389 .starts_with("-ERR Invalid stream ID")
24390 );
24391 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
24392
24393 assert!(
24394 f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
24395 .contains("Number of IDs must be a positive integer")
24396 );
24397 assert!(
24398 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
24399 .contains("The `numids` parameter must match the number of arguments")
24400 );
24401 assert!(
24404 f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
24405 .starts_with("-ERR syntax error")
24406 );
24407 assert!(
24408 f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
24409 .starts_with("-ERR syntax error")
24410 );
24411 f.run(&[b"SET", b"str", b"v"]);
24413 assert!(
24414 f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
24415 .starts_with("-WRONGTYPE")
24416 );
24417 }
24418
24419 #[test]
24421 fn xackdel_reports_what_the_group_was_holding() {
24422 let mut f = Fixture::new();
24423 for i in 1..=3 {
24424 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
24425 }
24426 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24427 f.run(&[
24428 b"XREADGROUP",
24429 b"GROUP",
24430 b"g",
24431 b"c",
24432 b"COUNT",
24433 b"1",
24434 b"STREAMS",
24435 b"s",
24436 b">",
24437 ]);
24438
24439 assert_eq!(
24443 f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
24444 "*2\r\n:1\r\n:-1\r\n"
24445 );
24446 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
24447
24448 assert_eq!(
24450 f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
24451 "*1\r\n:-1\r\n"
24452 );
24453 assert_eq!(
24454 f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
24455 "*1\r\n:-1\r\n"
24456 );
24457
24458 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
24461 f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
24462 assert_eq!(
24463 f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
24464 "*1\r\n:2\r\n"
24465 );
24466 assert_eq!(
24467 f.run(&[b"XPENDING", b"s", b"g"]),
24468 "*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"
24469 );
24470 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
24471 }
24472
24473 #[test]
24475 fn xnack_releases_an_entry_for_the_next_claim() {
24476 let mut f = Fixture::new();
24477 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24478 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24479 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24480 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24481 f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
24484
24485 assert_eq!(
24486 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
24487 ":1\r\n"
24488 );
24489 assert_eq!(
24493 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
24494 "*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"
24495 );
24496 assert_eq!(
24498 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
24499 "*1\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"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
24504 "*-1\r\n"
24505 );
24506 assert_eq!(
24508 f.run(&[
24509 b"XAUTOCLAIM",
24510 b"s",
24511 b"g",
24512 b"c2",
24513 b"99999999",
24514 b"-",
24515 b"JUSTID"
24516 ]),
24517 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
24518 );
24519
24520 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24524 assert!(
24525 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24526 .contains(":-1\r\n:1\r\n")
24527 );
24528 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24530 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24531 assert!(
24532 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24533 .contains(":-1\r\n:0\r\n")
24534 );
24535 f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
24537 assert!(
24538 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24539 .contains(":9223372036854775807\r\n")
24540 );
24541 f.run(&[
24542 b"XNACK",
24543 b"s",
24544 b"g",
24545 b"FATAL",
24546 b"IDS",
24547 b"1",
24548 b"1-1",
24549 b"RETRYCOUNT",
24550 b"3",
24551 ]);
24552 assert!(
24553 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24554 .contains(":-1\r\n:3\r\n")
24555 );
24556
24557 f.run(&[b"XACK", b"s", b"g", b"2-1"]);
24561 assert_eq!(
24562 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
24563 ":0\r\n"
24564 );
24565 assert_eq!(
24566 f.run(&[
24567 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
24568 ]),
24569 ":1\r\n"
24570 );
24571 assert!(
24572 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24573 .contains(":-1\r\n:0\r\n")
24574 );
24575 assert_eq!(
24577 f.run(&[
24578 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
24579 ]),
24580 ":0\r\n"
24581 );
24582
24583 assert_eq!(
24586 f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
24587 "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
24588 );
24589 assert!(
24590 f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
24591 .starts_with("-ERR")
24592 );
24593 assert!(
24595 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
24596 .contains("numids must be a positive integer")
24597 );
24598 assert!(
24599 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
24600 .contains("number of IDs doesn't match numids")
24601 );
24602 assert!(
24605 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
24606 .contains("Unrecognized XNACK option '2-1'")
24607 );
24608 }
24609
24610 #[test]
24612 fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
24613 let mut f = Fixture::new();
24614 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24615 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24616 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24617 f.run(&[
24618 b"XREADGROUP",
24619 b"GROUP",
24620 b"g",
24621 b"c1",
24622 b"COUNT",
24623 b"1",
24624 b"STREAMS",
24625 b"s",
24626 b">",
24627 ]);
24628
24629 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
24630 assert!(info.starts_with("*20\r\n"), "{info}");
24633 assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
24634 assert!(
24635 info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
24636 "{info}"
24637 );
24638 assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
24639 assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
24640
24641 let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
24642 assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
24643 assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
24644 assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
24645 assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
24646
24647 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
24651 let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
24652 assert!(consumers.starts_with("*2\r\n"), "{consumers}");
24653 assert!(
24654 consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
24655 "{consumers}"
24656 );
24657 let c1 = consumers.find("c1").unwrap();
24659 let c2 = consumers.find("c2").unwrap();
24660 assert!(c1 < c2, "{consumers}");
24661
24662 let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
24663 assert!(full.starts_with("*18\r\n"), "{full}");
24664 assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
24665 assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
24666
24667 assert!(
24668 f.run(&[b"XINFO", b"STREAM", b"missing"])
24669 .contains("no such key")
24670 );
24671 assert!(
24672 f.run(&[b"XINFO", b"GROUPS", b"missing"])
24673 .contains("no such key")
24674 );
24675 assert!(
24676 f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
24677 .starts_with("-NOGROUP")
24678 );
24679 assert!(
24680 f.run(&[b"XINFO", b"NOSUCH", b"s"])
24681 .contains("Try XINFO HELP")
24682 );
24683 assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
24684 assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
24685 }
24686
24687 #[test]
24689 fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
24690 let mut f = Fixture::new();
24691 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24692 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24693 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24694
24695 let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
24696 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");
24697 assert_eq!(
24698 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
24699 "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
24700 );
24701 assert_eq!(
24703 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
24704 "*0\r\n"
24705 );
24706 assert_eq!(
24707 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
24708 list
24709 );
24710 assert!(
24712 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
24713 .contains("syntax error")
24714 );
24715 assert!(
24716 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
24717 .contains("syntax error")
24718 );
24719 assert_eq!(
24720 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
24721 "*0\r\n"
24722 );
24723 assert!(
24724 f.run(&[b"XPENDING", b"missing", b"g"])
24725 .starts_with("-NOGROUP")
24726 );
24727 }
24728
24729 #[test]
24731 fn xsetid_will_not_go_below_what_is_there() {
24732 let mut f = Fixture::new();
24733 f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
24734 assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
24735 assert_eq!(
24736 f.run(&[
24737 b"XSETID",
24738 b"s",
24739 b"10-1",
24740 b"ENTRIESADDED",
24741 b"7",
24742 b"MAXDELETEDID",
24743 b"9-1"
24744 ]),
24745 "+OK\r\n"
24746 );
24747 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
24748 assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
24749 assert!(
24750 info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
24751 "{info}"
24752 );
24753
24754 assert!(
24755 f.run(&[b"XSETID", b"s", b"1-1"])
24756 .contains("smaller than the target stream top item")
24757 );
24758 assert!(
24759 f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
24760 .contains("entries_added must be positive")
24761 );
24762 assert!(
24763 f.run(&[b"XSETID", b"missing", b"1-1"])
24764 .contains("no such key")
24765 );
24766 }
24767
24768 #[test]
24770 fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
24771 let mut f = Fixture::new();
24772 f.run(&[b"HELLO", b"3"]);
24773 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24774 assert_eq!(
24777 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
24778 "%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"
24779 );
24780 assert_eq!(
24783 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
24784 "*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"
24785 );
24786 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
24787 }
24788
24789 struct Mem {
24795 blobs: Vec<Vec<u8>>,
24796 }
24797
24798 impl yo_kv::cold::Blocks for Mem {
24799 fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
24800 self.blobs.push(bytes.to_vec());
24801 Ok(yo_common::Addr::new(
24802 yo_common::Space::Log,
24803 (self.blobs.len() - 1) as u64,
24804 ))
24805 }
24806
24807 fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
24808 self.blobs
24809 .get(at.offset() as usize)
24810 .map(Vec::as_slice)
24811 .ok_or_else(|| {
24812 yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
24813 })
24814 }
24815
24816 fn bytes(&self) -> u64 {
24817 self.blobs.iter().map(|b| b.len() as u64).sum()
24818 }
24819 }
24820
24821 fn filled(attach: bool) -> (Fixture, usize) {
24834 let mut f = Fixture::new();
24835 if attach {
24836 f.server
24837 .striped(0)
24838 .hold_stripe(0)
24839 .attach(Box::new(Mem { blobs: Vec::new() }));
24840 }
24841 let val = vec![b'v'; 256];
24842 for i in 0..24000u32 {
24843 let k = format!("key:{i:08}");
24844 f.run(&[b"SET", k.as_bytes(), &val]);
24845 }
24846 let full = f.server.memory_bytes();
24847 assert!(full > 3 * 1024 * 1024, "the arena is several segments");
24848 (f, full)
24849 }
24850
24851 fn press(f: &mut Fixture, limit: usize) {
24857 let val = vec![b'v'; 256];
24858 for i in 0..3000u32 {
24859 let k = format!("new:{i:08}");
24860 assert_eq!(
24861 f.run(&[b"SET", k.as_bytes(), &val]),
24862 "+OK\r\n",
24863 "write {i} was refused"
24864 );
24865 f.server.refresh_memory();
24866 if f.server.memory_bytes() <= limit {
24867 return;
24868 }
24869 }
24870 panic!(
24871 "it never got under: {} against {limit}",
24872 f.server.memory_bytes()
24873 );
24874 }
24875
24876 #[test]
24877 fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
24878 let mut f = Fixture::new();
24879 assert_eq!(
24880 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
24881 "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
24882 "no limit is the default"
24883 );
24884 for (typed, bytes) in [
24887 (&b"0"[..], "0"),
24888 (b"1024", "1024"),
24889 (b"1k", "1000"),
24890 (b"1gb", "1073741824"),
24891 (b"-1", "-1"),
24892 ] {
24893 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
24894 assert_eq!(
24895 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
24896 format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
24897 "set {}",
24898 String::from_utf8_lossy(typed)
24899 );
24900 }
24901 for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
24902 assert_eq!(
24903 f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
24904 "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
24905 "refused {}",
24906 String::from_utf8_lossy(bad)
24907 );
24908 }
24909 let info = f.run(&[b"INFO", b"memory"]);
24911 assert!(info.contains("maxstore:-1"), "{info}");
24912 assert!(info.contains("yo_memory_regime:evict"), "{info}");
24913 assert!(info.contains("yo_store_bytes:0"), "{info}");
24914 }
24915
24916 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24917 #[test]
24918 fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
24919 let (mut f, full) = filled(true);
24923 let keys = f.run(&[b"DBSIZE"]);
24924 assert!(
24925 f.run(&[b"INFO", b"memory"])
24926 .contains("yo_memory_regime:migrate"),
24927 "a database with somewhere to put values migrates"
24928 );
24929
24930 let limit = full - 2 * 1024 * 1024;
24931 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
24932 f.run(&[
24933 b"CONFIG",
24934 b"SET",
24935 b"maxmemory",
24936 limit.to_string().as_bytes(),
24937 ]);
24938 press(&mut f, limit);
24939
24940 assert!(
24941 f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
24942 "nothing was thrown away"
24943 );
24944 let after: usize = f.run(&[b"DBSIZE"])[1..]
24945 .trim_end()
24946 .parse()
24947 .expect("a count");
24948 let before: usize = keys[1..].trim_end().parse().expect("a count");
24949 assert!(after > before, "the keys that came in are all still here");
24950 assert!(
24951 f.server.store_bytes() > 0,
24952 "and what came out of memory went to the file"
24953 );
24954 let val = format!("$256\r\n{}\r\n", "v".repeat(256));
24957 assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
24958 assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
24959 }
24960
24961 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24962 #[test]
24963 fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
24964 let (mut f, full) = filled(true);
24968 f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
24969 assert!(
24970 f.run(&[b"INFO", b"memory"])
24971 .contains("yo_memory_regime:evict"),
24972 "nothing may go to the file"
24973 );
24974
24975 let limit = full - 2 * 1024 * 1024;
24976 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
24977 f.run(&[
24978 b"CONFIG",
24979 b"SET",
24980 b"maxmemory",
24981 limit.to_string().as_bytes(),
24982 ]);
24983 press(&mut f, limit);
24984
24985 assert!(
24986 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
24987 "keys were thrown away, which is what was asked for"
24988 );
24989 assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
24990 }
24991
24992 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24993 #[test]
24994 fn a_full_file_goes_back_to_evicting() {
24995 let (mut f, full) = filled(true);
24999 f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
25000 let limit = full - 2 * 1024 * 1024;
25001 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
25002 f.run(&[
25003 b"CONFIG",
25004 b"SET",
25005 b"maxmemory",
25006 limit.to_string().as_bytes(),
25007 ]);
25008 press(&mut f, limit);
25009
25010 assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
25011 assert!(
25012 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
25013 "and then it started evicting"
25014 );
25015 assert!(
25016 f.run(&[b"INFO", b"memory"])
25017 .contains("yo_memory_regime:evict"),
25018 "and it says so"
25019 );
25020 }
25021 #[test]
25037 fn the_string_group_answers_the_same_however_many_stripes_there_are() {
25038 let script: &[&[&[u8]]] = &[
25039 &[b"SET", b"k1", b"v1"],
25042 &[b"SET", b"k2", b"v2"],
25043 &[b"GET", b"k1"],
25044 &[b"GET", b"nothing"],
25045 &[b"GETSET", b"k1", b"v1b"],
25046 &[b"SETNX", b"k1", b"no"],
25047 &[b"SETNX", b"k3", b"yes"],
25048 &[b"APPEND", b"k3", b"!"],
25049 &[b"STRLEN", b"k3"],
25050 &[b"SETRANGE", b"k3", b"1", b"XY"],
25051 &[b"GETRANGE", b"k3", b"0", b"-1"],
25052 &[b"INCR", b"n1"],
25053 &[b"INCRBY", b"n1", b"41"],
25054 &[b"DECRBY", b"n1", b"2"],
25055 &[b"INCRBYFLOAT", b"f1", b"1.5"],
25056 &[b"SETEX", b"e1", b"100", b"v"],
25057 &[b"PSETEX", b"e2", b"100000", b"v"],
25058 &[b"GETEX", b"e1", b"PERSIST"],
25059 &[b"GETDEL", b"k2"],
25060 &[b"GET", b"k2"],
25061 &[b"DIGEST", b"k1"],
25062 &[b"DELEX", b"k3"],
25063 &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
25066 &[b"MGET", b"a", b"b", b"c", b"missing"],
25067 &[b"MSETNX", b"d", b"4", b"e", b"5"],
25068 &[b"MSETNX", b"e", b"6", b"f", b"7"],
25069 &[b"MGET", b"d", b"e", b"f"],
25070 &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
25071 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
25072 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
25073 &[b"MGET", b"g", b"h"],
25074 &[b"SET", b"s1", b"ohmytext"],
25075 &[b"SET", b"s2", b"mynewtext"],
25076 &[b"LCS", b"s1", b"s2"],
25077 &[b"LCS", b"s1", b"s2", b"LEN"],
25078 &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
25079 &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
25080 &[b"LCS", b"s1", b"gone"],
25081 &[b"MSET", b"odd"],
25083 &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
25084 &[b"MGET"],
25085 ];
25086
25087 let mut one = Fixture::new();
25088 let mut many = Fixture::striped(8);
25089 for parts in script {
25090 let a = one.run(parts);
25091 let b = many.run(parts);
25092 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25093 }
25094 }
25095
25096 #[test]
25101 fn a_striped_database_spreads_the_keys_it_is_given() {
25102 let mut f = Fixture::striped(8);
25103 for i in 0..256 {
25104 let key = format!("key:{i}");
25105 f.run(&[b"SET", key.as_bytes(), b"v"]);
25106 }
25107 assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
25108 }
25109
25110 #[test]
25113 fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
25114 let mut one = Fixture::new();
25115 let mut many = Fixture::striped(8);
25116 for f in [&mut one, &mut many] {
25117 f.run(&[b"SET", b"str", b"v"]);
25118 f.server
25124 .striped(0)
25125 .hold(b"list")
25126 .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
25127 .expect("a new list");
25128 }
25129 assert_eq!(
25130 one.run(&[b"MGET", b"str", b"list", b"gone"]),
25131 many.run(&[b"MGET", b"str", b"list", b"gone"])
25132 );
25133 }
25134
25135 #[test]
25143 fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
25144 let script: &[&[&[u8]]] = &[
25145 &[b"SET", b"k1", b"v1"],
25146 &[b"SET", b"k2", b"v2"],
25147 &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
25148 &[b"TYPE", b"k1"],
25149 &[b"TYPE", b"gone"],
25150 &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
25151 &[b"EXPIRE", b"k1", b"100"],
25152 &[b"TTL", b"k1"],
25153 &[b"EXPIRE", b"k1", b"200", b"NX"],
25154 &[b"PERSIST", b"k1"],
25155 &[b"TTL", b"k1"],
25156 &[b"PEXPIREAT", b"k2", b"1900000000000"],
25157 &[b"EXPIRETIME", b"k2"],
25158 &[b"PEXPIRETIME", b"k2"],
25159 &[b"PERSIST", b"k2"],
25160 &[b"OBJECT", b"ENCODING", b"k1"],
25161 &[b"OBJECT", b"REFCOUNT", b"k1"],
25162 &[b"OBJECT", b"IDLETIME", b"k1"],
25163 &[b"OBJECT", b"FREQ", b"k1"],
25164 &[b"OBJECT", b"ENCODING", b"gone"],
25165 &[b"OBJECT", b"HELP"],
25166 &[b"RENAME", b"k1", b"k9"],
25167 &[b"GET", b"k9"],
25168 &[b"RENAME", b"gone", b"x"],
25169 &[b"RENAMENX", b"k9", b"k2"],
25170 &[b"RENAMENX", b"k9", b"k8"],
25171 &[b"GET", b"k8"],
25172 &[b"COPY", b"k8", b"c1"],
25173 &[b"COPY", b"k8", b"c1"],
25174 &[b"COPY", b"k8", b"c1", b"REPLACE"],
25175 &[b"COPY", b"k8", b"k8"],
25176 &[b"COPY", b"gone", b"c2"],
25177 &[b"COPY", b"k8", b"k8", b"DB", b"1"],
25178 &[b"COPY", b"k8", b"c9", b"DB", b"9"],
25179 &[b"MOVE", b"c1", b"1"],
25180 &[b"MOVE", b"c1", b"1"],
25181 &[b"MOVE", b"k8", b"0"],
25182 &[b"DEL", b"k2", b"gone"],
25183 &[b"UNLINK", b"k8", b"k8"],
25184 &[b"DBSIZE"],
25185 ];
25186
25187 let mut one = Fixture::new();
25188 let mut many = Fixture::striped(8);
25189 for parts in script {
25190 let a = one.run(parts);
25191 let b = many.run(parts);
25192 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25193 }
25194
25195 for f in [&mut one, &mut many] {
25201 f.run(&[b"SET", b"d1", b"payload"]);
25202 let payload = f
25203 .server
25204 .striped(0)
25205 .hold(b"d1")
25206 .dump(b"d1")
25207 .expect("a key that is there");
25208 assert!(
25209 f.run(&[b"DUMP", b"d1"])
25210 .starts_with(&format!("${}", payload.len())),
25211 "a payload of the length the store gave"
25212 );
25213 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
25214 assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
25215 assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
25216 assert_eq!(
25217 f.run(&[b"RESTORE", b"d2", b"0", &payload]),
25218 "-BUSYKEY Target key name already exists.\r\n"
25219 );
25220 assert_eq!(
25221 f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
25222 "-ERR DUMP payload version or checksum are wrong\r\n"
25223 );
25224 }
25225 }
25226
25227 #[test]
25234 fn a_scan_of_a_striped_database_walks_all_of_it() {
25235 let n = if cfg!(miri) { 80 } else { 500 };
25239 let mut f = Fixture::striped(8);
25240 for i in 0..n {
25241 let key = format!("key:{i}");
25242 f.run(&[b"SET", key.as_bytes(), b"v"]);
25243 }
25244
25245 let mut seen = Vec::new();
25246 let mut cursor = "0".to_owned();
25247 let mut calls = 0;
25248 loop {
25249 let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
25250 let (next, keys) = scan_reply(&reply);
25251 seen.extend(keys);
25252 cursor = next;
25253 calls += 1;
25254 assert!(calls < 5_000, "a scan that will not finish");
25255 if cursor == "0" {
25256 break;
25257 }
25258 }
25259 seen.sort();
25260 assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
25261 assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
25262
25263 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
25267 let (_, keys) = scan_reply(&reply);
25268 assert_eq!(keys.len(), 10, "key:40 through key:49");
25269 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
25270 let (_, keys) = scan_reply(&reply);
25271 assert!(keys.is_empty(), "nothing here is a list");
25272 }
25273
25274 #[test]
25280 fn a_random_key_can_come_from_any_stripe() {
25281 let mut f = Fixture::striped(8);
25282 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
25283 for i in 0..200 {
25284 let key = format!("key:{i}");
25285 f.run(&[b"SET", key.as_bytes(), b"v"]);
25286 }
25287 let mut homes = std::collections::HashSet::new();
25288 for _ in 0..200 {
25289 let got = f.run(&[b"RANDOMKEY"]);
25290 let key = got.split("\r\n").nth(1).expect("a key").to_owned();
25291 assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
25292 homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
25293 }
25294 assert_eq!(homes.len(), 8, "some stripe was never drawn from");
25295 }
25296
25297 fn apart(f: &mut Fixture, src: &str) -> String {
25301 let home = f.server.striped(0).stripe_of(src.as_bytes());
25302 for i in 0..1_000 {
25303 let dst = format!("dst:{i}");
25304 if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
25305 return dst;
25306 }
25307 }
25308 panic!("eight stripes and a thousand keys all landed in one place");
25309 }
25310
25311 #[test]
25314 fn a_rename_across_stripes_takes_everything_with_it() {
25315 let mut f = Fixture::striped(8);
25316 let dst = apart(&mut f, "src");
25317 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
25318
25319 f.run(&[b"SET", src, b"v"]);
25320 f.run(&[b"EXPIRE", src, b"100"]);
25321 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
25322 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
25323 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
25324 assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
25325
25326 f.server
25331 .striped(0)
25332 .hold(src)
25333 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
25334 .expect("a new list");
25335 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
25336 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
25337 assert_eq!(
25338 f.server.striped(0).hold(dst).llen(dst).expect("a list"),
25339 2,
25340 "the members are on the stripe the key moved to"
25341 );
25342
25343 f.run(&[b"SET", src, b"v"]);
25346 assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
25347 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
25348 assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
25349 }
25350
25351 #[test]
25353 fn a_copy_across_stripes_leaves_the_source_where_it_was() {
25354 let mut f = Fixture::striped(8);
25355 let dst = apart(&mut f, "src");
25356 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
25357
25358 f.run(&[b"SET", src, b"v"]);
25359 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
25360 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
25361 assert_eq!(
25362 f.run(&[b"COPY", src, dst]),
25363 ":0\r\n",
25364 "the destination is taken"
25365 );
25366 f.run(&[b"SET", src, b"w"]);
25367 assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
25368 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
25369
25370 f.run(&[b"DEL", src, dst]);
25374 f.server
25375 .striped(0)
25376 .hold(src)
25377 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
25378 .expect("a new list");
25379 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
25380 f.server
25381 .striped(0)
25382 .hold(src)
25383 .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
25384 .expect("a list that is there");
25385 assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
25386 assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
25387 }
25388
25389 #[test]
25397 fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
25398 let script: &[&[&[u8]]] = &[
25399 &[b"SET", b"k1", b"foobar"],
25400 &[b"SETBIT", b"b1", b"7", b"1"],
25401 &[b"SETBIT", b"b1", b"7", b"0"],
25402 &[b"GETBIT", b"k1", b"6"],
25403 &[b"GETBIT", b"k1", b"100"],
25404 &[b"BITCOUNT", b"k1"],
25405 &[b"BITCOUNT", b"k1", b"0", b"0"],
25406 &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
25407 &[b"BITPOS", b"k1", b"1"],
25408 &[b"BITPOS", b"k1", b"0", b"2"],
25409 &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
25410 &[
25411 b"BITFIELD",
25412 b"bf",
25413 b"SET",
25414 b"u8",
25415 b"0",
25416 b"255",
25417 b"GET",
25418 b"u8",
25419 b"0",
25420 ],
25421 &[
25422 b"BITFIELD",
25423 b"bf",
25424 b"OVERFLOW",
25425 b"SAT",
25426 b"INCRBY",
25427 b"u8",
25428 b"0",
25429 b"10",
25430 ],
25431 &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
25432 &[b"SET", b"s1", b"abc"],
25435 &[b"SET", b"s2", b"abd"],
25436 &[b"SET", b"s3", b"a"],
25437 &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
25438 &[b"GET", b"d1"],
25439 &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
25440 &[b"GET", b"d2"],
25441 &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
25442 &[b"STRLEN", b"d3"],
25443 &[b"BITOP", b"NOT", b"d4", b"s1"],
25444 &[b"STRLEN", b"d4"],
25445 &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
25446 &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
25447 &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
25448 &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
25449 &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
25452 &[b"EXISTS", b"d1"],
25453 &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
25454 &[b"GET", b"d9"],
25455 &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
25459 &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
25460 &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
25461 &[b"BITOP", b"DIFF", b"d1", b"s1"],
25462 &[b"BITOP", b"NOPE", b"d1", b"s1"],
25463 &[b"BITCOUNT", b"list"],
25464 &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
25465 ];
25466
25467 let mut one = Fixture::new();
25468 let mut many = Fixture::striped(8);
25469 for f in [&mut one, &mut many] {
25470 plant_list(f, b"list");
25471 }
25472 for parts in script {
25473 let a = one.run(parts);
25474 let b = many.run(parts);
25475 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25476 }
25477 }
25478
25479 fn plant_list(f: &mut Fixture, key: &[u8]) {
25485 f.server
25486 .striped(0)
25487 .hold(key)
25488 .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
25489 .expect("a new list");
25490 }
25491
25492 #[test]
25499 fn a_bitop_across_stripes_reads_every_source() {
25500 let mut f = Fixture::striped(8);
25501 let other = apart(&mut f, "src");
25502 let (src, far) = (b"src".as_slice(), other.as_bytes());
25503 assert_ne!(
25504 f.server.striped(0).stripe_of(src),
25505 f.server.striped(0).stripe_of(far),
25506 "the two keys are the point of the test"
25507 );
25508
25509 f.run(&[b"SET", src, b"abc"]);
25510 f.run(&[b"SET", far, b"abd"]);
25511 assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
25512 assert_eq!(
25513 f.run(&[b"GET", far]),
25514 "$3\r\nab`\r\n",
25515 "a destination that is also a source"
25516 );
25517 f.run(&[b"SET", far, b"abd"]);
25518 assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
25519 assert_eq!(
25520 f.run(&[b"GET", src]),
25521 "$3\r\n\0\0\x07\r\n",
25522 "and the other way round"
25523 );
25524
25525 f.run(&[b"SET", src, b"abc"]);
25529 f.run(&[b"DEL", far]);
25530 assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
25531 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
25532 f.run(&[b"SET", src, b"abc"]);
25533 f.run(&[b"DEL", far]);
25534 plant_list(&mut f, far);
25535 assert_eq!(
25536 f.run(&[b"BITOP", b"OR", b"out", src, far]),
25537 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25538 );
25539 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
25540 }
25541
25542 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
25548 #[test]
25549 fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
25550 let script: &[&[&[u8]]] = &[
25551 &[b"PFADD", b"h1", b"a", b"b", b"c"],
25552 &[b"PFADD", b"h1", b"a"],
25553 &[b"PFADD", b"h2"],
25554 &[b"PFADD", b"h2", b"c", b"d", b"e"],
25555 &[b"PFCOUNT", b"h1"],
25556 &[b"PFCOUNT", b"h2"],
25557 &[b"PFCOUNT", b"missing"],
25558 &[b"PFCOUNT", b"h1", b"h2"],
25560 &[b"PFCOUNT", b"h1", b"missing"],
25561 &[b"PFMERGE", b"m", b"h1", b"h2"],
25562 &[b"PFCOUNT", b"m"],
25563 &[b"STRLEN", b"m"],
25564 &[b"PFMERGE", b"m"],
25565 &[b"PFCOUNT", b"m"],
25566 &[b"PFMERGE", b"m2", b"missing"],
25567 &[b"PFCOUNT", b"m2"],
25568 &[b"PFDEBUG", b"ENCODING", b"h1"],
25571 &[b"PFDEBUG", b"DECODE", b"h1"],
25572 &[b"PFDEBUG", b"TODENSE", b"h1"],
25573 &[b"PFDEBUG", b"ENCODING", b"h1"],
25574 &[b"PFDEBUG", b"TODENSE", b"h1"],
25575 &[b"PFCOUNT", b"h1", b"h2"],
25576 &[b"PFSELFTEST"],
25577 &[b"SET", b"plain", b"not a sketch at all"],
25579 &[b"PFADD", b"plain", b"a"],
25580 &[b"PFCOUNT", b"plain"],
25581 &[b"PFCOUNT", b"h1", b"plain"],
25582 &[b"PFMERGE", b"plain", b"h1"],
25583 &[b"PFMERGE", b"m", b"plain"],
25584 &[b"PFDEBUG", b"ENCODING", b"gone"],
25585 &[b"PFDEBUG", b"NOPE", b"h1"],
25586 ];
25587
25588 let mut one = Fixture::new();
25589 let mut many = Fixture::striped(8);
25590 for parts in script {
25591 let a = one.run(parts);
25592 let b = many.run(parts);
25593 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25594 }
25595 }
25596
25597 #[test]
25606 fn the_set_group_answers_the_same_however_many_stripes_there_are() {
25607 const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
25608 let script: &[&[&[u8]]] = &[
25609 &[b"SADD", b"s1", b"a", b"b", b"c"],
25610 &[b"SADD", b"s1", b"a"],
25611 &[b"SADD", b"s2", b"b", b"c", b"d"],
25612 &[b"SADD", b"ints", b"1", b"2", b"3"],
25613 &[b"SCARD", b"s1"],
25614 &[b"SISMEMBER", b"s1", b"a"],
25615 &[b"SISMEMBER", b"s1", b"z"],
25616 &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
25617 &[b"SMEMBERS", b"s1"],
25618 &[b"SREM", b"s1", b"c"],
25619 &[b"SADD", b"s1", b"c"],
25620 &[b"SSCAN", b"s1", b"0"],
25621 &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
25622 &[b"SADD", b"one", b"m"],
25625 &[b"SRANDMEMBER", b"one"],
25626 &[b"SRANDMEMBER", b"one", b"-3"],
25627 &[b"SRANDMEMBER", b"gone"],
25628 &[b"SPOP", b"one"],
25629 &[b"SPOP", b"one"],
25630 &[b"SPOP", b"gone", b"2"],
25631 &[b"SMOVE", b"s1", b"s2", b"a"],
25633 &[b"SMOVE", b"s1", b"s2", b"zzz"],
25634 &[b"SMOVE", b"gone", b"s2", b"a"],
25635 &[b"SMEMBERS", b"s1"],
25636 &[b"SMEMBERS", b"s2"],
25637 &[b"SINTER", b"s1", b"s2"],
25639 &[b"SUNION", b"s1", b"s2"],
25640 &[b"SDIFF", b"s2", b"s1"],
25641 &[b"SINTER", b"s1", b"gone"],
25642 &[b"SUNION", b"s1", b"gone"],
25643 &[b"SDIFF", b"gone", b"s1"],
25644 &[b"SINTER", b"ints", b"s1"],
25645 &[b"SINTERCARD", b"2", b"s1", b"s2"],
25646 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
25647 &[b"SUNIONCARD", b"2", b"s1", b"s2"],
25648 &[b"SDIFFCARD", b"2", b"s2", b"s1"],
25649 &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
25650 &[b"SMEMBERS", b"d1"],
25651 &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
25652 &[b"SCARD", b"d2"],
25653 &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
25654 &[b"SCARD", b"d3"],
25655 &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
25658 &[b"EXISTS", b"d4"],
25659 &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
25661 &[b"SCARD", b"s2"],
25662 &[b"SET", b"str", b"v"],
25664 &[b"SADD", b"str", b"a"],
25665 &[b"SINTER", b"s1", b"str"],
25666 &[b"SINTERSTORE", b"d5", b"s1", b"str"],
25667 &[b"EXISTS", b"d5"],
25668 &[b"SMOVE", b"str", b"s2", b"a"],
25669 &[b"SMOVE", b"s1", b"str", b"b"],
25670 &[b"SMOVE", b"gone", b"str", b"b"],
25671 &[b"SINTERCARD", b"0", b"s1"],
25672 &[b"SINTERCARD", b"3", b"s1", b"s2"],
25673 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
25674 &[b"SPOP", b"s1", b"-1"],
25675 ];
25676
25677 let mut one = Fixture::new();
25678 let mut many = Fixture::striped(8);
25679 for parts in script {
25680 let a = one.run(parts);
25681 let b = many.run(parts);
25682 let name = String::from_utf8_lossy(parts[0]).to_uppercase();
25683 if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
25684 assert_eq!(sorted(&a), sorted(&b), "{name}");
25685 } else {
25686 assert_eq!(a, b, "{name}");
25687 }
25688 }
25689 }
25690
25691 #[test]
25693 fn a_set_operation_across_stripes_reads_every_set() {
25694 let mut f = Fixture::striped(8);
25695 let second = apart(&mut f, "s1");
25696 let third = apart(&mut f, &second);
25697 let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
25698
25699 f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
25700 f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
25701 assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
25702 assert_eq!(
25703 sorted(&f.run(&[b"SUNION", s1, s2])),
25704 ["a", "b", "c", "d"],
25705 "a union of two stripes is both of them"
25706 );
25707 assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
25708 assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
25709 assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
25710 assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
25711
25712 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
25714 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
25715 assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
25716 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
25717 assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
25718 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
25719
25720 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
25723 assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
25724 f.run(&[b"SET", s3, b"v"]);
25725 assert_eq!(
25726 f.run(&[b"SINTER", s1, s3]),
25727 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25728 );
25729 assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
25730 }
25731
25732 #[test]
25734 fn a_move_across_stripes_takes_the_member_with_it() {
25735 let mut f = Fixture::striped(8);
25736 let other = apart(&mut f, "src");
25737 let (src, dst) = (b"src".as_slice(), other.as_bytes());
25738
25739 f.run(&[b"SADD", src, b"a", b"b"]);
25740 f.run(&[b"SADD", dst, b"c"]);
25741 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
25742 assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
25743 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
25744 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
25745
25746 f.run(&[b"DEL", dst]);
25749 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
25750 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
25751 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
25752
25753 f.run(&[b"SET", dst, b"v"]);
25757 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
25758 f.run(&[b"SADD", src, b"b"]);
25759 assert_eq!(
25760 f.run(&[b"SMOVE", src, dst, b"b"]),
25761 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25762 );
25763 }
25764
25765 #[test]
25767 fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
25768 let mut f = Fixture::striped(8);
25769 let other = apart(&mut f, "src");
25770 let (src, far) = (b"src".as_slice(), other.as_bytes());
25771
25772 for i in 0..150 {
25773 let ele = format!("e:{i}");
25774 f.run(&[b"PFADD", src, ele.as_bytes()]);
25775 }
25776 for i in 150..200 {
25777 let ele = format!("e:{i}");
25778 f.run(&[b"PFADD", far, ele.as_bytes()]);
25779 }
25780 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
25783 assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
25784 assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
25785
25786 let dest = apart(&mut f, &other);
25789 assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
25790 assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
25791 assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
25792 assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
25793 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
25794 }
25795
25796 #[test]
25803 fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
25804 let script: &[&[&[u8]]] = &[
25805 &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
25806 &[b"ZADD", b"z1", b"NX", b"9", b"a"],
25807 &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
25808 &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
25809 &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
25810 &[b"ZINCRBY", b"z1", b"1.5", b"b"],
25811 &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
25812 &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
25813 &[b"ZADD", b"one", b"1", b"m"],
25814 &[b"ZCARD", b"z1"],
25815 &[b"ZCARD", b"gone"],
25816 &[b"ZSCORE", b"z1", b"a"],
25817 &[b"ZSCORE", b"z1", b"zz"],
25818 &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
25819 &[b"ZRANK", b"z1", b"c"],
25820 &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
25821 &[b"ZREVRANK", b"z1", b"c"],
25822 &[b"ZRANK", b"z1", b"gone"],
25823 &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
25824 &[b"ZCOUNT", b"z1", b"(1", b"3"],
25825 &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
25826 &[b"ZRANGE", b"z1", b"0", b"-1"],
25828 &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
25829 &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
25830 &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
25831 &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
25832 &[b"ZREVRANGE", b"z1", b"0", b"-1"],
25833 &[
25834 b"ZRANGEBYSCORE",
25835 b"z1",
25836 b"-inf",
25837 b"+inf",
25838 b"LIMIT",
25839 b"1",
25840 b"1",
25841 ],
25842 &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
25843 &[b"ZSCAN", b"z1", b"0"],
25844 &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
25845 &[b"ZRANDMEMBER", b"one"],
25848 &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
25849 &[b"ZRANDMEMBER", b"gone"],
25850 &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
25852 &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
25853 &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
25854 &[b"EXISTS", b"d0"],
25855 &[b"ZUNION", b"2", b"z1", b"z2"],
25857 &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
25858 &[
25859 b"ZUNION",
25860 b"2",
25861 b"z1",
25862 b"z2",
25863 b"WEIGHTS",
25864 b"2",
25865 b"3",
25866 b"AGGREGATE",
25867 b"MAX",
25868 b"WITHSCORES",
25869 ],
25870 &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
25871 &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
25872 &[b"ZDIFF", b"2", b"gone", b"z1"],
25873 &[b"ZINTERCARD", b"2", b"z1", b"z2"],
25874 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
25875 &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
25876 &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
25877 &[
25878 b"ZINTERSTORE",
25879 b"d2",
25880 b"2",
25881 b"z1",
25882 b"z2",
25883 b"AGGREGATE",
25884 b"MIN",
25885 ],
25886 &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
25887 &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
25888 &[b"ZCARD", b"d3"],
25889 &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
25892 &[b"EXISTS", b"d4"],
25893 &[b"SADD", b"plain", b"a", b"x"],
25896 &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
25897 &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
25898 &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
25900 &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
25901 &[b"ZREM", b"d5", b"x", b"nothere"],
25903 &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
25904 &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
25905 &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
25906 &[b"ZPOPMIN", b"z1"],
25907 &[b"ZPOPMAX", b"z1", b"2"],
25908 &[b"ZPOPMIN", b"gone"],
25909 &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
25910 &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
25911 &[b"SET", b"str", b"v"],
25913 &[b"ZADD", b"str", b"1", b"a"],
25914 &[b"ZSCORE", b"str", b"a"],
25915 &[b"ZADD", b"z1", b"nan", b"a"],
25916 &[b"ZUNION", b"2", b"z1", b"str"],
25917 &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
25918 &[b"EXISTS", b"d6"],
25919 &[b"ZINTERCARD", b"0", b"z1"],
25920 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
25921 &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
25922 &[b"ZMPOP", b"1", b"str", b"MIN"],
25923 &[b"ZPOPMIN", b"z1", b"-1"],
25924 ];
25925
25926 let mut one = Fixture::new();
25927 let mut many = Fixture::striped(8);
25928 for parts in script {
25929 let a = one.run(parts);
25930 let b = many.run(parts);
25931 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25932 }
25933 }
25934
25935 #[test]
25937 fn a_sorted_set_operation_across_stripes_reads_every_input() {
25938 let mut f = Fixture::striped(8);
25939 let second = apart(&mut f, "z1");
25940 let third = apart(&mut f, &second);
25941 let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
25942
25943 f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
25944 f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
25945 assert_eq!(
25948 f.run(&[b"ZUNION", b"2", z1, z2]),
25949 "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
25950 );
25951 assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
25952 assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
25953 assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
25954 assert_eq!(
25955 f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
25956 ":1\r\n"
25957 );
25958
25959 assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
25962 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
25963 assert_eq!(
25964 f.run(&[
25965 b"ZUNIONSTORE",
25966 z3,
25967 b"2",
25968 z1,
25969 z2,
25970 b"WEIGHTS",
25971 b"2",
25972 b"3",
25973 b"AGGREGATE",
25974 b"MAX"
25975 ]),
25976 ":3\r\n"
25977 );
25978 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
25979 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
25980 assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
25981 assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
25982 assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
25983
25984 let popped = format!(
25987 "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
25988 second.len()
25989 );
25990 assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
25991 f.run(&[b"ZADD", z2, b"3", b"b"]);
25992
25993 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
25996 assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
25997 f.run(&[b"SET", z3, b"v"]);
25998 assert_eq!(
25999 f.run(&[b"ZUNION", b"2", z1, z3]),
26000 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26001 );
26002 assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
26003
26004 assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
26008 assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
26009 assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
26010 }
26011
26012 #[test]
26014 fn a_range_store_across_stripes_copies_the_window() {
26015 let mut f = Fixture::striped(8);
26016 let other = apart(&mut f, "src");
26017 let third = apart(&mut f, &other);
26018 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
26019
26020 f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
26021 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
26022 assert_eq!(
26023 f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
26024 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
26025 );
26026 assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
26027
26028 assert_eq!(
26031 f.run(&[
26032 b"ZRANGESTORE",
26033 dst,
26034 src,
26035 b"+inf",
26036 b"-inf",
26037 b"BYSCORE",
26038 b"REV",
26039 b"LIMIT",
26040 b"0",
26041 b"2"
26042 ]),
26043 ":2\r\n"
26044 );
26045 assert_eq!(
26046 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
26047 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
26048 );
26049
26050 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
26053 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
26054 f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
26055 f.run(&[b"SET", plain, b"v"]);
26056 assert_eq!(
26057 f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
26058 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26059 );
26060 assert_eq!(
26061 f.run(&[b"ZCARD", dst]),
26062 ":3\r\n",
26063 "and left the destination"
26064 );
26065 }
26066
26067 #[test]
26074 fn the_list_group_answers_the_same_however_many_stripes_there_are() {
26075 let script: &[&[&[u8]]] = &[
26076 &[b"RPUSH", b"l1", b"a", b"b", b"c"],
26077 &[b"LPUSH", b"l1", b"z"],
26078 &[b"RPUSHX", b"l1", b"d"],
26079 &[b"LPUSHX", b"gone", b"x"],
26080 &[b"RPUSHX", b"gone", b"x"],
26081 &[b"LLEN", b"l1"],
26082 &[b"LLEN", b"gone"],
26083 &[b"LRANGE", b"l1", b"0", b"-1"],
26084 &[b"LRANGE", b"l1", b"1", b"2"],
26085 &[b"LRANGE", b"l1", b"5", b"9"],
26086 &[b"LINDEX", b"l1", b"0"],
26087 &[b"LINDEX", b"l1", b"-1"],
26088 &[b"LINDEX", b"l1", b"99"],
26089 &[b"LSET", b"l1", b"0", b"y"],
26090 &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
26091 &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
26092 &[b"LPOS", b"l1", b"b"],
26093 &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
26094 &[b"LPOS", b"l1", b"nothere"],
26095 &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
26096 &[b"LREM", b"l1", b"1", b"aa"],
26097 &[b"LTRIM", b"l1", b"0", b"3"],
26098 &[b"LRANGE", b"l1", b"0", b"-1"],
26099 &[b"LPOP", b"l1"],
26100 &[b"RPOP", b"l1"],
26101 &[b"LPOP", b"l1", b"2"],
26102 &[b"LPOP", b"gone"],
26103 &[b"LPOP", b"gone", b"2"],
26104 &[b"EXISTS", b"l1"],
26105 &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
26108 &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
26109 &[b"RPOPLPUSH", b"src", b"dst"],
26110 &[b"LRANGE", b"dst", b"0", b"-1"],
26111 &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
26112 &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
26113 &[
26114 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
26115 ],
26116 &[
26117 b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
26118 ],
26119 &[b"LRANGE", b"dst", b"0", b"-1"],
26120 &[
26121 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
26122 ],
26123 &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
26124 &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
26125 &[b"LMPOP", b"1", b"gone", b"LEFT"],
26126 &[b"RPUSH", b"q", b"a", b"b", b"c"],
26129 &[b"BLPOP", b"gone", b"q", b"0"],
26130 &[b"BRPOP", b"q", b"0"],
26131 &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
26132 &[b"RPUSH", b"q", b"x", b"y", b"z"],
26133 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
26134 &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
26135 &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
26136 &[b"BLPOP", b"q", b"0"],
26137 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
26138 &[b"SET", b"plain", b"v"],
26140 &[b"LPUSH", b"plain", b"a"],
26141 &[b"LLEN", b"plain"],
26142 &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
26143 &[b"LRANGE", b"dst", b"0", b"-1"],
26144 &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
26145 &[b"LSET", b"gone", b"0", b"v"],
26146 &[b"LSET", b"dst", b"99", b"v"],
26147 &[b"LPOP", b"dst", b"-1"],
26148 &[b"LMPOP", b"0", b"dst", b"LEFT"],
26149 &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
26150 ];
26151
26152 let mut one = Fixture::new();
26153 let mut many = Fixture::striped(8);
26154 for parts in script {
26155 let a = one.run(parts);
26156 let b = many.run(parts);
26157 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26158 }
26159 }
26160
26161 #[test]
26163 fn a_list_move_across_stripes_takes_the_elements_with_it() {
26164 let mut f = Fixture::striped(8);
26165 let other = apart(&mut f, "src");
26166 let third = apart(&mut f, &other);
26167 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
26168
26169 f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
26170 assert_eq!(
26171 f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
26172 "$1\r\na\r\n"
26173 );
26174 assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
26175 assert_eq!(
26176 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
26177 "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
26178 "one went on each end of the destination"
26179 );
26180 assert_eq!(
26181 f.run(&[b"LRANGE", src, b"0", b"-1"]),
26182 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
26183 );
26184
26185 assert_eq!(
26187 f.run(&[
26188 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
26189 ]),
26190 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
26191 );
26192 assert_eq!(
26193 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
26194 "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
26195 );
26196 assert_eq!(
26197 f.run(&[b"EXISTS", src]),
26198 ":0\r\n",
26199 "and the source is gone with its last element"
26200 );
26201
26202 f.run(&[b"RPUSH", src, b"e", b"f"]);
26205 assert_eq!(
26206 f.run(&[
26207 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
26208 ]),
26209 "*-1\r\n"
26210 );
26211 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
26212 assert_eq!(
26213 f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
26214 "$-1\r\n"
26215 );
26216 assert_eq!(
26217 f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
26218 "*-1\r\n"
26219 );
26220
26221 f.run(&[b"SET", plain, b"v"]);
26225 assert_eq!(
26226 f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
26227 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26228 );
26229 assert_eq!(
26230 f.run(&[b"LLEN", src]),
26231 ":2\r\n",
26232 "and left the source alone"
26233 );
26234 assert_eq!(
26235 f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
26236 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26237 );
26238 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
26239 }
26240
26241 #[test]
26247 fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
26248 let mut f = Fixture::striped(8);
26249 let other = apart(&mut f, "q");
26250 let (q, far) = (b"q".as_slice(), other.as_bytes());
26251
26252 assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
26253 assert_eq!(f.server.parked(), 1);
26254 f.run(&[b"RPUSH", far, b"v"]);
26255 let mut out = Out::new(Proto::Resp2);
26256 assert!(f.server.serve_waiter(7, 0, &mut out));
26257 let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
26258 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
26259 assert_eq!(
26260 f.run(&[b"EXISTS", far]),
26261 ":0\r\n",
26262 "and it took the element with it"
26263 );
26264
26265 f.server.forget_waiters(7);
26268 assert_eq!(
26269 f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
26270 Flow::Block
26271 );
26272 f.run(&[b"RPUSH", q, b"w"]);
26273 let mut out = Out::new(Proto::Resp2);
26274 assert!(f.server.serve_waiter(7, 0, &mut out));
26275 assert_eq!(
26276 core::str::from_utf8(out.as_slice()).expect("ascii"),
26277 "$1\r\nw\r\n"
26278 );
26279 assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
26280 }
26281
26282 #[test]
26288 fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
26289 let script: &[&[&[u8]]] = &[
26290 &[b"XADD", b"s", b"1-1", b"a", b"1"],
26291 &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
26292 &[b"XADD", b"s", b"3-1", b"d", b"4"],
26293 &[b"XADD", b"s", b"1-1", b"e", b"5"],
26294 &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
26295 &[b"XLEN", b"s"],
26296 &[b"XLEN", b"gone"],
26297 &[b"XRANGE", b"s", b"-", b"+"],
26298 &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
26299 &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
26300 &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
26301 &[b"XREVRANGE", b"s", b"+", b"-"],
26302 &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
26303 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
26304 &[b"XREAD", b"STREAMS", b"s", b"$"],
26305 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
26307 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
26308 &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
26309 &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
26310 &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
26311 &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
26312 &[
26313 b"XREADGROUP",
26314 b"GROUP",
26315 b"g",
26316 b"c1",
26317 b"COUNT",
26318 b"1",
26319 b"STREAMS",
26320 b"s",
26321 b"0",
26322 ],
26323 &[
26324 b"XREADGROUP",
26325 b"GROUP",
26326 b"nope",
26327 b"c1",
26328 b"STREAMS",
26329 b"s",
26330 b">",
26331 ],
26332 &[b"XPENDING", b"s", b"g"],
26333 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
26334 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
26335 &[b"XPENDING", b"s", b"nope"],
26336 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
26337 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
26338 &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
26339 &[b"XACK", b"s", b"g", b"1-1"],
26340 &[b"XACK", b"s", b"g", b"1-1"],
26341 &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
26342 &[b"XPENDING", b"s", b"g"],
26343 &[b"XINFO", b"STREAM", b"s"],
26344 &[b"XINFO", b"GROUPS", b"s"],
26345 &[b"XINFO", b"CONSUMERS", b"s", b"g"],
26346 &[b"XINFO", b"STREAM", b"gone"],
26347 &[b"XDEL", b"s", b"3-1"],
26349 &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
26350 &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
26351 &[b"XADD", b"s", b"9-1", b"z", b"9"],
26352 &[b"XTRIM", b"s", b"MAXLEN", b"1"],
26353 &[b"XTRIM", b"s", b"MINID", b"9"],
26354 &[b"XSETID", b"s", b"99-1"],
26355 &[b"XSETID", b"s", b"1-1"],
26356 &[b"XLEN", b"s"],
26357 &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
26358 &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
26359 &[b"XGROUP", b"DESTROY", b"s", b"g"],
26360 &[b"XGROUP", b"DESTROY", b"s", b"g"],
26361 &[b"SET", b"plain", b"v"],
26363 &[b"XADD", b"plain", b"1-1", b"a", b"1"],
26364 &[b"XLEN", b"plain"],
26365 &[b"XREAD", b"STREAMS", b"plain", b"0"],
26366 &[b"XRANGE", b"s", b"bogus", b"+"],
26367 &[b"XADD", b"s", b"1-1", b"a"],
26368 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
26369 &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
26370 ];
26371
26372 let mut one = Fixture::new();
26373 let mut many = Fixture::striped(8);
26374 for parts in script {
26375 let a = one.run(parts);
26376 let b = many.run(parts);
26377 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26378 }
26379 }
26380
26381 #[test]
26387 fn a_stream_read_across_stripes_reads_every_key() {
26388 let mut f = Fixture::striped(8);
26389 let other = apart(&mut f, "s1");
26390 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
26391
26392 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
26393 f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
26394 let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
26395 assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
26396 assert!(got.contains("1-1"), "the first one is in there: {got}");
26397 assert!(got.contains("2-1"), "and so is the second: {got}");
26398
26399 f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
26402 let got = f.run(&[
26403 b"XREADGROUP",
26404 b"GROUP",
26405 b"g",
26406 b"c",
26407 b"STREAMS",
26408 s1,
26409 s2,
26410 b">",
26411 b">",
26412 ]);
26413 assert!(got.starts_with("-NOGROUP"), "{got}");
26414 assert_eq!(
26415 f.run(&[b"XPENDING", s1, b"g"]),
26416 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
26417 "and read nothing from the key that did have the group"
26418 );
26419
26420 f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
26421 let got = f.run(&[
26422 b"XREADGROUP",
26423 b"GROUP",
26424 b"g",
26425 b"c",
26426 b"STREAMS",
26427 s1,
26428 s2,
26429 b">",
26430 b">",
26431 ]);
26432 assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
26433 }
26434
26435 #[test]
26437 fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
26438 let mut f = Fixture::striped(8);
26439 let other = apart(&mut f, "s1");
26440 let (s1, far) = (b"s1".as_slice(), other.as_bytes());
26441 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
26442 f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
26443
26444 assert_eq!(
26445 f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
26446 .0,
26447 Flow::Block
26448 );
26449 f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
26450 let mut out = Out::new(Proto::Resp2);
26451 assert!(f.server.serve_waiter(7, 0, &mut out));
26452 let want = format!(
26453 "*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",
26454 other.len()
26455 );
26456 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
26457 }
26458
26459 #[test]
26461 fn the_json_group_answers_the_same_however_many_stripes_there_are() {
26462 let script: &[&[&[u8]]] = &[
26463 &[
26464 b"JSON.SET",
26465 b"d",
26466 b"$",
26467 br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
26468 ],
26469 &[b"JSON.SET", b"d", b"$.a", b"2"],
26470 &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
26471 &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
26472 &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
26473 &[b"JSON.GET", b"d"],
26474 &[b"JSON.GET", b"d", b"$.b"],
26475 &[b"JSON.GET", b"gone", b"$"],
26476 &[b"JSON.TYPE", b"d", b"$.b"],
26477 &[b"JSON.TYPE", b"d", b"$.s"],
26478 &[b"JSON.TOGGLE", b"d", b"$.t"],
26479 &[b"JSON.ARRLEN", b"d", b"$.b"],
26480 &[b"JSON.OBJLEN", b"d", b"$"],
26481 &[b"JSON.OBJKEYS", b"d", b"$"],
26482 &[b"JSON.STRLEN", b"d", b"$.s"],
26483 &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
26484 &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
26485 &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
26486 &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
26487 &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
26488 &[b"JSON.ARRPOP", b"d", b"$.b"],
26489 &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
26490 &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
26491 &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
26492 &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
26493 &[b"JSON.RESP", b"d", b"$.b"],
26494 &[b"JSON.DEBUG", b"MEMORY", b"d"],
26495 &[b"JSON.CLEAR", b"d", b"$.b"],
26496 &[b"JSON.DEL", b"d", b"$.m"],
26497 &[b"JSON.FORGET", b"d", b"$.nothere"],
26498 &[
26500 b"JSON.MSET",
26501 b"m1",
26502 b"$",
26503 b"1",
26504 b"m2",
26505 b"$",
26506 b"2",
26507 b"m3",
26508 b"$",
26509 b"3",
26510 ],
26511 &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
26512 &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
26513 &[b"JSON.GET", b"m1", b"$"],
26514 &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
26515 &[b"JSON.GET", b"m2", b"$"],
26516 &[b"SET", b"plain", b"v"],
26518 &[b"JSON.GET", b"plain", b"$"],
26519 &[b"JSON.SET", b"plain", b"$", b"1"],
26520 &[b"JSON.MGET", b"m1", b"plain", b"$"],
26521 &[b"JSON.SET", b"d", b"$.b", b"["],
26522 &[b"JSON.DEL", b"plain"],
26523 ];
26524
26525 let mut one = Fixture::new();
26526 let mut many = Fixture::striped(8);
26527 for parts in script {
26528 let a = one.run(parts);
26529 let b = many.run(parts);
26530 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26531 }
26532 }
26533
26534 #[test]
26541 fn a_json_multi_write_across_stripes_reaches_every_key() {
26542 let mut f = Fixture::striped(8);
26543 let second = apart(&mut f, "m1");
26544 let third = apart(&mut f, &second);
26545 let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
26546
26547 assert_eq!(
26548 f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
26549 "+OK\r\n"
26550 );
26551 assert_eq!(
26552 f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
26553 "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
26554 );
26555
26556 assert_eq!(
26559 f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
26560 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
26561 );
26562 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
26563
26564 assert_eq!(
26568 f.run(&[
26569 b"JSON.MSET",
26570 m1,
26571 b"$",
26572 b"9",
26573 m2,
26574 b"$.deep",
26575 b"9",
26576 m3,
26577 b"$",
26578 b"7"
26579 ]),
26580 "$-1\r\n"
26581 );
26582 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
26583 assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
26584 assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
26585 }
26586
26587 #[test]
26589 fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
26590 let script: &[&[&[u8]]] = &[
26591 &[
26592 b"GEOADD",
26593 b"g",
26594 b"13.361389",
26595 b"38.115556",
26596 b"palermo",
26597 b"15.087269",
26598 b"37.502669",
26599 b"catania",
26600 ],
26601 &[
26602 b"GEOADD",
26603 b"g",
26604 b"NX",
26605 b"13.361389",
26606 b"38.115556",
26607 b"palermo",
26608 ],
26609 &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
26610 &[b"GEOPOS", b"g", b"palermo", b"nothere"],
26611 &[b"GEOHASH", b"g", b"palermo", b"catania"],
26612 &[b"GEODIST", b"g", b"palermo", b"catania"],
26613 &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
26614 &[b"GEODIST", b"g", b"palermo", b"nothere"],
26615 &[
26616 b"GEOSEARCH",
26617 b"g",
26618 b"FROMLONLAT",
26619 b"15",
26620 b"37",
26621 b"BYRADIUS",
26622 b"200",
26623 b"KM",
26624 b"ASC",
26625 b"WITHCOORD",
26626 b"WITHDIST",
26627 b"WITHHASH",
26628 ],
26629 &[
26630 b"GEOSEARCH",
26631 b"g",
26632 b"FROMMEMBER",
26633 b"palermo",
26634 b"BYBOX",
26635 b"400",
26636 b"400",
26637 b"KM",
26638 b"DESC",
26639 ],
26640 &[
26641 b"GEORADIUS",
26642 b"g",
26643 b"15",
26644 b"37",
26645 b"200",
26646 b"KM",
26647 b"COUNT",
26648 b"1",
26649 ],
26650 &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
26651 &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
26652 &[
26653 b"GEOSEARCHSTORE",
26654 b"dst",
26655 b"g",
26656 b"FROMLONLAT",
26657 b"15",
26658 b"37",
26659 b"BYRADIUS",
26660 b"200",
26661 b"KM",
26662 ],
26663 &[b"ZRANGE", b"dst", b"0", b"-1"],
26664 &[
26665 b"GEOSEARCHSTORE",
26666 b"dst",
26667 b"g",
26668 b"FROMLONLAT",
26669 b"15",
26670 b"37",
26671 b"BYRADIUS",
26672 b"1",
26673 b"M",
26674 b"STOREDIST",
26675 ],
26676 &[b"EXISTS", b"dst"],
26677 &[
26678 b"GEORADIUS",
26679 b"g",
26680 b"15",
26681 b"37",
26682 b"200",
26683 b"KM",
26684 b"STORE",
26685 b"dst",
26686 ],
26687 &[b"ZCARD", b"dst"],
26688 &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
26690 &[b"SET", b"plain", b"v"],
26691 &[b"GEOPOS", b"plain", b"a"],
26692 &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
26693 &[
26694 b"GEOSEARCHSTORE",
26695 b"dst",
26696 b"g",
26697 b"FROMLONLAT",
26698 b"15",
26699 b"37",
26700 b"BYRADIUS",
26701 b"200",
26702 b"KM",
26703 b"WITHCOORD",
26704 ],
26705 ];
26706
26707 let mut one = Fixture::new();
26708 let mut many = Fixture::striped(8);
26709 for parts in script {
26710 let a = one.run(parts);
26711 let b = many.run(parts);
26712 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26713 }
26714 }
26715
26716 #[test]
26718 fn a_geo_search_store_across_stripes_writes_what_it_found() {
26719 let mut f = Fixture::striped(8);
26720 let other = apart(&mut f, "g");
26721 let third = apart(&mut f, &other);
26722 let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
26723
26724 f.run(&[
26725 b"GEOADD",
26726 g,
26727 b"13.361389",
26728 b"38.115556",
26729 b"palermo",
26730 b"15.087269",
26731 b"37.502669",
26732 b"catania",
26733 ]);
26734 assert_eq!(
26735 f.run(&[
26736 b"GEOSEARCHSTORE",
26737 dst,
26738 g,
26739 b"FROMLONLAT",
26740 b"15",
26741 b"37",
26742 b"BYRADIUS",
26743 b"200",
26744 b"KM",
26745 b"ASC",
26746 ]),
26747 ":2\r\n"
26748 );
26749 assert_eq!(
26750 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
26751 "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
26752 "the geohash is the score, so the order is not the search order"
26753 );
26754 assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
26755
26756 assert_eq!(
26759 f.run(&[
26760 b"GEOSEARCHSTORE",
26761 dst,
26762 g,
26763 b"FROMMEMBER",
26764 b"palermo",
26765 b"BYRADIUS",
26766 b"200",
26767 b"KM",
26768 b"STOREDIST",
26769 ]),
26770 ":2\r\n"
26771 );
26772 assert_eq!(
26773 f.run(&[b"ZSCORE", dst, b"palermo"]),
26774 "$1\r\n0\r\n",
26775 "the centre is nought away from itself"
26776 );
26777
26778 assert_eq!(
26782 f.run(&[
26783 b"GEOSEARCHSTORE",
26784 dst,
26785 g,
26786 b"FROMLONLAT",
26787 b"0",
26788 b"0",
26789 b"BYRADIUS",
26790 b"1",
26791 b"M",
26792 ]),
26793 ":0\r\n"
26794 );
26795 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
26796 f.run(&[
26797 b"GEOSEARCHSTORE",
26798 dst,
26799 g,
26800 b"FROMLONLAT",
26801 b"15",
26802 b"37",
26803 b"BYRADIUS",
26804 b"200",
26805 b"KM",
26806 ]);
26807 f.run(&[b"SET", plain, b"v"]);
26808 assert_eq!(
26809 f.run(&[
26810 b"GEOSEARCHSTORE",
26811 dst,
26812 plain,
26813 b"FROMLONLAT",
26814 b"15",
26815 b"37",
26816 b"BYRADIUS",
26817 b"200",
26818 b"KM",
26819 ]),
26820 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26821 );
26822 assert_eq!(
26823 f.run(&[b"ZCARD", dst]),
26824 ":2\r\n",
26825 "and left the destination"
26826 );
26827 }
26828
26829 #[test]
26835 fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
26836 let script: &[&[&[u8]]] = &[
26837 &[
26838 b"TS.CREATE",
26839 b"ts:a",
26840 b"LABELS",
26841 b"sensor",
26842 b"a",
26843 b"room",
26844 b"1",
26845 ],
26846 &[b"TS.CREATE", b"ts:a"],
26847 &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
26848 &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
26849 &[
26850 b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
26851 ],
26852 &[
26853 b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
26854 ],
26855 &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
26856 &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
26857 &[b"TS.GET", b"ts:a"],
26858 &[b"TS.GET", b"gone"],
26859 &[b"TS.RANGE", b"ts:a", b"-", b"+"],
26860 &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
26861 &[
26862 b"TS.RANGE",
26863 b"ts:a",
26864 b"-",
26865 b"+",
26866 b"AGGREGATION",
26867 b"avg",
26868 b"2000",
26869 ],
26870 &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
26871 &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
26872 &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
26873 &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
26874 &[b"TS.READ", b"ts:a", b"0"],
26875 &[b"TS.READ", b"ts:a", b"+"],
26876 &[b"TS.QUERYINDEX", b"sensor=a"],
26878 &[b"TS.QUERYINDEX", b"room=1"],
26879 &[b"TS.QUERYINDEX", b"room=9"],
26880 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
26881 &[
26882 b"TS.QUERYLABELS",
26883 b"VALUES",
26884 b"sensor",
26885 b"FILTER",
26886 b"room=1",
26887 ],
26888 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
26889 &[
26890 b"TS.MGET",
26891 b"SELECTED_LABELS",
26892 b"sensor",
26893 b"FILTER",
26894 b"sensor=a",
26895 ],
26896 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
26897 &[
26898 b"TS.MREVRANGE",
26899 b"-",
26900 b"+",
26901 b"WITHLABELS",
26902 b"FILTER",
26903 b"sensor=a",
26904 ],
26905 &[
26906 b"TS.MRANGE",
26907 b"-",
26908 b"+",
26909 b"FILTER",
26910 b"room=1",
26911 b"GROUPBY",
26912 b"room",
26913 b"REDUCE",
26914 b"max",
26915 ],
26916 &[b"TS.INFO", b"ts:a"],
26917 &[
26919 b"TS.CREATERULE",
26920 b"ts:a",
26921 b"ts:down",
26922 b"AGGREGATION",
26923 b"avg",
26924 b"1000",
26925 ],
26926 &[b"TS.CREATE", b"ts:down"],
26927 &[
26928 b"TS.CREATERULE",
26929 b"ts:a",
26930 b"ts:down",
26931 b"AGGREGATION",
26932 b"avg",
26933 b"1000",
26934 ],
26935 &[b"TS.ADD", b"ts:a", b"5000", b"4"],
26936 &[b"TS.ADD", b"ts:a", b"6000", b"5"],
26937 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
26938 &[b"TS.GET", b"ts:down", b"LATEST"],
26939 &[b"TS.INFO", b"ts:down"],
26940 &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
26941 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
26942 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
26943 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
26944 &[b"TS.DEL", b"ts:a", b"0", b"1000"],
26945 &[b"SET", b"plain", b"v"],
26947 &[b"TS.ADD", b"plain", b"1", b"1"],
26948 &[b"TS.GET", b"plain"],
26949 &[b"TS.READ", b"plain", b"0"],
26950 &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
26951 &[b"TS.RANGE", b"gone", b"-", b"+"],
26952 &[b"TS.INFO", b"gone"],
26953 ];
26954
26955 let mut one = Fixture::new();
26956 let mut many = Fixture::striped(8);
26957 for parts in script {
26958 let a = one.run(parts);
26959 let b = many.run(parts);
26960 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26961 }
26962 }
26963
26964 #[test]
26974 fn a_compaction_rule_across_stripes_reaches_both_ends() {
26975 let mut many = Fixture::striped(8);
26976 let other = apart(&mut many, "src");
26977 let (src, dst) = (b"src".as_slice(), other.as_bytes());
26978 let mut one = Fixture::new();
26979 let mut both = |parts: &[&[u8]]| {
26980 let a = one.run(parts);
26981 let b = many.run(parts);
26982 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26983 a
26984 };
26985
26986 both(&[b"TS.CREATE", src]);
26987 both(&[b"TS.CREATE", dst]);
26988 assert_eq!(
26989 both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
26990 "+OK\r\n"
26991 );
26992 both(&[b"TS.ADD", src, b"1000", b"1"]);
26993 both(&[b"TS.ADD", src, b"1500", b"3"]);
26994 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
26997 let open = both(&[b"TS.GET", dst, b"LATEST"]);
26998 assert!(open.contains(":1000"), "the open bucket is folded: {open}");
26999
27000 both(&[b"TS.ADD", src, b"2000", b"5"]);
27003 let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
27004 assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
27005 assert!(got.contains(":1000"), "{got}");
27006
27007 both(&[b"TS.DEL", src, b"1000", b"1999"]);
27009 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
27010
27011 assert!(
27014 both(&[b"TS.INFO", dst]).contains("src"),
27015 "the source is named"
27016 );
27017 assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
27018 assert_eq!(
27019 both(&[b"TS.DELETERULE", src, dst]),
27020 "-ERR TSDB: compaction rule does not exist\r\n"
27021 );
27022 }
27023
27024 #[test]
27026 fn a_label_query_across_stripes_finds_every_series() {
27027 let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
27028 let mut many = Fixture::striped(8);
27029 let mut homes: Vec<usize> = names
27030 .iter()
27031 .map(|name| many.server.striped(0).stripe_of(name))
27032 .collect();
27033 homes.sort_unstable();
27034 homes.dedup();
27035 assert!(homes.len() > 1, "the six keys are not all on one stripe");
27036
27037 let mut one = Fixture::new();
27038 let mut both = |parts: &[&[u8]]| {
27039 let a = one.run(parts);
27040 let b = many.run(parts);
27041 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27042 a
27043 };
27044 for name in &names {
27045 both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
27046 both(&[b"TS.ADD", name, b"1000", b"1"]);
27047 }
27048
27049 let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
27050 assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
27051 assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
27052 assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
27053 assert_eq!(
27054 both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
27055 "*1\r\n$4\r\nroom\r\n"
27056 );
27057 }
27058
27059 #[test]
27066 fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
27067 let script: &[&[&[u8]]] = &[
27068 &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
27069 &[b"HMSET", b"h", b"c", b"3"],
27070 &[b"HSETNX", b"h", b"a", b"9"],
27071 &[b"HSETNX", b"h", b"d", b"4"],
27072 &[b"HGET", b"h", b"a"],
27073 &[b"HGET", b"h", b"nope"],
27074 &[b"HMGET", b"h", b"a", b"nope"],
27075 &[b"HLEN", b"h"],
27076 &[b"HEXISTS", b"h", b"a"],
27077 &[b"HSTRLEN", b"h", b"a"],
27078 &[b"HGETALL", b"h"],
27079 &[b"HKEYS", b"h"],
27080 &[b"HVALS", b"h"],
27081 &[b"HINCRBY", b"h", b"a", b"5"],
27082 &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
27083 &[b"HSCAN", b"h", b"0"],
27084 &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
27085 &[b"HSCAN", b"h", b"0", b"NOVALUES"],
27086 &[b"HDEL", b"h", b"d"],
27087 &[b"HSET", b"one", b"f", b"v"],
27088 &[b"HRANDFIELD", b"one"],
27089 &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
27090 &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
27092 &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
27093 &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
27094 &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
27095 &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
27096 &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
27097 &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
27098 &[b"HGET", b"h", b"b"],
27099 &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
27101 &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
27102 &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
27103 &[b"HGET", b"h", b"e"],
27104 &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
27106 &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
27107 &[b"HGETALL", b"imp"],
27108 &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
27109 &[b"HIMPORT", b"DISCARD", b"fs"],
27110 &[b"SET", b"plain", b"v"],
27112 &[b"HSET", b"plain", b"a", b"1"],
27113 &[b"HGETALL", b"plain"],
27114 &[b"HGET", b"gone", b"a"],
27115 &[b"HINCRBY", b"h", b"a", b"nan"],
27116 ];
27117
27118 let mut one = Fixture::new();
27119 let mut many = Fixture::striped(8);
27120 one.server.set_clock_ms(1_700_000_000_000);
27124 many.server.set_clock_ms(1_700_000_000_000);
27125 for parts in script {
27126 let a = one.run(parts);
27127 let b = many.run(parts);
27128 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27129 }
27130 }
27131
27132 #[test]
27134 fn the_array_group_answers_the_same_however_many_stripes_there_are() {
27135 let script: &[&[&[u8]]] = &[
27136 &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
27137 &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
27138 &[b"ARGET", b"a", b"1"],
27139 &[b"ARGET", b"a", b"99"],
27140 &[b"ARMGET", b"a", b"0", b"5", b"99"],
27141 &[b"ARGETRANGE", b"a", b"0", b"7"],
27142 &[b"ARLEN", b"a"],
27143 &[b"ARCOUNT", b"a"],
27144 &[b"ARINSERT", b"a", b"m", b"n"],
27145 &[b"ARSCAN", b"a", b"0", b"20"],
27146 &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
27147 &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
27148 &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
27149 &[b"ARLASTITEMS", b"a", b"2"],
27150 &[b"ARLASTITEMS", b"a", b"2", b"REV"],
27151 &[b"ARNEXT", b"a"],
27152 &[b"ARSEEK", b"a", b"3"],
27153 &[b"AROP", b"a", b"0", b"20", b"USED"],
27154 &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
27155 &[b"ARINFO", b"a"],
27156 &[b"ARINFO", b"a", b"FULL"],
27157 &[b"ARDEL", b"a", b"0"],
27158 &[b"ARDELRANGE", b"a", b"1", b"2"],
27159 &[b"ARCOUNT", b"a"],
27160 &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
27161 &[b"ARGETRANGE", b"r", b"0", b"9"],
27162 &[b"SET", b"plain", b"v"],
27164 &[b"ARGET", b"plain", b"0"],
27165 &[b"ARSET", b"plain", b"0", b"v"],
27166 &[b"ARGET", b"gone", b"0"],
27167 &[b"ARSET", b"a", b"bad", b"v"],
27168 ];
27169
27170 let mut one = Fixture::new();
27171 let mut many = Fixture::striped(8);
27172 for parts in script {
27173 let a = one.run(parts);
27174 let b = many.run(parts);
27175 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27176 }
27177 }
27178
27179 #[test]
27185 fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
27186 let script: &[&[&[u8]]] = &[
27187 &[b"G.NADD", b"g", b"n1", b"name", b"one"],
27188 &[b"G.NADD", b"g", b"n2", b"name", b"two"],
27189 &[b"G.NADD", b"g", b"n3"],
27190 &[b"G.NGET", b"g", b"n1"],
27191 &[b"G.NGET", b"g", b"gone"],
27192 &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
27193 &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
27194 &[b"G.OUT", b"g", b"n1", b"knows"],
27195 &[b"G.IN", b"g", b"n2", b"knows"],
27196 &[b"G.DEG", b"g", b"n1", b"knows"],
27197 &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
27198 &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
27199 &[b"G.PATH", b"g", b"n1", b"n3"],
27200 &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
27201 &[b"G.NDEL", b"g", b"n3"],
27202 &[b"G.NGET", b"g", b"n3"],
27203 &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
27205 &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
27206 &[b"VCARD", b"v"],
27207 &[b"VDIM", b"v"],
27208 &[b"VEMB", b"v", b"e1"],
27209 &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
27210 &[b"VSIM", b"v", b"ELE", b"e1"],
27211 &[b"VISMEMBER", b"v", b"e1"],
27212 &[b"VISMEMBER", b"v", b"gone"],
27213 &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
27214 &[b"VGETATTR", b"v", b"e1"],
27215 &[b"VRANGE", b"v", b"-", b"+"],
27216 &[b"VLINKS", b"v", b"e1"],
27217 &[b"VINFO", b"v"],
27218 &[b"VREM", b"v", b"e2"],
27219 &[b"VCARD", b"v"],
27220 &[b"SET", b"plain", b"v"],
27222 &[b"G.NGET", b"plain", b"n1"],
27223 &[b"VCARD", b"plain"],
27224 &[b"G.NADD", b"gone2", b"n"],
27225 &[b"VEMB", b"gone3", b"e"],
27226 ];
27227
27228 let mut one = Fixture::new();
27229 let mut many = Fixture::striped(8);
27230 for parts in script {
27231 let a = one.run(parts);
27232 let b = many.run(parts);
27233 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27234 }
27235 }
27236
27237 #[test]
27240 fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
27241 let script: &[&[&[u8]]] = &[
27242 &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
27244 &[b"BF.ADD", b"bf", b"a"],
27245 &[b"BF.ADD", b"bf", b"a"],
27246 &[b"BF.MADD", b"bf", b"b", b"c"],
27247 &[b"BF.EXISTS", b"bf", b"a"],
27248 &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
27249 &[b"BF.CARD", b"bf"],
27250 &[b"BF.INFO", b"bf"],
27251 &[b"BF.INFO", b"bf", b"CAPACITY"],
27252 &[b"BF.DEBUG", b"bf"],
27253 &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
27254 &[b"BF.EXISTS", b"made", b"x"],
27255 &[b"BF.SCANDUMP", b"bf", b"0"],
27256 &[b"CF.RESERVE", b"cf", b"100"],
27258 &[b"CF.ADD", b"cf", b"a"],
27259 &[b"CF.ADDNX", b"cf", b"a"],
27260 &[b"CF.COUNT", b"cf", b"a"],
27261 &[b"CF.EXISTS", b"cf", b"a"],
27262 &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
27263 &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
27264 &[b"CF.DEL", b"cf", b"a"],
27265 &[b"CF.COMPACT", b"cf"],
27266 &[b"CF.INFO", b"cf"],
27267 &[b"CF.DEBUG", b"cf"],
27268 &[b"CF.SCANDUMP", b"cf", b"0"],
27269 &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
27271 &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
27272 &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
27273 &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
27274 &[b"CMS.INFO", b"cms"],
27275 &[b"TOPK.RESERVE", b"tk", b"3"],
27277 &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
27278 &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
27279 &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
27280 &[b"TOPK.COUNT", b"tk", b"a", b"c"],
27281 &[b"TOPK.LIST", b"tk"],
27282 &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
27283 &[b"TOPK.INFO", b"tk"],
27284 &[b"TDIGEST.CREATE", b"td"],
27286 &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
27287 &[b"TDIGEST.MIN", b"td"],
27288 &[b"TDIGEST.MAX", b"td"],
27289 &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
27290 &[b"TDIGEST.CDF", b"td", b"3"],
27291 &[b"TDIGEST.RANK", b"td", b"3"],
27292 &[b"TDIGEST.REVRANK", b"td", b"3"],
27293 &[b"TDIGEST.BYRANK", b"td", b"0"],
27294 &[b"TDIGEST.BYREVRANK", b"td", b"0"],
27295 &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
27296 &[b"TDIGEST.INFO", b"td"],
27297 &[b"TDIGEST.RESET", b"td"],
27298 &[b"TDIGEST.MIN", b"td"],
27299 &[b"SET", b"plain", b"v"],
27301 &[b"BF.ADD", b"plain", b"a"],
27302 &[b"CF.ADD", b"plain", b"a"],
27303 &[b"CMS.QUERY", b"plain", b"a"],
27304 &[b"TOPK.ADD", b"plain", b"a"],
27305 &[b"TDIGEST.ADD", b"plain", b"1"],
27306 &[b"CMS.INFO", b"gone"],
27307 &[b"TOPK.INFO", b"gone"],
27308 &[b"TDIGEST.INFO", b"gone"],
27309 ];
27310
27311 let mut one = Fixture::new();
27312 let mut many = Fixture::striped(8);
27313 for parts in script {
27314 let a = one.run(parts);
27315 let b = many.run(parts);
27316 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27317 }
27318 }
27319
27320 #[test]
27327 fn a_sketch_merge_across_stripes_reads_every_source() {
27328 let mut many = Fixture::striped(8);
27329 let other = apart(&mut many, "s1");
27330 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
27331 let mut one = Fixture::new();
27332 let mut both = |parts: &[&[u8]]| {
27333 let a = one.run(parts);
27334 let b = many.run(parts);
27335 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27336 a
27337 };
27338
27339 for key in [b"cd".as_slice(), s1, s2] {
27343 both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
27344 }
27345 both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
27346 both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
27347 assert_eq!(
27348 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
27349 "+OK\r\n",
27350 "the merge took both sources"
27351 );
27352 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
27353 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
27355 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
27356 both(&[b"SET", b"plain", b"v"]);
27358 assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
27359 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
27360
27361 both(&[b"FLUSHALL"]);
27364 both(&[b"TDIGEST.CREATE", b"td"]);
27365 both(&[b"TDIGEST.CREATE", s1]);
27366 both(&[b"TDIGEST.CREATE", s2]);
27367 both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
27368 both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
27369 assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
27370 assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
27371 assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
27372 }
27373
27374 #[test]
27381 fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
27382 let script: &[&[&[u8]]] = &[
27383 &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
27384 &[b"SORT", b"l"],
27385 &[b"SORT", b"l", b"DESC"],
27386 &[b"SORT", b"l", b"ALPHA"],
27387 &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
27388 &[b"SORT_RO", b"l"],
27389 &[
27392 b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
27393 ],
27394 &[b"SORT", b"l", b"BY", b"w_*"],
27395 &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
27396 &[b"DEL", b"w_2"],
27397 &[b"SORT", b"l", b"BY", b"w_*"],
27398 &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
27401 &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
27402 &[b"HSET", b"h_1", b"f", b"9"],
27404 &[b"HSET", b"h_2", b"f", b"8"],
27405 &[b"HSET", b"h_3", b"f", b"7"],
27406 &[b"HSET", b"h_10", b"f", b"6"],
27407 &[b"SORT", b"l", b"BY", b"h_*->f"],
27408 &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
27409 &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
27411 &[b"LRANGE", b"out", b"0", b"-1"],
27412 &[b"SORT", b"l", b"STORE", b"l"],
27413 &[b"LRANGE", b"l", b"0", b"-1"],
27414 &[b"SORT", b"missing", b"STORE", b"out"],
27417 &[b"EXISTS", b"out"],
27418 &[b"SADD", b"s", b"c", b"a", b"b"],
27421 &[b"SORT", b"s", b"ALPHA"],
27422 &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
27423 &[b"LRANGE", b"out", b"0", b"-1"],
27424 &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
27425 &[b"SORT", b"z", b"BY", b"nosort"],
27426 &[b"SORT", b"z", b"ALPHA", b"DESC"],
27427 &[b"SET", b"str", b"v"],
27430 &[b"SORT", b"str"],
27431 &[b"RPUSH", b"words", b"one", b"two"],
27432 &[b"SORT", b"words"],
27433 &[b"SORT_RO", b"l", b"STORE", b"out"],
27434 ];
27435
27436 let mut one = Fixture::new();
27437 let mut many = Fixture::striped(8);
27438 for parts in script {
27439 let a = one.run(parts);
27440 let b = many.run(parts);
27441 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27442 }
27443 }
27444
27445 #[test]
27452 fn a_sort_across_stripes_reads_every_pattern_key() {
27453 let mut f = Fixture::striped(8);
27454 let out = apart(&mut f, "l");
27455 let (list, dest) = (b"l".as_slice(), out.as_bytes());
27456
27457 f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
27458 f.run(&[
27459 b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
27460 ]);
27461 f.run(&[
27462 b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
27463 ]);
27464
27465 let db = f.server.striped(0);
27468 let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
27469 .iter()
27470 .map(|k| db.stripe_of(k.as_slice()))
27471 .collect();
27472 assert!(
27473 weights.iter().any(|s| *s != weights[0]),
27474 "the four weight keys all landed on one stripe, so this proves nothing"
27475 );
27476
27477 assert_eq!(
27478 f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
27479 "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
27480 "the order came off the weights and the answer off the data keys"
27481 );
27482 assert_eq!(
27483 f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
27484 ":4\r\n"
27485 );
27486 assert_eq!(
27487 f.run(&[b"LRANGE", dest, b"0", b"-1"]),
27488 "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
27489 "the destination is on a stripe of its own and got the whole answer"
27490 );
27491 }
27492
27493 #[test]
27501 fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
27502 let mut f = Fixture::striped(8);
27503 let other = apart(&mut f, "h");
27504 let (first, second) = (b"h".as_slice(), other.as_bytes());
27505
27506 assert_eq!(
27507 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
27508 "+OK\r\n"
27509 );
27510 assert_eq!(
27511 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
27512 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
27513 "the read comes off one stripe and has to answer for all of them"
27514 );
27515 for key in [first, second] {
27516 f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
27517 assert_eq!(
27518 f.run(&[b"OBJECT", b"ENCODING", key]),
27519 "$8\r\nlistpack\r\n",
27520 "two fields is still under the ladder"
27521 );
27522 f.run(&[b"HSET", key, b"c", b"3"]);
27523 assert_eq!(
27524 f.run(&[b"OBJECT", b"ENCODING", key]),
27525 "$9\r\nhashtable\r\n",
27526 "three fields is over it, on whichever stripe the key is on"
27527 );
27528 }
27529
27530 assert_eq!(
27533 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
27534 "+OK\r\n"
27535 );
27536 let db = f.server.striped(0);
27537 assert!(
27538 (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
27539 "a stripe kept the old policy"
27540 );
27541 }
27542
27543 fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
27549 let search = f.server.search.lock();
27550 let index = search.named(name).expect("the index is there");
27551 (index.held.docs.len(), index.held.docs.last())
27552 }
27553
27554 #[test]
27557 fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
27558 let mut f = Fixture::new();
27559 f.run(&[
27560 b"FT.CREATE",
27561 b"ix",
27562 b"PREFIX",
27563 b"1",
27564 b"p:",
27565 b"SCHEMA",
27566 b"t",
27567 b"TEXT",
27568 ]);
27569 f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
27570 assert_eq!(held(&f, b"ix"), (1, 1));
27571 f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
27572 assert_eq!(held(&f, b"ix"), (1, 1));
27573
27574 f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
27577 f.run(&[b"HDEL", b"p:1", b"u"]);
27578 assert_eq!(held(&f, b"ix"), (1, 3));
27579 let search = f.server.search.lock();
27580 let index = search.named(b"ix").expect("there");
27581 assert_eq!(index.held.docs.id(b"p:1"), Some(3));
27582 }
27583
27584 #[test]
27587 fn a_fresh_index_reads_the_keys_that_were_already_there() {
27588 let mut f = Fixture::new();
27589 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27590 f.run(&[b"SET", b"p:str", b"not a hash"]);
27591 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
27592 f.run(&[
27593 b"FT.CREATE",
27594 b"ix",
27595 b"PREFIX",
27596 b"1",
27597 b"p:",
27598 b"SCHEMA",
27599 b"t",
27600 b"TEXT",
27601 ]);
27602
27603 assert_eq!(held(&f, b"ix"), (1, 1));
27604 let search = f.server.search.lock();
27605 let index = search.named(b"ix").expect("there");
27606 assert_eq!(index.trouble.whole().failures(), 0);
27607 }
27608
27609 #[test]
27612 fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
27613 let mut f = Fixture::new();
27614 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27615 f.run(&[
27616 b"FT.CREATE",
27617 b"ix",
27618 b"PREFIX",
27619 b"1",
27620 b"p:",
27621 b"SKIPINITIALSCAN",
27622 b"SCHEMA",
27623 b"t",
27624 b"TEXT",
27625 ]);
27626 assert_eq!(held(&f, b"ix"), (0, 0));
27627 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27628 assert_eq!(held(&f, b"ix"), (1, 1));
27629 }
27630
27631 #[test]
27639 fn only_a_real_change_gives_the_document_a_new_number() {
27640 let mut f = Fixture::new();
27641 f.run(&[
27642 b"FT.CREATE",
27643 b"ix",
27644 b"PREFIX",
27645 b"1",
27646 b"p:",
27647 b"SCHEMA",
27648 b"t",
27649 b"TEXT",
27650 ]);
27651 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27652 assert_eq!(held(&f, b"ix"), (1, 1));
27653
27654 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27655 assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
27656
27657 for quiet in [
27658 vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
27659 vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
27660 vec![b"HGET".as_slice(), b"p:1", b"t"],
27661 vec![b"HGETALL".as_slice(), b"p:1"],
27662 vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
27663 vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
27664 vec![
27665 b"HGETEX".as_slice(),
27666 b"p:1",
27667 b"EX",
27668 b"100",
27669 b"FIELDS",
27670 b"1",
27671 b"t",
27672 ],
27673 vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
27674 ] {
27675 f.run(&quiet);
27676 assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
27677 }
27678
27679 f.run(&[b"HSET", b"p:2", b"n", b"1"]);
27681 f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
27682 assert_eq!(held(&f, b"ix"), (2, 4));
27683 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
27689 assert_eq!(held(&f, b"ix"), (1, 5));
27690 }
27691
27692 #[test]
27698 fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
27699 fn refused(f: &Fixture, name: &[u8]) -> u64 {
27701 let search = f.server.search.lock();
27702 let index = search.named(name).expect("the index is there");
27703 index.trouble.whole().failures()
27704 }
27705
27706 let mut f = Fixture::new();
27707 f.run(&[
27708 b"FT.CREATE",
27709 b"ix",
27710 b"PREFIX",
27711 b"1",
27712 b"p:",
27713 b"SCHEMA",
27714 b"t",
27715 b"TEXT",
27716 ]);
27717 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27718 assert_eq!(held(&f, b"ix"), (1, 1));
27719 f.run(&[b"HDEL", b"p:1", b"t"]);
27720 assert_eq!(
27721 held(&f, b"ix"),
27722 (0, 1),
27723 "HDEL of the last field spends none"
27724 );
27725 assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
27726
27727 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
27728 assert_eq!(held(&f, b"ix"), (1, 2));
27729 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
27730 assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
27731 assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
27732
27733 f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
27734 assert_eq!(held(&f, b"ix"), (1, 4));
27735 f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
27736 assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
27737
27738 f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
27741 assert_eq!(held(&f, b"ix"), (1, 6));
27742 f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
27743 assert_eq!(held(&f, b"ix"), (0, 7));
27744 assert_eq!(refused(&f, b"ix"), 1);
27745 }
27746
27747 #[test]
27751 fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
27752 let mut f = Fixture::new();
27753 f.run(&[
27754 b"FT.CREATE",
27755 b"ix",
27756 b"PREFIX",
27757 b"1",
27758 b"p:",
27759 b"SCHEMA",
27760 b"t",
27761 b"TEXT",
27762 b"u",
27763 b"TEXT",
27764 ]);
27765 f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
27766 assert_eq!(held(&f, b"ix"), (1, 1));
27767 f.run(&[
27768 b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
27769 ]);
27770 assert_eq!(
27771 held(&f, b"ix"),
27772 (1, 3),
27773 "the key lived and the field did not"
27774 );
27775
27776 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
27778 assert_eq!(held(&f, b"ix"), (2, 4));
27779 f.run(&[
27780 b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
27781 ]);
27782 assert_eq!(held(&f, b"ix"), (1, 6));
27783 }
27784
27785 fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
27788 let search = f.server.search.lock();
27789 let index = search.named(name).expect("the index is there");
27790 index.held.docs.id(key)
27791 }
27792
27793 fn indexed() -> Fixture {
27796 let mut f = Fixture::new();
27797 f.run(&[
27798 b"FT.CREATE",
27799 b"ix",
27800 b"PREFIX",
27801 b"1",
27802 b"p:",
27803 b"SCHEMA",
27804 b"t",
27805 b"TEXT",
27806 ]);
27807 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27808 f
27809 }
27810
27811 #[test]
27814 fn a_key_a_keyspace_command_takes_away_loses_its_document() {
27815 for take in [
27816 vec![b"DEL".as_slice(), b"p:1"],
27817 vec![b"UNLINK".as_slice(), b"p:1"],
27818 vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
27819 vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
27820 ] {
27821 let mut f = indexed();
27822 assert_eq!(held(&f, b"ix"), (1, 1));
27823 f.run(&take);
27824 assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
27825 let search = f.server.search.lock();
27826 let index = search.named(b"ix").expect("the index is there");
27827 assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
27828 }
27829
27830 let mut f = indexed();
27832 f.run(&[b"EXPIRE", b"p:1", b"1000"]);
27833 assert_eq!(held(&f, b"ix"), (1, 1));
27834 f.run(&[b"PERSIST", b"p:1"]);
27835 assert_eq!(held(&f, b"ix"), (1, 1));
27836 }
27837
27838 #[test]
27842 fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
27843 let mut f = indexed();
27844 f.run(&[b"RENAME", b"p:1", b"p:2"]);
27845 assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
27846 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
27847 assert_eq!(number(&f, b"ix", b"p:1"), None);
27848
27849 f.run(&[b"RENAME", b"p:2", b"q:1"]);
27850 assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
27851
27852 f.run(&[b"RENAME", b"q:1", b"p:3"]);
27853 assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
27854 assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
27855
27856 f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
27859 assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
27860 assert_eq!(held(&f, b"ix"), (2, 3));
27861 f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
27862 assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
27863 }
27864
27865 #[test]
27868 fn a_rename_over_a_document_leaves_one_of_them() {
27869 let mut f = indexed();
27870 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
27871 assert_eq!(held(&f, b"ix"), (2, 2));
27872 f.run(&[b"RENAME", b"p:1", b"p:2"]);
27873 assert_eq!(held(&f, b"ix"), (1, 2));
27874 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
27875 }
27876
27877 #[test]
27881 fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
27882 let mut f = indexed();
27883 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
27884 f.run(&[b"COPY", b"q:1", b"p:2"]);
27885 assert_eq!(held(&f, b"ix"), (2, 2));
27886 assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
27887
27888 f.run(&[b"COPY", b"p:1", b"q:2"]);
27890 assert_eq!(held(&f, b"ix"), (2, 2));
27891
27892 f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
27894 assert_eq!(held(&f, b"ix"), (2, 3));
27895 assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
27896
27897 f.run(&[b"SET", b"s:1", b"plain"]);
27900 f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
27901 assert_eq!(held(&f, b"ix"), (1, 3));
27902 let dump = f.run(&[b"DUMP", b"q:1"]);
27903 assert!(dump.starts_with('$'), "{dump}");
27904 }
27905
27906 #[test]
27911 fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
27912 let mut f = indexed();
27913 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
27914 assert_eq!(held(&f, b"ix"), (2, 2));
27915 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
27918 assert_eq!(held(&f, b"ix"), (2, 3));
27919 f.run(&[b"DEL", b"p:2"]);
27922 assert_eq!(held(&f, b"ix"), (1, 3));
27923 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
27924 assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
27925
27926 let dump = f.run(&[b"DUMP", b"p:1"]);
27928 assert!(dump.starts_with('$'), "{dump}");
27929 f.run(&[b"SELECT", b"1"]);
27930 f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
27931 f.run(&[b"RENAME", b"q:1", b"p:3"]);
27932 assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
27933 }
27934
27935 #[test]
27938 fn a_move_leaves_the_document_where_it_is() {
27939 let mut f = indexed();
27940 f.run(&[b"MOVE", b"p:1", b"1"]);
27941 assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
27942 assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
27943
27944 f.run(&[b"SELECT", b"1"]);
27945 f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
27946 assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
27947 f.run(&[b"DEL", b"p:1"]);
27948 assert_eq!(held(&f, b"ix"), (0, 2));
27949 }
27950
27951 #[test]
27953 fn a_flush_drops_the_indexes() {
27954 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
27955 let mut f = indexed();
27956 f.run(&[flush]);
27957 assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
27958 assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
27959 }
27960
27961 let mut f = indexed();
27964 f.run(&[b"SELECT", b"9"]);
27965 f.run(&[b"FLUSHDB"]);
27966 assert!(f.server.search.lock().is_empty());
27967 }
27968
27969 fn tagged() -> Fixture {
27972 let mut f = Fixture::new();
27973 f.run(&[
27974 b"FT.CREATE",
27975 b"tv",
27976 b"PREFIX",
27977 b"1",
27978 b"tv:",
27979 b"SCHEMA",
27980 b"g",
27981 b"AS",
27982 b"gg",
27983 b"TAG",
27984 b"h",
27985 b"TAG",
27986 b"SEPARATOR",
27987 b"|",
27988 b"CASESENSITIVE",
27989 b"n",
27990 b"NUMERIC",
27991 ]);
27992 f.run(&[
27993 b"HSET",
27994 b"tv:1",
27995 b"g",
27996 b"Red, BLUE ",
27997 b"h",
27998 b"Aa|bB",
27999 b"n",
28000 b"1",
28001 ]);
28002 f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
28003 f
28004 }
28005
28006 #[test]
28010 fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
28011 let mut f = tagged();
28012 assert_eq!(
28013 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
28014 "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
28015 );
28016 assert_eq!(
28017 f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
28018 "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
28019 );
28020 }
28021
28022 #[test]
28025 fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
28026 let mut f = tagged();
28027 for (name, want) in [
28028 (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
28029 (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
28030 (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
28031 ] {
28032 assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
28033 }
28034 assert_eq!(
28035 f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
28036 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
28037 );
28038 }
28039
28040 #[test]
28043 fn asking_for_tag_values_counts_a_use_of_the_index() {
28044 let mut f = tagged();
28045 let uses = |f: &mut Fixture| {
28046 let reply = f.run(&[b"FT.INFO", b"tv"]);
28047 let at = reply.find("number_of_uses").expect("the field is reported");
28048 let value = reply[at..].split("\r\n").nth(1).unwrap();
28049 value.trim_start_matches(':').parse::<i64>().unwrap()
28050 };
28051 let before = uses(&mut f);
28052 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
28053 f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
28054 assert_eq!(uses(&mut f), before + 3);
28056 }
28057
28058 #[test]
28061 fn a_tag_field_with_nothing_in_it_answers_empty() {
28062 let mut f = Fixture::new();
28063 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
28064 assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
28065 }
28066
28067 #[test]
28070 fn a_dictionary_is_not_a_key() {
28071 let mut f = Fixture::new();
28072 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
28073 assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
28074 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
28075 assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
28076 }
28077
28078 #[test]
28081 fn a_dictionary_counts_the_terms_it_had_not_seen() {
28082 let mut f = Fixture::new();
28083 assert_eq!(
28084 f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
28085 ":3\r\n"
28086 );
28087 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
28088 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
28089 assert_eq!(
28090 f.run(&[b"FT.DICTDUMP", b"d"]),
28091 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
28092 );
28093 assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
28094 }
28095
28096 #[test]
28100 fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
28101 let mut f = Fixture::new();
28102 assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
28103 assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
28104 }
28105
28106 #[test]
28108 fn a_flush_drops_the_dictionaries() {
28109 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
28110 let mut f = Fixture::new();
28111 f.run(&[b"FT.DICTADD", b"d", b"a"]);
28112 f.run(&[flush]);
28113 assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
28114 }
28115 }
28116
28117 fn profiling() -> Fixture {
28122 let mut f = Fixture::new();
28123 f.run(&[
28124 b"FT.CREATE",
28125 b"ix",
28126 b"PREFIX",
28127 b"1",
28128 b"p:",
28129 b"SCHEMA",
28130 b"t",
28131 b"TEXT",
28132 b"n",
28133 b"NUMERIC",
28134 ]);
28135 f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
28136 f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
28137 f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
28138 f
28139 }
28140
28141 fn timeless(reply: &str) -> String {
28144 const KEYS: &[&str] = &[
28145 "+Total profile time",
28146 "+Parsing time",
28147 "+Workers queue time",
28148 "+Pipeline creation time",
28149 "+Time",
28150 ];
28151 let mut out = String::new();
28152 let mut parts = reply.split("\r\n").peekable();
28153 while let Some(part) = parts.next() {
28154 out.push_str(part);
28155 out.push_str("\r\n");
28156 if !KEYS.contains(&part) {
28157 continue;
28158 }
28159 match parts.next() {
28162 Some(head) if head.starts_with('$') => {
28163 parts.next();
28164 }
28165 _ => {}
28166 }
28167 out.push_str("<t>\r\n");
28168 }
28169 out.truncate(out.len() - 2);
28171 out
28172 }
28173
28174 #[test]
28177 fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
28178 let mut f = profiling();
28179 assert_eq!(
28180 timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
28181 "*2\r\n\
28182 *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\
28183 $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\
28184 *4\r\n+Shards\r\n*1\r\n*14\r\n\
28185 +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
28186 +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
28187 +Warning\r\n*1\r\n+None\r\n\
28188 +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
28189 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
28190 +Estimated number of matches\r\n:2\r\n\
28191 +Result processors profile\r\n*4\r\n\
28192 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28193 *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28194 *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28195 *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28196 +Coordinator\r\n*0\r\n"
28197 );
28198 let mut g = profiling();
28199 g.run(&[b"HELLO", b"3"]);
28200 let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
28201 assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
28202 assert!(
28203 three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
28204 "{three}"
28205 );
28206 assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
28207 assert!(
28208 three.contains(
28209 "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
28210 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
28211 +Estimated number of matches\r\n:2\r\n"
28212 ),
28213 "{three}"
28214 );
28215 }
28216
28217 #[test]
28220 fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
28221 let mut f = profiling();
28222 let tree = |f: &mut Fixture, query: &[u8]| {
28223 let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
28224 let at = reply.find("+Iterators profile").expect("a tree");
28225 let end = reply.find("+Result processors").expect("a list of steps");
28226 reply[at..end].to_string()
28227 };
28228 assert_eq!(
28229 tree(&mut f, b"alpha beta"),
28230 "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
28231 +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
28232 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
28233 +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
28234 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
28235 +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
28236 );
28237 assert!(tree(&mut f, b"alpha|beta").starts_with(
28238 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
28239 +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
28240 ));
28241 assert!(tree(&mut f, b"-alpha").starts_with(
28244 "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
28245 +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
28246 ));
28247 assert!(tree(&mut f, b"~alpha").starts_with(
28248 "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
28249 +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
28250 ));
28251 assert_eq!(
28253 tree(&mut f, b"*"),
28254 "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
28255 +Number of reading operations\r\n:3\r\n"
28256 );
28257 assert!(tree(&mut f, b"@n:[1 2]").starts_with(
28258 "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
28259 $19\r\n1.000000 - 2.000000\r\n"
28260 ));
28261 }
28262
28263 #[test]
28266 fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
28267 let mut f = profiling();
28268 f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
28269 let tree = |f: &mut Fixture, words: &[&[u8]]| {
28270 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
28271 argv.extend_from_slice(words);
28272 let reply = timeless(&f.run(&argv));
28273 let at = reply.find("+Iterators profile").expect("a tree");
28274 let end = reply.find("+Result processors").expect("a list of steps");
28275 reply[at..end].to_string()
28276 };
28277 assert_eq!(
28278 tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
28279 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
28280 +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
28281 +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
28282 +The number of iterators in the union is 2\r\n"
28283 );
28284 assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
28285 assert!(
28286 tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
28287 .contains("+Child iterators\r\n*2\r\n")
28288 );
28289 assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
28293 assert!(
28294 tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
28295 );
28296 }
28297
28298 #[test]
28301 fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
28302 let mut f = profiling();
28303 let steps = |f: &mut Fixture, words: &[&[u8]]| {
28304 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
28305 argv.extend_from_slice(words);
28306 let reply = timeless(&f.run(&argv));
28307 let at = reply.find("+Result processors").expect("a list of steps");
28308 let end = reply.find("+Coordinator").expect("an end");
28309 let mut out = Vec::new();
28310 let mut parts = reply[at..end].split("\r\n").peekable();
28311 while let Some(part) = parts.next() {
28312 if part == "+Type" {
28313 out.push(parts.next().unwrap_or_default().to_string());
28314 }
28315 }
28316 out
28317 };
28318 assert_eq!(
28319 steps(&mut f, &[]),
28320 ["+Index", "+Scorer", "+Sorter", "+Loader"]
28321 );
28322 assert_eq!(
28323 steps(&mut f, &[b"NOCONTENT"]),
28324 ["+Index", "+Scorer", "+Sorter"]
28325 );
28326 assert_eq!(
28329 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
28330 ["+Index", "+Counter"]
28331 );
28332 assert_eq!(
28335 steps(&mut f, &[b"SORTBY", b"n"]),
28336 ["+Index", "+Sorter", "+Loader"]
28337 );
28338 assert_eq!(
28339 steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
28340 ["+Index", "+Scorer", "+Sorter", "+Loader"]
28341 );
28342 assert_eq!(
28343 steps(&mut f, &[b"HIGHLIGHT"]),
28344 ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
28345 );
28346 assert_eq!(
28347 steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
28348 ["+Index", "+Scorer", "+Sorter"]
28349 );
28350 }
28351
28352 #[test]
28355 fn a_pipeline_names_every_step_after_what_it_runs() {
28356 let mut f = profiling();
28357 let steps = |f: &mut Fixture, words: &[&[u8]]| {
28358 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
28359 argv.extend_from_slice(words);
28360 let reply = timeless(&f.run(&argv));
28361 let at = reply.find("+Result processors").expect("a list of steps");
28362 let end = reply.find("+Coordinator").expect("an end");
28363 let mut out = Vec::new();
28364 let mut parts = reply[at..end].split("\r\n").peekable();
28365 while let Some(part) = parts.next() {
28366 if part == "+Type" {
28367 out.push(parts.next().unwrap_or_default().to_string());
28368 }
28369 }
28370 out
28371 };
28372 assert_eq!(steps(&mut f, &[]), ["+Index"]);
28373 assert_eq!(
28374 steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
28375 ["+Index", "+Projector - Literal 1"]
28376 );
28377 assert_eq!(
28378 steps(
28379 &mut f,
28380 &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
28381 ),
28382 ["+Index", "+Loader", "+Projector - Operator *"]
28383 );
28384 assert_eq!(
28385 steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
28386 ["+Index", "+Loader", "+Filter - Predicate >"]
28387 );
28388 assert_eq!(
28389 steps(
28390 &mut f,
28391 &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
28392 ),
28393 ["+Index", "+Loader", "+Grouper"]
28394 );
28395 assert_eq!(
28396 steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
28397 ["+Index", "+Loader", "+Sorter"]
28398 );
28399 assert_eq!(
28400 steps(&mut f, &[b"LIMIT", b"0", b"2"]),
28401 ["+Index", "+Pager/Limiter"]
28402 );
28403 assert_eq!(
28406 steps(
28407 &mut f,
28408 &[
28409 b"ADDSCORES",
28410 b"LOAD",
28411 b"1",
28412 b"@n",
28413 b"APPLY",
28414 b"@__score",
28415 b"AS",
28416 b"s"
28417 ]
28418 ),
28419 [
28420 "+Index",
28421 "+Scorer",
28422 "+Loader",
28423 "+Projector - Property __score"
28424 ]
28425 );
28426 }
28427
28428 #[test]
28436 fn a_sortable_field_is_read_without_the_key_being_opened() {
28437 let mut f = Fixture::new();
28438 f.run(&[
28439 b"FT.CREATE",
28440 b"sx",
28441 b"PREFIX",
28442 b"1",
28443 b"s:",
28444 b"SCHEMA",
28445 b"n",
28446 b"NUMERIC",
28447 b"SORTABLE",
28448 b"g",
28449 b"TAG",
28450 ]);
28451 f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
28452 f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
28453 let loads = |f: &mut Fixture, words: &[&[u8]]| {
28454 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
28455 argv.extend_from_slice(words);
28456 f.run(&argv).contains("+Loader")
28457 };
28458 assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
28459 assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
28460 assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
28461 assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
28462 assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
28463 assert!(loads(
28464 &mut f,
28465 &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
28466 ));
28467 assert!(loads(&mut f, &[b"LOAD", b"*"]));
28468 }
28469
28470 #[test]
28473 fn a_profile_checks_its_own_words_before_it_runs_anything() {
28474 let mut f = profiling();
28475 assert_eq!(
28476 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
28477 "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
28478 );
28479 assert_eq!(
28480 f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
28481 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
28482 );
28483 assert_eq!(
28486 f.run(&[
28487 b"FT.PROFILE",
28488 b"ix",
28489 b"LIMITED",
28490 b"SEARCH",
28491 b"QUERY",
28492 b"alpha"
28493 ]),
28494 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
28495 );
28496 assert_eq!(
28497 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
28498 "-The QUERY keyword is expected\r\n"
28499 );
28500 assert_eq!(
28501 f.run(&[
28502 b"FT.PROFILE",
28503 b"ix",
28504 b"AGGREGATE",
28505 b"QUERY",
28506 b"alpha",
28507 b"WITHCURSOR"
28508 ]),
28509 "-FT.PROFILE does not support cursor\r\n"
28510 );
28511 assert_eq!(
28514 f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
28515 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
28516 );
28517 assert_eq!(
28518 f.run(&[
28519 b"FT.PROFILE",
28520 b"ix",
28521 b"SEARCH",
28522 b"QUERY",
28523 b"alpha",
28524 b"extra"
28525 ]),
28526 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
28527 );
28528 }
28529
28530 #[test]
28532 fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
28533 let mut f = profiling();
28534 let one = f.run(&[
28535 b"FT.PROFILE",
28536 b"ix",
28537 b"search",
28538 b"limited",
28539 b"query",
28540 b"alpha",
28541 ]);
28542 let two = f.run(&[
28543 b"FT.PROFILE",
28544 b"ix",
28545 b"SEARCH",
28546 b"LIMITED",
28547 b"QUERY",
28548 b"alpha",
28549 ]);
28550 assert_eq!(timeless(&one), timeless(&two));
28551 }
28552
28553 #[test]
28558 fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
28559 let mut f = profiling();
28560 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
28561 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28562
28563 let mut f = profiling();
28564 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28565 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28566
28567 let mut f = profiling();
28568 assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
28569 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28570
28571 let mut f = profiling();
28572 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
28573 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28574 }
28575
28576 #[test]
28579 fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
28580 let mut f = profiling();
28581 let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
28582 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
28583 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
28584 assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
28586 }
28587
28588 #[test]
28591 fn dropping_the_documents_leaves_a_key_the_index_never_read() {
28592 let mut f = profiling();
28593 f.run(&[b"SET", b"p:4", b"alpha"]);
28594 f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
28595 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28596 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28597 assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
28598 }
28599
28600 #[test]
28603 fn another_index_over_the_same_keys_loses_the_documents_too() {
28604 let mut f = profiling();
28605 f.run(&[
28606 b"FT.CREATE",
28607 b"other",
28608 b"PREFIX",
28609 b"1",
28610 b"p:",
28611 b"SCHEMA",
28612 b"t",
28613 b"TEXT",
28614 ]);
28615 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28616 assert_eq!(
28617 f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
28618 "*1\r\n:0\r\n"
28619 );
28620 }
28621
28622 #[test]
28625 fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
28626 let mut f = profiling();
28627 assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
28628 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28629 assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
28630 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28631 }
28632
28633 #[test]
28638 fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
28639 let mut f = Fixture::new();
28640 assert_eq!(
28641 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28642 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28643 );
28644 assert_eq!(
28645 f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
28646 "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
28647 );
28648 let mut g = Fixture::new();
28649 g.run(&[b"HELLO", b"3"]);
28650 assert_eq!(
28651 g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28652 "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28653 );
28654 assert_eq!(
28655 g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
28656 "%1\r\n+EXTLOAD\r\n_\r\n"
28657 );
28658 }
28659
28660 #[test]
28663 fn a_help_row_carries_the_description_and_the_value_together() {
28664 let mut f = Fixture::new();
28665 assert_eq!(
28666 f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
28667 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
28668 +Value\r\n$3\r\n500\r\n"
28669 );
28670 let mut g = Fixture::new();
28671 g.run(&[b"HELLO", b"3"]);
28672 assert_eq!(
28673 g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
28674 "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
28675 +Value\r\n$3\r\n500\r\n"
28676 );
28677 }
28678
28679 #[test]
28682 fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
28683 let mut f = Fixture::new();
28684 assert_eq!(
28685 f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
28686 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28687 );
28688 for name in [
28689 b"TIMEOUT*".as_slice(),
28690 b"?IMEOUT",
28691 b"*TIMEOUT*",
28692 b"TIME",
28693 b"NOSUCH",
28694 b"",
28695 ] {
28696 assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
28697 }
28698 assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
28699 assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
28700 }
28701
28702 #[test]
28705 fn a_read_ignores_whatever_follows_the_name() {
28706 let mut f = Fixture::new();
28707 assert_eq!(
28708 f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
28709 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28710 );
28711 assert_eq!(
28712 f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
28713 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
28714 +Value\r\n$3\r\n500\r\n"
28715 );
28716 }
28717
28718 #[test]
28721 fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
28722 let mut f = Fixture::new();
28723 assert_eq!(
28724 f.run(&[b"FT.CONFIG"]),
28725 "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
28726 );
28727 for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
28728 let want = format!(
28729 "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
28730 String::from_utf8_lossy(sub)
28731 );
28732 assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
28733 }
28734 assert_eq!(
28735 f.run(&[b"ft.config", b"get"]),
28736 "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
28737 );
28738 assert_eq!(
28739 f.run(&[b"FT.CONFIG", b"bogus"]),
28740 "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
28741 );
28742 }
28743
28744 #[test]
28747 fn a_write_checks_the_name_then_the_setting_then_the_value() {
28748 let mut f = Fixture::new();
28749 for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
28750 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
28751 cmd.extend(tail);
28752 assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
28753 }
28754 for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
28755 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
28756 cmd.extend(tail);
28757 assert_eq!(
28758 f.run(&cmd),
28759 "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
28760 );
28761 }
28762 assert_eq!(
28763 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
28764 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
28765 );
28766 }
28767
28768 #[test]
28771 fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
28772 let mut f = Fixture::new();
28773 assert_eq!(
28774 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
28775 "+OK\r\n"
28776 );
28777 assert_eq!(
28778 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
28779 "+EXCESSARGS\r\n"
28780 );
28781 assert_eq!(
28782 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28783 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
28784 );
28785 }
28786
28787 #[test]
28790 fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
28791 let mut f = Fixture::new();
28792 for (given, want) in [
28793 (b"0x10".as_slice(), "16"),
28794 (b"0X1f", "31"),
28795 (b"+0x10", "16"),
28796 (b"+5", "5"),
28797 (b"010", "10"),
28798 (b"08", "8"),
28799 (b"0777", "777"),
28800 (b"1e3", "1000"),
28801 (b"0.0", "0"),
28802 (b"-0.0", "0"),
28803 ] {
28804 assert_eq!(
28805 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28806 "+OK\r\n",
28807 "{given:?}"
28808 );
28809 let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
28810 assert_eq!(
28811 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28812 want,
28813 "{given:?}"
28814 );
28815 }
28816 for given in [
28817 b" 5".as_slice(),
28818 b"5 ",
28819 b"1.5",
28820 b"1e-3",
28821 b"x",
28822 b"",
28823 b"0b11",
28824 b"0xg",
28825 b"nan",
28826 b"inf",
28827 b"1e100",
28828 b"99999999999999999999",
28829 ] {
28830 assert_eq!(
28831 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28832 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
28833 "{given:?}"
28834 );
28835 }
28836 }
28837
28838 #[test]
28841 fn a_negative_is_answered_by_whichever_reader_found_it() {
28842 let mut f = Fixture::new();
28843 for given in [b"-1".as_slice(), b"-16"] {
28844 assert_eq!(
28845 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28846 "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
28847 "{given:?}"
28848 );
28849 }
28850 for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
28851 assert_eq!(
28852 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28853 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
28854 "{given:?}"
28855 );
28856 }
28857 let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
28858 for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
28859 assert_eq!(
28860 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
28861 "+OK\r\n",
28862 "{given:?}"
28863 );
28864 assert_eq!(
28865 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
28866 unlimited,
28867 "{given:?}"
28868 );
28869 }
28870 }
28871
28872 #[test]
28875 fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
28876 let mut f = Fixture::new();
28877 for (given, want) in [
28878 (b"2147483647".as_slice(), "2147483647"),
28879 (b"2147483648", "unlimited"),
28880 (b"4294967295", "unlimited"),
28881 (b"9223372036854775806", "unlimited"),
28882 (b"0", "0"),
28883 ] {
28884 assert_eq!(
28885 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
28886 "+OK\r\n",
28887 "{given:?}"
28888 );
28889 let want = format!(
28890 "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
28891 want.len()
28892 );
28893 assert_eq!(
28894 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
28895 want,
28896 "{given:?}"
28897 );
28898 }
28899 }
28900
28901 #[test]
28904 fn a_number_out_of_range_names_the_limit_it_crossed() {
28905 let mut f = Fixture::new();
28906 let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
28907 for (name, given) in [
28908 (b"MINPREFIX".as_slice(), b"0".as_slice()),
28909 (b"MAX_AGGREGATE_GROUPS", b"0"),
28910 (b"BM25STD_TANH_FACTOR", b"0"),
28911 (b"DEFAULT_DIALECT", b"0"),
28912 (b"MINSTEMLEN", b"4294967296"),
28913 (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
28914 (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
28915 (b"CONNECT_TIMEOUT", b"2147483648"),
28916 ] {
28917 assert_eq!(
28918 f.run(&[b"FT.CONFIG", b"SET", name, given]),
28919 bounds,
28920 "{name:?}"
28921 );
28922 }
28923 for (name, given, want) in [
28924 (
28925 b"MINSTEMLEN".as_slice(),
28926 b"1".as_slice(),
28927 "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
28928 ),
28929 (
28930 b"MAX_AGGREGATE_GROUPS",
28931 b"67108865",
28932 "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
28933 ),
28934 (
28935 b"WORKERS",
28936 b"17",
28937 "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
28938 ),
28939 (
28940 b"_NUMERIC_RANGES_PARENTS",
28941 b"3",
28942 "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
28943 depth for balance\r\n",
28944 ),
28945 (
28946 b"DEFAULT_DIALECT",
28947 b"5",
28948 "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
28949 ),
28950 (
28951 b"_BG_INDEX_MEM_PCT_THR",
28952 b"101",
28953 "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
28954 100%\r\n",
28955 ),
28956 (
28957 b"BM25STD_TANH_FACTOR",
28958 b"10001",
28959 "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
28960 inclusive\r\n",
28961 ),
28962 (
28963 b"BG_INDEX_SLEEP_DURATION_US",
28964 b"1000000",
28965 "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
28966 999999 (usleep POSIX limit)\r\n",
28967 ),
28968 ] {
28969 assert_eq!(
28970 f.run(&[b"FT.CONFIG", b"SET", name, given]),
28971 want,
28972 "{name:?}"
28973 );
28974 }
28975 }
28976
28977 #[test]
28980 fn the_trimming_delays_are_checked_against_one_another() {
28981 let mut f = Fixture::new();
28982 assert_eq!(
28983 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
28984 "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
28985 _MAX_TRIM_DELAY_MS (5000)\r\n"
28986 );
28987 assert_eq!(
28988 f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
28989 "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
28990 _MIN_TRIM_DELAY_MS (2000)\r\n"
28991 );
28992 assert_eq!(
28993 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
28994 "+OK\r\n"
28995 );
28996 }
28997
28998 #[test]
29001 fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
29002 let mut f = Fixture::new();
29003 assert_eq!(
29004 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
29005 "+OK\r\n"
29006 );
29007 assert_eq!(
29008 f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
29009 "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
29010 );
29011 assert_eq!(
29012 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
29013 "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
29014 );
29015 assert_eq!(
29016 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
29017 "+OK\r\n"
29018 );
29019 assert_eq!(
29020 f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
29021 "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
29022 );
29023 assert_eq!(
29024 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
29025 "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
29026 );
29027 let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
29028 for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
29029 assert_eq!(
29030 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
29031 bad,
29032 "{given:?}"
29033 );
29034 }
29035 assert_eq!(
29036 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
29037 "+OK\r\n"
29038 );
29039 }
29040
29041 #[test]
29044 fn a_yes_or_no_setting_takes_those_two_words_only() {
29045 let mut f = Fixture::new();
29046 assert_eq!(
29047 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
29048 "+OK\r\n"
29049 );
29050 assert_eq!(
29051 f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
29052 "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
29053 );
29054 for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
29055 assert_eq!(
29056 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
29057 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
29058 "{given:?}"
29059 );
29060 }
29061 }
29062
29063 #[test]
29066 fn two_names_for_one_setting_move_together() {
29067 let mut f = Fixture::new();
29068 f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
29069 assert_eq!(
29070 f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
29071 "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
29072 );
29073 f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
29074 assert_eq!(
29075 f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
29076 "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
29077 );
29078 let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
29079 let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
29080 f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
29081 assert_eq!(
29082 f.run(&[b"FT.CONFIG", b"GET", short]),
29083 "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
29084 );
29085 assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
29086 assert_eq!(
29087 f.run(&[b"FT.CONFIG", b"GET", long]),
29088 "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
29089 );
29090 }
29091
29092 #[test]
29094 fn a_password_reads_back_as_stars_whatever_was_written() {
29095 let mut f = Fixture::new();
29096 assert_eq!(
29097 f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
29098 "+OK\r\n"
29099 );
29100 assert_eq!(
29101 f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
29102 "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
29103 );
29104 }
29105
29106 #[test]
29109 fn a_flush_leaves_the_settings_alone() {
29110 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
29111 let mut f = Fixture::new();
29112 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
29113 f.run(&[flush]);
29114 assert_eq!(
29115 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
29116 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
29117 "{flush:?}"
29118 );
29119 }
29120 }
29121
29122 fn debugging() -> Fixture {
29127 let mut f = Fixture::new();
29128 f.run(&[
29129 b"FT.CREATE",
29130 b"dx",
29131 b"PREFIX",
29132 b"1",
29133 b"d:",
29134 b"SCHEMA",
29135 b"t",
29136 b"TEXT",
29137 b"g",
29138 b"TAG",
29139 b"n",
29140 b"NUMERIC",
29141 b"s",
29142 b"TEXT",
29143 b"SORTABLE",
29144 ]);
29145 f.run(&[
29146 b"HSET",
29147 b"d:1",
29148 b"t",
29149 b"running dogs",
29150 b"g",
29151 b"red,blue",
29152 b"n",
29153 b"1",
29154 b"s",
29155 b"Alpha",
29156 ]);
29157 f.run(&[
29158 b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
29159 ]);
29160 f.run(&[
29161 b"HSET",
29162 b"d:3",
29163 b"t",
29164 b"dogs alpha",
29165 b"g",
29166 b"green",
29167 b"n",
29168 b"3",
29169 ]);
29170 f
29171 }
29172
29173 #[test]
29176 fn a_term_dump_lists_the_stems_beside_the_words() {
29177 let mut f = debugging();
29178 assert_eq!(
29179 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
29180 "*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\
29181 $4\r\ndogs\r\n$7\r\nrunning\r\n"
29182 );
29183 }
29184
29185 #[test]
29188 fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
29189 let mut f = debugging();
29190 assert_eq!(
29191 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29192 "*2\r\n:1\r\n:2\r\n"
29193 );
29194 assert_eq!(
29195 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
29196 "*2\r\n:1\r\n:2\r\n"
29197 );
29198 for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
29199 assert_eq!(
29200 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
29201 "-Can not find the inverted index\r\n",
29202 "{term:?}"
29203 );
29204 }
29205 }
29206
29207 #[test]
29210 fn a_tag_dump_pairs_every_value_with_its_documents() {
29211 let mut f = debugging();
29212 assert_eq!(
29213 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
29214 "*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\
29215 *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
29216 );
29217 }
29218
29219 #[test]
29222 fn a_number_dump_answers_a_single_range() {
29223 let mut f = debugging();
29224 assert_eq!(
29225 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
29226 "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
29227 );
29228 }
29229
29230 #[test]
29233 fn a_geo_field_is_dumped_as_a_numeric_one() {
29234 let mut f = Fixture::new();
29235 f.run(&[
29236 b"FT.CREATE",
29237 b"gx",
29238 b"PREFIX",
29239 b"1",
29240 b"q:",
29241 b"SCHEMA",
29242 b"loc",
29243 b"GEO",
29244 b"gg",
29245 b"AS",
29246 b"tag",
29247 b"TAG",
29248 ]);
29249 f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
29250 f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
29251 assert_eq!(
29252 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
29253 "*1\r\n*2\r\n:1\r\n:2\r\n"
29254 );
29255 assert_eq!(
29256 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
29257 "-Could not find given field in index spec\r\n"
29258 );
29259 }
29260
29261 #[test]
29264 fn a_dump_takes_the_attribute_and_not_the_identifier() {
29265 let mut f = Fixture::new();
29266 f.run(&[
29267 b"FT.CREATE",
29268 b"zx",
29269 b"PREFIX",
29270 b"1",
29271 b"z:",
29272 b"SCHEMA",
29273 b"gg",
29274 b"AS",
29275 b"tag",
29276 b"TAG",
29277 ]);
29278 f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
29279 assert_eq!(
29280 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
29281 "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
29282 );
29283 assert_eq!(
29284 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
29285 "-Could not find given field in index spec\r\n"
29286 );
29287 }
29288
29289 #[test]
29292 fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
29293 let mut f = debugging();
29294 assert_eq!(
29295 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
29296 "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
29297 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
29298 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
29299 +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\
29300 $5\r\nvalue\r\n$5\r\nalpha\r\n"
29301 );
29302 let mut g = debugging();
29303 g.run(&[b"HELLO", b"3"]);
29304 assert_eq!(
29305 g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
29306 "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
29307 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
29308 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
29309 +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\
29310 $5\r\nvalue\r\n$5\r\nalpha\r\n"
29311 );
29312 }
29313
29314 #[test]
29317 fn a_document_with_no_sortable_value_drops_the_key() {
29318 let mut f = debugging();
29319 assert_eq!(
29320 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
29321 "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
29322 +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"
29323 );
29324 }
29325
29326 #[test]
29329 fn the_flag_word_spells_out_the_bits_it_carries() {
29330 let mut f = Fixture::new();
29331 f.run(&[
29332 b"FT.CREATE",
29333 b"nx",
29334 b"NOOFFSETS",
29335 b"PREFIX",
29336 b"1",
29337 b"o:",
29338 b"SCHEMA",
29339 b"t",
29340 b"TEXT",
29341 ]);
29342 f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
29343 assert!(
29344 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
29345 .contains("$6\r\n(0x0):\r\n")
29346 );
29347 }
29348
29349 #[test]
29352 fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
29353 let mut f = debugging();
29354 assert!(
29355 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
29356 .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
29357 );
29358 }
29359
29360 #[test]
29363 fn a_document_row_reads_its_keyword_at_a_fixed_place() {
29364 let mut f = debugging();
29365 let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
29366 assert_eq!(
29367 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
29368 want
29369 );
29370 assert_eq!(
29371 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
29372 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
29373 );
29374 assert_eq!(
29375 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
29376 "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
29377 );
29378 }
29379
29380 #[test]
29383 fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
29384 let mut f = debugging();
29385 assert_eq!(
29386 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
29387 "-Document not found in index\r\n"
29388 );
29389 assert_eq!(
29390 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
29391 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
29392 );
29393 }
29394
29395 #[test]
29398 fn a_document_number_goes_both_ways() {
29399 let mut f = debugging();
29400 assert_eq!(
29401 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
29402 "$3\r\nd:2\r\n"
29403 );
29404 assert_eq!(
29405 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
29406 ":2\r\n"
29407 );
29408 assert_eq!(
29409 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
29410 ":0\r\n"
29411 );
29412 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
29413 for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
29414 assert_eq!(
29415 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
29416 "-document was removed\r\n",
29417 "{id:?}"
29418 );
29419 }
29420 }
29421
29422 #[test]
29425 fn a_document_number_is_read_the_strict_way() {
29426 let mut f = debugging();
29427 for id in [
29428 b"x".as_slice(),
29429 b"1.5",
29430 b" 1",
29431 b"+1",
29432 b"01",
29433 b"0x1",
29434 b"",
29435 b"9223372036854775808",
29436 b"18446744073709551615",
29437 ] {
29438 assert_eq!(
29439 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
29440 "-bad id given\r\n",
29441 "{id:?}"
29442 );
29443 }
29444 }
29445
29446 #[test]
29449 fn a_dump_keeps_a_number_the_table_has_given_up() {
29450 let mut f = debugging();
29451 f.run(&[b"DEL", b"d:2"]);
29452 assert_eq!(
29453 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29454 "*2\r\n:1\r\n:2\r\n"
29455 );
29456 assert_eq!(
29457 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
29458 "-document was removed\r\n"
29459 );
29460 assert_eq!(
29461 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
29462 ":0\r\n"
29463 );
29464 }
29465
29466 #[test]
29469 fn a_rewrite_takes_a_number_of_its_own() {
29470 let mut f = debugging();
29471 f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
29472 assert_eq!(
29473 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
29474 ":4\r\n"
29475 );
29476 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
29477 assert_eq!(
29478 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
29479 "-document was removed\r\n"
29480 );
29481 assert_eq!(
29482 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29483 "*2\r\n:1\r\n:2\r\n"
29484 );
29485 }
29486
29487 #[test]
29489 fn a_dump_follows_an_alias() {
29490 let mut f = debugging();
29491 f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
29492 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
29493 assert_eq!(
29494 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
29495 "$3\r\nd:1\r\n"
29496 );
29497 }
29498
29499 #[test]
29502 fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
29503 let mut f = debugging();
29504 assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
29505 assert_eq!(
29506 f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
29507 "-Can not create a search ctx\r\n"
29508 );
29509 assert_eq!(
29510 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
29511 "-Can not create a search ctx\r\n"
29512 );
29513 }
29514
29515 #[test]
29518 fn an_empty_field_dumps_as_nothing_at_all() {
29519 let mut f = Fixture::new();
29520 f.run(&[
29521 b"FT.CREATE",
29522 b"ex",
29523 b"PREFIX",
29524 b"1",
29525 b"e:",
29526 b"SCHEMA",
29527 b"t",
29528 b"TEXT",
29529 b"g",
29530 b"TAG",
29531 b"n",
29532 b"NUMERIC",
29533 ]);
29534 assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
29535 assert_eq!(
29536 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
29537 "*0\r\n"
29538 );
29539 assert_eq!(
29540 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
29541 "*0\r\n"
29542 );
29543 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
29544 }
29545
29546 #[test]
29549 fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
29550 let mut f = debugging();
29551 for (sub, extra) in [
29552 (b"DUMP_TERMS".as_slice(), 1),
29553 (b"GET_MAX_DOC_ID", 1),
29554 (b"DUMP_INVIDX", 2),
29555 (b"DUMP_TAGIDX", 2),
29556 (b"DUMP_NUMIDX", 2),
29557 (b"IDTODOCID", 2),
29558 (b"DOCIDTOID", 2),
29559 ] {
29560 let want = format!(
29561 "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
29562 str::from_utf8(sub).unwrap()
29563 );
29564 for given in [extra - 1, extra + 1] {
29565 let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
29566 cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
29567 assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
29568 }
29569 let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
29570 right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
29571 assert_ne!(f.run(&right), want, "{sub:?}");
29572 }
29573 assert_eq!(
29574 f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
29575 "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
29576 );
29577 }
29578
29579 #[test]
29582 fn the_help_names_the_subcommands_that_answer() {
29583 let mut f = Fixture::new();
29584 let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
29585 $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
29586 $14\r\nGET_MAX_DOC_ID\r\n";
29587 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
29588 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
29589 }
29590
29591 #[test]
29596 fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
29597 let mut f = Fixture::new();
29598 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29599 assert_eq!(
29600 f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
29601 "+OK\r\n"
29602 );
29603 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
29604 assert_eq!(
29605 f.run(&[b"FT.SYNDUMP", b"e"]),
29606 "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
29607 $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
29608 );
29609 }
29610
29611 #[test]
29615 fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
29616 let mut f = Fixture::new();
29617 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29618 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
29619 assert_eq!(
29620 f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
29621 "$69\r\nUNION {\n jogging\n ~gr(expanded)\n +jog(expanded)\n jog(expanded)\n}\n\r\n"
29622 );
29623 }
29624
29625 #[test]
29634 fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
29635 let mut f = Fixture::new();
29636 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29637 f.run(&[b"HSET", b"d1", b"t", b"boy"]);
29638 f.run(&[b"HSET", b"d2", b"t", b"runs"]);
29639 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
29640 assert_eq!(
29641 f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
29642 "*2\r\n:1\r\n$2\r\nd1\r\n"
29643 );
29644 assert_eq!(
29645 f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
29646 "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
29647 );
29648 }
29649
29650 #[test]
29653 fn a_synonym_command_on_a_name_that_is_not_there_fails() {
29654 let mut f = Fixture::new();
29655 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
29656 assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
29657 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
29658 }
29659
29660 #[test]
29664 fn params_counts_the_words_before_it_pairs_them_up() {
29665 let mut f = Fixture::new();
29666 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29667 let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
29668 Expected an argument, but none provided\r\n";
29669 let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
29670 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
29671 assert_eq!(
29672 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
29673 none
29674 );
29675 assert_eq!(
29676 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
29677 odd
29678 );
29679 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
29680 assert_eq!(
29681 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
29682 "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
29683 );
29684 }
29685
29686 fn vectored(f: &mut Fixture) {
29696 f.run(&[
29697 b"FT.CREATE",
29698 b"h",
29699 b"SCHEMA",
29700 b"t",
29701 b"TEXT",
29702 b"v",
29703 b"VECTOR",
29704 b"FLAT",
29705 b"6",
29706 b"TYPE",
29707 b"FLOAT32",
29708 b"DIM",
29709 b"2",
29710 b"DISTANCE_METRIC",
29711 b"L2",
29712 ]);
29713 let at: [&[u8]; 5] = [
29714 b"\x00\x00\x80\x40\x00\x00\x00\x00",
29715 b"\x00\x00\x40\x40\x00\x00\x00\x00",
29716 b"\x00\x00\x00\x40\x00\x00\x00\x00",
29717 b"\x00\x00\x80\x3f\x00\x00\x00\x00",
29718 b"\x00\x00\x00\x00\x00\x00\x00\x00",
29719 ];
29720 for (n, point) in at.iter().enumerate() {
29721 let key = format!("d{}", n + 1);
29722 let word: &[u8] = match n {
29723 0 | 1 | 3 => b"alpha",
29724 _ => b"beta",
29725 };
29726 f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
29727 }
29728 }
29729
29730 const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
29732
29733 #[test]
29737 fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
29738 let mut f = Fixture::new();
29739 vectored(&mut f);
29740 assert_eq!(
29741 f.run(&[
29742 b"FT.SEARCH",
29743 b"h",
29744 b"*=>[KNN 5 @v $vec]",
29745 b"PARAMS",
29746 b"2",
29747 b"vec",
29748 ORIGIN,
29749 b"DIALECT",
29750 b"2",
29751 b"NOCONTENT",
29752 ]),
29753 "*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"
29754 );
29755 assert_eq!(
29756 f.run(&[
29757 b"FT.SEARCH",
29758 b"h",
29759 b"*=>[KNN 3 @v $vec]",
29760 b"PARAMS",
29761 b"2",
29762 b"vec",
29763 ORIGIN,
29764 b"DIALECT",
29765 b"2",
29766 b"NOCONTENT",
29767 ]),
29768 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
29769 );
29770 }
29771
29772 #[test]
29775 fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
29776 let mut f = Fixture::new();
29777 vectored(&mut f);
29778 for (radius, want) in [
29779 ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
29780 ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
29781 (
29782 "9",
29783 "*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",
29784 ),
29785 ] {
29786 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
29787 assert_eq!(
29788 f.run(&[
29789 b"FT.SEARCH",
29790 b"h",
29791 query.as_bytes(),
29792 b"PARAMS",
29793 b"2",
29794 b"vec",
29795 ORIGIN,
29796 b"DIALECT",
29797 b"2",
29798 b"NOCONTENT",
29799 ]),
29800 want,
29801 "radius {radius}"
29802 );
29803 }
29804 }
29805
29806 #[test]
29810 fn a_knn_measures_what_the_query_in_front_of_it_matched() {
29811 let mut f = Fixture::new();
29812 vectored(&mut f);
29813 assert_eq!(
29814 f.run(&[
29815 b"FT.SEARCH",
29816 b"h",
29817 b"alpha=>[KNN 2 @v $vec]",
29818 b"PARAMS",
29819 b"2",
29820 b"vec",
29821 ORIGIN,
29822 b"DIALECT",
29823 b"2",
29824 b"NOCONTENT",
29825 ]),
29826 "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
29827 );
29828 }
29829
29830 #[test]
29839 fn a_count_and_a_radius_are_refused_in_their_own_words() {
29840 let mut f = Fixture::new();
29841 vectored(&mut f);
29842 let ask = |f: &mut Fixture, query: &str| {
29843 f.run(&[
29844 b"FT.SEARCH",
29845 b"h",
29846 query.as_bytes(),
29847 b"PARAMS",
29848 b"2",
29849 b"vec",
29850 ORIGIN,
29851 b"DIALECT",
29852 b"2",
29853 b"NOCONTENT",
29854 ])
29855 };
29856 for (query, at, near) in [
29857 ("*=>[KNN -1 @v $vec]", 8, "-1"),
29858 ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
29859 ("*=>[KNN +3 @v $vec]", 8, "+3"),
29860 ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
29861 ("*=>[KNN abc @v $vec]", 8, "abc"),
29862 ("*=>[KNN 3 $vec]", 10, "vec"),
29863 ("*=>[KNN 3 @v vec]", 13, "vec"),
29864 ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
29865 ] {
29866 assert_eq!(
29867 ask(&mut f, query),
29868 format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
29869 "{query}"
29870 );
29871 }
29872
29873 let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29876 query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
29877 assert_eq!(
29878 ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
29879 "*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"
29880 );
29881 assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
29882 assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
29883
29884 for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
29885 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
29886 assert_eq!(
29887 ask(&mut f, &query),
29888 format!(
29889 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29890 negative radius ({printed}) given in a range query\r\n"
29891 ),
29892 "{query}"
29893 );
29894 }
29895 assert_eq!(
29897 ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
29898 "*2\r\n:1\r\n$2\r\nd5\r\n"
29899 );
29900 }
29901
29902 #[test]
29905 fn a_count_that_came_from_params_is_read_by_its_own_rules() {
29906 let mut f = Fixture::new();
29907 vectored(&mut f);
29908 let ask = |f: &mut Fixture, count: &[u8]| {
29909 f.run(&[
29910 b"FT.SEARCH",
29911 b"h",
29912 b"*=>[KNN $k @v $vec]",
29913 b"PARAMS",
29914 b"4",
29915 b"vec",
29916 ORIGIN,
29917 b"k",
29918 count,
29919 b"DIALECT",
29920 b"2",
29921 b"NOCONTENT",
29922 ])
29923 };
29924 let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
29925 assert_eq!(ask(&mut f, b"3"), three);
29926 assert_eq!(ask(&mut f, b" 3"), three);
29927 assert_eq!(ask(&mut f, b"+3"), three);
29928 for bad in [
29929 &b"3.0"[..],
29930 b"0x3",
29931 b"-1",
29932 b"abc",
29933 b"",
29934 b"99999999999999999999",
29935 ] {
29936 let value = String::from_utf8_lossy(bad).into_owned();
29937 assert_eq!(
29938 ask(&mut f, bad),
29939 format!(
29940 "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
29941 for parameter `k`\r\n"
29942 ),
29943 "{value}"
29944 );
29945 }
29946 assert_eq!(
29947 ask(&mut f, b"288230376151711777"),
29948 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29949 query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
29950 );
29951 }
29952
29953 #[test]
29956 fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
29957 let mut f = Fixture::new();
29958 vectored(&mut f);
29959 assert_eq!(
29960 f.run(&[
29961 b"FT.SEARCH",
29962 b"h",
29963 b"*=>[KNN 5 @v $vec]",
29964 b"PARAMS",
29965 b"2",
29966 b"vec",
29967 b"abc",
29968 b"DIALECT",
29969 b"2",
29970 b"NOCONTENT",
29971 ]),
29972 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29973 query vector blob size (3) does not match index's expected size (8).\r\n"
29974 );
29975 }
29976
29977 #[test]
29982 fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
29983 let mut f = Fixture::new();
29984 vectored(&mut f);
29985 let ask = |f: &mut Fixture, query: &str| {
29986 f.run(&[
29987 b"FT.SEARCH",
29988 b"h",
29989 query.as_bytes(),
29990 b"PARAMS",
29991 b"2",
29992 b"vec",
29993 ORIGIN,
29994 b"DIALECT",
29995 b"2",
29996 b"LIMIT",
29997 b"0",
29998 b"1",
29999 ])
30000 };
30001 assert_eq!(
30002 ask(&mut f, "*=>[KNN 3 @v $vec]"),
30003 "*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"
30004 );
30005 assert_eq!(
30006 ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
30007 "*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"
30008 );
30009 assert_eq!(
30010 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
30011 "*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"
30012 );
30013 assert_eq!(
30014 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
30015 "*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"
30016 );
30017 }
30018
30019 #[test]
30026 fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
30027 let mut f = Fixture::new();
30028 vectored(&mut f);
30029 let ask = |f: &mut Fixture, ret: &[&[u8]]| {
30030 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
30031 args.extend_from_slice(ret);
30032 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
30033 f.run(&args)
30034 };
30035 assert_eq!(
30036 ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
30037 "*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"
30038 );
30039 assert_eq!(
30040 ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
30041 "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
30042 );
30043 assert_eq!(
30044 ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
30045 "*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"
30046 );
30047 assert_eq!(
30048 ask(&mut f, &[b"RETURN", b"1", b"t"]),
30049 "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
30050 );
30051 assert_eq!(
30054 ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
30055 "*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"
30056 );
30057 assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
30058 }
30059
30060 #[test]
30064 fn a_sortby_can_name_a_distance_the_query_yielded() {
30065 let mut f = Fixture::new();
30066 vectored(&mut f);
30067 let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
30068 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
30069 if desc {
30070 args.push(b"DESC");
30071 }
30072 args.extend_from_slice(&[
30073 b"PARAMS",
30074 b"2",
30075 b"vec",
30076 ORIGIN,
30077 b"DIALECT",
30078 b"2",
30079 b"NOCONTENT",
30080 ]);
30081 f.run(&args)
30082 };
30083 assert_eq!(
30084 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
30085 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
30086 );
30087 assert_eq!(
30088 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
30089 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
30090 );
30091 assert_eq!(
30092 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
30093 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
30094 );
30095 let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
30098 not loaded nor in schema\r\n";
30099 assert_eq!(
30100 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
30101 missing
30102 );
30103 assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
30104 assert_eq!(
30107 ask(&mut f, "foo(", b"zz", false),
30108 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
30109 );
30110 }
30111
30112 #[test]
30114 fn two_vector_clauses_answer_two_distances() {
30115 let mut f = Fixture::new();
30116 vectored(&mut f);
30117 assert_eq!(
30118 f.run(&[
30119 b"FT.SEARCH",
30120 b"h",
30121 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
30122 b"RETURN",
30123 b"2",
30124 b"rr",
30125 b"__v_score",
30126 b"PARAMS",
30127 b"2",
30128 b"vec",
30129 ORIGIN,
30130 b"DIALECT",
30131 b"2",
30132 ]),
30133 "*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"
30134 );
30135 }
30136
30137 #[test]
30141 fn an_aggregation_answers_a_distance_nothing_asked_for() {
30142 let mut f = Fixture::new();
30143 vectored(&mut f);
30144 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
30145 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
30146 args.extend_from_slice(rest);
30147 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
30148 f.run(&args)
30149 };
30150 assert_eq!(
30151 ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
30152 "*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"
30153 );
30154 assert_eq!(
30155 ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
30156 "*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"
30157 );
30158 assert_eq!(
30159 ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
30160 "*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"
30161 );
30162 assert_eq!(
30164 ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
30165 "*3\r\n:1\r\n*0\r\n*0\r\n"
30166 );
30167 assert_eq!(
30168 ask(
30169 &mut f,
30170 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
30171 &[]
30172 ),
30173 "*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"
30174 );
30175 }
30176
30177 #[test]
30181 fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
30182 let mut f = Fixture::new();
30183 vectored(&mut f);
30184 const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
30186 let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
30187 f.run(&[
30188 b"FT.AGGREGATE",
30189 b"h",
30190 query.as_bytes(),
30191 b"LOAD",
30192 b"1",
30193 b"@t",
30194 b"PARAMS",
30195 b"2",
30196 b"vec",
30197 vec,
30198 b"DIALECT",
30199 b"2",
30200 ])
30201 };
30202 assert_eq!(
30203 ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
30204 "*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"
30205 );
30206 assert_eq!(
30208 ask(
30209 &mut f,
30210 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
30211 MIDDLE
30212 ),
30213 "*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"
30214 );
30215 }
30216
30217 #[test]
30221 fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
30222 let mut f = Fixture::new();
30223 vectored(&mut f);
30224 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
30225 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
30226 args.extend_from_slice(rest);
30227 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
30228 f.run(&args)
30229 };
30230 let knn = "*=>[KNN 2 @v $vec]";
30231 assert_eq!(
30232 ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
30233 "*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"
30234 );
30235 assert_eq!(
30236 ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
30237 "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
30238 );
30239 assert_eq!(
30240 ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
30241 "*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"
30242 );
30243 assert_eq!(
30244 ask(
30245 &mut f,
30246 knn,
30247 &[
30248 b"GROUPBY",
30249 b"1",
30250 b"@t",
30251 b"REDUCE",
30252 b"MAX",
30253 b"1",
30254 b"@__v_score",
30255 b"AS",
30256 b"m"
30257 ]
30258 ),
30259 "*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"
30260 );
30261 assert_eq!(
30262 ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
30263 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
30264 `__v_score`\r\n"
30265 );
30266 assert_eq!(
30267 ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
30268 "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
30269 );
30270 assert_eq!(
30271 ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
30272 "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
30273 schema\r\n"
30274 );
30275 }
30276
30277 #[test]
30285 fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
30286 let mut f = Fixture::new();
30287 vectored(&mut f);
30288 let ask = |f: &mut Fixture, rest: &[&[u8]]| {
30289 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
30290 args.extend_from_slice(rest);
30291 f.run(&args)
30292 };
30293 assert_eq!(
30294 ask(
30295 &mut f,
30296 &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
30297 ),
30298 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
30299 );
30300 assert_eq!(
30301 ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
30302 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
30303 );
30304 assert_eq!(
30305 ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
30306 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
30307 );
30308 assert_eq!(
30311 ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
30312 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
30313 );
30314 assert_eq!(
30315 ask(
30316 &mut f,
30317 &[
30318 b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
30319 ]
30320 ),
30321 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
30322 );
30323 assert_eq!(
30324 ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
30325 "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
30326 );
30327 }
30328
30329 #[test]
30332 fn a_vector_step_says_which_way_it_was_answered() {
30333 let mut f = Fixture::new();
30334 vectored(&mut f);
30335 let tree = |f: &mut Fixture, query: &[u8]| {
30336 let reply = timeless(&f.run(&[
30337 b"FT.PROFILE",
30338 b"h",
30339 b"AGGREGATE",
30340 b"QUERY",
30341 query,
30342 b"PARAMS",
30343 b"2",
30344 b"vec",
30345 ORIGIN,
30346 b"DIALECT",
30347 b"2",
30348 ]));
30349 let at = reply.find("+Iterators profile").expect("a tree");
30350 let end = reply.find("+Result processors").expect("a list of steps");
30351 reply[at..end].to_string()
30352 };
30353 assert_eq!(
30354 tree(&mut f, b"*=>[KNN 3 @v $vec]"),
30355 "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
30356 +Number of reading operations\r\n:3\r\n\
30357 +Vector search mode\r\n+STANDARD_KNN\r\n"
30358 );
30359 assert_eq!(
30361 tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
30362 tree(&mut f, b"*=>[KNN 3 @v $vec]")
30363 );
30364 assert_eq!(
30367 tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
30368 "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
30369 +Number of reading operations\r\n:4\r\n"
30370 );
30371 assert_eq!(
30372 tree(
30373 &mut f,
30374 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
30375 ),
30376 "+Iterators profile\r\n*8\r\n\
30377 +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
30378 +Number of reading operations\r\n:4\r\n\
30379 +Vector search mode\r\n+RANGE_QUERY\r\n"
30380 );
30381 }
30382
30383 #[test]
30387 fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
30388 let mut f = Fixture::new();
30389 vectored(&mut f);
30390 let ask = |f: &mut Fixture, query: &[u8]| {
30391 timeless(&f.run(&[
30392 b"FT.PROFILE",
30393 b"h",
30394 b"AGGREGATE",
30395 b"QUERY",
30396 query,
30397 b"PARAMS",
30398 b"2",
30399 b"vec",
30400 ORIGIN,
30401 b"DIALECT",
30402 b"2",
30403 ]))
30404 };
30405 let cut = |reply: &str| {
30406 let at = reply.find("+Iterators profile").expect("a tree");
30407 reply[at..].to_string()
30408 };
30409 assert_eq!(
30410 cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
30411 "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
30412 +Number of reading operations\r\n:3\r\n\
30413 +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
30414 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
30415 +Number of reading operations\r\n:3\r\n\
30416 +Estimated number of matches\r\n:3\r\n\
30417 +Result processors profile\r\n*2\r\n\
30418 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
30419 *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
30420 +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
30421 );
30422 assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
30425 "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
30426 +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
30427 ));
30428 assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
30431 }
30432
30433 #[test]
30437 fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
30438 let mut f = profiling();
30439 let steps = |f: &mut Fixture, words: &[&[u8]]| {
30440 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
30441 argv.extend_from_slice(words);
30442 let reply = timeless(&f.run(&argv));
30443 let at = reply.find("+Result processors").expect("a list of steps");
30444 reply[at..].to_string()
30445 };
30446 assert_eq!(
30447 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
30448 "+Result processors profile\r\n*2\r\n\
30449 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
30450 *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
30451 +Coordinator\r\n*0\r\n"
30452 );
30453 assert!(
30454 steps(
30455 &mut f,
30456 &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
30457 )
30458 .contains("+Type\r\n+Counter\r\n")
30459 );
30460 assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
30462 "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
30463 +Results processed\r\n:2\r\n"
30464 ));
30465 }
30466
30467 #[test]
30473 fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
30474 let mut f = Fixture::new();
30475 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30476 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30477 f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
30478 assert_eq!(
30479 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
30480 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
30481 *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"
30482 );
30483 }
30484
30485 #[test]
30489 fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
30490 let mut f = Fixture::new();
30491 f.run(&[b"HELLO", b"3"]);
30492 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30493 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30494 assert_eq!(
30495 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
30496 "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
30497 *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
30498 );
30499 }
30500
30501 #[test]
30505 fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
30506 let mut f = Fixture::new();
30507 f.run(&[
30508 b"FT.CREATE",
30509 b"e",
30510 b"SCHEMA",
30511 b"a",
30512 b"TEXT",
30513 b"NOSTEM",
30514 b"b",
30515 b"TEXT",
30516 b"NOSTEM",
30517 ]);
30518 f.run(&[b"HSET", b"d1", b"b", b"world"]);
30519 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
30520 assert_eq!(
30521 f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
30522 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
30523 );
30524 }
30525
30526 #[test]
30530 fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
30531 let mut f = Fixture::new();
30532 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30533 f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
30534 assert_eq!(
30535 f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
30536 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
30537 *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"
30538 );
30539 assert_eq!(
30540 f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
30541 "*0\r\n"
30542 );
30543 assert_eq!(
30544 f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
30545 "-Dict does not exist: nope\r\n"
30546 );
30547 }
30548
30549 #[test]
30553 fn a_spellcheck_reads_its_arguments_leniently() {
30554 let mut f = Fixture::new();
30555 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30556 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30557 let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
30558 *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
30559 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
30560 let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
30561 let args: &[&[u8]] = &[
30562 b"FT.SPELLCHECK",
30563 b"e",
30564 b"helqp",
30565 b"DISTANCE",
30566 b"1",
30567 b"DISTANCE",
30568 b"4",
30569 ];
30570 assert_eq!(f.run(args), none);
30571 assert_eq!(
30572 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
30573 "-bad distance given, distance must be a natural number between 1 to 4\r\n"
30574 );
30575 assert_eq!(
30576 f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
30577 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
30578 );
30579 }
30580
30581 #[test]
30586 fn an_add_answers_how_many_suggestions_are_in_there_now() {
30587 let mut f = Fixture::new();
30588 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
30589 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
30590 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
30591 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
30592 assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
30593 }
30594
30595 #[test]
30598 fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
30599 let mut f = Fixture::new();
30600 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30601 assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
30602 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
30603 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
30604 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
30605 assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
30606 assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
30607 assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
30608 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
30609 }
30610
30611 #[test]
30614 fn deleting_the_last_suggestion_deletes_the_key() {
30615 let mut f = Fixture::new();
30616 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30617 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
30618 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
30619 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
30620 assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
30621 }
30622
30623 #[test]
30626 fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
30627 let mut f = Fixture::new();
30628 f.run(&[b"SET", b"s", b"x"]);
30629 for cmd in [
30630 vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
30631 vec![&b"FT.SUGGET"[..], b"s", b"t"],
30632 vec![&b"FT.SUGDEL"[..], b"s", b"t"],
30633 vec![&b"FT.SUGLEN"[..], b"s"],
30634 ] {
30635 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
30636 }
30637 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
30638 }
30639
30640 #[test]
30643 fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
30644 let mut f = Fixture::new();
30645 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30646 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
30647 f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
30648 assert_eq!(
30649 f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
30650 "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
30651 $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
30652 $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
30653 );
30654 assert_eq!(
30655 f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
30656 "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
30657 );
30658 assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
30659 }
30660
30661 #[test]
30663 fn fuzzy_allows_one_edit_and_nothing_allows_two() {
30664 let mut f = Fixture::new();
30665 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
30666 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
30667 assert_eq!(
30668 f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
30669 "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
30670 );
30671 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
30672 }
30673
30674 #[test]
30676 fn a_lookup_answers_five_unless_it_is_told_otherwise() {
30677 let mut f = Fixture::new();
30678 for (term, score) in [
30679 (&b"a1"[..], &b"1"[..]),
30680 (b"a2", b"2"),
30681 (b"a3", b"3"),
30682 (b"a4", b"4"),
30683 (b"a5", b"5"),
30684 (b"a6", b"6"),
30685 ] {
30686 f.run(&[b"FT.SUGADD", b"s", term, score]);
30687 }
30688 assert_eq!(
30689 f.run(&[b"FT.SUGGET", b"s", b"a"]),
30690 "*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"
30691 );
30692 assert_eq!(
30693 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
30694 "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
30695 );
30696 assert!(
30698 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
30699 .starts_with("*6\r\n")
30700 );
30701 }
30702
30703 #[test]
30706 fn a_payload_comes_back_beside_the_term_or_a_null_does() {
30707 let mut f = Fixture::new();
30708 f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
30709 assert_eq!(
30710 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30711 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30712 );
30713 f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
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", b"PAYLOAD", b""]);
30721 assert_eq!(
30722 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30723 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30724 );
30725 f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
30727 assert_eq!(
30728 f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
30729 "*2\r\n$5\r\nother\r\n$-1\r\n"
30730 );
30731 }
30732
30733 #[test]
30737 fn incr_adds_to_the_score_that_is_already_there() {
30738 let mut f = Fixture::new();
30739 for _ in 0..3 {
30740 f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
30741 }
30742 assert_eq!(
30743 f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
30744 "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
30745 );
30746 }
30747
30748 #[test]
30750 fn the_suggestion_errors_are_the_lines_the_module_sends() {
30751 let mut f = Fixture::new();
30752 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30753 assert_eq!(
30754 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
30755 "-ERR invalid score\r\n"
30756 );
30757 assert_eq!(
30759 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
30760 "-Unknown argument `NOPE`\r\n"
30761 );
30762 assert_eq!(
30763 f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
30764 "-Invalid payload: Expected an argument, but none provided\r\n"
30765 );
30766 assert!(
30768 f.run(&[
30769 b"FT.SUGADD",
30770 b"s",
30771 b"t",
30772 b"1",
30773 b"PAYLOAD",
30774 b"a",
30775 b"PAYLOAD",
30776 b"b"
30777 ])
30778 .contains("wrong number of arguments")
30779 );
30780 assert_eq!(
30781 f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
30782 "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
30783 );
30784 for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
30788 assert_eq!(
30789 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30790 "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
30791 "{}",
30792 String::from_utf8_lossy(max)
30793 );
30794 }
30795 for max in [
30796 &b"abc"[..],
30797 b"0.0",
30798 b"00",
30799 b"-0",
30800 b"+0",
30801 b"0.5",
30802 b"-1.5",
30803 b"1e400",
30804 ] {
30805 assert_eq!(
30806 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30807 "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
30808 "{}",
30809 String::from_utf8_lossy(max)
30810 );
30811 }
30812 for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
30813 assert_eq!(
30814 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30815 "*1\r\n$3\r\none\r\n",
30816 "{}",
30817 String::from_utf8_lossy(max)
30818 );
30819 }
30820 assert_eq!(
30821 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
30822 "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
30823 );
30824 assert_eq!(
30827 f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
30828 "-ERR invalid score\r\n"
30829 );
30830 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
30831 }
30832
30833 #[test]
30837 fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
30838 let mut f = Fixture::new();
30839 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30840 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
30841 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
30842 assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
30843 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
30844 assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
30845 assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
30846 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
30847 }
30848
30849 #[test]
30852 fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
30853 let mut f = Fixture::new();
30854 f.run(&[
30855 b"FT.CREATE",
30856 b"ix",
30857 b"PREFIX",
30858 b"1",
30859 b"p:",
30860 b"SCHEMA",
30861 b"n",
30862 b"NUMERIC",
30863 ]);
30864 f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
30865 assert_eq!(held(&f, b"ix"), (0, 0));
30866
30867 let reply = f.run(&[b"FT.INFO", b"ix"]);
30868 assert!(
30869 reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
30870 "{reply}"
30871 );
30872 assert!(reply.contains("hash_indexing_failures"), "{reply}");
30873 }
30874
30875 #[test]
30878 fn an_index_can_only_be_made_on_database_zero() {
30879 let mut f = Fixture::new();
30880 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
30881 f.run(&[b"SELECT", b"1"]);
30882 let refused = "-Cannot create index on db != 0\r\n";
30883 assert_eq!(
30884 f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
30885 refused
30886 );
30887 assert_eq!(
30889 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
30890 refused
30891 );
30892 assert_eq!(
30894 f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
30895 refused
30896 );
30897 assert_eq!(
30899 f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
30900 "+OK\r\n"
30901 );
30902 assert_eq!(f.server.search.lock().len(), 1);
30903 }
30904
30905 #[test]
30911 fn the_scan_is_one_database_and_the_following_is_all_of_them() {
30912 let mut f = Fixture::new();
30913 f.run(&[b"SELECT", b"1"]);
30914 f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
30915 f.run(&[b"SELECT", b"0"]);
30916 f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
30917 f.run(&[
30918 b"FT.CREATE",
30919 b"ix",
30920 b"PREFIX",
30921 b"1",
30922 b"p:",
30923 b"SCHEMA",
30924 b"t",
30925 b"TEXT",
30926 ]);
30927 assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
30928
30929 f.run(&[b"SELECT", b"1"]);
30930 f.run(&[b"HSET", b"p:8", b"t", b"later"]);
30931 assert_eq!(
30932 held(&f, b"ix"),
30933 (2, 2),
30934 "and then it follows every database"
30935 );
30936 }
30937
30938 fn corpus(f: &mut Fixture) {
30941 f.run(&[
30942 b"FT.CREATE",
30943 b"sx",
30944 b"PREFIX",
30945 b"1",
30946 b"d:",
30947 b"SCHEMA",
30948 b"t",
30949 b"TEXT",
30950 b"g",
30951 b"TAG",
30952 b"n",
30953 b"NUMERIC",
30954 ]);
30955 for (key, text, tag, number) in [
30956 (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
30957 (b"d:2", "alpha gamma", "bb", "2"),
30958 (b"d:3", "delta", "cc", "3"),
30959 (b"d:4", "alpha beta gamma", "aa,cc", "4"),
30960 ] {
30961 f.run(&[
30962 b"HSET",
30963 key,
30964 b"t",
30965 text.as_bytes(),
30966 b"g",
30967 tag.as_bytes(),
30968 b"n",
30969 number.as_bytes(),
30970 ]);
30971 }
30972 }
30973
30974 fn sortable(f: &mut Fixture) {
30978 f.run(&[
30979 b"FT.CREATE",
30980 b"sy",
30981 b"PREFIX",
30982 b"1",
30983 b"s:",
30984 b"SCHEMA",
30985 b"t",
30986 b"TEXT",
30987 b"SORTABLE",
30988 b"n",
30989 b"NUMERIC",
30990 b"SORTABLE",
30991 b"body",
30992 b"AS",
30993 b"b",
30994 b"TEXT",
30995 b"SORTABLE",
30996 b"p",
30997 b"TEXT",
30998 ]);
30999 for (key, text, number) in [
31000 (b"s:1".as_slice(), "Banana Split", "2"),
31001 (b"s:2", "apple", "10"),
31002 ] {
31003 f.run(&[
31004 b"HSET",
31005 key,
31006 b"t",
31007 text.as_bytes(),
31008 b"n",
31009 number.as_bytes(),
31010 b"body",
31011 text.as_bytes(),
31012 b"p",
31013 b"alpha",
31014 ]);
31015 }
31016 f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
31019 }
31020
31021 #[test]
31024 fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
31025 let mut f = Fixture::new();
31026 sortable(&mut f);
31027 assert_eq!(
31028 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
31029 "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
31030 );
31031 assert_eq!(
31032 f.run(&[
31033 b"FT.SEARCH",
31034 b"sy",
31035 b"alpha",
31036 b"SORTBY",
31037 b"n",
31038 b"DESC",
31039 b"NOCONTENT"
31040 ]),
31041 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
31042 );
31043 assert_eq!(
31046 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
31047 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
31048 );
31049 }
31050
31051 #[test]
31054 fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
31055 let mut f = Fixture::new();
31056 sortable(&mut f);
31057 f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
31058 assert_eq!(
31059 f.run(&[
31060 b"FT.SEARCH",
31061 b"sy",
31062 b"alpha",
31063 b"SORTBY",
31064 b"p",
31065 b"NOCONTENT",
31066 b"LIMIT",
31067 b"0",
31068 b"2"
31069 ]),
31070 "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
31071 );
31072 assert_eq!(
31075 f.run(&[
31076 b"FT.SEARCH",
31077 b"sy",
31078 b"alpha",
31079 b"SORTBY",
31080 b"p",
31081 b"WITHSORTKEYS",
31082 b"NOCONTENT",
31083 b"LIMIT",
31084 b"2",
31085 b"1"
31086 ]),
31087 "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
31088 );
31089 }
31090
31091 #[test]
31094 fn a_search_can_send_the_value_it_sorted_by_back() {
31095 let mut f = Fixture::new();
31096 sortable(&mut f);
31097 assert_eq!(
31098 f.run(&[
31099 b"FT.SEARCH",
31100 b"sy",
31101 b"alpha",
31102 b"SORTBY",
31103 b"n",
31104 b"WITHSORTKEYS",
31105 b"NOCONTENT"
31106 ]),
31107 concat!(
31108 "*7\r\n:3\r\n",
31109 "$3\r\ns:1\r\n$2\r\n#2\r\n",
31110 "$3\r\ns:2\r\n$3\r\n#10\r\n",
31111 "$3\r\ns:3\r\n$-1\r\n"
31112 )
31113 );
31114 assert_eq!(
31115 f.run(&[
31116 b"FT.SEARCH",
31117 b"sy",
31118 b"alpha",
31119 b"SORTBY",
31120 b"t",
31121 b"WITHSORTKEYS",
31122 b"NOCONTENT"
31123 ]),
31124 concat!(
31125 "*7\r\n:3\r\n",
31126 "$3\r\ns:2\r\n$6\r\n$apple\r\n",
31127 "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
31128 "$3\r\ns:3\r\n$-1\r\n"
31129 )
31130 );
31131 assert_eq!(
31134 f.run(&[
31135 b"FT.SEARCH",
31136 b"sy",
31137 b"banana",
31138 b"WITHSORTKEYS",
31139 b"NOCONTENT"
31140 ]),
31141 "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
31142 );
31143 }
31144
31145 #[test]
31148 fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
31149 let mut f = Fixture::new();
31150 sortable(&mut f);
31151 assert_eq!(
31155 f.run(&[
31156 b"FT.SEARCH",
31157 b"sy",
31158 b"alpha",
31159 b"SORTBY",
31160 b"b",
31161 b"LIMIT",
31162 b"0",
31163 b"1"
31164 ]),
31165 concat!(
31166 "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
31167 "$1\r\nb\r\n$5\r\napple\r\n",
31168 "$1\r\nt\r\n$5\r\napple\r\n",
31169 "$1\r\nn\r\n$2\r\n10\r\n",
31170 "$4\r\nbody\r\n$5\r\napple\r\n",
31171 "$1\r\np\r\n$5\r\nalpha\r\n"
31172 )
31173 );
31174 assert_eq!(
31177 f.run(&[
31178 b"FT.SEARCH",
31179 b"sy",
31180 b"alpha",
31181 b"SORTBY",
31182 b"b",
31183 b"RETURN",
31184 b"2",
31185 b"p",
31186 b"b",
31187 b"LIMIT",
31188 b"0",
31189 b"1"
31190 ]),
31191 concat!(
31192 "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
31193 "$1\r\nb\r\n$5\r\napple\r\n",
31194 "$1\r\np\r\n$5\r\nalpha\r\n"
31195 )
31196 );
31197 }
31198
31199 #[test]
31201 fn a_search_refuses_the_sorts_it_cannot_run() {
31202 let mut f = Fixture::new();
31203 sortable(&mut f);
31204 assert_eq!(
31205 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
31206 "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
31207 );
31208 assert_eq!(
31209 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
31210 "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
31211 );
31212 assert_eq!(
31213 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
31214 "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
31215 );
31216 assert_eq!(
31217 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
31218 "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
31219 );
31220 assert_eq!(
31223 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
31224 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
31225 );
31226 }
31227
31228 fn marking(f: &mut Fixture) {
31232 f.run(&[
31233 b"FT.CREATE",
31234 b"mk",
31235 b"ON",
31236 b"HASH",
31237 b"PREFIX",
31238 b"1",
31239 b"m:",
31240 b"SCHEMA",
31241 b"a",
31242 b"TEXT",
31243 b"b",
31244 b"TEXT",
31245 b"n",
31246 b"NUMERIC",
31247 b"g",
31248 b"TAG",
31249 ]);
31250 f.run(&[
31251 b"HSET",
31252 b"m:1",
31253 b"a",
31254 b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
31255 b"b",
31256 b"t1 t2 t3 t4 t5 t6 t7 t8",
31257 b"n",
31258 b"1",
31259 b"g",
31260 b"red",
31261 ]);
31262 }
31263
31264 #[test]
31267 fn a_summarize_cuts_a_field_down_to_what_matched() {
31268 let mut f = Fixture::new();
31269 marking(&mut f);
31270 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
31271 assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
31272 assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
31274 assert!(got.contains("$1\r\nr\r\n"), "{got}");
31276 }
31277
31278 #[test]
31282 fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
31283 let mut f = Fixture::new();
31284 marking(&mut f);
31285 let got = f.run(&[
31286 b"FT.SEARCH",
31287 b"mk",
31288 b"fox",
31289 b"SUMMARIZE",
31290 b"FRAGS",
31291 b"1",
31292 b"LEN",
31293 b"20",
31294 ]);
31295 assert!(
31296 got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
31297 "{got}"
31298 );
31299 let two = f.run(&[
31302 b"FT.SEARCH",
31303 b"mk",
31304 b"fox",
31305 b"SUMMARIZE",
31306 b"FRAGS",
31307 b"2",
31308 b"LEN",
31309 b"20",
31310 ]);
31311 assert!(
31312 two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
31313 "{two}"
31314 );
31315 }
31316
31317 #[test]
31320 fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
31321 let mut f = Fixture::new();
31322 marking(&mut f);
31323 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
31324 assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
31325 let both = f.run(&[
31326 b"FT.SEARCH",
31327 b"mk",
31328 b"fox",
31329 b"SUMMARIZE",
31330 b"LEN",
31331 b"2",
31332 b"HIGHLIGHT",
31333 ]);
31334 assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
31335 assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
31337 assert!(both.contains("$3\r\nred\r\n"), "{both}");
31338 let split = f.run(&[
31340 b"FT.SEARCH",
31341 b"mk",
31342 b"fox",
31343 b"SUMMARIZE",
31344 b"FIELDS",
31345 b"1",
31346 b"b",
31347 b"LEN",
31348 b"2",
31349 b"HIGHLIGHT",
31350 b"FIELDS",
31351 b"1",
31352 b"a",
31353 ]);
31354 assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
31355 }
31356
31357 #[test]
31359 fn a_highlight_does_not_mark_a_tag() {
31360 let mut f = Fixture::new();
31361 marking(&mut f);
31362 f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
31363 let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
31364 assert!(!got.contains("<b>"), "{got}");
31365 assert!(got.contains("red and blue"), "{got}");
31366 }
31367
31368 #[test]
31371 fn a_search_answers_a_total_and_then_the_rows() {
31372 let mut f = Fixture::new();
31373 corpus(&mut f);
31374 assert_eq!(
31375 f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
31376 "*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"
31377 );
31378 f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
31381 assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
31382 assert_eq!(
31385 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
31386 "*2\r\n:1\r\n$3\r\nd:3\r\n"
31387 );
31388 assert_eq!(
31389 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
31390 "*1\r\n:3\r\n"
31391 );
31392 }
31393
31394 #[test]
31397 fn the_window_is_ten_rows_and_a_million_wide_at_most() {
31398 let mut f = Fixture::new();
31399 corpus(&mut f);
31400 assert_eq!(
31401 f.run(&[
31402 b"FT.SEARCH",
31403 b"sx",
31404 b"alpha",
31405 b"NOCONTENT",
31406 b"LIMIT",
31407 b"1",
31408 b"1"
31409 ]),
31410 "*2\r\n:3\r\n$3\r\nd:2\r\n"
31411 );
31412 assert_eq!(
31413 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
31414 "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
31415 );
31416 assert_eq!(
31417 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
31418 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
31419 );
31420 assert_eq!(
31421 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
31422 "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
31423 );
31424 assert_eq!(
31425 f.run(&[
31426 b"FT.SEARCH",
31427 b"sx",
31428 b"alpha",
31429 b"NOCONTENT",
31430 b"LIMIT",
31431 b"999999",
31432 b"1000000"
31433 ]),
31434 "*1\r\n:3\r\n"
31435 );
31436 }
31437
31438 #[test]
31442 fn a_return_of_nothing_is_not_the_same_as_nocontent() {
31443 let mut f = Fixture::new();
31444 corpus(&mut f);
31445 let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
31446 assert_eq!(
31447 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
31448 bare
31449 );
31450 assert_eq!(
31451 f.run(&[
31452 b"FT.SEARCH",
31453 b"sx",
31454 b"delta",
31455 b"NOCONTENT",
31456 b"RETURN",
31457 b"1",
31458 b"t"
31459 ]),
31460 bare
31461 );
31462 assert_eq!(
31463 f.run(&[
31464 b"FT.SEARCH",
31465 b"sx",
31466 b"delta",
31467 b"RETURN",
31468 b"0",
31469 b"RETURN",
31470 b"1",
31471 b"t"
31472 ]),
31473 "*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"
31474 );
31475 }
31476
31477 #[test]
31480 fn the_count_after_return_counts_words() {
31481 let mut f = Fixture::new();
31482 corpus(&mut f);
31483 assert_eq!(
31486 f.run(&[
31487 b"FT.SEARCH",
31488 b"sx",
31489 b"delta",
31490 b"RETURN",
31491 b"3",
31492 b"t",
31493 b"AS",
31494 b"x"
31495 ]),
31496 "*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"
31497 );
31498 assert_eq!(
31501 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
31502 "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
31503 );
31504 assert_eq!(
31505 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
31506 "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
31507 );
31508 assert_eq!(
31512 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
31513 "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
31514 );
31515 }
31516
31517 #[test]
31520 fn a_filter_is_a_range_written_outside_the_query() {
31521 let mut f = Fixture::new();
31522 corpus(&mut f);
31523 assert_eq!(
31524 f.run(&[
31525 b"FT.SEARCH",
31526 b"sx",
31527 b"alpha",
31528 b"NOCONTENT",
31529 b"FILTER",
31530 b"n",
31531 b"2",
31532 b"4"
31533 ]),
31534 "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
31535 );
31536 assert_eq!(
31537 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
31538 "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
31539 );
31540 assert_eq!(
31541 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
31542 "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
31543 );
31544 assert_eq!(
31545 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
31546 "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
31547 );
31548 for field in [b"g".as_slice(), b"nope"] {
31551 assert_eq!(
31552 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
31553 "*1\r\n:0\r\n"
31554 );
31555 }
31556 }
31557
31558 #[test]
31561 fn the_index_is_found_before_the_arguments_are_read() {
31562 let mut f = Fixture::new();
31563 corpus(&mut f);
31564 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
31565 assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
31566 assert_eq!(
31567 f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
31568 missing
31569 );
31570 assert_eq!(
31573 f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
31574 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
31575 );
31576 }
31577
31578 #[test]
31581 fn inkeys_comes_off_the_total() {
31582 let mut f = Fixture::new();
31583 corpus(&mut f);
31584 assert_eq!(
31585 f.run(&[
31586 b"FT.SEARCH",
31587 b"sx",
31588 b"alpha",
31589 b"NOCONTENT",
31590 b"INKEYS",
31591 b"1",
31592 b"d:1"
31593 ]),
31594 "*2\r\n:1\r\n$3\r\nd:1\r\n"
31595 );
31596 assert_eq!(
31597 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
31598 "*1\r\n:0\r\n"
31599 );
31600 }
31601
31602 #[test]
31609 fn the_fields_are_read_from_the_session_database() {
31610 let mut f = Fixture::new();
31611 corpus(&mut f);
31612 f.run(&[b"SELECT", b"1"]);
31613 f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
31614 assert_eq!(
31617 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
31618 "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
31619 );
31620 assert_eq!(
31621 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
31622 "*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"
31623 );
31624 }
31625
31626 #[test]
31629 fn the_third_protocol_answers_a_map_of_five() {
31630 let mut f = Fixture::new();
31631 corpus(&mut f);
31632 f.out = Out::new(Proto::Resp3);
31633 assert_eq!(
31634 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
31635 concat!(
31636 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
31637 "%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",
31638 "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
31639 )
31640 );
31641 }
31642
31643 #[test]
31647 fn a_window_of_nothing_has_to_start_at_the_top() {
31648 let mut f = Fixture::new();
31649 corpus(&mut f);
31650 let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
31651 assert_eq!(
31652 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31653 refused
31654 );
31655 assert_eq!(
31656 f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31657 refused
31658 );
31659 assert_eq!(
31660 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31661 refused
31662 );
31663 assert_eq!(
31664 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
31665 "*1\r\n:3\r\n"
31666 );
31667 }
31668
31669 #[test]
31672 fn an_aggregation_answers_a_count_and_then_the_properties() {
31673 let mut f = Fixture::new();
31674 corpus(&mut f);
31675 assert_eq!(
31676 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
31677 "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
31678 );
31679 assert_eq!(
31683 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
31684 concat!(
31685 "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
31686 "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
31687 "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
31688 )
31689 );
31690 assert_eq!(
31694 f.run(&[
31695 b"FT.AGGREGATE",
31696 b"sx",
31697 b"alpha",
31698 b"LOAD",
31699 b"1",
31700 b"@n",
31701 b"LIMIT",
31702 b"1",
31703 b"2"
31704 ]),
31705 "*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"
31706 );
31707 assert_eq!(
31709 f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
31710 "*1\r\n:0\r\n"
31711 );
31712 }
31713
31714 #[test]
31718 fn a_load_counts_words_and_can_rename_what_it_reads() {
31719 let mut f = Fixture::new();
31720 corpus(&mut f);
31721 assert_eq!(
31723 f.run(&[
31724 b"FT.AGGREGATE",
31725 b"sx",
31726 b"alpha",
31727 b"LOAD",
31728 b"3",
31729 b"@t",
31730 b"AS",
31731 b"text"
31732 ]),
31733 concat!(
31734 "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
31735 "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
31736 "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
31737 )
31738 );
31739 assert_eq!(
31740 f.run(&[
31741 b"FT.AGGREGATE",
31742 b"sx",
31743 b"alpha",
31744 b"LOAD",
31745 b"*",
31746 b"LIMIT",
31747 b"0",
31748 b"1"
31749 ]),
31750 concat!(
31751 "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
31752 "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
31753 )
31754 );
31755 assert_eq!(
31757 f.run(&[
31758 b"FT.AGGREGATE",
31759 b"sx",
31760 b"alpha",
31761 b"LOAD",
31762 b"2",
31763 b"@n",
31764 b"@nope",
31765 b"LIMIT",
31766 b"0",
31767 b"2"
31768 ]),
31769 "*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"
31770 );
31771 }
31772
31773 #[test]
31776 fn a_load_refuses_a_count_it_cannot_use() {
31777 let mut f = Fixture::new();
31778 corpus(&mut f);
31779 let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
31780 assert_eq!(
31781 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
31782 format!("{head}Expected number of fields or `*`\r\n")
31783 );
31784 assert_eq!(
31785 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
31786 format!("{head}Value is outside acceptable bounds\r\n")
31787 );
31788 assert_eq!(
31789 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
31790 format!("{head}Expected an argument, but none provided\r\n")
31791 );
31792 assert_eq!(
31793 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
31794 format!("{head}Expected an argument, but none provided\r\n")
31795 );
31796 assert_eq!(
31800 f.run(&[
31801 b"FT.AGGREGATE",
31802 b"sx",
31803 b"alpha",
31804 b"LOAD",
31805 b"2",
31806 b"@t",
31807 b"AS"
31808 ]),
31809 "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
31810 );
31811 assert_eq!(
31814 f.run(&[
31815 b"FT.AGGREGATE",
31816 b"sx",
31817 b"alpha",
31818 b"LOAD",
31819 b"2",
31820 b"@t",
31821 b"AS",
31822 b"VERBATIM"
31823 ]),
31824 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
31825 );
31826 assert_eq!(
31830 f.run(&[
31831 b"FT.AGGREGATE",
31832 b"sx",
31833 b"alpha",
31834 b"LOAD",
31835 b"0",
31836 b"LIMIT",
31837 b"0",
31838 b"1"
31839 ]),
31840 "*2\r\n:1\r\n*0\r\n"
31841 );
31842 }
31843
31844 #[test]
31847 fn a_pipeline_step_closes_the_door_on_the_search_words() {
31848 let mut f = Fixture::new();
31849 corpus(&mut f);
31850 assert_eq!(
31851 f.run(&[
31852 b"FT.AGGREGATE",
31853 b"sx",
31854 b"alpha",
31855 b"LOAD",
31856 b"1",
31857 b"@t",
31858 b"VERBATIM"
31859 ]),
31860 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
31861 );
31862 assert_eq!(
31863 f.run(&[
31864 b"FT.AGGREGATE",
31865 b"sx",
31866 b"alpha",
31867 b"LIMIT",
31868 b"0",
31869 b"1",
31870 b"VERBATIM"
31871 ]),
31872 "*2\r\n:1\r\n*0\r\n"
31873 );
31874 for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
31877 let name = core::str::from_utf8(word).expect("the three words are text");
31878 assert_eq!(
31879 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
31880 format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
31881 );
31882 }
31883 }
31884
31885 #[test]
31888 fn addscores_writes_a_shorter_score_than_withscores() {
31889 let mut f = Fixture::new();
31890 corpus(&mut f);
31891 assert_eq!(
31892 f.run(&[
31893 b"FT.AGGREGATE",
31894 b"sx",
31895 b"alpha",
31896 b"ADDSCORES",
31897 b"LOAD",
31898 b"1",
31899 b"@n",
31900 b"LIMIT",
31901 b"0",
31902 b"2"
31903 ]),
31904 concat!(
31905 "*3\r\n:1\r\n",
31906 "*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",
31907 "*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"
31908 )
31909 );
31910 assert_eq!(
31914 f.run(&[
31915 b"FT.AGGREGATE",
31916 b"sx",
31917 b"alpha",
31918 b"NOCONTENT",
31919 b"WITHSCORES",
31920 b"LIMIT",
31921 b"0",
31922 b"2"
31923 ]),
31924 "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
31925 );
31926 assert_eq!(
31927 f.run(&[
31928 b"FT.AGGREGATE",
31929 b"sx",
31930 b"alpha",
31931 b"WITHSORTKEYS",
31932 b"LOAD",
31933 b"1",
31934 b"@n",
31935 b"LIMIT",
31936 b"0",
31937 b"1"
31938 ]),
31939 "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
31940 );
31941 }
31942
31943 #[test]
31946 fn a_normalising_scorer_answers_the_rows_backwards() {
31947 let mut f = Fixture::new();
31948 corpus(&mut f);
31949 assert_eq!(
31950 f.run(&[
31951 b"FT.AGGREGATE",
31952 b"sx",
31953 b"alpha",
31954 b"SCORER",
31955 b"BM25STD.NORM",
31956 b"ADDSCORES",
31957 b"LOAD",
31958 b"1",
31959 b"@n",
31960 b"LIMIT",
31961 b"1",
31962 b"2"
31963 ]),
31964 concat!(
31965 "*3\r\n:3\r\n",
31966 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
31967 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
31968 )
31969 );
31970 assert_eq!(
31973 f.run(&[
31974 b"FT.AGGREGATE",
31975 b"sx",
31976 b"alpha",
31977 b"SCORER",
31978 b"BM25STD.NORM",
31979 b"LOAD",
31980 b"1",
31981 b"@n",
31982 b"LIMIT",
31983 b"1",
31984 b"2"
31985 ]),
31986 "*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"
31987 );
31988 }
31989
31990 #[test]
31993 fn an_aggregation_answers_a_map_of_five_as_well() {
31994 let mut f = Fixture::new();
31995 corpus(&mut f);
31996 f.out = Out::new(Proto::Resp3);
31997 assert_eq!(
31998 f.run(&[
31999 b"FT.AGGREGATE",
32000 b"sx",
32001 b"alpha",
32002 b"ADDSCORES",
32003 b"WITHSCORES",
32004 b"WITHSORTKEYS",
32005 b"LOAD",
32006 b"1",
32007 b"@n",
32008 b"LIMIT",
32009 b"0",
32010 b"1"
32011 ]),
32012 concat!(
32013 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
32014 "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
32015 "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
32016 "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
32017 "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
32018 )
32019 );
32020 assert_eq!(
32023 f.run(&[
32024 b"FT.AGGREGATE",
32025 b"sx",
32026 b"alpha",
32027 b"NOCONTENT",
32028 b"LIMIT",
32029 b"0",
32030 b"1"
32031 ]),
32032 concat!(
32033 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
32034 "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
32035 )
32036 );
32037 }
32038 const INFO_FIELDS: &[&str] = &[
32046 "id",
32047 "addr",
32048 "laddr",
32049 "fd",
32050 "name",
32051 "age",
32052 "idle",
32053 "flags",
32054 "db",
32055 "sub",
32056 "psub",
32057 "ssub",
32058 "multi",
32059 "watch",
32060 "qbuf",
32061 "qbuf-free",
32062 "argv-mem",
32063 "multi-mem",
32064 "rbs",
32065 "rbp",
32066 "obl",
32067 "oll",
32068 "omem",
32069 "omem-shared",
32070 "omem-unshared",
32071 "tot-mem",
32072 "events",
32073 "cmd",
32074 "user",
32075 "redir",
32076 "resp",
32077 "lib-name",
32078 "lib-ver",
32079 "io-thread",
32080 "tot-net-in",
32081 "tot-net-out",
32082 "tot-cmds",
32083 "read-events",
32084 "avg-pipeline-len-sum",
32085 "avg-pipeline-len-cnt",
32086 ];
32087
32088 fn client_info(f: &mut Fixture) -> Vec<(String, String)> {
32091 let reply = f.run(&[b"CLIENT", b"INFO"]);
32092 let body = reply.split_once("\r\n").expect("a bulk header").1;
32093 let line = body.trim_end_matches("\r\n").trim_start_matches("txt:");
32096 assert!(
32097 line.ends_with('\n'),
32098 "the report ends in a newline: {line:?}"
32099 );
32100 line.trim_end()
32101 .split(' ')
32102 .map(|pair| {
32103 let (name, value) = pair.split_once('=').expect("name=value");
32104 (name.to_string(), value.to_string())
32105 })
32106 .collect()
32107 }
32108
32109 fn client_field(f: &mut Fixture, name: &str) -> String {
32111 client_info(f)
32112 .into_iter()
32113 .find(|(n, _)| n == name)
32114 .map(|(_, v)| v)
32115 .unwrap_or_else(|| panic!("no {name} field"))
32116 }
32117
32118 #[test]
32119 fn client_info_names_every_field_a_real_server_names() {
32120 let mut f = Fixture::new();
32121 let got: Vec<String> = client_info(&mut f).into_iter().map(|(n, _)| n).collect();
32122 assert_eq!(got, INFO_FIELDS);
32123 }
32124
32125 #[test]
32128 fn a_connection_with_no_socket_reports_no_address_and_no_descriptor() {
32129 let mut f = Fixture::new();
32130 assert_eq!(client_field(&mut f, "addr"), "");
32131 assert_eq!(client_field(&mut f, "laddr"), "");
32132 assert_eq!(client_field(&mut f, "fd"), "-1");
32133 assert_eq!(client_field(&mut f, "id"), "7");
32134 }
32135
32136 #[test]
32137 fn client_setname_takes_a_name_back_and_refuses_one_with_a_space_in_it() {
32138 let mut f = Fixture::new();
32139 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$-1\r\n");
32140 assert_eq!(f.run(&[b"CLIENT", b"SETNAME", b"worker"]), "+OK\r\n");
32141 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
32142 assert_eq!(client_field(&mut f, "name"), "worker");
32143 assert_eq!(
32144 f.run(&[b"CLIENT", b"SETNAME", b"two words"]),
32145 "-ERR Client names cannot contain spaces, newlines or special characters.\r\n"
32146 );
32147 assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
32149 }
32150
32151 #[test]
32154 fn reset_clears_the_name_and_the_switches_and_keeps_the_library() {
32155 let mut f = Fixture::new();
32156 f.run(&[b"CLIENT", b"SETNAME", b"worker"]);
32157 f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME", b"yo-py"]);
32158 f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"1.2.3"]);
32159 f.run(&[b"CLIENT", b"NO-EVICT", b"on"]);
32160 f.run(&[b"CLIENT", b"NO-TOUCH", b"on"]);
32161 assert_eq!(client_field(&mut f, "flags"), "eT");
32162
32163 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
32164 assert_eq!(client_field(&mut f, "name"), "");
32165 assert_eq!(client_field(&mut f, "flags"), "N");
32166 assert_eq!(client_field(&mut f, "lib-name"), "yo-py");
32167 assert_eq!(client_field(&mut f, "lib-ver"), "1.2.3");
32168 }
32169
32170 #[test]
32171 fn client_setinfo_complains_the_way_a_real_server_does() {
32172 let mut f = Fixture::new();
32173 assert_eq!(
32174 f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME"]),
32175 "-ERR wrong number of arguments for 'client|setinfo' command\r\n"
32176 );
32177 assert_eq!(
32178 f.run(&[b"CLIENT", b"SETINFO", b"NOPE", b"x"]),
32179 "-ERR Unrecognized option 'NOPE'\r\n"
32180 );
32181 assert_eq!(
32182 f.run(&[b"CLIENT", b"SETINFO", b"lib-name", b"ok x"]),
32183 "-ERR lib-name cannot contain spaces, newlines or special characters.\r\n"
32184 );
32185 assert_eq!(
32186 f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"has space"]),
32187 "-ERR lib-ver cannot contain spaces, newlines or special characters.\r\n"
32188 );
32189 }
32190
32191 #[test]
32192 fn client_refuses_a_subcommand_it_does_not_have_and_arguments_it_did_not_ask_for() {
32193 let mut f = Fixture::new();
32194 assert_eq!(
32195 f.run(&[b"CLIENT", b"NOPE"]),
32196 "-ERR unknown subcommand 'NOPE'. Try CLIENT HELP.\r\n"
32197 );
32198 assert_eq!(
32199 f.run(&[b"CLIENT", b"GETNAME", b"extra"]),
32200 "-ERR wrong number of arguments for 'client|getname' command\r\n"
32201 );
32202 assert_eq!(
32203 f.run(&[b"CLIENT", b"NO-EVICT", b"maybe"]),
32204 "-ERR syntax error\r\n"
32205 );
32206 assert_eq!(
32207 f.run(&[b"CLIENT", b"REPLY", b"BAD"]),
32208 "-ERR syntax error\r\n"
32209 );
32210 }
32211
32212 #[test]
32216 fn client_info_counts_the_three_subscribe_namespaces_apart() {
32217 let mut f = Fixture::new();
32218 f.run(&[b"HELLO", b"3"]);
32221 f.run(&[b"SUBSCRIBE", b"a", b"b"]);
32222 f.run(&[b"PSUBSCRIBE", b"p*"]);
32223 f.run(&[b"SSUBSCRIBE", b"s"]);
32224 let info = client_info(&mut f);
32225 let at = |name: &str| {
32226 info.iter()
32227 .find(|(n, _)| n == name)
32228 .map(|(_, v)| v.clone())
32229 .unwrap()
32230 };
32231 assert_eq!(at("sub"), "2");
32232 assert_eq!(at("psub"), "1");
32233 assert_eq!(at("ssub"), "1");
32234 assert_eq!(at("flags"), "P");
32235 forget_session(&f.server, &mut f.session);
32236 }
32237
32238 #[test]
32241 fn client_info_reports_itself_as_the_command_running() {
32242 let mut f = Fixture::new();
32243 assert_eq!(client_field(&mut f, "cmd"), "client|info");
32244 f.run(&[b"GET", b"nothing"]);
32245 assert_eq!(client_field(&mut f, "cmd"), "client|info");
32248 }
32249
32250 #[test]
32252 fn the_command_field_is_lower_case_however_the_client_spelled_it() {
32253 let mut f = Fixture::new();
32254 let reply = f.run(&[b"CLIENT", b"Info"]);
32255 assert!(reply.contains("cmd=client|info"), "{reply}");
32256 }
32257
32258 #[test]
32259 fn client_help_lists_the_subcommands_that_are_here() {
32260 let mut f = Fixture::new();
32261 let reply = f.run(&[b"CLIENT", b"HELP"]);
32262 for sub in ["ID", "GETNAME", "SETNAME", "SETINFO", "INFO", "REPLY"] {
32263 assert!(reply.contains(sub), "no {sub} in {reply}");
32264 }
32265 assert!(!reply.contains("TRACKING"), "{reply}");
32268 }
32269
32270 fn commands(stream: &str) -> Vec<Vec<String>> {
32278 let mut out = Vec::new();
32279 let mut rest = stream;
32280 while let Some(tail) = rest.strip_prefix('*') {
32281 let (n, tail) = tail.split_once("\r\n").expect("a header ends");
32282 let mut one = Vec::new();
32283 let mut tail = tail;
32284 for _ in 0..n.parse::<usize>().expect("a count") {
32285 let body = tail.strip_prefix('$').expect("a bulk string");
32286 let (len, body) = body.split_once("\r\n").expect("a length ends");
32287 let len: usize = len.parse().expect("a length");
32288 one.push(body[..len].to_string());
32289 tail = &body[len + 2..];
32290 }
32291 out.push(one);
32292 rest = tail;
32293 }
32294 assert!(rest.is_empty(), "left over: {rest:?}");
32295 out
32296 }
32297
32298 fn only(stream: &str) -> Vec<String> {
32300 let mut each = commands(stream);
32301 assert_eq!(each.len(), 1, "expected one command: {stream:?}");
32302 each.pop().expect("one command")
32303 }
32304
32305 #[test]
32306 fn the_stream_opens_with_a_select_and_says_it_once() {
32307 let mut f = Fixture::replicated();
32308 assert_eq!(
32309 commands(&f.crossed(&[b"SET", b"k", b"v"])),
32310 vec![
32311 vec!["SELECT".to_string(), "0".to_string()],
32312 vec!["SET".to_string(), "k".to_string(), "v".to_string()],
32313 ]
32314 );
32315 assert_eq!(only(&f.crossed(&[b"SET", b"k2", b"v"])), ["SET", "k2", "v"]);
32317 f.run(&[b"SELECT", b"3"]);
32320 assert_eq!(
32321 commands(&f.crossed(&[b"SET", b"k3", b"v"])),
32322 vec![
32323 vec!["SELECT".to_string(), "3".to_string()],
32324 vec!["SET".to_string(), "k3".to_string(), "v".to_string()],
32325 ]
32326 );
32327 }
32328
32329 #[test]
32334 fn a_relative_deadline_crosses_as_the_instant_it_resolved_to() {
32335 let mut f = Fixture::replicated();
32336 f.crossed(&[b"SET", b"seed", b"1"]);
32337 for parts in [
32338 &[b"SET".as_slice(), b"k", b"v", b"EX", b"100"][..],
32339 &[b"SETEX".as_slice(), b"k", b"100", b"v"][..],
32340 ] {
32341 let words = only(&f.crossed(parts));
32342 assert_eq!(&words[..3], ["SET", "k", "v"], "{words:?}");
32343 assert_eq!(words[3], "PXAT", "{words:?}");
32344 let at: i64 = words[4].parse().expect("an instant");
32345 assert!(at > f.server.clock.now_ms() as i64, "{words:?}");
32346 }
32347 for parts in [
32348 &[b"EXPIRE".as_slice(), b"k", b"50"][..],
32349 &[b"PEXPIRE".as_slice(), b"k", b"50000"][..],
32350 &[b"EXPIREAT".as_slice(), b"k", b"99999999999"][..],
32351 &[b"GETEX".as_slice(), b"k", b"EX", b"100"][..],
32352 ] {
32353 let words = only(&f.crossed(parts));
32354 assert_eq!(&words[..2], ["PEXPIREAT", "k"], "{words:?}");
32355 }
32356 assert_eq!(
32357 only(&f.crossed(&[b"GETEX", b"k", b"PERSIST"])),
32358 ["PERSIST", "k"]
32359 );
32360 }
32361
32362 #[test]
32364 fn nothing_crosses_for_a_read_or_for_a_failure() {
32365 let mut f = Fixture::replicated();
32366 f.crossed(&[b"SET", b"k", b"v"]);
32367 for parts in [
32368 &[b"GET".as_slice(), b"k"][..],
32369 &[b"TYPE".as_slice(), b"k"][..],
32370 &[b"STRLEN".as_slice(), b"k"][..],
32371 &[b"EXISTS".as_slice(), b"k"][..],
32372 &[b"PING".as_slice()][..],
32373 &[b"LPUSH".as_slice(), b"k", b"a"][..],
32376 &[b"INCR".as_slice(), b"k"][..],
32377 ] {
32378 assert_eq!(f.crossed(parts), "", "{parts:?}");
32379 }
32380 }
32381
32382 #[test]
32390 fn a_write_that_changed_nothing_still_crosses() {
32391 let mut f = Fixture::replicated();
32392 f.crossed(&[b"SET", b"k", b"v"]);
32393 assert_eq!(
32394 only(&f.crossed(&[b"DEL", b"nosuchkey"])),
32395 ["DEL", "nosuchkey"]
32396 );
32397 assert_eq!(only(&f.crossed(&[b"SET", b"k", b"v"])), ["SET", "k", "v"]);
32398 for parts in [
32400 &[b"SPOP".as_slice(), b"nosuchset"][..],
32401 &[b"EXPIRE".as_slice(), b"nosuchkey", b"100"][..],
32402 &[
32403 b"XADD".as_slice(),
32404 b"nosuchstream",
32405 b"NOMKSTREAM",
32406 b"*",
32407 b"f",
32408 b"v",
32409 ][..],
32410 ] {
32411 assert_eq!(f.crossed(parts), "", "{parts:?}");
32412 }
32413 }
32414
32415 #[test]
32418 fn a_condition_that_held_crosses_without_it() {
32419 let mut f = Fixture::replicated();
32420 f.crossed(&[b"SET", b"seed", b"1"]);
32421 assert_eq!(only(&f.crossed(&[b"SETNX", b"k", b"v"])), ["SET", "k", "v"]);
32422 assert_eq!(
32423 only(&f.crossed(&[b"SET", b"k", b"w", b"XX"])),
32424 ["SET", "k", "w"]
32425 );
32426 }
32427
32428 #[test]
32430 fn a_random_or_derived_write_crosses_as_what_it_did() {
32431 let mut f = Fixture::replicated();
32432 f.crossed(&[b"SET", b"seed", b"1"]);
32433 f.crossed(&[b"SADD", b"s", b"one", b"two"]);
32434 let words = only(&f.crossed(&[b"SPOP", b"s"]));
32435 assert_eq!(&words[..2], ["SREM", "s"], "{words:?}");
32436 assert!(words[2] == "one" || words[2] == "two", "{words:?}");
32437 let words = only(&f.crossed(&[b"SPOP", b"s"]));
32442 assert_eq!(&words[..2], ["SREM", "s"], "{words:?}");
32443 f.crossed(&[b"SADD", b"s", b"one", b"two"]);
32446 assert_eq!(only(&f.crossed(&[b"SPOP", b"s", b"2"])), ["DEL", "s"]);
32447 f.crossed(&[b"SET", b"n", b"1"]);
32448 assert_eq!(
32449 only(&f.crossed(&[b"INCRBYFLOAT", b"n", b"1.5"])),
32450 ["SET", "n", "2.5", "KEEPTTL"]
32451 );
32452 assert_eq!(only(&f.crossed(&[b"GETDEL", b"n"])), ["DEL", "n"]);
32453 let words = only(&f.crossed(&[b"XADD", b"st", b"*", b"f", b"v"]));
32454 assert_eq!(&words[..2], ["XADD", "st"], "{words:?}");
32455 assert_ne!(words[2], "*", "an auto id has to be resolved: {words:?}");
32456 assert_eq!(&words[3..], ["f", "v"], "{words:?}");
32457 }
32458
32459 #[test]
32466 fn an_expiry_a_read_noticed_crosses_as_a_deletion() {
32467 let mut f = Fixture::replicated();
32468 f.crossed(&[b"SET", b"k", b"v", b"PX", b"50"]);
32469 f.advance(100);
32470 assert_eq!(only(&f.crossed(&[b"GET", b"k"])), ["DEL", "k"]);
32471 f.run(&[b"SET", b"k2", b"v", b"PX", b"50"]);
32473 f.crossed(&[b"PING"]);
32474 f.advance(100);
32475 assert_eq!(
32476 commands(&f.crossed(&[b"LPUSH", b"k2", b"a"])),
32477 vec![
32478 vec!["DEL".to_string(), "k2".to_string()],
32479 vec!["LPUSH".to_string(), "k2".to_string(), "a".to_string()],
32480 ]
32481 );
32482 }
32483
32484 #[test]
32489 fn a_blocking_command_that_parked_crosses_nothing() {
32490 let mut f = Fixture::replicated();
32491 f.crossed(&[b"SET", b"seed", b"1"]);
32492 assert_eq!(f.flow(&[b"BLPOP", b"gone", b"0"]).0, Flow::Block);
32493 assert_eq!(f.server.stream_since(f.mark).0, "");
32494 f.run(&[b"XADD", b"st", b"1-1", b"f", b"v"]);
32495 f.run(&[b"XGROUP", b"CREATE", b"st", b"g", b"$"]);
32496 f.crossed(&[b"PING"]);
32497 f.run(&[
32500 b"XREADGROUP",
32501 b"GROUP",
32502 b"g",
32503 b"c",
32504 b"COUNT",
32505 b"1",
32506 b"STREAMS",
32507 b"st",
32508 b">",
32509 ]);
32510 assert_eq!(
32511 only(&f.crossed(&[b"PING"])),
32512 ["XGROUP", "CREATECONSUMER", "st", "g", "c"]
32513 );
32514 }
32515
32516 #[test]
32519 fn every_xgroup_subcommand_that_changed_something_crosses() {
32520 let mut f = Fixture::replicated();
32521 f.crossed(&[b"XADD", b"st", b"1-1", b"f", b"v"]);
32522 assert_eq!(
32525 only(&f.crossed(&[b"XGROUP", b"CREATE", b"st", b"g", b"$"])),
32526 ["XGROUP", "CREATE", "st", "g", "1-1"]
32527 );
32528 assert_eq!(
32529 only(&f.crossed(&[b"XGROUP", b"CREATECONSUMER", b"st", b"g", b"c"])),
32530 ["XGROUP", "CREATECONSUMER", "st", "g", "c"]
32531 );
32532 assert_eq!(
32533 only(&f.crossed(&[b"XGROUP", b"SETID", b"st", b"g", b"0"])),
32534 ["XGROUP", "SETID", "st", "g", "0-0"]
32535 );
32536 assert_eq!(
32537 only(&f.crossed(&[b"XGROUP", b"DELCONSUMER", b"st", b"g", b"c"])),
32538 ["XGROUP", "DELCONSUMER", "st", "g", "c"]
32539 );
32540 assert_eq!(
32541 only(&f.crossed(&[b"XGROUP", b"DESTROY", b"st", b"g"])),
32542 ["XGROUP", "DESTROY", "st", "g"]
32543 );
32544 assert_eq!(f.crossed(&[b"XGROUP", b"DESTROY", b"st", b"g"]), "");
32546 }
32547
32548 #[test]
32553 fn a_publish_crosses_with_nobody_listening() {
32554 let mut f = Fixture::replicated();
32555 f.crossed(&[b"SET", b"seed", b"1"]);
32556 assert_eq!(
32557 only(&f.crossed(&[b"PUBLISH", b"news", b"hello"])),
32558 ["PUBLISH", "news", "hello"]
32559 );
32560 assert_eq!(
32561 only(&f.crossed(&[b"SPUBLISH", b"news", b"hello"])),
32562 ["SPUBLISH", "news", "hello"]
32563 );
32564 }
32565
32566 #[test]
32574 fn a_replica_asking_to_carry_on_is_caught_up_from_the_backlog() {
32575 let mut f = Fixture::replicated();
32576 f.crossed(&[b"SET", b"k", b"v"]);
32577 let id = f.server.repl_id();
32578 let had = f.mark;
32579 f.crossed(&[b"SET", b"k2", b"later"]);
32580 let asked = (had + 1).to_string();
32581 let reply = f.run(&[b"PSYNC", &id, asked.as_bytes()]);
32582 assert!(reply.starts_with("+CONTINUE "), "{reply}");
32583 assert!(reply.contains("later"), "{reply}");
32584 assert_eq!(reply.matches("k2").count(), 1, "{reply}");
32586 }
32587
32588 #[test]
32590 fn a_replica_with_no_history_is_sent_a_snapshot() {
32591 let mut f = Fixture::replicated();
32592 f.crossed(&[b"SET", b"k", b"v"]);
32593 let reply = f.run(&[b"PSYNC", b"?", b"-1"]);
32594 assert!(reply.starts_with("+FULLRESYNC "), "{reply}");
32595 let body = reply.split_once("\r\n").expect("a header ends").1;
32597 assert!(body.starts_with('$'), "{body:?}");
32598 assert!(!body.ends_with("\r\n"), "{body:?}");
32599 }
32600
32601 #[test]
32605 fn a_read_only_replica_refuses_a_write_and_answers_a_read() {
32606 let mut f = Fixture::new();
32607 f.run(&[b"SET", b"k", b"v"]);
32608 f.server.pretend_following("127.0.0.1", 6379, true);
32609 assert_eq!(
32610 f.run(&[b"SET", b"k", b"other"]),
32611 "-READONLY You can't write against a read only replica.\r\n"
32612 );
32613 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
32614 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
32616 }
32617
32618 #[test]
32621 fn a_master_takes_writes_however_the_read_only_setting_is_left() {
32622 let mut f = Fixture::new();
32623 f.server.set_replica_read_only(true);
32624 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32625 f.server.pretend_following("127.0.0.1", 6379, true);
32626 assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32627 f.server.set_replica_read_only(false);
32629 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32630 f.server.set_replica_read_only(true);
32631 f.server.pretend_master();
32634 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32635 }
32636
32637 #[test]
32639 fn the_link_to_the_master_writes_through_the_read_only_refusal() {
32640 let mut f = Fixture::new();
32641 f.server.pretend_following("127.0.0.1", 6379, true);
32642 assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32643 f.session.serve_master(true);
32644 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32645 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
32646 }
32647
32648 #[test]
32651 fn a_transaction_on_a_read_only_replica_is_refused_whole() {
32652 let mut f = Fixture::new();
32653 f.server.pretend_following("127.0.0.1", 6379, true);
32654 f.run(&[b"MULTI"]);
32655 assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32656 assert!(f.run(&[b"EXEC"]).starts_with("-EXECABORT"));
32657 }
32658
32659 #[test]
32661 fn a_replica_says_who_it_follows_in_info_and_in_role() {
32662 let mut f = Fixture::new();
32663 assert!(f.run(&[b"INFO", b"replication"]).contains("role:master"));
32664 f.server.pretend_following("10.0.0.4", 7000, true);
32665 let info = f.run(&[b"INFO", b"replication"]);
32666 assert!(info.contains("role:slave"), "{info}");
32667 assert!(info.contains("master_host:10.0.0.4"), "{info}");
32668 assert!(info.contains("master_port:7000"), "{info}");
32669 assert!(info.contains("master_link_status:up"), "{info}");
32670 assert!(info.contains("slave_read_only:1"), "{info}");
32671 let role = f.run(&[b"ROLE"]);
32674 assert!(role.starts_with("*5\r\n$5\r\nslave\r\n"), "{role}");
32675 assert!(role.contains("10.0.0.4"), "{role}");
32676 assert!(role.contains("connected"), "{role}");
32677 f.server.pretend_following("10.0.0.4", 7000, false);
32679 assert!(
32680 f.run(&[b"INFO", b"replication"])
32681 .contains("master_link_status:down"),
32682 "a link that is not up is down"
32683 );
32684 assert!(f.run(&[b"ROLE"]).contains("connect"));
32685 }
32686
32687 #[test]
32690 fn replicaof_on_an_embedded_server_says_it_is_not_available() {
32691 let mut f = Fixture::new();
32692 let said = f.run(&[b"REPLICAOF", b"127.0.0.1", b"6379"]);
32693 assert!(
32694 said.contains("not available on an embedded server"),
32695 "{said}"
32696 );
32697 assert!(
32700 f.run(&[b"REPLICAOF", b"127.0.0.1"])
32701 .starts_with("-ERR wrong number")
32702 );
32703 assert_eq!(
32704 f.run(&[b"SLAVEOF", b"127.0.0.1", b"abc"]),
32705 "-ERR Invalid master port\r\n"
32706 );
32707 assert_eq!(
32708 f.run(&[b"REPLICAOF", b"127.0.0.1", b"99999"]),
32709 "-ERR Invalid master port\r\n"
32710 );
32711 }
32712
32713 #[test]
32716 fn a_promotion_keeps_the_old_history_as_the_second_id() {
32717 let f = Fixture::new();
32718 let was = f.server.repl_id();
32719 f.server.promote();
32720 assert_ne!(f.server.repl_id(), was);
32721 let info = {
32722 let mut f = f;
32723 f.run(&[b"INFO", b"replication"])
32724 };
32725 let was = String::from_utf8_lossy(&was).into_owned();
32726 assert!(info.contains(&format!("master_replid2:{was}")), "{info}");
32727 }
32728
32729 #[test]
32732 fn change_repl_id_takes_a_new_id_and_forgets_the_old_one() {
32733 let mut f = Fixture::new();
32734 let was = f.server.repl_id();
32735 f.server.promote();
32736 assert_eq!(f.run(&[b"DEBUG", b"CHANGE-REPL-ID"]), "+OK\r\n");
32737 assert_ne!(f.server.repl_id(), was);
32738 let info = f.run(&[b"INFO", b"replication"]);
32739 assert!(
32740 info.contains(&format!("master_replid2:{}", "0".repeat(40))),
32741 "{info}"
32742 );
32743 }
32744
32745 #[test]
32749 fn failover_refuses_in_the_order_the_reference_refuses() {
32750 let mut f = Fixture::new();
32751 assert_eq!(
32753 f.run(&[b"FAILOVER", b"ABORT"]),
32754 "-ERR No failover in progress.\r\n"
32755 );
32756 assert_eq!(
32760 f.run(&[b"FAILOVER", b"TIMEOUT", b"0"]),
32761 "-ERR FAILOVER timeout must be greater than 0\r\n"
32762 );
32763 assert_eq!(
32764 f.run(&[b"FAILOVER", b"TIMEOUT", b"-1"]),
32765 "-ERR FAILOVER timeout must be greater than 0\r\n"
32766 );
32767 assert!(
32768 f.run(&[b"FAILOVER", b"TIMEOUT", b"abc"])
32769 .starts_with("-ERR value is not an integer")
32770 );
32771 assert_eq!(f.run(&[b"FAILOVER", b"bogus"]), "-ERR syntax error\r\n");
32774 assert_eq!(
32775 f.run(&[b"FAILOVER", b"TIMEOUT", b"1", b"TIMEOUT", b"2"]),
32776 "-ERR syntax error\r\n"
32777 );
32778 assert_eq!(
32779 f.run(&[b"FAILOVER", b"FORCE", b"FORCE"]),
32780 "-ERR syntax error\r\n"
32781 );
32782 assert_eq!(f.run(&[b"FAILOVER", b"TO", b"h"]), "-ERR syntax error\r\n");
32785 assert_eq!(
32787 f.run(&[b"FAILOVER", b"ABORT", b"TIMEOUT", b"1"]),
32788 "-ERR syntax error\r\n"
32789 );
32790 assert_eq!(
32793 f.run(&[b"FAILOVER"]),
32794 "-ERR FAILOVER requires connected replicas.\r\n"
32795 );
32796 assert_eq!(
32797 f.run(&[b"FAILOVER", b"FORCE"]),
32798 "-ERR FAILOVER requires connected replicas.\r\n"
32799 );
32800 f.server.pretend_following("10.0.0.4", 7000, true);
32802 assert_eq!(
32803 f.run(&[b"FAILOVER"]),
32804 "-ERR FAILOVER is not valid when server is a replica.\r\n"
32805 );
32806 }
32807
32808 #[test]
32811 fn a_server_that_is_not_failing_over_says_no_failover() {
32812 let mut f = Fixture::new();
32813 assert!(
32814 f.run(&[b"INFO", b"replication"])
32815 .contains("master_failover_state:no-failover"),
32816 "the field is there and says nothing is going on"
32817 );
32818 }
32819
32820 #[test]
32823 fn a_transaction_crosses_as_its_commands() {
32824 let mut f = Fixture::replicated();
32825 f.crossed(&[b"SET", b"seed", b"1"]);
32826 f.run(&[b"MULTI"]);
32827 f.run(&[b"SET", b"a", b"1"]);
32828 f.run(&[b"INCR", b"a"]);
32829 assert_eq!(
32830 commands(&f.crossed(&[b"EXEC"])),
32831 vec![
32832 vec!["SET".to_string(), "a".to_string(), "1".to_string()],
32833 vec!["INCR".to_string(), "a".to_string()],
32834 ]
32835 );
32836 }
32837
32838 const FOO: u16 = 12182;
32844
32845 const BAR: u16 = 5061;
32847
32848 fn clustered() -> Fixture {
32849 let mut server = Server::new();
32850 server.enable_cluster("", 7000);
32851 server.cluster_own_everything();
32852 let other = server.cluster_pretend_node(
32853 "5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f",
32854 "10.0.0.9",
32855 7002,
32856 );
32857 assert_eq!(other, 1, "the made up node is the second one in the table");
32858 Fixture::on(server)
32859 }
32860
32861 #[test]
32865 fn cluster_slots_comes_out_in_slot_order() {
32866 let mut f = clustered();
32867 for slot in 0..100u16 {
32868 f.server.cluster_hand_over(slot, 1);
32869 }
32870 let reply = f.run(&[b"CLUSTER", b"SLOTS"]);
32871 assert!(
32872 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"),
32873 "the other node's run is first because it starts at slot 0: {reply}"
32874 );
32875 assert!(
32876 reply.contains("*3\r\n:100\r\n:16383\r\n"),
32877 "and this node's run is the rest of them: {reply}"
32878 );
32879 }
32880
32881 #[test]
32883 fn a_key_on_another_node_is_moved_there() {
32884 let mut f = clustered();
32885 f.server.cluster_hand_over(BAR, 1);
32886 assert_eq!(
32887 f.run(&[b"GET", b"bar"]),
32888 format!("-MOVED {BAR} 10.0.0.9:7002\r\n")
32889 );
32890 assert_eq!(f.run(&[b"GET", b"foo"]), "$-1\r\n");
32892 }
32893
32894 #[test]
32897 fn a_command_with_no_keys_never_redirects() {
32898 let mut f = clustered();
32899 for slot in 0..16384u16 {
32900 f.server.cluster_hand_over(slot, 1);
32901 }
32902 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
32903 assert_eq!(f.run(&[b"ECHO", b"hi"]), "$2\r\nhi\r\n");
32904 }
32905
32906 #[test]
32909 fn two_slots_in_one_command_is_a_cross_slot() {
32910 let mut f = clustered();
32911 assert_eq!(
32912 f.run(&[b"MGET", b"foo", b"bar"]),
32913 "-CROSSSLOT Keys in request don't hash to the same slot\r\n"
32914 );
32915 assert_eq!(
32917 f.run(&[b"MGET", b"{t}foo", b"{t}bar"]),
32918 "*2\r\n$-1\r\n$-1\r\n"
32919 );
32920 }
32921
32922 #[test]
32925 fn a_hole_is_reported_before_the_cross_slot() {
32926 let mut server = Server::new();
32927 server.enable_cluster("", 7000);
32928 let mut f = Fixture::on(server);
32929 assert_eq!(
32930 f.run(&[b"MGET", b"foo", b"bar"]),
32931 "-CLUSTERDOWN Hash slot not served\r\n"
32932 );
32933 f.server.cluster_own_everything();
32935 assert_eq!(
32936 f.run(&[b"MGET", b"foo", b"bar"]),
32937 "-CROSSSLOT Keys in request don't hash to the same slot\r\n"
32938 );
32939 }
32940
32941 #[test]
32945 fn a_migrating_slot_asks_for_the_keys_that_have_gone() {
32946 let mut f = clustered();
32947 f.run(&[b"SET", b"foo", b"1"]);
32948 f.server.cluster_moving(FOO, Some(1), None);
32949 assert_eq!(f.run(&[b"GET", b"foo"]), "$1\r\n1\r\n");
32951 assert_eq!(
32953 f.run(&[b"GET", b"{foo}gone"]),
32954 format!("-ASK {FOO} 10.0.0.9:7002\r\n")
32955 );
32956 }
32957
32958 #[test]
32961 fn a_half_moved_slot_is_a_try_again() {
32962 let mut f = clustered();
32963 f.run(&[b"SET", b"{t}here", b"1"]);
32964 let slot = cluster::key_slot(b"{t}here");
32965 f.server.cluster_moving(slot, Some(1), None);
32966 assert_eq!(
32967 f.run(&[b"MGET", b"{t}here", b"{t}gone"]),
32968 "-TRYAGAIN Multiple keys request during rehashing of slot\r\n"
32969 );
32970 }
32971
32972 #[test]
32975 fn asking_lets_one_command_into_an_importing_slot() {
32976 let mut f = clustered();
32977 f.server.cluster_hand_over(FOO, 1);
32978 f.server.cluster_moving(FOO, None, Some(1));
32979 let moved = format!("-MOVED {FOO} 10.0.0.9:7002\r\n");
32980 assert_eq!(f.run(&[b"GET", b"foo"]), moved);
32981 assert_eq!(f.run(&[b"ASKING"]), "+OK\r\n");
32982 assert_eq!(f.run(&[b"SET", b"foo", b"1"]), "+OK\r\n");
32983 assert_eq!(f.run(&[b"GET", b"foo"]), moved);
32985 }
32986
32987 #[test]
32992 fn restore_asking_gets_into_an_importing_slot_on_its_own() {
32993 let mut f = clustered();
32994 f.server.cluster_hand_over(FOO, 1);
32995 f.server.cluster_moving(FOO, None, Some(1));
32996 f.run(&[b"SET", b"scratch", b"1"]);
32999 let dumped = f.raw(&[b"DUMP", b"scratch"]);
33000 let payload =
33001 &dumped[dumped.iter().position(|b| *b == b'\n').unwrap() + 1..dumped.len() - 2];
33002 let payload = payload.to_vec();
33003 assert_eq!(
33005 f.run(&[b"RESTORE", b"foo", b"0", &payload]),
33006 format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
33007 );
33008 assert_eq!(
33010 f.run(&[b"RESTORE-ASKING", b"foo", b"0", &payload]),
33011 "+OK\r\n"
33012 );
33013 assert_eq!(
33016 f.run(&[b"GET", b"foo"]),
33017 format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
33018 );
33019 }
33020
33021 #[test]
33025 fn closing_an_import_takes_a_higher_epoch() {
33026 let mut f = clustered();
33027 f.server.cluster_hand_over(FOO, 1);
33028 f.server.cluster_moving(FOO, None, Some(1));
33029 let me = f.run(&[b"CLUSTER", b"MYID"]);
33030 let me = me[me.find("\r\n").unwrap() + 2..me.len() - 2].to_owned();
33031 assert_eq!(
33032 f.run(&[
33033 b"CLUSTER",
33034 b"SETSLOT",
33035 FOO.to_string().as_bytes(),
33036 b"NODE",
33037 me.as_bytes()
33038 ]),
33039 "+OK\r\n"
33040 );
33041 assert_eq!(f.run(&[b"CLUSTER", b"BUMPEPOCH"]), "+STILL 1\r\n");
33044 assert_eq!(f.run(&[b"GET", b"foo"]), "$-1\r\n");
33046 }
33047
33048 #[test]
33051 fn a_plain_hand_over_does_not_touch_the_epoch() {
33052 let mut f = clustered();
33053 let me = f.run(&[b"CLUSTER", b"MYID"]);
33054 let me = me[me.find("\r\n").unwrap() + 2..me.len() - 2].to_owned();
33055 assert_eq!(
33056 f.run(&[
33057 b"CLUSTER",
33058 b"SETSLOT",
33059 FOO.to_string().as_bytes(),
33060 b"NODE",
33061 me.as_bytes()
33062 ]),
33063 "+OK\r\n"
33064 );
33065 assert_eq!(
33066 f.run(&[b"CLUSTER", b"BUMPEPOCH"]),
33067 "+BUMPED 1\r\n",
33068 "the epoch was still zero, so this is the first thing to move it"
33069 );
33070 }
33071
33072 #[test]
33075 fn a_slot_with_keys_in_it_is_not_handed_over() {
33076 let mut f = clustered();
33077 f.run(&[b"SET", b"foo", b"1"]);
33078 let them = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
33079 assert_eq!(
33080 f.run(&[
33081 b"CLUSTER",
33082 b"SETSLOT",
33083 FOO.to_string().as_bytes(),
33084 b"NODE",
33085 them
33086 ]),
33087 format!(
33088 "-ERR Can't assign hashslot {FOO} to a different node while I still hold keys for this hash slot.\r\n"
33089 )
33090 );
33091 f.run(&[b"DEL", b"foo"]);
33093 assert_eq!(
33094 f.run(&[
33095 b"CLUSTER",
33096 b"SETSLOT",
33097 FOO.to_string().as_bytes(),
33098 b"NODE",
33099 them
33100 ]),
33101 "+OK\r\n"
33102 );
33103 assert_eq!(
33104 f.run(&[b"GET", b"foo"]),
33105 format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
33106 );
33107 }
33108
33109 #[test]
33118 fn the_slot_migration_protocol_is_shut_to_a_client() {
33119 let mut f = clustered();
33120 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS"]);
33123 assert_eq!(
33124 reply,
33125 "-ERR wrong number of arguments for 'cluster|syncslots' command\r\n"
33126 );
33127 assert_eq!(flow, Flow::Continue);
33128 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
33129 assert_eq!(
33130 reply,
33131 "-ERR CLUSTER SYNCSLOTS subcommands are only allowed for internal clients\r\n"
33132 );
33133 assert_eq!(flow, Flow::Close, "and the socket goes with it");
33134 }
33135
33136 #[test]
33139 fn the_internal_login_wants_the_cluster_secret() {
33140 let mut f = Fixture::new();
33141 assert_eq!(
33142 f.run(&[b"AUTH", b"internal connection", b"x"]),
33143 "-ERR Cannot authenticate as an internal connection on non-cluster instances\r\n"
33144 );
33145 assert_eq!(
33146 f.run(&[b"DEBUG", b"INTERNAL_SECRET"]),
33147 "-ERR Internal secret is missing\r\n"
33148 );
33149 let mut f = clustered();
33150 assert_eq!(
33151 f.run(&[b"AUTH", b"internal connection", b"x"]),
33152 "-WRONGPASS invalid internal password\r\n"
33153 );
33154 assert_eq!(
33157 f.run(&[b"AUTH", b"INTERNAL CONNECTION", b"x"]),
33158 "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
33159 );
33160 let secret = f.server.cluster_secret();
33161 assert_eq!(secret.len(), 40, "forty characters, like a node id");
33162 assert_eq!(
33163 f.run(&[b"DEBUG", b"INTERNAL_SECRET"]),
33164 format!(":{}\r\n", yo_common::crc::crc16(secret.as_bytes())),
33165 "what comes back is a checksum, so a test can see two nodes agree \
33166 and nobody can log in with what they read"
33167 );
33168 assert_eq!(
33169 f.run(&[b"AUTH", b"internal connection", secret.as_bytes()]),
33170 "+OK\r\n"
33171 );
33172 assert_eq!(
33173 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]),
33174 "+OK\r\n"
33175 );
33176 }
33177
33178 #[test]
33181 fn conf_says_ok_after_an_option_it_did_not_know() {
33182 let mut f = clustered();
33183 assert_eq!(f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]), "+OK\r\n");
33184 assert_eq!(
33185 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"zzz", b"1"]),
33186 "-ERR Unknown option zzz\r\n+OK\r\n"
33187 );
33188 assert_eq!(
33191 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"quantum"]),
33192 "+OK\r\n"
33193 );
33194 assert_eq!(
33196 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"node-id", b"abc"]),
33197 "-ERR Invalid node id length 3\r\n"
33198 );
33199 let unknown = b"1111111111111111111111111111111111111111";
33200 assert_eq!(
33201 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"node-id", unknown]),
33202 "-ERR Node 1111111111111111111111111111111111111111 not found in cluster\r\n"
33203 );
33204 assert_eq!(
33205 f.run(&[
33206 b"CLUSTER",
33207 b"SYNCSLOTS",
33208 b"CONF",
33209 b"node-id",
33210 b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f"
33211 ]),
33212 "+OK\r\n"
33213 );
33214 assert_eq!(
33216 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"slot-info", b"5:10:2"]),
33217 "+OK\r\n"
33218 );
33219 for bad in [b"zz".as_slice(), b"16384:0:0", b"5:10:2:3", b"5:-1:0"] {
33220 assert_eq!(
33221 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"slot-info", bad]),
33222 format!(
33223 "-ERR Invalid slot info: {}\r\n",
33224 String::from_utf8_lossy(bad)
33225 )
33226 );
33227 }
33228 assert_eq!(
33231 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"asm-task", b"x"]),
33232 "-ERR CLUSTER SYNCSLOTS CONF ASM-TASK only allowed on replica\r\n"
33233 );
33234 assert_eq!(
33235 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT", b"UNMARK"]),
33236 "+OK\r\n"
33237 );
33238 let (flow, _) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
33239 assert_eq!(flow, Flow::Close, "and the door shuts again");
33240 }
33241
33242 #[test]
33245 fn the_slot_ranges_of_a_sync_are_checked_in_full() {
33246 let mut f = clustered();
33247 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33248 let id = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
33249 fn sync<'a>(id: &'a [u8], slots: &[&'a [u8]]) -> Vec<&'a [u8]> {
33250 let mut parts: Vec<&[u8]> = vec![b"CLUSTER", b"SYNCSLOTS", b"SYNC", id];
33251 parts.extend_from_slice(slots);
33252 parts
33253 }
33254 let bar = BAR.to_string();
33255 let bar = bar.as_bytes();
33256 assert_eq!(
33257 f.run(&sync(id, &[b"5", b"4"])),
33258 "-ERR start slot number 5 is greater than end slot number 4\r\n"
33259 );
33260 assert_eq!(
33261 f.run(&sync(id, &[b"99999", b"2"])),
33262 "-ERR Invalid or out of range slot\r\n"
33263 );
33264 assert_eq!(
33267 f.run(&sync(id, &[b"1", b"2", b"2", b"3"])),
33268 "-ERR Slot 2 specified multiple times\r\n"
33269 );
33270 assert_eq!(
33273 f.run(&sync(id, &[b"1", b"2", b"3", b"4"])),
33274 "+RDBCHANNELSYNCSLOTS\r\n"
33275 );
33276 assert_eq!(
33277 f.run(&[b"CLUSTER", b"MIGRATION", b"CANCEL", b"ALL"]),
33278 ":1\r\n"
33279 );
33280 f.server.cluster_hand_over(BAR, 1);
33282 assert_eq!(
33283 f.run(&sync(id, &[bar])),
33284 "-ERR syntax error\r\n",
33285 "one slot number is not a range, and a shape it does not know is a \
33286 syntax error rather than a count it can complain about"
33287 );
33288 assert_eq!(
33289 f.run(&sync(id, &[bar, bar])),
33290 "-ERR This node is not the owner of the slots\r\n"
33291 );
33292 f.server.cluster_moving(FOO, Some(1), None);
33294 assert_eq!(
33295 f.run(&sync(id, &[b"1", b"2"])),
33296 "-ERR all slot states must be STABLE to start a slot migration task.\r\n"
33297 );
33298 }
33299
33300 #[test]
33308 fn a_sync_and_an_rdbchannel_hand_over_the_slots_asked_for() {
33309 let mut f = clustered();
33310 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33311 f.run(&[b"SET", b"foo", b"in the range"]);
33312 f.run(&[b"SET", b"bar", b"outside it"]);
33313 let id = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
33314 let foo = FOO.to_string();
33315 let foo = foo.as_bytes();
33316
33317 assert_eq!(
33319 f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ALL"]),
33320 "*0\r\n"
33321 );
33322 assert_eq!(
33323 f.run(&[b"CLUSTER", b"MIGRATION", b"CANCEL", b"ALL"]),
33324 ":0\r\n"
33325 );
33326
33327 assert_eq!(
33330 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"RDBCHANNEL", id]),
33331 "-ERR No slot migration task in progress\r\n"
33332 );
33333 assert_eq!(
33334 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"SYNC", id, foo, foo]),
33335 "+RDBCHANNELSYNCSLOTS\r\n"
33336 );
33337 let status = f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ALL"]);
33339 assert!(status.starts_with("*1\r\n"), "{status:?}");
33340 assert!(status.contains("wait-rdbchannel"), "{status:?}");
33341 assert!(status.contains("migrate"), "{status:?}");
33342 assert_eq!(
33344 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"SYNC", id, foo, foo]),
33345 "-ERR Another ASM task is already in progress\r\n"
33346 );
33347
33348 let snapshot = f.raw(&[b"CLUSTER", b"SYNCSLOTS", b"RDBCHANNEL", id]);
33349 let text = String::from_utf8_lossy(&snapshot);
33350 assert!(text.starts_with("+SLOTSSNAPSHOT\r\n"), "{text:?}");
33351 assert!(text.contains("$8\r\nFUNCTION\r\n"), "{text:?}");
33352 assert!(
33353 text.contains("$3\r\nSET\r\n$3\r\nfoo\r\n$12\r\nin the range\r\n"),
33354 "{text:?}"
33355 );
33356 assert!(!text.contains("$3\r\nbar\r\n"), "{text:?}");
33357 assert!(
33358 text.ends_with("$7\r\nCLUSTER\r\n$9\r\nSYNCSLOTS\r\n$12\r\nSNAPSHOT-EOF\r\n"),
33359 "{text:?}"
33360 );
33361 let status = f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ID", id]);
33363 assert!(status.contains("send-stream"), "{status:?}");
33364 assert_eq!(
33365 f.run(&[b"CLUSTER", b"MIGRATION", b"CANCEL", b"ID", id]),
33366 ":1\r\n"
33367 );
33368 let status = f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ID", id]);
33370 assert!(status.contains("canceled"), "{status:?}");
33371 assert!(
33372 status.contains("Cancelled due to user request"),
33373 "{status:?}"
33374 );
33375 }
33376
33377 #[test]
33380 fn a_replica_only_hears_the_settings_and_only_from_its_master() {
33381 let mut server = Server::new();
33382 server.enable_cluster("", 7000);
33383 let of = server.cluster_pretend_node(
33384 "5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f",
33385 "10.0.0.9",
33386 7002,
33387 );
33388 server.cluster_pretend_follower(of);
33389 let mut f = Fixture::on(server);
33390 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33391 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
33392 assert_eq!(
33393 reply,
33394 "-ERR CLUSTER SYNCSLOTS subcommands are only allowed for master\r\n"
33395 );
33396 assert_eq!(flow, Flow::Close);
33397 f.session.serve_master(true);
33401 assert_eq!(
33402 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]),
33403 "+OK\r\n"
33404 );
33405 assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"SNAPSHOT-EOF"]), "");
33406 assert_eq!(
33407 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"asm-task", b"x"]),
33408 "-ERR Failed to handle master task: x\r\n+OK\r\n",
33409 "there is no migration for a replica to follow along with yet, and \
33410 this option is one the reference keeps going past as well"
33411 );
33412 }
33413
33414 #[test]
33417 fn the_one_way_arms_of_the_protocol_say_nothing_back() {
33418 let mut f = clustered();
33419 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33420 assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"ACK", b"x", b"1"]), "");
33421 assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"FAIL", b"boom"]), "");
33422 let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"STREAM-EOF"]);
33425 assert_eq!(reply, "");
33426 assert_eq!(flow, Flow::Close);
33427 let mut f = clustered();
33429 f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33430 assert_eq!(
33431 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"RDBCHANNEL", b"abc"]),
33432 "-ERR Invalid task id\r\n"
33433 );
33434 assert_eq!(
33435 f.run(&[
33436 b"CLUSTER",
33437 b"SYNCSLOTS",
33438 b"RDBCHANNEL",
33439 b"0000000000000000000000000000000000000000"
33440 ]),
33441 "-ERR No slot migration task in progress\r\n"
33442 );
33443 assert_eq!(
33444 f.run(&[b"CLUSTER", b"SYNCSLOTS", b"NONSENSE"]),
33445 "-ERR syntax error\r\n"
33446 );
33447 }
33448
33449 #[test]
33452 fn a_transaction_is_refused_when_it_is_queued() {
33453 let mut f = clustered();
33454 f.server.cluster_hand_over(BAR, 1);
33455 assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
33456 assert_eq!(
33457 f.run(&[b"GET", b"bar"]),
33458 format!("-MOVED {BAR} 10.0.0.9:7002\r\n")
33459 );
33460 assert_eq!(
33461 f.run(&[b"EXEC"]),
33462 "-EXECABORT Transaction discarded because of previous errors.\r\n"
33463 );
33464 }
33465
33466 #[test]
33469 fn select_and_swapdb_are_not_cluster_commands() {
33470 let mut f = clustered();
33471 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
33472 assert_eq!(
33473 f.run(&[b"SELECT", b"1"]),
33474 "-ERR SELECT is not allowed in cluster mode\r\n"
33475 );
33476 assert_eq!(
33477 f.run(&[b"SWAPDB", b"0", b"1"]),
33478 "-ERR SWAPDB is not allowed in cluster mode\r\n"
33479 );
33480 }
33481
33482 #[test]
33485 fn a_plain_server_has_no_cluster_in_it() {
33486 let mut f = Fixture::new();
33487 for argv in [
33488 &[b"CLUSTER".as_slice(), b"INFO".as_slice()][..],
33489 &[b"CLUSTER", b"MYID"],
33490 &[b"CLUSTER", b"SLOTS"],
33491 &[b"CLUSTER", b"HELP"],
33492 &[b"ASKING"],
33493 &[b"READONLY"],
33494 &[b"READWRITE"],
33495 ] {
33496 assert_eq!(
33497 f.run(argv),
33498 "-ERR This instance has cluster support disabled\r\n",
33499 "{argv:?}"
33500 );
33501 }
33502 assert_eq!(
33504 f.run(&[b"CLUSTER", b"KEYSLOT"]),
33505 "-ERR wrong number of arguments for 'cluster|keyslot' command\r\n"
33506 );
33507 }
33508}