yo_kv/sort.rs
1//! `SORT` and `SORT_RO`, the one keyspace command that reads keys nobody named.
2//!
3//! Everything else in [`crate::keyspace`] asks a question about the key in front
4//! of it. `SORT mylist BY weight_* GET data_*` reads `mylist`, then reads a
5//! different key for every element in it to decide the order, then reads another
6//! one for every element to decide what to answer with. That is why it is the
7//! command Redis marks as not deterministic, why `SORT_RO` exists at all, and
8//! why it lives in a file of its own instead of as another arm of the keyspace
9//! match.
10//!
11//! # What it sorts
12//!
13//! A list, a set or a sorted set. Anything else is `WRONGTYPE` and a missing key
14//! is an empty answer, not an error.
15//!
16//! # The four ways it can order things
17//!
18//! Numerically on the elements, which is the default and which fails the whole
19//! command if any element is not a number. Numerically on a `BY` lookup, where
20//! an element whose lookup missed scores zero. Alphabetically on the elements.
21//! Alphabetically on a `BY` lookup, where a miss sorts before every hit.
22//!
23//! Ties never fall through to whatever order the elements arrived in. Two equal
24//! scores are broken by comparing the elements themselves, which is what makes
25//! the answer the same on two servers holding the same set, and a set has no
26//! order of its own to fall back on anyway.
27//!
28//! # Not sorting
29//!
30//! A `BY` pattern with no `*` in it cannot name a different key per element, so
31//! Redis reads it as an instruction not to sort rather than as a pattern that
32//! resolves to one key for everything. `BY nosort` is the idiom and there is
33//! nothing special about the word.
34//!
35//! There is one hole in that, and it is the reason the `store` flag is in
36//! [`Sort`] rather than being left to the caller. A set has no order, so
37//! `SORT myset BY nosort` may answer its members in any order at all, which is
38//! fine for a client that asked for exactly that and is not fine for a `STORE`,
39//! because then two servers that agree about the set disagree about the list
40//! they wrote. So a `BY nosort` over a set with a `STORE` sorts alphabetically
41//! after all. Redis does the same thing for the same reason, and also does it
42//! for a call from a script, which we do not, because a script here reaches the
43//! same method any other caller does.
44//!
45//! # Patterns
46//!
47//! A pattern is not a glob. The first `*` in it is replaced with the element and
48//! the result is a key name, so `weight_*` on the element `a` reads `weight_a`.
49//! A pattern with no `*` looks up nothing and misses every time. `#` on its own
50//! means the element itself, which is only useful in a `GET`.
51//!
52//! A pattern may reach into a hash instead of at a string, with `->` after the
53//! `*`: `h_*->field` reads the field `field` of the hash `h_<element>`. The
54//! split is on the first `->` that comes after the `*` and there has to be at
55//! least one byte after it, so a pattern ending in `->` is a key name with a
56//! `->` on the end and not a hash lookup with an empty field.
57//!
58//! A lookup that lands on a key of the wrong type is a miss and not an error.
59//! `BY h_*` over keys holding lists gives every element a weight of nothing,
60//! which sorts them all equal and lets the tie break do the work.
61//!
62//! # Stripes
63//!
64//! This is the only thing in the store that takes a whole database rather than
65//! one keyspace, and it is why. The key it sorts is on one stripe, the key a
66//! `BY` names for an element is on whichever stripe that name lands on, the key
67//! a `GET` names is on another, and a `STORE` destination is on a fourth. None
68//! of those can be worked out before the command runs, because the names are
69//! built out of the elements, so there is nothing here to route once at the top
70//! the way every other command is routed.
71//!
72//! So the stripes are taken before the command starts rather than one at a
73//! time as the names appear. A sort with a pattern in it takes every stripe of
74//! the database, because the names are built out of the elements and the
75//! stripes those land on are, between them, all of them. A sort without one
76//! takes the one stripe its key is on, and the two stripes its key and its
77//! destination are on when there is a `STORE`.
78//!
79//! # Divergence
80//!
81//! Redis compares strings with `strcoll` when there is no `STORE`, and with a
82//! byte compare when there is, because a stored result has to be the same on
83//! every replica and `strcoll` answers to `LC_COLLATE`. Redis calls
84//! `setlocale(LC_COLLATE, "")` at startup, so the order `SORT ... ALPHA` puts
85//! two strings in depends on the environment the server was started with, and
86//! the same server started twice can answer differently.
87//!
88//! This compares bytes always. That is the `STORE` behaviour applied everywhere,
89//! it is what a client gets from Redis under the C locale, and it is the only
90//! choice that makes the answer a property of the data. It also means a member
91//! with a zero byte in it sorts on all of its bytes, where `strcoll` stops at
92//! the zero. Registered in `divergences.toml`.
93
94use crate::db::{Db, Holds};
95use crate::keyspace::wrong_type;
96use crate::lookups;
97use crate::news::{self, What};
98use crate::value::Kind;
99use crate::zsets::Window;
100use std::cmp::Ordering;
101use yo_common::{Code, Error, Result, num};
102
103/// What Redis says when a numeric sort meets an element that is not a number.
104const NOT_A_DOUBLE: &str = "One or more scores can't be converted into double";
105
106/// What `SORT` was asked to do, everything except the key and the destination.
107///
108/// Borrowed from the caller's arguments rather than owned, because on the wire
109/// path every field of this is a slice of the command that is already in memory
110/// and copying them would be copying the command.
111#[derive(Debug, Clone, Copy, Default)]
112pub struct Sort<'a> {
113 /// The `BY` pattern, if there was one.
114 pub by: Option<&'a [u8]>,
115 /// The `GET` patterns, in the order they were given, `#` included.
116 pub get: &'a [&'a [u8]],
117 /// `LIMIT offset count`, if there was one.
118 ///
119 /// Both halves are signed because both halves are on the wire as integers
120 /// and Redis takes them without complaint. A negative offset is clamped to
121 /// the front and a negative count means everything from the offset on.
122 pub limit: Option<(i64, i64)>,
123 /// `DESC`, which reverses whatever order the rest of this produced.
124 pub desc: bool,
125 /// `ALPHA`, which compares bytes where the default compares numbers.
126 pub alpha: bool,
127 /// Whether the answer is going into a key rather than back to the caller.
128 ///
129 /// Set by [`Db::sort_store`] and not by the caller. It is in here
130 /// rather than a separate argument because it changes the ordering and not
131 /// just what happens to the result. See the module doc.
132 pub store: bool,
133}
134
135/// One element on its way through the sort, with whatever it is ordered by.
136///
137/// The weight is one of the two, never both, and which one is decided once for
138/// the whole command rather than per element.
139struct Weighted {
140 /// The element itself, which is also the tie break and may be the answer.
141 elem: Vec<u8>,
142 /// The number to sort on, under a numeric sort.
143 score: f64,
144 /// The bytes to sort on, under an alphabetic sort with a `BY`. `None` is a
145 /// lookup that missed and sorts before every hit.
146 text: Option<Vec<u8>>,
147}
148
149impl Db {
150 /// `SORT key [BY pattern] [LIMIT offset count] [GET pattern ...] [ASC|DESC]
151 /// [ALPHA]`, and the whole of `SORT_RO`.
152 ///
153 /// One row per element that survived the `LIMIT`, or one row per `GET`
154 /// pattern per element if there were any. A row is `None` where a `GET`
155 /// pattern missed, which is a nil on the wire, and `GET #` never misses.
156 ///
157 /// This allocates the elements, which nothing else on the read path does. It
158 /// has to: the ordering is decided by reading other keys, and reading
159 /// another key needs the database that the elements are borrowed from. Redis
160 /// has the same problem and solves it by holding refcounted pointers, which
161 /// is the same copy with the copy moved to whoever wrote the value. The copy
162 /// is also what lets a `BY` or a `GET` reach a stripe other than the one the
163 /// elements came from.
164 pub fn sort(&self, key: &[u8], opts: &Sort<'_>) -> Result<Vec<Option<Vec<u8>>>> {
165 let mut opts = *opts;
166 opts.store = false;
167 let mut held = self.reach(key, None, &opts);
168 self.sorted(&mut held, key, &opts)
169 }
170
171 /// `SORT key ... STORE destination`, which answers the length of the list it
172 /// wrote.
173 ///
174 /// An empty result deletes the destination rather than leaving an empty list
175 /// behind, because a list is never empty and a key that holds one that is
176 /// would be a key `TYPE` answers `list` for and `LLEN` answers zero for.
177 ///
178 /// A `GET` pattern that missed stores an empty string, where the same miss
179 /// sent to a client is a nil. There is no nil in a list, so this is the only
180 /// thing it could be, and it is what Redis stores.
181 ///
182 /// The destination is on its own stripe, which is not generally the stripe
183 /// the elements came from, and it is held from the start alongside the rest
184 /// rather than reached for once the sort is done, so that nothing can write
185 /// into it between the read and the store.
186 pub fn sort_store(&self, key: &[u8], dest: &[u8], opts: &Sort<'_>) -> Result<usize> {
187 let mut opts = *opts;
188 opts.store = true;
189 let mut held = self.reach(key, Some(dest), &opts);
190 let rows = self.sorted(&mut held, key, &opts)?;
191 let onto = self.stripe_of(dest);
192 if rows.is_empty() {
193 held.stripe_mut(onto).del(dest);
194 return Ok(0);
195 }
196 // Written over whatever was there rather than appended to it, so that
197 // `SORT k STORE k` reads k, throws away what it read, then writes the
198 // answer. Redis is the same, and it is the reason the elements had to
199 // be copied out before any of this.
200 let owned: Vec<Vec<u8>> = rows.into_iter().map(Option::unwrap_or_default).collect();
201 held.stripe_mut(onto)
202 .put_list(dest, owned.iter().map(Vec::as_slice))
203 }
204
205 /// Every stripe this sort can touch, held, in stripe order.
206 ///
207 /// A `BY` or a `GET` with a `*` in it builds a key name out of each element,
208 /// and which stripes those names land on cannot be known before the elements
209 /// have been read. So a sort with a pattern in it takes the whole database
210 /// and a sort without one takes the one or two stripes it does name. The
211 /// wide form is the price of a command whose keys it cannot know in advance,
212 /// and it is no wider than what a client already gets from Redis, where a
213 /// sort holds the whole server for its whole run.
214 fn reach(&self, key: &[u8], dest: Option<&[u8]>, opts: &Sort<'_>) -> Holds<'_> {
215 if patterned(opts) {
216 return self.hold_many(0..self.width());
217 }
218 let named = std::iter::once(self.stripe_of(key));
219 self.hold_many(named.chain(dest.map(|d| self.stripe_of(d))))
220 }
221
222 /// The body both of them share.
223 fn sorted(
224 &self,
225 held: &mut Holds<'_>,
226 key: &[u8],
227 opts: &Sort<'_>,
228 ) -> Result<Vec<Option<Vec<u8>>>> {
229 let kind = held.stripe_mut(self.stripe_of(key)).kind_of(key);
230 // The lookup above is the one the sort's own key gets counted for, and
231 // the read below is the same key again. The `BY` and `GET` patterns are
232 // outside this on purpose, since a real server counts each of those.
233 // See [`crate::lookups::quiet`].
234 let elems = {
235 let _quiet = lookups::quiet();
236 self.elements(held, key, kind)?
237 };
238
239 // A `BY` with no `*` cannot name a key per element, so it is an order to
240 // leave things alone. The exception is the one the module doc explains.
241 let mut alpha = opts.alpha;
242 let mut by = opts.by;
243 let mut dontsort = by.is_some_and(|p| !p.contains(&b'*'));
244 if dontsort && kind == Some(Kind::Set) && opts.store {
245 dontsort = false;
246 alpha = true;
247 by = None;
248 }
249
250 let ordered = if dontsort {
251 // The natural order of whatever it is, backwards if `DESC` was
252 // given. A list is head to tail, a sorted set is by score, and a set
253 // has no order to reverse but reversing it costs nothing and keeps
254 // this one branch instead of two.
255 let mut e = elems;
256 if opts.desc {
257 e.reverse();
258 }
259 e
260 } else {
261 self.order(held, elems, by, alpha, opts.desc)?
262 };
263
264 let window = limit(ordered.len(), opts.limit);
265 self.emit(held, &ordered[window], opts.get)
266 }
267
268 /// Copy the elements out of whatever holds them.
269 ///
270 /// Owned, for the reason [`Db::sort`] gives. A missing key is an empty
271 /// list and not an error, so `SORT nosuchkey` answers nothing.
272 fn elements(
273 &self,
274 held: &mut Holds<'_>,
275 key: &[u8],
276 kind: Option<Kind>,
277 ) -> Result<Vec<Vec<u8>>> {
278 let mut out = Vec::new();
279 // One stripe for the whole of this, since it is all the same key.
280 let db = held.stripe_mut(self.stripe_of(key));
281 match kind {
282 None => {}
283 Some(Kind::List) => {
284 for e in db.lrange(key, 0, -1)? {
285 let mut v = Vec::new();
286 e.write_to(&mut v);
287 out.push(v);
288 }
289 }
290 Some(Kind::Set) => {
291 if let Some(members) = db.smembers(key)? {
292 for m in members {
293 let mut v = Vec::new();
294 m.write_to(&mut v);
295 out.push(v);
296 }
297 }
298 }
299 Some(Kind::Zset) => {
300 let n = db.zcard(key)?;
301 let w = Window {
302 from: 0,
303 count: n,
304 rev: false,
305 };
306 out.reserve(n);
307 db.zwalk(key, w, |m, _| {
308 let mut v = Vec::new();
309 m.write_to(&mut v);
310 out.push(v);
311 })?;
312 }
313 Some(_) => return Err(wrong_type()),
314 }
315 Ok(out)
316 }
317
318 /// Weigh every element and put them in order.
319 fn order(
320 &self,
321 held: &mut Holds<'_>,
322 elems: Vec<Vec<u8>>,
323 by: Option<&[u8]>,
324 alpha: bool,
325 desc: bool,
326 ) -> Result<Vec<Vec<u8>>> {
327 let mut weighed = Vec::with_capacity(elems.len());
328 for elem in elems {
329 let looked = match by {
330 Some(pattern) => self.by_pattern(held, pattern, &elem),
331 None => None,
332 };
333 let (score, text) = if alpha {
334 // Without a `BY` the element is its own sort key, and the tie
335 // break already compares elements, so there is nothing to carry.
336 (0.0, if by.is_some() { looked } else { None })
337 } else {
338 let raw = match by {
339 // A lookup that missed weighs nothing. Redis leaves the
340 // score at zero rather than failing, which means a numeric
341 // `BY` over keys that do not exist is a pure tie break.
342 Some(_) => match looked {
343 Some(v) => v,
344 None => {
345 weighed.push(Weighted {
346 elem,
347 score: 0.0,
348 text: None,
349 });
350 continue;
351 }
352 },
353 None => elem.clone(),
354 };
355 let n =
356 num::parse_f64(&raw).ok_or_else(|| Error::new(Code::Invalid, NOT_A_DOUBLE))?;
357 if n.is_nan() {
358 return Err(Error::new(Code::Invalid, NOT_A_DOUBLE));
359 }
360 (n, None)
361 };
362 weighed.push(Weighted { elem, score, text });
363 }
364
365 // Stable is not needed, since the tie break is total, but it is what
366 // `sort_by` gives and asking for the unstable one to save nothing would
367 // be trading a guarantee for no gain.
368 weighed.sort_by(|a, b| {
369 let cmp = if alpha {
370 match (&a.text, &b.text) {
371 // Both missing, or no `BY` at all, so the elements decide.
372 (None, None) => a.elem.cmp(&b.elem),
373 // A miss sorts before a hit.
374 (None, Some(_)) => Ordering::Less,
375 (Some(_), None) => Ordering::Greater,
376 (Some(x), Some(y)) => x.cmp(y).then_with(|| a.elem.cmp(&b.elem)),
377 }
378 } else {
379 // No NaN can reach here, so the partial compare is total.
380 a.score
381 .partial_cmp(&b.score)
382 .unwrap_or(Ordering::Equal)
383 .then_with(|| a.elem.cmp(&b.elem))
384 };
385 if desc { cmp.reverse() } else { cmp }
386 });
387 Ok(weighed.into_iter().map(|w| w.elem).collect())
388 }
389
390 /// Build the answer, which is the elements themselves or a `GET` per element.
391 fn emit(
392 &self,
393 held: &mut Holds<'_>,
394 elems: &[Vec<u8>],
395 get: &[&[u8]],
396 ) -> Result<Vec<Option<Vec<u8>>>> {
397 if get.is_empty() {
398 return Ok(elems.iter().map(|e| Some(e.clone())).collect());
399 }
400 let mut out = Vec::with_capacity(elems.len() * get.len());
401 for elem in elems {
402 for pattern in get {
403 if *pattern == b"#" {
404 out.push(Some(elem.clone()));
405 } else {
406 out.push(self.by_pattern(held, pattern, elem));
407 }
408 }
409 }
410 Ok(out)
411 }
412
413 /// Read the key a pattern names for one element.
414 ///
415 /// `None` for a pattern with no `*`, for a key that is not there, and for a
416 /// key that is there and holds the wrong type. The last one is a miss rather
417 /// than an error on purpose: a pattern is a guess about a naming convention
418 /// and one key that does not fit the convention should not fail a command
419 /// over ten thousand elements.
420 ///
421 /// The name is built out of the element, so the stripe it lands on is not
422 /// known until here and two elements of the same key are read from two
423 /// different stripes as often as not. Every stripe is already held by then,
424 /// which is what [`Db::reach`] is for.
425 fn by_pattern(&self, held: &mut Holds<'_>, pattern: &[u8], elem: &[u8]) -> Option<Vec<u8>> {
426 let star = pattern.iter().position(|&c| c == b'*')?;
427 // The field split is looked for after the `*`, so a `->` in the prefix
428 // is part of the key name. And there has to be something after it, so a
429 // pattern ending in `->` names a key whose name ends in `->`.
430 let arrow = pattern[star + 1..]
431 .windows(2)
432 .position(|w| w == b"->")
433 .map(|i| star + 1 + i)
434 .filter(|&i| i + 2 < pattern.len());
435
436 let (key_part, field) = match arrow {
437 Some(i) => (&pattern[..i], Some(&pattern[i + 2..])),
438 None => (pattern, None),
439 };
440
441 let mut key = Vec::with_capacity(key_part.len() + elem.len());
442 key.extend_from_slice(&key_part[..star]);
443 key.extend_from_slice(elem);
444 key.extend_from_slice(&key_part[star + 1..]);
445
446 let stripe = held.stripe_mut(self.stripe_of(&key));
447 // Whether the name was taken at all, which is not the same question as
448 // whether this read got anything and is only asked when the answer has
449 // somewhere to go. A key holding the wrong type was found, and so was a
450 // hash that does not have the field, and neither of those is a miss.
451 let (found, missed) = match field {
452 Some(f) => {
453 let got = stripe.hget(&key, f, |t| {
454 t.map(|t| {
455 let mut v = Vec::new();
456 t.write_to(&mut v);
457 v
458 })
459 });
460 match got {
461 Ok(None) => (None, news::listening() && stripe.kind_of(&key).is_none()),
462 Ok(found) => (found, false),
463 Err(_) => (None, false),
464 }
465 }
466 None => match stripe.get(&key) {
467 Ok(None) => (None, true),
468 Ok(found) => (found.map(|s| s.to_vec()), false),
469 Err(_) => (None, false),
470 },
471 };
472 if missed {
473 news::say(&key, What::Missed);
474 }
475 found
476 }
477}
478
479/// Whether this sort can name a key that was not given on the wire.
480///
481/// A `BY` with no `*` names nothing, and neither does a `GET #`, which is the
482/// element itself. Anything else with a `*` in it is a key per element.
483fn patterned(opts: &Sort<'_>) -> bool {
484 opts.by.is_some_and(|p| p.contains(&b'*')) || opts.get.iter().any(|&p| p != b"#")
485}
486
487/// Which slice of the sorted elements the `LIMIT` asked for.
488///
489/// Redis clamps rather than complains at every edge: a negative offset is the
490/// front, a negative count is everything left, an offset past the end is an
491/// empty answer and a count that runs past the end stops at it.
492fn limit(len: usize, limit: Option<(i64, i64)>) -> std::ops::Range<usize> {
493 let Some((offset, count)) = limit else {
494 return 0..len;
495 };
496 let start = usize::try_from(offset).unwrap_or(0).min(len);
497 let end = if count < 0 {
498 len
499 } else {
500 start
501 .saturating_add(usize::try_from(count).unwrap_or(0))
502 .min(len)
503 };
504 start..end
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510 use crate::lists::End;
511 use crate::strings::SetOptions;
512 use crate::zsets::ZAdd;
513
514 /// The answer as flat bytes, with a missed `GET` written as the word `nil`,
515 /// which no test here stores as a value.
516 fn flat(rows: Vec<Option<Vec<u8>>>) -> Vec<String> {
517 rows.into_iter()
518 .map(|r| match r {
519 Some(v) => String::from_utf8_lossy(&v).into_owned(),
520 None => "nil".to_string(),
521 })
522 .collect()
523 }
524
525 /// One list element as a string, for reading back what a `STORE` wrote.
526 fn text(e: crate::listpack::Entry<'_>) -> String {
527 let mut v = Vec::new();
528 e.write_to(&mut v);
529 String::from_utf8_lossy(&v).into_owned()
530 }
531
532 fn list(db: &mut Db, key: &[u8], items: &[&str]) {
533 db.at(key)
534 .push(key, End::Right, items.iter().map(|s| s.as_bytes()))
535 .expect("a fresh list takes elements");
536 }
537
538 #[test]
539 fn numbers_sort_as_numbers_and_not_as_text() {
540 let mut db = Db::new();
541 list(&mut db, b"l", &["10", "9", "100", "1"]);
542 let opts = Sort::default();
543 assert_eq!(flat(db.sort(b"l", &opts).unwrap()), ["1", "9", "10", "100"]);
544 let alpha = Sort {
545 alpha: true,
546 ..Sort::default()
547 };
548 assert_eq!(
549 flat(db.sort(b"l", &alpha).unwrap()),
550 ["1", "10", "100", "9"]
551 );
552 }
553
554 #[test]
555 fn an_element_that_is_not_a_number_fails_the_whole_command() {
556 let mut db = Db::new();
557 list(&mut db, b"l", &["1", "two", "3"]);
558 let err = db.sort(b"l", &Sort::default()).unwrap_err();
559 assert_eq!(err.message(), NOT_A_DOUBLE);
560 // And the same elements under ALPHA are fine, which is the whole point
561 // of the option.
562 let alpha = Sort {
563 alpha: true,
564 ..Sort::default()
565 };
566 assert_eq!(flat(db.sort(b"l", &alpha).unwrap()), ["1", "3", "two"]);
567 }
568
569 #[test]
570 fn desc_reverses_and_limit_takes_a_window_of_what_is_left() {
571 let mut db = Db::new();
572 list(&mut db, b"l", &["3", "1", "5", "2", "4"]);
573 let opts = Sort {
574 desc: true,
575 limit: Some((1, 2)),
576 ..Sort::default()
577 };
578 assert_eq!(flat(db.sort(b"l", &opts).unwrap()), ["4", "3"]);
579 // A negative count is everything from the offset on, and an offset past
580 // the end is nothing at all.
581 let rest = Sort {
582 limit: Some((3, -1)),
583 ..Sort::default()
584 };
585 assert_eq!(flat(db.sort(b"l", &rest).unwrap()), ["4", "5"]);
586 let past = Sort {
587 limit: Some((99, 5)),
588 ..Sort::default()
589 };
590 assert!(db.sort(b"l", &past).unwrap().is_empty());
591 let before = Sort {
592 limit: Some((-4, 2)),
593 ..Sort::default()
594 };
595 assert_eq!(flat(db.sort(b"l", &before).unwrap()), ["1", "2"]);
596 }
597
598 #[test]
599 fn by_reads_a_key_for_every_element() {
600 let mut db = Db::new();
601 list(&mut db, b"l", &["a", "b", "c"]);
602 db.at(b"w_a").set(b"w_a", b"3", SetOptions::PLAIN).unwrap();
603 db.at(b"w_b").set(b"w_b", b"1", SetOptions::PLAIN).unwrap();
604 db.at(b"w_c").set(b"w_c", b"2", SetOptions::PLAIN).unwrap();
605 let opts = Sort {
606 by: Some(b"w_*"),
607 ..Sort::default()
608 };
609 assert_eq!(flat(db.sort(b"l", &opts).unwrap()), ["b", "c", "a"]);
610 }
611
612 #[test]
613 fn a_by_lookup_that_missed_weighs_nothing_and_the_element_breaks_the_tie() {
614 let mut db = Db::new();
615 list(&mut db, b"l", &["c", "a", "b"]);
616 db.at(b"w_b").set(b"w_b", b"5", SetOptions::PLAIN).unwrap();
617 let opts = Sort {
618 by: Some(b"w_*"),
619 ..Sort::default()
620 };
621 // `a` and `c` both weigh zero, so they come first in element order, and
622 // `b` weighs five and comes last.
623 assert_eq!(flat(db.sort(b"l", &opts).unwrap()), ["a", "c", "b"]);
624 }
625
626 #[test]
627 fn under_alpha_a_missed_by_sorts_before_every_hit() {
628 let mut db = Db::new();
629 list(&mut db, b"l", &["c", "a", "b"]);
630 db.at(b"w_b")
631 .set(b"w_b", b"zzz", SetOptions::PLAIN)
632 .unwrap();
633 db.at(b"w_c")
634 .set(b"w_c", b"aaa", SetOptions::PLAIN)
635 .unwrap();
636 let opts = Sort {
637 by: Some(b"w_*"),
638 alpha: true,
639 ..Sort::default()
640 };
641 assert_eq!(flat(db.sort(b"l", &opts).unwrap()), ["a", "c", "b"]);
642 }
643
644 #[test]
645 fn a_pattern_can_reach_into_a_hash() {
646 let mut db = Db::new();
647 list(&mut db, b"l", &["a", "b"]);
648 db.at(b"h_a")
649 .hset(b"h_a", [(&b"w"[..], &b"2"[..])].into_iter())
650 .unwrap();
651 db.at(b"h_b")
652 .hset(b"h_b", [(&b"w"[..], &b"1"[..])].into_iter())
653 .unwrap();
654 let opts = Sort {
655 by: Some(b"h_*->w"),
656 ..Sort::default()
657 };
658 assert_eq!(flat(db.sort(b"l", &opts).unwrap()), ["b", "a"]);
659 // And a pattern that ends in an arrow is a key name, not a hash lookup
660 // with no field, so it reads a string key called `h_a->`.
661 db.at(b"h_a->")
662 .set(b"h_a->", b"9", SetOptions::PLAIN)
663 .unwrap();
664 let trailing = Sort {
665 by: Some(b"h_*->"),
666 ..Sort::default()
667 };
668 assert_eq!(flat(db.sort(b"l", &trailing).unwrap()), ["b", "a"]);
669 }
670
671 #[test]
672 fn get_answers_other_keys_and_a_hash_of_them() {
673 let mut db = Db::new();
674 list(&mut db, b"l", &["2", "1"]);
675 db.at(b"d_1")
676 .set(b"d_1", b"one", SetOptions::PLAIN)
677 .unwrap();
678 db.at(b"d_2")
679 .set(b"d_2", b"two", SetOptions::PLAIN)
680 .unwrap();
681 let get: [&[u8]; 2] = [b"#", b"d_*"];
682 let opts = Sort {
683 get: &get,
684 ..Sort::default()
685 };
686 assert_eq!(
687 flat(db.sort(b"l", &opts).unwrap()),
688 ["1", "one", "2", "two"]
689 );
690 // A miss is a nil and not a skipped row, because the reply is positional.
691 db.at(b"d_2").del(b"d_2");
692 assert_eq!(
693 flat(db.sort(b"l", &opts).unwrap()),
694 ["1", "one", "2", "nil"]
695 );
696 }
697
698 #[test]
699 fn a_lookup_at_the_wrong_type_is_a_miss_and_not_an_error() {
700 let mut db = Db::new();
701 list(&mut db, b"l", &["a"]);
702 list(&mut db, b"d_a", &["x"]);
703 let get: [&[u8]; 1] = [b"d_*"];
704 let opts = Sort {
705 get: &get,
706 alpha: true,
707 ..Sort::default()
708 };
709 assert_eq!(flat(db.sort(b"l", &opts).unwrap()), ["nil"]);
710 }
711
712 #[test]
713 fn by_without_a_star_leaves_the_order_alone() {
714 let mut db = Db::new();
715 list(&mut db, b"l", &["3", "1", "2"]);
716 let opts = Sort {
717 by: Some(b"nosort"),
718 ..Sort::default()
719 };
720 assert_eq!(flat(db.sort(b"l", &opts).unwrap()), ["3", "1", "2"]);
721 // And DESC still reverses it, because there is an order to reverse.
722 let back = Sort {
723 by: Some(b"nosort"),
724 desc: true,
725 ..Sort::default()
726 };
727 assert_eq!(flat(db.sort(b"l", &back).unwrap()), ["2", "1", "3"]);
728 }
729
730 #[test]
731 fn a_set_stored_without_a_sort_is_sorted_anyway() {
732 let mut db = Db::new();
733 for m in ["c", "a", "b"] {
734 db.at(b"s").sadd(b"s", [m.as_bytes()].into_iter()).unwrap();
735 }
736 let opts = Sort {
737 by: Some(b"nosort"),
738 ..Sort::default()
739 };
740 assert_eq!(db.sort_store(b"s", b"out", &opts).unwrap(), 3);
741 let got: Vec<String> = db
742 .at(b"out")
743 .lrange(b"out", 0, -1)
744 .unwrap()
745 .map(text)
746 .collect();
747 assert_eq!(got, ["a", "b", "c"]);
748 }
749
750 #[test]
751 fn a_sorted_set_comes_out_in_score_order_when_nothing_says_otherwise() {
752 let mut db = Db::new();
753 db.at(b"z")
754 .zadd(
755 b"z",
756 [(3.0, &b"c"[..]), (1.0, &b"a"[..]), (2.0, &b"b"[..])].into_iter(),
757 ZAdd::default(),
758 )
759 .unwrap();
760 let opts = Sort {
761 by: Some(b"nosort"),
762 ..Sort::default()
763 };
764 assert_eq!(flat(db.sort(b"z", &opts).unwrap()), ["a", "b", "c"]);
765 }
766
767 #[test]
768 fn storing_an_empty_result_removes_the_destination() {
769 let mut db = Db::new();
770 list(&mut db, b"out", &["stale"]);
771 assert_eq!(
772 db.sort_store(b"missing", b"out", &Sort::default()).unwrap(),
773 0
774 );
775 assert!(!db.at(b"out").exists(b"out"));
776 }
777
778 #[test]
779 fn a_stored_get_that_missed_is_an_empty_string() {
780 let mut db = Db::new();
781 list(&mut db, b"l", &["1"]);
782 let get: [&[u8]; 1] = [b"d_*"];
783 let opts = Sort {
784 get: &get,
785 ..Sort::default()
786 };
787 assert_eq!(db.sort_store(b"l", b"out", &opts).unwrap(), 1);
788 assert_eq!(db.at(b"out").llen(b"out").unwrap(), 1);
789 }
790
791 #[test]
792 fn a_missing_key_is_empty_and_a_wrong_type_is_an_error() {
793 let mut db = Db::new();
794 assert!(db.sort(b"nosuchkey", &Sort::default()).unwrap().is_empty());
795 db.at(b"str").set(b"str", b"x", SetOptions::PLAIN).unwrap();
796 assert_eq!(
797 db.sort(b"str", &Sort::default()).unwrap_err().code(),
798 Code::WrongType
799 );
800 }
801
802 #[test]
803 fn sorting_into_the_key_being_sorted_works() {
804 let mut db = Db::new();
805 list(&mut db, b"l", &["3", "1", "2"]);
806 assert_eq!(db.sort_store(b"l", b"l", &Sort::default()).unwrap(), 3);
807 let got: Vec<String> = db.at(b"l").lrange(b"l", 0, -1).unwrap().map(text).collect();
808 assert_eq!(got, ["1", "2", "3"]);
809 }
810}