Skip to main content

nodedb_query/
json_expr.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Expression evaluation against a JSON row.
4//!
5//! Scan handlers materialize rows as `serde_json::Value` before applying
6//! ORDER BY and window functions. Both evaluate arbitrary expressions per row
7//! and both must surface an evaluation failure — a zero divisor in
8//! `ORDER BY 100 / weight` fails the statement with SQLSTATE `22012` rather
9//! than sorting the row under NULL.
10
11use crate::expr::{EvalError, SqlExpr};
12use crate::json_ops::compare_json_numbers;
13
14/// Evaluate `expr` against a JSON row.
15///
16/// Column and literal references short-circuit; anything else goes through the
17/// shared row evaluator, so arithmetic, function calls, and `CASE` all behave
18/// exactly as they do in a projection or WHERE clause.
19pub fn eval_expr_on_json(
20    expr: &SqlExpr,
21    doc: &serde_json::Value,
22) -> Result<serde_json::Value, EvalError> {
23    match expr {
24        SqlExpr::Column(name) => Ok(doc.get(name).cloned().unwrap_or(serde_json::Value::Null)),
25        SqlExpr::Literal(v) => Ok(serde_json::Value::from(v.clone())),
26        other => {
27            let row = nodedb_types::Value::from(doc.clone());
28            Ok(serde_json::Value::from(other.eval(&row)?))
29        }
30    }
31}
32
33/// Total order over JSON values for sorting.
34///
35/// NULLs sort first; numbers compare exactly (integers past `2^53` and
36/// integer/float pairs without rounding); strings lexicographically; mixed
37/// or composite values fall back to their rendered form so the ordering
38/// stays deterministic and total.
39pub fn compare_json(a: &serde_json::Value, b: &serde_json::Value) -> std::cmp::Ordering {
40    use serde_json::Value;
41    match (a, b) {
42        (Value::Null, Value::Null) => std::cmp::Ordering::Equal,
43        (Value::Null, _) => std::cmp::Ordering::Less,
44        (_, Value::Null) => std::cmp::Ordering::Greater,
45        (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
46        (Value::Number(x), Value::Number(y)) => match compare_json_numbers(x, y) {
47            Some(order) => order,
48            None => a.to_string().cmp(&b.to_string()),
49        },
50        (Value::String(x), Value::String(y)) => x.cmp(y),
51        _ => a.to_string().cmp(&b.to_string()),
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use serde_json::json;
59    use std::cmp::Ordering;
60
61    fn sorted(mut values: Vec<serde_json::Value>) -> Vec<serde_json::Value> {
62        values.sort_by(compare_json);
63        values
64    }
65
66    /// `2^53 + 1` and `2^53` collapse to one `f64`. Integers order exactly.
67    #[test]
68    fn integers_past_two_pow_53_order_exactly() {
69        let above = json!(9_007_199_254_740_993_i64);
70        let at = json!(9_007_199_254_740_992_i64);
71        assert_eq!(compare_json(&above, &at), Ordering::Greater);
72        assert_eq!(compare_json(&at, &above), Ordering::Less);
73        assert_eq!(sorted(vec![above.clone(), at.clone()]), vec![at, above]);
74    }
75
76    #[test]
77    fn nanosecond_timestamps_order_exactly() {
78        let t0 = json!(1_700_000_000_000_000_001_i64);
79        let t1 = json!(1_700_000_000_000_000_002_i64);
80        let t2 = json!(1_700_000_000_000_000_003_i64);
81        assert_eq!(
82            sorted(vec![t2.clone(), t0.clone(), t1.clone()]),
83            vec![t0, t1, t2]
84        );
85    }
86
87    #[test]
88    fn u64_against_i64_orders_exactly() {
89        let u_max = json!(u64::MAX);
90        let i_max = json!(i64::MAX);
91        let i_min = json!(i64::MIN);
92        assert_eq!(compare_json(&u_max, &i_max), Ordering::Greater);
93        assert_eq!(compare_json(&i_max, &u_max), Ordering::Less);
94        assert_eq!(
95            sorted(vec![u_max.clone(), i_min.clone(), i_max.clone()]),
96            vec![i_min, i_max, u_max]
97        );
98    }
99
100    #[test]
101    fn integer_against_float_near_two_pow_53_orders_without_rounding() {
102        let above = json!(9_007_199_254_740_993_i64);
103        let float = json!(9_007_199_254_740_992.0_f64);
104        assert_eq!(compare_json(&above, &float), Ordering::Greater);
105        assert_eq!(compare_json(&float, &above), Ordering::Less);
106        assert_eq!(
107            compare_json(&json!(9_007_199_254_740_992_i64), &float),
108            Ordering::Equal
109        );
110    }
111
112    #[test]
113    fn small_values_keep_their_order() {
114        assert_eq!(
115            sorted(vec![json!(2), json!(null), json!(1.5), json!(-1), json!(1)]),
116            vec![json!(null), json!(-1), json!(1), json!(1.5), json!(2)]
117        );
118        assert_eq!(compare_json(&json!(3), &json!(3.0)), Ordering::Equal);
119        assert_eq!(compare_json(&json!("a"), &json!("b")), Ordering::Less);
120        assert_eq!(compare_json(&json!(false), &json!(true)), Ordering::Less);
121    }
122
123    /// A NaN has no JSON number form: it reaches the sort as NULL.
124    #[test]
125    fn nan_sorts_as_null() {
126        let nan = json!(f64::NAN);
127        assert!(nan.is_null());
128        assert_eq!(compare_json(&nan, &json!(1)), Ordering::Less);
129        assert_eq!(compare_json(&nan, &json!(null)), Ordering::Equal);
130    }
131}