use std::cmp::Ordering;
use serde_json::{Map, Value};
use crate::parser::{analyzer::LiteralResolver, ast::{Column, ComparatorOp, Function, Literal, Predicate, ScalarExpr, Truth}};
pub struct Eval;
impl Eval {
pub fn eval_scalar(expr: &ScalarExpr, row: &Map<String, Value>) -> Value {
match expr {
ScalarExpr::Literal(l) => match l {
Literal::Null => Value::Null,
Literal::Bool(b) => Value::Bool(*b),
Literal::Int(i) => Self::json_i(*i),
Literal::Float(f)=> Self::json_f(f.into_inner()),
Literal::String(s)=> Value::String(s.clone()),
},
ScalarExpr::Column(c) => {
let key = match c {
Column::WithCollection { collection, name } => format!("{}.{}", collection, name),
Column::Name { name } => name.clone(), };
row.get(&key).cloned().unwrap_or(Value::Null)
}
ScalarExpr::Function(f) => Self::eval_scalar_function(f, row),
ScalarExpr::WildCard | ScalarExpr::WildCardWithCollection(_) |
ScalarExpr::Parameter | ScalarExpr::Args(_) => Value::Null, }
}
fn eval_scalar_function(f: &Function, row: &Map<String, Value>) -> Value {
let lname = f.name.to_ascii_lowercase();
let args: Vec<Value> = f.args.iter().map(|a| Self::eval_scalar(a, row)).collect();
match (lname.as_str(), args.as_slice()) {
("upper", [Value::String(s)]) => Value::String(s.to_uppercase()),
("lower", [Value::String(s)]) => Value::String(s.to_lowercase()),
("trim", [Value::String(s)]) => Value::String(s.trim().to_string()),
("length", [Value::String(s)]) => Self::json_i(s.chars().count() as i64),
_ => Value::Null, }
}
pub fn eval_predicate3(predicate: &Predicate, row: &Map<String, Value>) -> Truth {
match predicate {
Predicate::And(v) => v.iter().fold(Truth::True, |acc, x| acc.and(Self::eval_predicate3(x, row))),
Predicate::Or(v) => v.iter().fold(Truth::False, |acc, x| acc.or(Self::eval_predicate3(x, row))),
Predicate::Compare { left, op, right } => {
let l = Self::eval_scalar(left, row);
let r = Self::eval_scalar(right, row);
Self::lit_cmp3(&l, *op, &r)
}
Predicate::IsNull { expr, negated } => {
let v = Self::eval_scalar(expr, row);
let t = if v.is_null() { Truth::True } else { Truth::False };
if *negated { t.not() } else { t }
}
Predicate::InList { expr, list, negated } => {
let v = Self::eval_scalar(expr, row);
let mut found = false;
let mut has_null = false;
for e in list {
let ev = Self::eval_scalar(e, row);
if ev.is_null() { has_null = true; continue; }
if Self::value_equal(&v, &ev) { found = true; break; }
}
let t = if found { Truth::True } else if has_null { Truth::Unknown } else { Truth::False };
if *negated { t.not() } else { t }
}
Predicate::Like { expr, pattern, negated } => {
let v = Self::eval_scalar(expr, row);
let p = Self::eval_scalar(pattern, row);
let t = match (v, p) {
(Value::String(s), Value::String(pat)) => LiteralResolver::eval_like(&s, &pat),
(Value::Null, _) | (_, Value::Null) => Truth::Unknown,
_ => Truth::Unknown,
};
if *negated { t.not() } else { t }
}
Predicate::Const3(t) => *t,
}
}
fn lit_cmp3(l: &Value, op: ComparatorOp, r: &Value) -> Truth {
if l.is_null() || r.is_null() { return Truth::Unknown; }
match (l, r) {
(Value::Bool(a), Value::Bool(b)) => match op {
ComparatorOp::Eq => if a == b { Truth::True } else { Truth::False },
ComparatorOp::NotEq => if a != b { Truth::True } else { Truth::False },
_ => Truth::Unknown
},
(Value::Number(a), Value::Number(b)) => {
let x = a.as_f64().unwrap(); let y = b.as_f64().unwrap();
let ord = x.partial_cmp(&y);
match (op, ord) {
(ComparatorOp::Eq, Some(Ordering::Equal)) => Truth::True,
(ComparatorOp::NotEq, Some(Ordering::Equal)) => Truth::False,
(ComparatorOp::NotEq, _) => Truth::True,
(ComparatorOp::Lt, Some(Ordering::Less)) => Truth::True,
(ComparatorOp::LtEq, Some(Ordering::Less|Ordering::Equal)) => Truth::True,
(ComparatorOp::Gt, Some(Ordering::Greater)) => Truth::True,
(ComparatorOp::GtEq, Some(Ordering::Greater|Ordering::Equal)) => Truth::True,
_ => Truth::False,
}
}
(Value::String(a), Value::String(b)) => match op {
ComparatorOp::Eq => if a == b { Truth::True } else { Truth::False },
ComparatorOp::NotEq => if a != b { Truth::True } else { Truth::False },
_ => Truth::Unknown
},
_ => Truth::Unknown,
}
}
fn json_i(i: i64) -> Value { Value::Number(serde_json::Number::from(i)) }
fn json_f(f: f64) -> Value { serde_json::Number::from_f64(f).map(Value::Number).unwrap_or(Value::Null) }
fn value_equal(a: &Value, b: &Value) -> bool {
use serde_json::Value::*;
match (a, b) {
(Null, Null) => true,
(Bool(x), Bool(y)) => x == y,
(Number(x), Number(y)) => x.as_f64() == y.as_f64(),
(String(x), String(y)) => x == y,
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use serde_json::{Map, Value};
use crate::{executor::eval::Eval, parser::ast::{Column, ComparatorOp, Function, Literal, Predicate, ScalarExpr, Truth}};
fn row(pairs: &[(&str, Value)]) -> Map<String, Value> {
let mut m = Map::new();
for (k, v) in pairs {
m.insert((*k).to_string(), v.clone());
}
m
}
fn lit_i(i: i64) -> ScalarExpr { ScalarExpr::Literal(Literal::Int(i)) }
fn lit_f(f: f64) -> ScalarExpr { ScalarExpr::Literal(Literal::Float(ordered_float::NotNan::new(f).unwrap())) }
fn lit_s(s: &str) -> ScalarExpr { ScalarExpr::Literal(Literal::String(s.to_string())) }
fn lit_b(b: bool) -> ScalarExpr { ScalarExpr::Literal(Literal::Bool(b)) }
fn lit_null() -> ScalarExpr { ScalarExpr::Literal(Literal::Null) }
fn col_q(coll: &str, name: &str) -> ScalarExpr {
ScalarExpr::Column(Column::WithCollection{ collection: coll.into(), name: name.into() })
}
fn col_u(name: &str) -> ScalarExpr {
ScalarExpr::Column(Column::Name{ name: name.into() })
}
fn fun(name: &str, args: Vec<ScalarExpr>) -> ScalarExpr {
ScalarExpr::Function(Function{ name: name.into(), args, distinct: false })
}
#[test]
fn scalar_literals_eval_correctly() {
let m = Map::new();
assert_eq!(Eval::eval_scalar(&lit_null(), &m), Value::Null);
assert_eq!(Eval::eval_scalar(&lit_b(true), &m), Value::Bool(true));
assert_eq!(Eval::eval_scalar(&lit_i(42), &m), Value::Number(42.into()));
assert_eq!(Eval::eval_scalar(&lit_f(1.5), &m), Value::Number(serde_json::Number::from_f64(1.5).unwrap()));
assert_eq!(Eval::eval_scalar(&lit_s("x"), &m), Value::String("x".into()));
}
#[test]
fn scalar_column_lookup_qualified_and_unqualified() {
let m = row(&[
("t.id", Value::Number(1.into())),
("name", Value::String("Ana".into())),
]);
assert_eq!(Eval::eval_scalar(&col_q("t","id"), &m), Value::Number(1.into()));
assert_eq!(Eval::eval_scalar(&col_u("name"), &m), Value::String("Ana".into()));
assert_eq!(Eval::eval_scalar(&col_q("t","missing"), &m), Value::Null);
}
#[test]
fn scalar_wildcard_is_never_evaluated_but_returns_null_if_seen() {
let m = Map::new();
assert!(matches!(Eval::eval_scalar(&ScalarExpr::WildCard, &m), Value::Null));
assert!(matches!(Eval::eval_scalar(&ScalarExpr::WildCardWithCollection("t".into()), &m), Value::Null));
}
#[test]
fn scalar_functions_work_upper_lower_trim_length() {
let m = Map::new();
assert_eq!(
Eval::eval_scalar(&fun("upper", vec![lit_s("aBc")]), &m),
Value::String("ABC".into())
);
assert_eq!(
Eval::eval_scalar(&fun("lower", vec![lit_s("aBc")]), &m),
Value::String("abc".into())
);
assert_eq!(
Eval::eval_scalar(&fun("trim", vec![lit_s(" hi ")]), &m),
Value::String("hi".into())
);
assert_eq!(
Eval::eval_scalar(&fun("length", vec![lit_s("hé")]), &m),
Value::Number(2.into()) );
assert_eq!(Eval::eval_scalar(&fun("upper", vec![lit_i(1)]), &m), Value::Null);
}
#[test]
fn compare_numbers_behave() {
let m = Map::new();
let make = |l: ScalarExpr, op: ComparatorOp, r: ScalarExpr| Predicate::Compare{ left: l, op, right: r };
assert!(matches!(Eval::eval_predicate3(&make(lit_i(2), ComparatorOp::Gt, lit_i(1)), &m), Truth::True));
assert!(matches!(Eval::eval_predicate3(&make(lit_f(2.0), ComparatorOp::Lt, lit_f(3.0)), &m), Truth::True));
assert!(matches!(Eval::eval_predicate3(&make(lit_i(2), ComparatorOp::Eq, lit_f(2.0)), &m), Truth::True));
assert!(matches!(Eval::eval_predicate3(&make(lit_f(2.0), ComparatorOp::NotEq, lit_f(2.1)), &m), Truth::True));
}
#[test]
fn compare_strings_and_bools_eq_only_and_null_is_unknown() {
let m = Map::new();
let make = |l, op, r| Predicate::Compare{ left: l, op, right: r };
assert!(matches!(Eval::eval_predicate3(&make(lit_s("a"), ComparatorOp::Eq, lit_s("a")), &m), Truth::True));
assert!(matches!(Eval::eval_predicate3(&make(lit_s("a"), ComparatorOp::NotEq, lit_s("b")), &m), Truth::True));
assert!(matches!(Eval::eval_predicate3(&make(lit_s("a"), ComparatorOp::Lt, lit_s("b")), &m), Truth::Unknown));
assert!(matches!(Eval::eval_predicate3(&make(lit_b(true), ComparatorOp::Eq, lit_b(true)), &m), Truth::True));
assert!(matches!(Eval::eval_predicate3(&make(lit_b(true), ComparatorOp::Gt, lit_b(false)), &m), Truth::Unknown));
assert!(matches!(Eval::eval_predicate3(&make(lit_null(), ComparatorOp::Eq, lit_i(1)), &m), Truth::Unknown));
assert!(matches!(Eval::eval_predicate3(&make(lit_i(1), ComparatorOp::Lt, lit_null()), &m), Truth::Unknown));
}
#[test]
fn is_null_and_is_not_null() {
let m = row(&[
("t.a", Value::Null),
("t.b", Value::String("x".into())),
]);
let is_null_a = Predicate::IsNull { expr: col_q("t","a"), negated: false };
let is_not_null_b = Predicate::IsNull { expr: col_q("t","b"), negated: true };
assert!(matches!(Eval::eval_predicate3(&is_null_a, &m), Truth::True));
assert!(matches!(Eval::eval_predicate3(&is_not_null_b, &m), Truth::True));
}
#[test]
fn in_and_not_in_with_null_semantics() {
let m = Map::new();
let p_true = Predicate::InList {
expr: lit_i(2),
list: vec![lit_i(1), lit_null(), lit_i(2)],
negated: false
};
assert!(matches!(Eval::eval_predicate3(&p_true, &m), Truth::True));
let p_unknown = Predicate::InList {
expr: lit_i(3),
list: vec![lit_i(1), lit_null(), lit_i(2)],
negated: false
};
assert!(matches!(Eval::eval_predicate3(&p_unknown, &m), Truth::Unknown));
let p_notin_true = Predicate::InList {
expr: lit_i(3),
list: vec![lit_i(1), lit_i(2)],
negated: true
};
assert!(matches!(Eval::eval_predicate3(&p_notin_true, &m), Truth::True));
let p_notin_unknown = Predicate::InList {
expr: lit_i(3),
list: vec![lit_i(1), lit_null()],
negated: true
};
assert!(matches!(Eval::eval_predicate3(&p_notin_unknown, &m), Truth::Unknown));
}
#[test]
fn like_and_not_like_case_insensitive_and_escape() {
let m = Map::new();
let like_p = Predicate::Like {
expr: ScalarExpr::Literal(Literal::String("Hello123".into())),
pattern: ScalarExpr::Literal(Literal::String("he%2_".into())),
negated: false
};
assert!(matches!(Eval::eval_predicate3(&like_p, &m), Truth::True));
let like_escape = Predicate::Like {
expr: ScalarExpr::Literal(Literal::String("a_c".into())),
pattern: ScalarExpr::Literal(Literal::String("a\\_c".into())),
negated: false
};
assert!(matches!(Eval::eval_predicate3(&like_escape, &m), Truth::True));
let not_like = Predicate::Like {
expr: ScalarExpr::Literal(Literal::String("abc".into())),
pattern: ScalarExpr::Literal(Literal::String("a_c".into())),
negated: true
};
assert!(matches!(Eval::eval_predicate3(¬_like, &m), Truth::False));
let like_null = Predicate::Like {
expr: ScalarExpr::Literal(Literal::Null),
pattern: ScalarExpr::Literal(Literal::String("a%".into())),
negated: false
};
assert!(matches!(Eval::eval_predicate3(&like_null, &m), Truth::Unknown));
}
#[test]
fn and_or_three_valued_logic() {
let m = Map::new();
let t = Predicate::Compare { left: lit_i(2), op: ComparatorOp::Gt, right: lit_i(1) }; let f = Predicate::Compare { left: lit_i(2), op: ComparatorOp::Lt, right: lit_i(1) }; let u = Predicate::Compare { left: lit_i(1), op: ComparatorOp::Eq, right: lit_null() };
assert!(matches!(Eval::eval_predicate3(&Predicate::And(vec![t.clone(), u.clone()]), &m), Truth::Unknown));
assert!(matches!(Eval::eval_predicate3(&Predicate::And(vec![f.clone(), u.clone()]), &m), Truth::False));
assert!(matches!(Eval::eval_predicate3(&Predicate::Or(vec![t, u.clone()]), &m), Truth::True));
assert!(matches!(Eval::eval_predicate3(&Predicate::Or(vec![f, u]), &m), Truth::Unknown));
}
}