use super::funejson::Value; use std::cmp::Ordering; // The type ranks that give mixed values a total order. Absent sorts last in // both directions, so it is ranked above every present value and then excluded // from the direction flip below. const RANK_BOOL: u8 = 0; const RANK_NUMBER: u8 = 1; const RANK_STRING: u8 = 2; const RANK_ABSENT: u8 = 3; /// # Panics /// Panics if the value at the key is a list or a map. fn rank_of(value: &Value, key: &str) -> u8 { match value { // `Value::get` returns Null for a missing field, and a document that // nulls a field means the same thing, so both rank as absent. Value::Null => RANK_ABSENT, Value::Bool(_) => RANK_BOOL, Value::Int(_) | Value::Float(_) => RANK_NUMBER, Value::Str(_) => RANK_STRING, _ => panic!("cannot sort by the list or map at \"{}\"", key), } } fn compare_scalars(a: &Value, b: &Value) -> Ordering { match (a, b) { // Integers compare as integers: going through f64 would collapse // values above 2^53 that are genuinely different. (Value::Int(x), Value::Int(y)) => x.cmp(y), // Rust compares &str by UTF-8 bytes, which is Unicode code point order, // the same order Python uses and the one TypeScript spells out by hand. (Value::Str(x), Value::Str(y)) => x.cmp(y), (Value::Bool(x), Value::Bool(y)) => x.cmp(y), _ => a.as_f64().partial_cmp(&b.as_f64()).unwrap_or(Ordering::Equal), } } /// Stably sort `records` by `key`, ascending or descending. /// /// Ties keep their input order in both directions, which is what lets a user /// sort by one column and then another and get the multi-column sort they /// expect rather than a reshuffle. `sort_by` is a stable sort, and the /// comparator returns Equal for ties rather than falling back to position. /// /// Records are `Value` because they are open JSON maps; a struct would be a /// lie about data whose shape differs from one record to the next. /// /// # Panics /// Panics on an unknown direction, an empty key, or a list or map at the key. pub fn sort_by(records: &[Value], key: &str, direction: &str) -> Vec { if key.is_empty() { panic!("sort_by needs a non-empty key name"); } if direction != "asc" && direction != "desc" { panic!("direction must be \"asc\" or \"desc\", received \"{}\"", direction); } // Rank every record up front. Panicking mid-comparison would make the // failure depend on which comparisons the sort happened to perform. let ranks: Vec = records.iter().map(|r| rank_of(r.get(key), key)).collect(); let descending = direction == "desc"; let mut order: Vec = (0..records.len()).collect(); order.sort_by(|&i, &j| { let (ri, rj) = (ranks[i], ranks[j]); // Absent values sink to the bottom whichever way the sort runs: nobody // wants the rows they know nothing about at the top of a descending table. if ri == RANK_ABSENT || rj == RANK_ABSENT { return match (ri, rj) { (RANK_ABSENT, RANK_ABSENT) => Ordering::Equal, (RANK_ABSENT, _) => Ordering::Greater, _ => Ordering::Less, }; } let base = if ri != rj { ri.cmp(&rj) } else { compare_scalars(records[i].get(key), records[j].get(key)) }; if descending { base.reverse() } else { base } }); order.into_iter().map(|i| records[i].clone()).collect() } pub fn fune_vector(args: &[Value]) -> Value { Value::Arr(sort_by(args[0].as_arr(), args[1].as_str(), args[2].as_str())) }