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