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