Skip to main content

nu_command/
sort_utils.rs

1use nu_engine::ClosureEval;
2use nu_protocol::shell_error::generic::GenericError;
3use nu_protocol::{PipelineData, Record, ShellError, Span, Value, ast::CellPath};
4use nu_utils::IgnoreCaseExt;
5use std::cmp::Ordering;
6
7/// A specification of sort order for `sort_by`.
8///
9/// A closure comparator allows the user to return custom ordering to sort by.
10/// A cell path comparator uses the value referred to by the cell path as the sorting key.
11pub enum Comparator {
12    KeyClosure(ClosureEval),
13    CustomClosure(ClosureEval),
14    CellPath(CellPath),
15}
16
17/// Sort a slice of `Value`s.
18///
19/// Sort has the following invariants, in order of precedence:
20/// - Null values (Nothing type) are always sorted to the end.
21/// - For natural sort, numeric values (numeric strings, ints, and floats) appear first, sorted by numeric value
22/// - Values appear by order of `Value`'s `PartialOrd`.
23/// - Sorting for values with equal ordering is stable.
24///
25/// Generally, values of different types are ordered by order of appearance in the `Value` enum.
26/// However, this is not always the case. For example, ints and floats will be grouped together since
27/// `Value`'s `PartialOrd` defines a non-decreasing ordering between non-decreasing integers and floats.
28pub fn sort(vec: &mut [Value], insensitive: bool, natural: bool) -> Result<(), ShellError> {
29    // allow the comparator function to indicate error
30    // by mutating this option captured by the closure,
31    // since sort_by closure must be infallible
32    let mut compare_err: Option<ShellError> = None;
33
34    vec.sort_by(|a, b| {
35        // we've already hit an error, bail out now
36        if compare_err.is_some() {
37            return Ordering::Equal;
38        }
39
40        compare_values(a, b, insensitive, natural).unwrap_or_else(|err| {
41            compare_err.get_or_insert(err);
42            Ordering::Equal
43        })
44    });
45
46    if let Some(err) = compare_err {
47        Err(err)
48    } else {
49        Ok(())
50    }
51}
52
53/// Sort a slice of `Value`s by criteria specified by one or multiple `Comparator`s.
54pub fn sort_by(
55    vec: &mut [Value],
56    mut comparators: Vec<Comparator>,
57    head_span: Span,
58    insensitive: bool,
59    natural: bool,
60) -> Result<(), ShellError> {
61    if comparators.is_empty() {
62        return Err(ShellError::Generic(GenericError::new(
63            "expected name",
64            "requires a cell path or closure to sort data",
65            head_span,
66        )));
67    }
68
69    // allow the comparator function to indicate error
70    // by mutating this option captured by the closure,
71    // since sort_by closure must be infallible
72    let mut compare_err: Option<ShellError> = None;
73
74    vec.sort_by(|a, b| {
75        compare_by(
76            a,
77            b,
78            &mut comparators,
79            head_span,
80            insensitive,
81            natural,
82            &mut compare_err,
83        )
84    });
85
86    if let Some(err) = compare_err {
87        Err(err)
88    } else {
89        Ok(())
90    }
91}
92
93/// Sort a record's key-value pairs.
94///
95/// Can sort by key or by value.
96pub fn sort_record(
97    record: Record,
98    sort_by_value: bool,
99    reverse: bool,
100    insensitive: bool,
101    natural: bool,
102) -> Result<Record, ShellError> {
103    let mut input_pairs: Vec<(String, Value)> = record.into_iter().collect();
104
105    // allow the comparator function to indicate error
106    // by mutating this option captured by the closure,
107    // since sort_by closure must be infallible
108    let mut compare_err: Option<ShellError> = None;
109
110    if sort_by_value {
111        input_pairs.sort_by(|a, b| {
112            // we've already hit an error, bail out now
113            if compare_err.is_some() {
114                return Ordering::Equal;
115            }
116
117            compare_values(&a.1, &b.1, insensitive, natural).unwrap_or_else(|err| {
118                compare_err.get_or_insert(err);
119                Ordering::Equal
120            })
121        });
122    } else {
123        input_pairs.sort_by(|a, b| compare_strings(&a.0, &b.0, insensitive, natural));
124    };
125
126    if let Some(err) = compare_err {
127        return Err(err);
128    }
129
130    if reverse {
131        input_pairs.reverse()
132    }
133
134    Ok(input_pairs.into_iter().collect())
135}
136
137pub fn compare_by(
138    left: &Value,
139    right: &Value,
140    comparators: &mut [Comparator],
141    span: Span,
142    insensitive: bool,
143    natural: bool,
144    error: &mut Option<ShellError>,
145) -> Ordering {
146    // we've already hit an error, bail out now
147    if error.is_some() {
148        return Ordering::Equal;
149    }
150    for cmp in comparators.iter_mut() {
151        let result = match cmp {
152            Comparator::CellPath(cell_path) => {
153                compare_cell_path(left, right, cell_path, insensitive, natural)
154            }
155            Comparator::KeyClosure(closure) => {
156                compare_key_closure(left, right, closure, span, insensitive, natural)
157            }
158            Comparator::CustomClosure(closure) => {
159                compare_custom_closure(left, right, closure, span)
160            }
161        };
162        match result {
163            Ok(Ordering::Equal) => {}
164            Ok(ordering) => return ordering,
165            Err(err) => {
166                // don't bother continuing through the remaining comparators as we've hit an error
167                // don't overwrite if there's an existing error
168                error.get_or_insert(err);
169                return Ordering::Equal;
170            }
171        }
172    }
173    Ordering::Equal
174}
175
176/// Determines whether a value should be sorted as a string
177///
178/// If we're natural sorting, we want to sort strings, integers, and floats alphanumerically, so we should string sort.
179/// Otherwise, we only want to string sort if both values are strings or globs (to enable case insensitive comparison)
180fn should_sort_as_string(val: &Value, natural: bool) -> bool {
181    matches!(
182        (val, natural),
183        (&Value::String { .. }, _)
184            | (&Value::Glob { .. }, _)
185            | (&Value::Int { .. }, true)
186            | (&Value::Float { .. }, true)
187    )
188}
189
190/// Simple wrapper around `should_sort_as_string` to determine if two values
191/// should be compared as strings.
192fn should_string_compare(left: &Value, right: &Value, natural: bool) -> bool {
193    should_sort_as_string(left, natural) && should_sort_as_string(right, natural)
194}
195
196pub fn compare_values(
197    left: &Value,
198    right: &Value,
199    insensitive: bool,
200    natural: bool,
201) -> Result<Ordering, ShellError> {
202    if should_string_compare(left, right, natural) {
203        Ok(compare_strings(
204            &left.coerce_str()?,
205            &right.coerce_str()?,
206            insensitive,
207            natural,
208        ))
209    } else {
210        match (left, right) {
211            (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
212                Ok(lhs.total_cmp(rhs))
213            }
214            (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
215                Ok((*lhs as f64).total_cmp(rhs))
216            }
217            (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
218                Ok(lhs.total_cmp(&(*rhs as f64)))
219            }
220            _ => Ok(left.partial_cmp(right).unwrap_or(Ordering::Equal)),
221        }
222    }
223}
224
225pub fn compare_strings(left: &str, right: &str, insensitive: bool, natural: bool) -> Ordering {
226    fn compare_inner<T>(left: T, right: T, natural: bool) -> Ordering
227    where
228        T: AsRef<str> + Ord,
229    {
230        if natural {
231            alphanumeric_sort::compare_str(left, right)
232        } else {
233            left.cmp(&right)
234        }
235    }
236
237    // only allocate a String if necessary for case folding
238    if insensitive {
239        compare_inner(left.to_folded_case(), right.to_folded_case(), natural)
240    } else {
241        compare_inner(left, right, natural)
242    }
243}
244
245pub fn compare_cell_path(
246    left: &Value,
247    right: &Value,
248    cell_path: &CellPath,
249    insensitive: bool,
250    natural: bool,
251) -> Result<Ordering, ShellError> {
252    let left = left.follow_cell_path(&cell_path.members)?;
253    let right = right.follow_cell_path(&cell_path.members)?;
254    compare_values(&left, &right, insensitive, natural)
255}
256
257pub fn compare_key_closure(
258    left: &Value,
259    right: &Value,
260    closure_eval: &mut ClosureEval,
261    span: Span,
262    insensitive: bool,
263    natural: bool,
264) -> Result<Ordering, ShellError> {
265    let left_key = closure_eval
266        .run_with_value(left.clone())?
267        .into_value(span)?;
268    let right_key = closure_eval
269        .run_with_value(right.clone())?
270        .into_value(span)?;
271    compare_values(&left_key, &right_key, insensitive, natural)
272}
273
274pub fn compare_custom_closure(
275    left: &Value,
276    right: &Value,
277    closure_eval: &mut ClosureEval,
278    span: Span,
279) -> Result<Ordering, ShellError> {
280    closure_eval
281        .add_arg(left.clone())
282        .add_arg(right.clone())
283        .run_with_input(PipelineData::value(
284            Value::list(vec![left.clone(), right.clone()], span),
285            None,
286        ))
287        .and_then(|data| data.into_value(span))
288        .map(|val| {
289            if val.is_true() {
290                Ordering::Less
291            } else {
292                Ordering::Greater
293            }
294        })
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use nu_protocol::{ast::PathMember, casing::Casing, record};
301
302    #[test]
303    fn test_sort_floats_strict() {
304        // Values that are equal under epsilon tolerance but different strictly
305        let a = Value::test_float(7.0);
306        let b = Value::test_float(7.0 + 0.6 * f64::EPSILON * 7.0);
307        let c = Value::test_float(7.0 + 1.2 * f64::EPSILON * 7.0);
308
309        // Under epsilon tolerance:
310        // a == b (diff = 0.6 * EPS * 7.0 < prec = 1.0 * EPS * 7.0)
311        // b == c (diff = 0.6 * EPS * 7.0 < prec = 1.0... * EPS * 7.0)
312        // a < c  (diff = 1.2 * EPS * 7.0 > prec = 1.0 * EPS * 7.0)
313        // This non-transitivity causes panics in sort.
314
315        let mut list = vec![c.clone(), b.clone(), a.clone()];
316
317        // This should not panic now and should sort strictly
318        assert!(sort(&mut list, false, false).is_ok());
319
320        assert_eq!(list, vec![a, b, c]);
321    }
322
323    #[test]
324    fn test_sort_basic() {
325        let mut list = vec![
326            Value::test_string("foo"),
327            Value::test_int(2),
328            Value::test_int(3),
329            Value::test_string("bar"),
330            Value::test_int(1),
331            Value::test_string("baz"),
332        ];
333
334        assert!(sort(&mut list, false, false).is_ok());
335        assert_eq!(
336            list,
337            vec![
338                Value::test_int(1),
339                Value::test_int(2),
340                Value::test_int(3),
341                Value::test_string("bar"),
342                Value::test_string("baz"),
343                Value::test_string("foo")
344            ]
345        );
346    }
347
348    #[test]
349    fn test_sort_nothing() {
350        // Nothing values should always be sorted to the end of any list
351        let mut list = vec![
352            Value::test_int(1),
353            Value::test_nothing(),
354            Value::test_int(2),
355            Value::test_string("foo"),
356            Value::test_nothing(),
357            Value::test_string("bar"),
358        ];
359
360        assert!(sort(&mut list, false, false).is_ok());
361        assert_eq!(
362            list,
363            vec![
364                Value::test_int(1),
365                Value::test_int(2),
366                Value::test_string("bar"),
367                Value::test_string("foo"),
368                Value::test_nothing(),
369                Value::test_nothing()
370            ]
371        );
372
373        // Ensure that nothing values are sorted after *all* types,
374        // even types which may follow `Nothing` in the PartialOrd order
375
376        // unstable_name_collision
377        // can be switched to std intersperse when stabilized
378        let mut values: Vec<Value> =
379            itertools::intersperse(Value::test_values(), Value::test_nothing()).collect();
380
381        let nulls = values
382            .iter()
383            .filter(|item| item == &&Value::test_nothing())
384            .count();
385
386        assert!(sort(&mut values, false, false).is_ok());
387
388        // check if the last `nulls` values of the sorted list are indeed null
389        assert_eq!(&values[(nulls - 1)..], vec![Value::test_nothing(); nulls])
390    }
391
392    #[test]
393    fn test_sort_natural_basic() {
394        let mut list = vec![
395            Value::test_string("foo99"),
396            Value::test_string("foo9"),
397            Value::test_string("foo1"),
398            Value::test_string("foo100"),
399            Value::test_string("foo10"),
400            Value::test_string("1"),
401            Value::test_string("10"),
402            Value::test_string("100"),
403            Value::test_string("9"),
404            Value::test_string("99"),
405        ];
406
407        assert!(sort(&mut list, false, false).is_ok());
408        assert_eq!(
409            list,
410            vec![
411                Value::test_string("1"),
412                Value::test_string("10"),
413                Value::test_string("100"),
414                Value::test_string("9"),
415                Value::test_string("99"),
416                Value::test_string("foo1"),
417                Value::test_string("foo10"),
418                Value::test_string("foo100"),
419                Value::test_string("foo9"),
420                Value::test_string("foo99"),
421            ]
422        );
423
424        assert!(sort(&mut list, false, true).is_ok());
425        assert_eq!(
426            list,
427            vec![
428                Value::test_string("1"),
429                Value::test_string("9"),
430                Value::test_string("10"),
431                Value::test_string("99"),
432                Value::test_string("100"),
433                Value::test_string("foo1"),
434                Value::test_string("foo9"),
435                Value::test_string("foo10"),
436                Value::test_string("foo99"),
437                Value::test_string("foo100"),
438            ]
439        );
440    }
441
442    #[test]
443    fn test_sort_natural_mixed_types() {
444        let mut list = vec![
445            Value::test_string("1"),
446            Value::test_int(99),
447            Value::test_int(1),
448            Value::test_float(1000.0),
449            Value::test_int(9),
450            Value::test_string("9"),
451            Value::test_int(100),
452            Value::test_string("99"),
453            Value::test_float(2.0),
454            Value::test_string("100"),
455            Value::test_int(10),
456            Value::test_string("10"),
457        ];
458
459        assert!(sort(&mut list, false, false).is_ok());
460        assert_eq!(
461            list,
462            vec![
463                Value::test_int(1),
464                Value::test_float(2.0),
465                Value::test_int(9),
466                Value::test_int(10),
467                Value::test_int(99),
468                Value::test_int(100),
469                Value::test_float(1000.0),
470                Value::test_string("1"),
471                Value::test_string("10"),
472                Value::test_string("100"),
473                Value::test_string("9"),
474                Value::test_string("99")
475            ]
476        );
477
478        assert!(sort(&mut list, false, true).is_ok());
479        assert_eq!(
480            list,
481            vec![
482                Value::test_int(1),
483                Value::test_string("1"),
484                Value::test_float(2.0),
485                Value::test_int(9),
486                Value::test_string("9"),
487                Value::test_int(10),
488                Value::test_string("10"),
489                Value::test_int(99),
490                Value::test_string("99"),
491                Value::test_int(100),
492                Value::test_string("100"),
493                Value::test_float(1000.0),
494            ]
495        );
496    }
497
498    #[test]
499    fn test_sort_natural_no_numeric_values() {
500        // If list contains no numeric strings, it should be sorted the
501        // same with or without natural sorting
502        let mut normal = vec![
503            Value::test_string("golf"),
504            Value::test_bool(false),
505            Value::test_string("alfa"),
506            Value::test_string("echo"),
507            Value::test_int(7),
508            Value::test_int(10),
509            Value::test_bool(true),
510            Value::test_string("uniform"),
511            Value::test_int(3),
512            Value::test_string("tango"),
513        ];
514        let mut natural = normal.clone();
515
516        assert!(sort(&mut normal, false, false).is_ok());
517        assert!(sort(&mut natural, false, true).is_ok());
518        assert_eq!(normal, natural);
519    }
520
521    #[test]
522    fn test_sort_natural_type_order() {
523        // This test is to prevent regression to a previous natural sort behavior
524        // where values of different types would be intermixed.
525        // Only numeric values (ints, floats, and numeric strings) should be intermixed
526        //
527        // This list would previously be incorrectly sorted like this:
528        // ╭────┬─────────╮
529        // │  0 │       1 │
530        // │  1 │ golf    │
531        // │  2 │ false   │
532        // │  3 │       7 │
533        // │  4 │      10 │
534        // │  5 │ alfa    │
535        // │  6 │ true    │
536        // │  7 │ uniform │
537        // │  8 │ true    │
538        // │  9 │       3 │
539        // │ 10 │ false   │
540        // │ 11 │ tango   │
541        // ╰────┴─────────╯
542
543        let mut list = vec![
544            Value::test_string("golf"),
545            Value::test_int(1),
546            Value::test_bool(false),
547            Value::test_string("alfa"),
548            Value::test_int(7),
549            Value::test_int(10),
550            Value::test_bool(true),
551            Value::test_string("uniform"),
552            Value::test_bool(true),
553            Value::test_int(3),
554            Value::test_bool(false),
555            Value::test_string("tango"),
556        ];
557
558        assert!(sort(&mut list, false, true).is_ok());
559        assert_eq!(
560            list,
561            vec![
562                Value::test_bool(false),
563                Value::test_bool(false),
564                Value::test_bool(true),
565                Value::test_bool(true),
566                Value::test_int(1),
567                Value::test_int(3),
568                Value::test_int(7),
569                Value::test_int(10),
570                Value::test_string("alfa"),
571                Value::test_string("golf"),
572                Value::test_string("tango"),
573                Value::test_string("uniform")
574            ]
575        );
576
577        // Only ints, floats, and numeric strings should be intermixed
578        // While binary primitives and datetimes can be coerced into strings, it doesn't make sense to sort them with numbers
579        // Binary primitives can hold multiple values, not just one, so shouldn't be compared to single values
580        // Datetimes don't have a single obvious numeric representation, and if we chose one it would be ambiguous to the user
581
582        let year_three = chrono::NaiveDate::from_ymd_opt(3, 1, 1)
583            .unwrap()
584            .and_hms_opt(0, 0, 0)
585            .unwrap()
586            .and_utc();
587
588        let mut list = vec![
589            Value::test_int(10),
590            Value::test_float(6.0),
591            Value::test_int(1),
592            Value::test_binary([3]),
593            Value::test_string("2"),
594            Value::test_date(year_three.into()),
595            Value::test_int(4),
596            Value::test_binary([52]),
597            Value::test_float(9.0),
598            Value::test_string("5"),
599            Value::test_date(chrono::DateTime::UNIX_EPOCH.into()),
600            Value::test_int(7),
601            Value::test_string("8"),
602            Value::test_float(3.0),
603            Value::test_string("foobar"),
604        ];
605        assert!(sort(&mut list, false, true).is_ok());
606        assert_eq!(
607            list,
608            vec![
609                Value::test_int(1),
610                Value::test_string("2"),
611                Value::test_float(3.0),
612                Value::test_int(4),
613                Value::test_string("5"),
614                Value::test_float(6.0),
615                Value::test_int(7),
616                Value::test_string("8"),
617                Value::test_float(9.0),
618                Value::test_int(10),
619                Value::test_string("foobar"),
620                // the ordering of date and binary here may change if the PartialOrd order is changed,
621                // but they should not be intermixed with the above
622                Value::test_date(year_three.into()),
623                Value::test_date(chrono::DateTime::UNIX_EPOCH.into()),
624                Value::test_binary([3]),
625                Value::test_binary([52]),
626            ]
627        );
628    }
629
630    #[test]
631    fn test_sort_insensitive() {
632        // Test permutations between insensitive and natural
633        // Ensure that strings with equal insensitive orderings
634        // are sorted stably. (FOO then foo, bar then BAR)
635        let source = vec![
636            Value::test_string("FOO"),
637            Value::test_string("foo"),
638            Value::test_int(100),
639            Value::test_string("9"),
640            Value::test_string("bar"),
641            Value::test_int(10),
642            Value::test_string("baz"),
643            Value::test_string("BAR"),
644        ];
645        let mut list;
646
647        // sensitive + non-natural
648        list = source.clone();
649        assert!(sort(&mut list, false, false).is_ok());
650        assert_eq!(
651            list,
652            vec![
653                Value::test_int(10),
654                Value::test_int(100),
655                Value::test_string("9"),
656                Value::test_string("BAR"),
657                Value::test_string("FOO"),
658                Value::test_string("bar"),
659                Value::test_string("baz"),
660                Value::test_string("foo"),
661            ]
662        );
663
664        // sensitive + natural
665        list = source.clone();
666        assert!(sort(&mut list, false, true).is_ok());
667        assert_eq!(
668            list,
669            vec![
670                Value::test_string("9"),
671                Value::test_int(10),
672                Value::test_int(100),
673                Value::test_string("BAR"),
674                Value::test_string("FOO"),
675                Value::test_string("bar"),
676                Value::test_string("baz"),
677                Value::test_string("foo"),
678            ]
679        );
680
681        // insensitive + non-natural
682        list = source.clone();
683        assert!(sort(&mut list, true, false).is_ok());
684        assert_eq!(
685            list,
686            vec![
687                Value::test_int(10),
688                Value::test_int(100),
689                Value::test_string("9"),
690                Value::test_string("bar"),
691                Value::test_string("BAR"),
692                Value::test_string("baz"),
693                Value::test_string("FOO"),
694                Value::test_string("foo"),
695            ]
696        );
697
698        // insensitive + natural
699        list = source.clone();
700        assert!(sort(&mut list, true, true).is_ok());
701        assert_eq!(
702            list,
703            vec![
704                Value::test_string("9"),
705                Value::test_int(10),
706                Value::test_int(100),
707                Value::test_string("bar"),
708                Value::test_string("BAR"),
709                Value::test_string("baz"),
710                Value::test_string("FOO"),
711                Value::test_string("foo"),
712            ]
713        );
714    }
715
716    // Helper function to assert that two records are equal
717    // with their key-value pairs in the same order
718    fn assert_record_eq(a: Record, b: Record) {
719        assert_eq!(
720            a.into_iter().collect::<Vec<_>>(),
721            b.into_iter().collect::<Vec<_>>(),
722        )
723    }
724
725    #[test]
726    fn test_sort_record_keys() {
727        // Basic record sort test
728        let record = record! {
729            "golf" => Value::test_string("bar"),
730            "alfa" => Value::test_string("foo"),
731            "echo" => Value::test_int(123),
732        };
733
734        let sorted = sort_record(record, false, false, false, false).unwrap();
735        assert_record_eq(
736            sorted,
737            record! {
738                "alfa" => Value::test_string("foo"),
739                "echo" => Value::test_int(123),
740                "golf" => Value::test_string("bar"),
741            },
742        );
743    }
744
745    #[test]
746    fn test_sort_record_values() {
747        // This test is to prevent a regression where integers and strings would be
748        // intermixed non-naturally when sorting a record by value without the natural flag:
749        //
750        // This record would previously be incorrectly sorted like this:
751        // ╭─────────┬─────╮
752        // │ alfa    │ 1   │
753        // │ charlie │ 1   │
754        // │ india   │ 10  │
755        // │ juliett │ 10  │
756        // │ foxtrot │ 100 │
757        // │ hotel   │ 100 │
758        // │ delta   │ 9   │
759        // │ echo    │ 9   │
760        // │ bravo   │ 99  │
761        // │ golf    │ 99  │
762        // ╰─────────┴─────╯
763
764        let record = record! {
765            "alfa" => Value::test_string("1"),
766            "bravo" => Value::test_int(99),
767            "charlie" => Value::test_int(1),
768            "delta" => Value::test_int(9),
769            "echo" => Value::test_string("9"),
770            "foxtrot" => Value::test_int(100),
771            "golf" => Value::test_string("99"),
772            "hotel" => Value::test_string("100"),
773            "india" => Value::test_int(10),
774            "juliett" => Value::test_string("10"),
775        };
776
777        // non-natural sort
778        let sorted = sort_record(record.clone(), true, false, false, false).unwrap();
779        assert_record_eq(
780            sorted,
781            record! {
782                "charlie" => Value::test_int(1),
783                "delta" => Value::test_int(9),
784                "india" => Value::test_int(10),
785                "bravo" => Value::test_int(99),
786                "foxtrot" => Value::test_int(100),
787                "alfa" => Value::test_string("1"),
788                "juliett" => Value::test_string("10"),
789                "hotel" => Value::test_string("100"),
790                "echo" => Value::test_string("9"),
791                "golf" => Value::test_string("99"),
792            },
793        );
794
795        // natural sort
796        let sorted = sort_record(record.clone(), true, false, false, true).unwrap();
797        assert_record_eq(
798            sorted,
799            record! {
800                "alfa" => Value::test_string("1"),
801                "charlie" => Value::test_int(1),
802                "delta" => Value::test_int(9),
803                "echo" => Value::test_string("9"),
804                "india" => Value::test_int(10),
805                "juliett" => Value::test_string("10"),
806                "bravo" => Value::test_int(99),
807                "golf" => Value::test_string("99"),
808                "foxtrot" => Value::test_int(100),
809                "hotel" => Value::test_string("100"),
810            },
811        );
812    }
813
814    #[test]
815    fn test_sort_equivalent() {
816        // Ensure that sort, sort_by, and record sort have equivalent sorting logic
817        let phonetic = vec![
818            "alfa", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india",
819            "juliett", "kilo", "lima", "mike", "november", "oscar", "papa", "quebec", "romeo",
820            "sierra", "tango", "uniform", "victor", "whiskey", "xray", "yankee", "zulu",
821        ];
822
823        // filter out errors, since we can't sort_by on those
824        let mut values: Vec<Value> = Value::test_values()
825            .into_iter()
826            .filter(|val| !matches!(val, Value::Error { .. }))
827            .collect();
828
829        // reverse sort test values
830        values.sort_by(|a, b| b.partial_cmp(a).unwrap());
831
832        let mut list = values.clone();
833        let mut table: Vec<Value> = values
834            .clone()
835            .into_iter()
836            .map(|val| Value::test_record(record! { "value" => val }))
837            .collect();
838        let record = Record::from_iter(phonetic.into_iter().map(str::to_string).zip(values));
839
840        let comparator = Comparator::CellPath(CellPath {
841            members: vec![PathMember::String {
842                val: "value".to_string(),
843                span: Span::test_data(),
844                optional: false,
845                casing: Casing::Sensitive,
846            }],
847        });
848
849        assert!(sort(&mut list, false, false).is_ok());
850        assert!(
851            sort_by(
852                &mut table,
853                vec![comparator],
854                Span::test_data(),
855                false,
856                false
857            )
858            .is_ok()
859        );
860
861        let record_sorted = sort_record(record.clone(), true, false, false, false).unwrap();
862        let record_vals: Vec<Value> = record_sorted.into_iter().map(|pair| pair.1).collect();
863
864        let table_vals: Vec<Value> = table
865            .clone()
866            .into_iter()
867            .map(|record| record.into_record().unwrap().remove("value").unwrap())
868            .collect();
869
870        assert_eq!(list, record_vals);
871        assert_eq!(record_vals, table_vals);
872        // list == table_vals by transitive property
873    }
874}