use super::funejson::Value; use std::collections::HashSet; /// Largest integer JavaScript can hold exactly; beyond it the three languages disagree. const SAFE_INTEGER: i64 = 9007199254740991; /// The identity of a record, or `None` when it has none. /// /// Rendered exactly as collections.group-by-key renders a group name, so the /// two capabilities agree on what "the same key" means. /// /// # Panics /// Panics on a float, a list or a map, or an integer outside the safe range. fn key_of(value: &Value, key: &str) -> Option { match value { // A record with no key is not a duplicate of anything: two records // that both lack an id are two unknowns, not one thing seen twice. Value::Null => None, Value::Str(s) => Some(s.clone()), Value::Bool(b) => Some((if *b { "true" } else { "false" }).to_string()), Value::Int(i) => { if i.abs() > SAFE_INTEGER { panic!("cannot dedupe by the out-of-range number {} at \"{}\"", i, key); } Some(i.to_string()) } Value::Float(f) => panic!("cannot dedupe by the fractional number {} at \"{}\"", f, key), _ => panic!("cannot dedupe by the list or map at \"{}\"", key), } } /// Remove records that share the value at `key`, keeping the first or the /// last of each set. /// /// Survivors keep their own positions: with "last", the survivor sits where /// the last occurrence was, which is what "latest wins" means in a change feed. /// /// # Panics /// Panics on an empty key, an unknown `keep`, or an ungroupable key value. pub fn dedupe_by_key(records: &[Value], key: &str, keep: &str) -> Vec { if key.is_empty() { panic!("dedupe_by_key needs a non-empty key name"); } if keep != "first" && keep != "last" { panic!("keep must be \"first\" or \"last\", received \"{}\"", keep); } // Every key is rendered up front, so a bad value panics whichever mode runs. let keys: Vec> = records.iter().map(|r| key_of(r.get(key), key)).collect(); let mut seen: HashSet = HashSet::new(); let mut survives = vec![false; records.len()]; let mut order: Vec = (0..records.len()).collect(); // Walking backwards for "last" makes the last occurrence the first one seen. if keep == "last" { order.reverse(); } for i in order { match &keys[i] { None => survives[i] = true, Some(k) => { if seen.insert(k.clone()) { survives[i] = true; } } } } records .iter() .enumerate() .filter(|(i, _)| survives[*i]) .map(|(_, r)| r.clone()) .collect() } pub fn fune_vector(args: &[Value]) -> Value { Value::Arr(dedupe_by_key(args[0].as_arr(), args[1].as_str(), args[2].as_str())) }