Skip to main content

reddb_engine/query/
evaluator.rs

1//! Scalar expression evaluator — single owner of value-level
2//! evaluation for SQL scalar `Expr` trees.
3//!
4//! Today scalar expression evaluation is fragmented:
5//!
6//! - `query::filter::Predicate::evaluate` runs comparison predicates
7//!   against a single column value.
8//! - `query::filter_compiled::CompiledFilter::evaluate` runs the
9//!   compiled filter opcode tape over a row slot, but still defers to
10//!   `Predicate::evaluate` per opcode for the actual comparison logic.
11//! - Several inlined arms in `query::core` and `query::executor`
12//!   compute projections, DEFAULT / CHECK expressions, RETURNING
13//!   columns, COMPUTED columns, and ON CONFLICT updates by walking
14//!   the AST and dispatching arithmetic / function calls inline.
15//! - `query::expr_typing` knows how to type an `Expr` against a
16//!   `Scope` but never produces a `Value` — it stops at `TypedExpr`.
17//!
18//! Operator / function / cast resolution is centralised in
19//! `schema::coercion_spine`, but **value-level dispatch** is not — so
20//! adding a new operator overload, a new implicit cast edge, or a
21//! new built-in function requires synchronised edits across each of
22//! the inline evaluators above.
23//!
24//! This module is the first vertical slice of the deep evaluator
25//! interface tracked by `draft-53-scalar-expression-evaluator`. It
26//! exposes:
27//!
28//! - [`Row`]: pluggable column-binding lookup so callers can wire
29//!   either a planner-resolved slot vector or a flat column-name map.
30//! - [`evaluate`]: the single entry point. Walks an `Expr`, resolves
31//!   operators / functions / casts through `coercion_spine`, applies
32//!   the implicit casts the spine asks for, and produces a `Value`.
33//! - [`EvalError`]: typed diagnostic surface — every failure mode the
34//!   evaluator can encounter at runtime is enumerated here so callers
35//!   don't have to parse strings.
36//!
37//! The slice is intentionally additive: callers in `filter.rs`,
38//! `filter_compiled.rs`, and `executor.rs` still own their own
39//! evaluation paths. Subsequent slices migrate those callers onto
40//! `evaluate` once the dispatch surface has proven out under the
41//! focused test set in this module.
42//!
43//! ## Semantics summary
44//!
45//! - **NULL propagation.** Arithmetic, comparison, concat, and CAST
46//!   propagate `Value::Null` — any `Null` operand short-circuits to
47//!   `Null`. `AND` / `OR` follow SQL three-valued logic.
48//! - **Arithmetic overflow.** Signed checked arithmetic; overflow
49//!   surfaces as [`EvalError::ArithmeticOverflow`] rather than wrap
50//!   or panic.
51//! - **Division.** Division by zero surfaces as
52//!   [`EvalError::DivisionByZero`]. Integer `/` always promotes to
53//!   `Float` per the operator catalog; integer `%` stays integer.
54//! - **Implicit cast triggers.** When the operator catalog has no
55//!   exact-type overload, the spine returns the per-operand
56//!   coercions; this evaluator applies them via
57//!   [`coerce::coerce_via_catalog`] before dispatch.
58//! - **Unknown function rejection.** Calls that don't resolve in the
59//!   built-in function catalog produce
60//!   [`EvalError::UnknownFunction`]. Variadic / catalog-resolved
61//!   functions still return through the same dispatch.
62
63use std::sync::Arc;
64
65use super::runtime::values::runtime_exact_integer_cmp;
66use reddb_rql::ast::{BinOp, Expr, FieldRef, UnaryOp};
67use reddb_types::coerce::coerce_via_catalog;
68use reddb_types::coercion_spine;
69use reddb_types::function_catalog::FUNCTION_CATALOG;
70use reddb_types::operator_catalog::OperatorEntry;
71use reddb_types::{DataType, Value};
72
73/// Pluggable row-binding lookup. The evaluator stays agnostic of
74/// whether the caller has a slot vector indexed by planner-assigned
75/// position, a flat `HashMap<String, Value>`, or a graph binding —
76/// every consumer just provides a `Row` impl that resolves a
77/// `FieldRef` to a `Value`.
78pub trait Row {
79    /// Resolve a column / property reference. Returns `None` when the
80    /// reference is unknown (the evaluator surfaces this as
81    /// [`EvalError::UnknownColumn`]) or `Some(Value::Null)` when the
82    /// column exists but the row's value is null.
83    fn get(&self, field: &FieldRef) -> Option<Value>;
84}
85
86/// Trivial `Row` impl over a flat `(table, column) -> Value` closure
87/// — useful for tests and for callers that only have a column-name
88/// map.
89impl<F> Row for F
90where
91    F: Fn(&FieldRef) -> Option<Value>,
92{
93    fn get(&self, field: &FieldRef) -> Option<Value> {
94        self(field)
95    }
96}
97
98/// Errors surfaced by [`evaluate`]. Every variant is a runtime
99/// failure shape — type-resolution failures live in
100/// `expr_typing::TypeError`, not here.
101#[derive(Debug, Clone, PartialEq)]
102pub enum EvalError {
103    /// Column / property not present in the row binding.
104    UnknownColumn(FieldRef),
105    /// Query parameter placeholders are not resolved at this layer
106    /// — the bind phase substitutes a concrete value before
107    /// evaluation.
108    UnboundParameter(usize),
109    /// Operator catalog has no overload that accepts these operand
110    /// types, even after considering implicit coercions.
111    OperatorMismatch {
112        op: BinOp,
113        lhs: DataType,
114        rhs: DataType,
115    },
116    /// Unary operator doesn't accept the operand type.
117    UnaryMismatch { op: UnaryOp, operand: DataType },
118    /// Function catalog has no overload matching this call's
119    /// argument types. Includes user-defined functions because
120    /// today's catalog is the static built-in table only.
121    UnknownFunction { name: String, args: Vec<DataType> },
122    /// Implicit cast required by the spine failed at runtime — the
123    /// catalog said the conversion was legal at `Implicit` context
124    /// but the value's bytes couldn't be converted (e.g. overflow on
125    /// `BigInt` → `Integer`).
126    ImplicitCastFailed {
127        from: DataType,
128        to: DataType,
129        reason: String,
130    },
131    /// Explicit `CAST(x AS T)` failed at runtime.
132    CastFailed {
133        from: DataType,
134        to: DataType,
135        reason: String,
136    },
137    /// Signed-integer overflow during arithmetic.
138    ArithmeticOverflow { op: BinOp },
139    /// `n / 0` or `n % 0`.
140    DivisionByZero,
141    /// Numeric scalar evaluation produced an undefined or non-finite
142    /// float result (domain error, overflow, NaN, or infinity).
143    InvalidNumericResult { function: String, reason: String },
144    /// JSON_PARSE input was not valid JSON text.
145    JsonParseFailed { reason: String },
146    /// `IN (...)` against an empty value list — preserves the legacy
147    /// "always false" semantic but recorded explicitly so the
148    /// optimiser can fold it.
149    EmptyInList,
150}
151
152impl std::fmt::Display for EvalError {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        match self {
155            EvalError::UnknownColumn(field) => write!(f, "unknown column: {field:?}"),
156            EvalError::UnboundParameter(idx) => {
157                write!(f, "unbound query parameter: ${idx}")
158            }
159            EvalError::OperatorMismatch { op, lhs, rhs } => {
160                write!(f, "operator {op:?} not defined for ({lhs:?}, {rhs:?})")
161            }
162            EvalError::UnaryMismatch { op, operand } => {
163                write!(f, "unary {op:?} not defined for {operand:?}")
164            }
165            EvalError::UnknownFunction { name, args } => {
166                write!(f, "unknown function: {name}({args:?})")
167            }
168            EvalError::ImplicitCastFailed { from, to, reason } => {
169                write!(f, "implicit cast {from:?} -> {to:?} failed: {reason}")
170            }
171            EvalError::CastFailed { from, to, reason } => {
172                write!(f, "cast {from:?} -> {to:?} failed: {reason}")
173            }
174            EvalError::ArithmeticOverflow { op } => {
175                write!(f, "arithmetic overflow in {op:?}")
176            }
177            EvalError::DivisionByZero => write!(f, "division by zero"),
178            EvalError::InvalidNumericResult { function, reason } => {
179                write!(f, "invalid numeric result in {function}: {reason}")
180            }
181            EvalError::JsonParseFailed { reason } => {
182                write!(f, "JSON_PARSE failed to parse JSON: {reason}")
183            }
184            EvalError::EmptyInList => write!(f, "IN list is empty"),
185        }
186    }
187}
188
189impl std::error::Error for EvalError {}
190
191/// Evaluate a scalar `Expr` against a row binding. Single entry
192/// point for the deep evaluator interface — every recursive call
193/// folds back through this function so the resolution surface stays
194/// uniform.
195pub fn evaluate(expr: &Expr, row: &dyn Row) -> Result<Value, EvalError> {
196    match expr {
197        Expr::Literal { value, .. } => Ok(value.clone()),
198        Expr::Column { field, .. } => row
199            .get(field)
200            .ok_or_else(|| EvalError::UnknownColumn(field.clone())),
201        Expr::Parameter { index, .. } => Err(EvalError::UnboundParameter(*index)),
202        Expr::UnaryOp { op, operand, .. } => eval_unary(*op, operand, row),
203        Expr::BinaryOp { op, lhs, rhs, .. } => eval_binary(*op, lhs, rhs, row),
204        Expr::Cast { inner, target, .. } => eval_cast(inner, *target, row),
205        Expr::FunctionCall { name, args, .. } => eval_function(name, args, row),
206        Expr::Case {
207            branches, else_, ..
208        } => eval_case(branches, else_.as_deref(), row),
209        Expr::IsNull {
210            operand, negated, ..
211        } => {
212            let v = evaluate(operand, row)?;
213            let is_null = v.is_null();
214            Ok(Value::Boolean(if *negated { !is_null } else { is_null }))
215        }
216        Expr::InList {
217            target,
218            values,
219            negated,
220            ..
221        } => eval_in_list(target, values, *negated, row),
222        Expr::Between {
223            target,
224            low,
225            high,
226            negated,
227            ..
228        } => eval_between(target, low, high, *negated, row),
229        Expr::Subquery { .. } => Err(EvalError::UnknownFunction {
230            name: "SUBQUERY".to_string(),
231            args: Vec::new(),
232        }),
233        Expr::WindowFunctionCall { name, .. } => Err(EvalError::UnknownFunction {
234            name: format!("{name} OVER (...)"),
235            args: Vec::new(),
236        }),
237    }
238}
239
240fn eval_unary(op: UnaryOp, operand: &Expr, row: &dyn Row) -> Result<Value, EvalError> {
241    let v = evaluate(operand, row)?;
242    if v.is_null() {
243        return Ok(Value::Null);
244    }
245    match op {
246        UnaryOp::Neg => match &v {
247            Value::Integer(n) => n
248                .checked_neg()
249                .map(Value::Integer)
250                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
251            Value::BigInt(n) => n
252                .checked_neg()
253                .map(Value::BigInt)
254                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
255            Value::Float(n) => Ok(Value::Float(-*n)),
256            Value::Decimal(n) => n
257                .checked_neg()
258                .map(Value::Decimal)
259                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
260            other => Err(EvalError::UnaryMismatch {
261                op,
262                operand: other.data_type(),
263            }),
264        },
265        UnaryOp::Not => match &v {
266            Value::Boolean(b) => Ok(Value::Boolean(!b)),
267            other => Err(EvalError::UnaryMismatch {
268                op,
269                operand: other.data_type(),
270            }),
271        },
272    }
273}
274
275fn eval_binary(op: BinOp, lhs: &Expr, rhs: &Expr, row: &dyn Row) -> Result<Value, EvalError> {
276    // Logical ops use SQL three-valued logic so we eval both sides
277    // and short-circuit on Null *after* type-checking; arithmetic /
278    // comparison / concat short-circuit before dispatch.
279    let l = evaluate(lhs, row)?;
280    let r = evaluate(rhs, row)?;
281
282    match op {
283        BinOp::And => return three_valued_and(&l, &r),
284        BinOp::Or => return three_valued_or(&l, &r),
285        _ => {}
286    }
287
288    if l.is_null() || r.is_null() {
289        return Ok(Value::Null);
290    }
291
292    // Compare integer representations before numeric promotion can round large
293    // values or reject a u64 that exceeds the signed range.
294    if matches!(
295        op,
296        BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge
297    ) {
298        if let Some(order) = runtime_exact_integer_cmp(&l, &r) {
299            return Ok(Value::Boolean(match op {
300                BinOp::Eq => order.is_eq(),
301                BinOp::Ne => !order.is_eq(),
302                BinOp::Lt => order.is_lt(),
303                BinOp::Le => !order.is_gt(),
304                BinOp::Gt => order.is_gt(),
305                BinOp::Ge => !order.is_lt(),
306                _ => unreachable!("integer comparison operator was admitted"),
307            }));
308        }
309    }
310
311    let lhs_dt = l.data_type();
312    let rhs_dt = r.data_type();
313    let (entry, coercions) =
314        coercion_spine::resolve_binop(op, lhs_dt, rhs_dt).ok_or(EvalError::OperatorMismatch {
315            op,
316            lhs: lhs_dt,
317            rhs: rhs_dt,
318        })?;
319
320    let l = match coercions.at(0) {
321        Some(target) => apply_implicit_cast(&l, lhs_dt, target)?,
322        None => l,
323    };
324    let r = match coercions.at(1) {
325        Some(target) => apply_implicit_cast(&r, rhs_dt, target)?,
326        None => r,
327    };
328
329    dispatch_binop(op, entry, l, r)
330}
331
332fn dispatch_binop(
333    op: BinOp,
334    entry: &OperatorEntry,
335    l: Value,
336    r: Value,
337) -> Result<Value, EvalError> {
338    match op {
339        BinOp::Add => arith_add(entry, l, r),
340        BinOp::Sub => arith_sub(entry, l, r),
341        BinOp::Mul => arith_mul(entry, l, r),
342        BinOp::Div => arith_div(entry, l, r),
343        BinOp::Mod => arith_mod(entry, l, r),
344        BinOp::Concat => match (&l, &r) {
345            (Value::Text(a), Value::Text(b)) => {
346                let mut s = String::with_capacity(a.len() + b.len());
347                s.push_str(a);
348                s.push_str(b);
349                Ok(Value::Text(Arc::from(s)))
350            }
351            _ => Err(EvalError::OperatorMismatch {
352                op,
353                lhs: l.data_type(),
354                rhs: r.data_type(),
355            }),
356        },
357        BinOp::Eq => Ok(Value::Boolean(values_equal(&l, &r))),
358        BinOp::Ne => Ok(Value::Boolean(!values_equal(&l, &r))),
359        BinOp::Lt => cmp_op(op, l, r, |o| o == std::cmp::Ordering::Less),
360        BinOp::Le => cmp_op(op, l, r, |o| o != std::cmp::Ordering::Greater),
361        BinOp::Gt => cmp_op(op, l, r, |o| o == std::cmp::Ordering::Greater),
362        BinOp::Ge => cmp_op(op, l, r, |o| o != std::cmp::Ordering::Less),
363        BinOp::And | BinOp::Or => unreachable!("handled before dispatch"),
364    }
365}
366
367fn arith_add(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
368    match entry.return_type {
369        DataType::Integer => match (l, r) {
370            (Value::Integer(a), Value::Integer(b)) => a
371                .checked_add(b)
372                .map(Value::Integer)
373                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
374            _ => unreachable_after_coercion("Add", DataType::Integer),
375        },
376        DataType::BigInt => match (l, r) {
377            (Value::BigInt(a), Value::BigInt(b)) => a
378                .checked_add(b)
379                .map(Value::BigInt)
380                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
381            _ => unreachable_after_coercion("Add", DataType::BigInt),
382        },
383        DataType::Float => checked_float_binop(BinOp::Add, as_f64(&l) + as_f64(&r)),
384        DataType::Decimal => match (l, r) {
385            (Value::Decimal(a), Value::Decimal(b)) => a
386                .checked_add(b)
387                .map(Value::Decimal)
388                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
389            _ => unreachable_after_coercion("Add", DataType::Decimal),
390        },
391        other => Err(EvalError::OperatorMismatch {
392            op: BinOp::Add,
393            lhs: other,
394            rhs: other,
395        }),
396    }
397}
398
399fn arith_sub(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
400    match entry.return_type {
401        DataType::Integer => match (l, r) {
402            (Value::Integer(a), Value::Integer(b)) => a
403                .checked_sub(b)
404                .map(Value::Integer)
405                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
406            _ => unreachable_after_coercion("Sub", DataType::Integer),
407        },
408        DataType::BigInt => match (l, r) {
409            (Value::BigInt(a), Value::BigInt(b)) => a
410                .checked_sub(b)
411                .map(Value::BigInt)
412                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
413            _ => unreachable_after_coercion("Sub", DataType::BigInt),
414        },
415        DataType::Float => checked_float_binop(BinOp::Sub, as_f64(&l) - as_f64(&r)),
416        DataType::Decimal => match (l, r) {
417            (Value::Decimal(a), Value::Decimal(b)) => a
418                .checked_sub(b)
419                .map(Value::Decimal)
420                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
421            _ => unreachable_after_coercion("Sub", DataType::Decimal),
422        },
423        other => Err(EvalError::OperatorMismatch {
424            op: BinOp::Sub,
425            lhs: other,
426            rhs: other,
427        }),
428    }
429}
430
431fn arith_mul(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
432    match entry.return_type {
433        DataType::Integer => match (l, r) {
434            (Value::Integer(a), Value::Integer(b)) => a
435                .checked_mul(b)
436                .map(Value::Integer)
437                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mul }),
438            _ => unreachable_after_coercion("Mul", DataType::Integer),
439        },
440        DataType::BigInt => match (l, r) {
441            (Value::BigInt(a), Value::BigInt(b)) => a
442                .checked_mul(b)
443                .map(Value::BigInt)
444                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mul }),
445            _ => unreachable_after_coercion("Mul", DataType::BigInt),
446        },
447        DataType::Float => checked_float_binop(BinOp::Mul, as_f64(&l) * as_f64(&r)),
448        other => Err(EvalError::OperatorMismatch {
449            op: BinOp::Mul,
450            lhs: other,
451            rhs: other,
452        }),
453    }
454}
455
456fn arith_div(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
457    // Operator catalog promotes integer / integer to Float —
458    // mirror that here so behavior stays identical to the typer.
459    match entry.return_type {
460        DataType::Float => {
461            let denom = as_f64(&r);
462            if denom == 0.0 {
463                return Err(EvalError::DivisionByZero);
464            }
465            checked_float_binop(BinOp::Div, as_f64(&l) / denom)
466        }
467        other => Err(EvalError::OperatorMismatch {
468            op: BinOp::Div,
469            lhs: other,
470            rhs: other,
471        }),
472    }
473}
474
475fn arith_mod(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
476    match entry.return_type {
477        DataType::Integer => match (l, r) {
478            (Value::Integer(_), Value::Integer(0)) => Err(EvalError::DivisionByZero),
479            (Value::Integer(a), Value::Integer(b)) => a
480                .checked_rem(b)
481                .map(Value::Integer)
482                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mod }),
483            _ => unreachable_after_coercion("Mod", DataType::Integer),
484        },
485        DataType::BigInt => match (l, r) {
486            (Value::BigInt(_), Value::BigInt(0)) => Err(EvalError::DivisionByZero),
487            (Value::BigInt(a), Value::BigInt(b)) => a
488                .checked_rem(b)
489                .map(Value::BigInt)
490                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mod }),
491            _ => unreachable_after_coercion("Mod", DataType::BigInt),
492        },
493        other => Err(EvalError::OperatorMismatch {
494            op: BinOp::Mod,
495            lhs: other,
496            rhs: other,
497        }),
498    }
499}
500
501fn unreachable_after_coercion(op: &'static str, expected: DataType) -> Result<Value, EvalError> {
502    Err(EvalError::OperatorMismatch {
503        op: match op {
504            "Add" => BinOp::Add,
505            "Sub" => BinOp::Sub,
506            "Mul" => BinOp::Mul,
507            "Div" => BinOp::Div,
508            "Mod" => BinOp::Mod,
509            _ => BinOp::Add,
510        },
511        lhs: expected,
512        rhs: expected,
513    })
514}
515
516fn checked_float_binop(op: BinOp, value: f64) -> Result<Value, EvalError> {
517    if value.is_finite() {
518        Ok(Value::Float(value))
519    } else {
520        Err(EvalError::InvalidNumericResult {
521            function: format!("{op:?}"),
522            reason: "result is NaN or infinite".to_string(),
523        })
524    }
525}
526
527fn as_f64(v: &Value) -> f64 {
528    match v {
529        Value::Float(x) => *x,
530        Value::Integer(x) => *x as f64,
531        Value::BigInt(x) => *x as f64,
532        Value::UnsignedInteger(x) => *x as f64,
533        Value::Decimal(x) => reddb_types::types::decimal_to_f64(*x),
534        _ => 0.0,
535    }
536}
537
538fn cmp_op<F>(op: BinOp, l: Value, r: Value, pick: F) -> Result<Value, EvalError>
539where
540    F: Fn(std::cmp::Ordering) -> bool,
541{
542    let ord = compare_values(&l, &r).ok_or(EvalError::OperatorMismatch {
543        op,
544        lhs: l.data_type(),
545        rhs: r.data_type(),
546    })?;
547    Ok(Value::Boolean(pick(ord)))
548}
549
550/// Total ordering for the numeric and text families that the
551/// catalog declares comparison overloads for. Returns `None` when
552/// the values aren't comparable — callers surface this as
553/// [`EvalError::OperatorMismatch`].
554fn compare_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
555    if let Some(order) = runtime_exact_integer_cmp(a, b) {
556        return Some(order);
557    }
558    match (a, b) {
559        (Value::Integer(x), Value::Integer(y)) => Some(x.cmp(y)),
560        (Value::BigInt(x), Value::BigInt(y)) => Some(x.cmp(y)),
561        (Value::Float(x), Value::Float(y)) => x.partial_cmp(y),
562        (Value::Text(x), Value::Text(y)) => Some(x.as_ref().cmp(y.as_ref())),
563        (Value::Boolean(x), Value::Boolean(y)) => Some(x.cmp(y)),
564        (Value::Timestamp(x), Value::Timestamp(y)) => Some(x.cmp(y)),
565        (Value::TimestampMs(x), Value::TimestampMs(y)) => Some(x.cmp(y)),
566        (Value::Date(x), Value::Date(y)) => Some(x.cmp(y)),
567        (Value::Time(x), Value::Time(y)) => Some(x.cmp(y)),
568        (Value::Uuid(x), Value::Uuid(y)) => Some(x.cmp(y)),
569        (Value::Decimal(x), Value::Decimal(y)) => Some(x.cmp(y)),
570        // Cross-numeric — operand coercion should have homogenised
571        // these but if a caller invokes the evaluator with mixed
572        // numerics directly, fall back to f64 ordering.
573        (Value::Integer(_) | Value::Float(_) | Value::BigInt(_), _) => {
574            let l = as_f64(a);
575            let r = as_f64(b);
576            l.partial_cmp(&r)
577        }
578        _ => None,
579    }
580}
581
582fn values_equal(a: &Value, b: &Value) -> bool {
583    if let Some(order) = runtime_exact_integer_cmp(a, b) {
584        return order.is_eq();
585    }
586    match (a, b) {
587        (Value::Float(x), Value::Float(y)) => x == y,
588        _ => a == b,
589    }
590}
591
592fn three_valued_and(l: &Value, r: &Value) -> Result<Value, EvalError> {
593    match (l, r) {
594        (Value::Boolean(false), _) | (_, Value::Boolean(false)) => Ok(Value::Boolean(false)),
595        (Value::Boolean(true), Value::Boolean(true)) => Ok(Value::Boolean(true)),
596        (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
597        _ => Err(EvalError::OperatorMismatch {
598            op: BinOp::And,
599            lhs: l.data_type(),
600            rhs: r.data_type(),
601        }),
602    }
603}
604
605fn three_valued_or(l: &Value, r: &Value) -> Result<Value, EvalError> {
606    match (l, r) {
607        (Value::Boolean(true), _) | (_, Value::Boolean(true)) => Ok(Value::Boolean(true)),
608        (Value::Boolean(false), Value::Boolean(false)) => Ok(Value::Boolean(false)),
609        (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
610        _ => Err(EvalError::OperatorMismatch {
611            op: BinOp::Or,
612            lhs: l.data_type(),
613            rhs: r.data_type(),
614        }),
615    }
616}
617
618fn apply_implicit_cast(value: &Value, src: DataType, target: DataType) -> Result<Value, EvalError> {
619    if src == target {
620        return Ok(value.clone());
621    }
622    coerce_via_catalog(value, src, target, None).map_err(|reason| EvalError::ImplicitCastFailed {
623        from: src,
624        to: target,
625        reason,
626    })
627}
628
629fn eval_cast(inner: &Expr, target: DataType, row: &dyn Row) -> Result<Value, EvalError> {
630    let v = evaluate(inner, row)?;
631    if v.is_null() {
632        return Ok(Value::Null);
633    }
634    let src = v.data_type();
635    if src == target {
636        return Ok(v);
637    }
638    coerce_via_catalog(&v, src, target, None).map_err(|reason| EvalError::CastFailed {
639        from: src,
640        to: target,
641        reason,
642    })
643}
644
645fn eval_function(name: &str, args: &[Expr], row: &dyn Row) -> Result<Value, EvalError> {
646    // COALESCE has SQL-special semantics that the catalog can't
647    // express (variadic + uniform arg type unifies poorly with
648    // first-non-null). Handle it before catalog dispatch so we
649    // preserve `COALESCE(int, int) -> int` rather than coercing
650    // every argument to Text.
651    if name.eq_ignore_ascii_case("COALESCE") {
652        for arg in args {
653            let v = evaluate(arg, row)?;
654            if !v.is_null() {
655                return Ok(v);
656            }
657        }
658        return Ok(Value::Null);
659    }
660
661    let arg_values: Vec<Value> = args
662        .iter()
663        .map(|a| evaluate(a, row))
664        .collect::<Result<Vec<_>, _>>()?;
665    let arg_types: Vec<DataType> = arg_values.iter().map(|v| v.data_type()).collect();
666
667    // Strict NULL propagation for built-in scalar functions: any
668    // null arg short-circuits the call to NULL. Only applies when
669    // the function name exists in the catalog so unknown functions
670    // with null args still surface as `UnknownFunction` rather than
671    // silently returning null.
672    if arg_values.iter().any(Value::is_null)
673        && FUNCTION_CATALOG
674            .iter()
675            .any(|e| e.name.eq_ignore_ascii_case(name))
676    {
677        return Ok(Value::Null);
678    }
679
680    let (entry, coercions) =
681        coercion_spine::resolve_function(name, &arg_types).ok_or_else(|| {
682            EvalError::UnknownFunction {
683                name: name.to_string(),
684                args: arg_types.clone(),
685            }
686        })?;
687
688    // Apply per-arg implicit casts.
689    let mut coerced: Vec<Value> = Vec::with_capacity(arg_values.len());
690    for (idx, value) in arg_values.into_iter().enumerate() {
691        let src = arg_types[idx];
692        match coercions.at(idx) {
693            Some(target) if src != target => {
694                coerced.push(apply_implicit_cast(&value, src, target)?);
695            }
696            _ => coerced.push(value),
697        }
698    }
699
700    // NULL propagation for built-ins: if any non-variadic argument
701    // is null, return null. Variadic / aggregate semantics handle
702    // null differently and aren't in scope for this slice.
703    if !entry.variadic && coerced.iter().any(|v| v.is_null()) {
704        return Ok(Value::Null);
705    }
706
707    dispatch_function(entry.name, &coerced)
708}
709
710fn dispatch_function(name: &str, args: &[Value]) -> Result<Value, EvalError> {
711    match name {
712        "UPPER" => match &args[0] {
713            Value::Text(s) => Ok(Value::Text(Arc::from(s.to_uppercase()))),
714            other => Err(EvalError::UnknownFunction {
715                name: name.to_string(),
716                args: vec![other.data_type()],
717            }),
718        },
719        "LOWER" => match &args[0] {
720            Value::Text(s) => Ok(Value::Text(Arc::from(s.to_lowercase()))),
721            other => Err(EvalError::UnknownFunction {
722                name: name.to_string(),
723                args: vec![other.data_type()],
724            }),
725        },
726        "TRIM" => trim_function(name, args, true, true),
727        "LTRIM" => trim_function(name, args, true, false),
728        "RTRIM" => trim_function(name, args, false, true),
729        "LENGTH" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => match &args[0] {
730            Value::Text(s) => Ok(Value::Integer(s.chars().count() as i64)),
731            other => Err(EvalError::UnknownFunction {
732                name: name.to_string(),
733                args: vec![other.data_type()],
734            }),
735        },
736        "OCTET_LENGTH" => match &args[0] {
737            Value::Text(s) => Ok(Value::Integer(s.len() as i64)),
738            Value::Blob(b) => Ok(Value::Integer(b.len() as i64)),
739            other => Err(EvalError::UnknownFunction {
740                name: name.to_string(),
741                args: vec![other.data_type()],
742            }),
743        },
744        "CONCAT" => {
745            let mut out = String::new();
746            for arg in args {
747                let Value::Text(value) = arg else {
748                    return Err(EvalError::UnknownFunction {
749                        name: name.to_string(),
750                        args: args.iter().map(Value::data_type).collect(),
751                    });
752                };
753                out.push_str(value);
754            }
755            Ok(Value::Text(Arc::from(out)))
756        }
757        "JSON_PARSE" => json_parse_value(&args[0]),
758        "JSON_EXTRACT" => Ok(json_extract_value(&args[0], &args[1], false)),
759        "JSON_EXTRACT_TEXT" => Ok(json_extract_value(&args[0], &args[1], true)),
760        "CONTAINS" => Ok(contains_value(&args[0], &args[1])),
761        "ABS" => match &args[0] {
762            Value::Integer(n) => n
763                .checked_abs()
764                .map(Value::Integer)
765                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
766            Value::BigInt(n) => n
767                .checked_abs()
768                .map(Value::BigInt)
769                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
770            Value::Float(n) => Ok(Value::Float(n.abs())),
771            Value::Decimal(n) => n
772                .checked_abs()
773                .map(Value::Decimal)
774                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
775            other => Err(EvalError::UnknownFunction {
776                name: name.to_string(),
777                args: vec![other.data_type()],
778            }),
779        },
780        "SQRT" => unary_math(name, args, |x| {
781            if x < 0.0 {
782                return Err("input must be greater than or equal to zero");
783            }
784            Ok(x.sqrt())
785        }),
786        "POWER" | "POW" => binary_math(name, args, |base, exp| Ok(base.powf(exp))),
787        "EXP" => unary_math(name, args, |x| Ok(x.exp())),
788        "LN" => unary_math(name, args, |x| {
789            if x <= 0.0 {
790                return Err("input must be greater than zero");
791            }
792            Ok(x.ln())
793        }),
794        "LOG" if args.len() == 1 => unary_math(name, args, |x| {
795            if x <= 0.0 {
796                return Err("input must be greater than zero");
797            }
798            Ok(x.log10())
799        }),
800        "LOG" => binary_math(name, args, |base, x| {
801            if base <= 0.0 {
802                return Err("base must be greater than zero");
803            }
804            if base == 1.0 {
805                return Err("base must not equal one");
806            }
807            if x <= 0.0 {
808                return Err("input must be greater than zero");
809            }
810            Ok(x.log(base))
811        }),
812        "LOG10" => unary_math(name, args, |x| {
813            if x <= 0.0 {
814                return Err("input must be greater than zero");
815            }
816            Ok(x.log10())
817        }),
818        "SIN" => unary_math(name, args, |x| Ok(x.sin())),
819        "COS" => unary_math(name, args, |x| Ok(x.cos())),
820        "TAN" => unary_math(name, args, |x| Ok(x.tan())),
821        "ASIN" | "ARCSIN" => unary_math(name, args, |x| {
822            if !(-1.0..=1.0).contains(&x) {
823                return Err("input must be between -1 and 1");
824            }
825            Ok(x.asin())
826        }),
827        "ACOS" | "ARCCOS" => unary_math(name, args, |x| {
828            if !(-1.0..=1.0).contains(&x) {
829                return Err("input must be between -1 and 1");
830            }
831            Ok(x.acos())
832        }),
833        "ATAN" | "ARCTAN" => unary_math(name, args, |x| Ok(x.atan())),
834        "ATAN2" => binary_math(name, args, |y, x| Ok(y.atan2(x))),
835        "COT" => unary_math(name, args, |x| {
836            let tan = x.tan();
837            if tan == 0.0 {
838                return Err("input must not produce zero tangent");
839            }
840            Ok(1.0 / tan)
841        }),
842        "DEGREES" => unary_math(name, args, |x| Ok(x.to_degrees())),
843        "RADIANS" => unary_math(name, args, |x| Ok(x.to_radians())),
844        "PI" => checked_math_result(name, std::f64::consts::PI),
845        // Functions whose runtime body the slice doesn't yet cover
846        // surface as UnknownFunction with the resolved arg types so
847        // callers can see the catalog matched but the dispatch
848        // didn't. Subsequent slices fill in CONCAT, time functions, …
849        other => Err(EvalError::UnknownFunction {
850            name: other.to_string(),
851            args: args.iter().map(|v| v.data_type()).collect(),
852        }),
853    }
854}
855
856fn trim_function(name: &str, args: &[Value], left: bool, right: bool) -> Result<Value, EvalError> {
857    if args.len() != 1 {
858        return Err(EvalError::UnknownFunction {
859            name: name.to_string(),
860            args: args.iter().map(|v| v.data_type()).collect(),
861        });
862    }
863
864    match &args[0] {
865        Value::Text(s) => {
866            let trimmed = match (left, right) {
867                (true, true) => s.trim(),
868                (true, false) => s.trim_start(),
869                (false, true) => s.trim_end(),
870                (false, false) => s,
871            };
872            Ok(Value::Text(Arc::from(trimmed)))
873        }
874        other => Err(EvalError::UnknownFunction {
875            name: name.to_string(),
876            args: vec![other.data_type()],
877        }),
878    }
879}
880
881fn unary_math<F>(name: &str, args: &[Value], op: F) -> Result<Value, EvalError>
882where
883    F: FnOnce(f64) -> Result<f64, &'static str>,
884{
885    let input = math_arg(name, args.first(), 0)?;
886    let value = op(input).map_err(|reason| EvalError::InvalidNumericResult {
887        function: name.to_string(),
888        reason: reason.to_string(),
889    })?;
890    checked_math_result(name, value)
891}
892
893fn binary_math<F>(name: &str, args: &[Value], op: F) -> Result<Value, EvalError>
894where
895    F: FnOnce(f64, f64) -> Result<f64, &'static str>,
896{
897    let left = math_arg(name, args.first(), 0)?;
898    let right = math_arg(name, args.get(1), 1)?;
899    let value = op(left, right).map_err(|reason| EvalError::InvalidNumericResult {
900        function: name.to_string(),
901        reason: reason.to_string(),
902    })?;
903    checked_math_result(name, value)
904}
905
906fn math_arg(name: &str, value: Option<&Value>, index: usize) -> Result<f64, EvalError> {
907    let value = value.ok_or_else(|| EvalError::UnknownFunction {
908        name: name.to_string(),
909        args: Vec::new(),
910    })?;
911    let numeric = as_f64(value);
912    if numeric.is_finite() {
913        Ok(numeric)
914    } else {
915        Err(EvalError::InvalidNumericResult {
916            function: name.to_string(),
917            reason: format!("argument {index} is NaN or infinite"),
918        })
919    }
920}
921
922fn checked_math_result(name: &str, value: f64) -> Result<Value, EvalError> {
923    if value.is_finite() {
924        Ok(Value::Float(value))
925    } else {
926        Err(EvalError::InvalidNumericResult {
927            function: name.to_string(),
928            reason: "result is NaN or infinite".to_string(),
929        })
930    }
931}
932
933fn json_extract_value(input: &Value, path: &Value, as_text: bool) -> Value {
934    let Value::Text(path) = path else {
935        return Value::Null;
936    };
937    let Some(json) = value_to_json(input) else {
938        return Value::Null;
939    };
940    let Some(steps) = parse_json_path(path) else {
941        return Value::Null;
942    };
943    let Some(target) = json_path_get(&json, &steps) else {
944        return Value::Null;
945    };
946
947    if as_text {
948        match target {
949            crate::serde_json::Value::String(value) => Value::text(value.clone()),
950            crate::serde_json::Value::Null => Value::Null,
951            crate::serde_json::Value::Bool(value) => Value::text(value.to_string()),
952            crate::serde_json::Value::Integer(value) => Value::text(value.to_string()),
953            crate::serde_json::Value::Number(value) => Value::text(value.to_string()),
954            other => Value::text(other.to_string_compact()),
955        }
956    } else {
957        Value::text(target.to_string_compact())
958    }
959}
960
961fn json_parse_value(input: &Value) -> Result<Value, EvalError> {
962    let Value::Text(text) = input else {
963        return Err(EvalError::UnknownFunction {
964            name: "JSON_PARSE".to_string(),
965            args: vec![input.data_type()],
966        });
967    };
968    crate::serde_json::from_str::<crate::serde_json::Value>(text)
969        .map(|json| Value::Json(json.to_string_compact().into_bytes()))
970        .map_err(|err| EvalError::JsonParseFailed {
971            reason: err.to_string(),
972        })
973}
974
975fn contains_value(input: &Value, needle: &Value) -> Value {
976    let Value::Text(needle) = needle else {
977        return Value::Null;
978    };
979    Value::Boolean(value_contains(input, needle))
980}
981
982fn value_contains(value: &Value, needle: &str) -> bool {
983    match value {
984        Value::Array(values) => values.iter().any(|value| value_contains(value, needle)),
985        Value::Json(_) => value_to_json(value)
986            .as_ref()
987            .is_some_and(|json| json_value_contains(json, needle)),
988        Value::Text(value) => value.contains(needle),
989        other => other.display_string().contains(needle),
990    }
991}
992
993fn json_value_contains(value: &crate::serde_json::Value, needle: &str) -> bool {
994    match value {
995        crate::serde_json::Value::Array(values) => values
996            .iter()
997            .any(|value| json_value_contains(value, needle)),
998        // Recurse into object *values* so a needle nested inside a JSON
999        // object matches — e.g. `CONTAINS(vision_detections, 'person')`
1000        // over a derived `[{"label":"person",...}]` array (#1275). Keys
1001        // are deliberately not matched: containment is about the data, not
1002        // the field names.
1003        crate::serde_json::Value::Object(map) => {
1004            map.values().any(|value| json_value_contains(value, needle))
1005        }
1006        crate::serde_json::Value::String(value) => value == needle,
1007        crate::serde_json::Value::Integer(value) => value.to_string() == needle,
1008        crate::serde_json::Value::Number(value) => value.to_string() == needle,
1009        crate::serde_json::Value::Decimal(value) => value == needle,
1010        crate::serde_json::Value::Bool(value) => value.to_string() == needle,
1011        crate::serde_json::Value::Null => false,
1012    }
1013}
1014
1015fn value_to_json(value: &Value) -> Option<crate::serde_json::Value> {
1016    match value {
1017        Value::Null => Some(crate::serde_json::Value::Null),
1018        // A document body may be the native binary container (PRD-1398); decode
1019        // it to JSON so json_extract / CONTAINS see the same shape as a plain
1020        // JSON body.
1021        Value::Json(bytes) => reddb_types::document_body_codec::decode_container_to_json(bytes)
1022            .or_else(|| crate::serde_json::from_slice(bytes).ok()),
1023        Value::Text(value) => crate::serde_json::from_str(value).ok(),
1024        _ => None,
1025    }
1026}
1027
1028enum JsonPathStep<'a> {
1029    Field(&'a str),
1030    Index(usize),
1031}
1032
1033fn parse_json_path(path: &str) -> Option<Vec<JsonPathStep<'_>>> {
1034    let path = path.trim();
1035    let rest = path.strip_prefix('$').unwrap_or(path);
1036    let mut steps = Vec::new();
1037    let bytes = rest.as_bytes();
1038    let mut index = 0;
1039    while index < bytes.len() {
1040        match bytes[index] {
1041            b'.' => {
1042                index += 1;
1043                let start = index;
1044                while index < bytes.len() && bytes[index] != b'.' && bytes[index] != b'[' {
1045                    index += 1;
1046                }
1047                if start == index {
1048                    return None;
1049                }
1050                steps.push(JsonPathStep::Field(
1051                    std::str::from_utf8(&bytes[start..index]).ok()?,
1052                ));
1053            }
1054            b'[' => {
1055                index += 1;
1056                let start = index;
1057                while index < bytes.len() && bytes[index] != b']' {
1058                    index += 1;
1059                }
1060                if index >= bytes.len() {
1061                    return None;
1062                }
1063                steps.push(JsonPathStep::Index(
1064                    std::str::from_utf8(&bytes[start..index])
1065                        .ok()?
1066                        .parse()
1067                        .ok()?,
1068                ));
1069                index += 1;
1070            }
1071            _ => return None,
1072        }
1073    }
1074    Some(steps)
1075}
1076
1077fn json_path_get<'a>(
1078    root: &'a crate::serde_json::Value,
1079    steps: &[JsonPathStep<'_>],
1080) -> Option<&'a crate::serde_json::Value> {
1081    let mut current = root;
1082    for step in steps {
1083        current = match (step, current) {
1084            (JsonPathStep::Field(name), crate::serde_json::Value::Object(map)) => map.get(*name)?,
1085            (JsonPathStep::Index(index), crate::serde_json::Value::Array(values)) => {
1086                values.get(*index)?
1087            }
1088            _ => return None,
1089        };
1090    }
1091    Some(current)
1092}
1093
1094fn eval_case(
1095    branches: &[(Expr, Expr)],
1096    else_: Option<&Expr>,
1097    row: &dyn Row,
1098) -> Result<Value, EvalError> {
1099    for (cond, value) in branches {
1100        let c = evaluate(cond, row)?;
1101        if matches!(c, Value::Boolean(true)) {
1102            return evaluate(value, row);
1103        }
1104    }
1105    match else_ {
1106        Some(e) => evaluate(e, row),
1107        None => Ok(Value::Null),
1108    }
1109}
1110
1111fn eval_in_list(
1112    target: &Expr,
1113    values: &[Expr],
1114    negated: bool,
1115    row: &dyn Row,
1116) -> Result<Value, EvalError> {
1117    if values.is_empty() {
1118        return Err(EvalError::EmptyInList);
1119    }
1120    let needle = evaluate(target, row)?;
1121    if needle.is_null() {
1122        return Ok(Value::Null);
1123    }
1124    let mut saw_null = false;
1125    for v in values {
1126        let candidate = evaluate(v, row)?;
1127        if candidate.is_null() {
1128            saw_null = true;
1129            continue;
1130        }
1131        if values_equal(&needle, &candidate) {
1132            return Ok(Value::Boolean(!negated));
1133        }
1134    }
1135    if saw_null {
1136        return Ok(Value::Null);
1137    }
1138    Ok(Value::Boolean(negated))
1139}
1140
1141fn eval_between(
1142    target: &Expr,
1143    low: &Expr,
1144    high: &Expr,
1145    negated: bool,
1146    row: &dyn Row,
1147) -> Result<Value, EvalError> {
1148    let v = evaluate(target, row)?;
1149    let lo = evaluate(low, row)?;
1150    let hi = evaluate(high, row)?;
1151    if v.is_null() || lo.is_null() || hi.is_null() {
1152        return Ok(Value::Null);
1153    }
1154    let lo_ok = compare_values(&v, &lo)
1155        .ok_or(EvalError::OperatorMismatch {
1156            op: BinOp::Ge,
1157            lhs: v.data_type(),
1158            rhs: lo.data_type(),
1159        })
1160        .map(|o| o != std::cmp::Ordering::Less)?;
1161    let hi_ok = compare_values(&v, &hi)
1162        .ok_or(EvalError::OperatorMismatch {
1163            op: BinOp::Le,
1164            lhs: v.data_type(),
1165            rhs: hi.data_type(),
1166        })
1167        .map(|o| o != std::cmp::Ordering::Greater)?;
1168    let inside = lo_ok && hi_ok;
1169    Ok(Value::Boolean(if negated { !inside } else { inside }))
1170}
1171
1172#[cfg(test)]
1173mod tests {
1174    use super::*;
1175    use reddb_rql::ast::Span;
1176
1177    fn lit(v: Value) -> Expr {
1178        Expr::Literal {
1179            value: v,
1180            span: Span::synthetic(),
1181        }
1182    }
1183
1184    fn binop(op: BinOp, l: Expr, r: Expr) -> Expr {
1185        Expr::BinaryOp {
1186            op,
1187            lhs: Box::new(l),
1188            rhs: Box::new(r),
1189            span: Span::synthetic(),
1190        }
1191    }
1192
1193    fn empty_row() -> impl Row {
1194        |_field: &FieldRef| -> Option<Value> { None }
1195    }
1196
1197    #[test]
1198    fn contains_descends_into_json_object_values() {
1199        // A derived component-detections array (#1275): CONTAINS must find
1200        // a label nested inside the object elements, not just top-level
1201        // array strings.
1202        let detections = br#"[{"label":"person","confidence":0.9,"bbox":[0,0,1,1]},
1203                              {"label":"car","confidence":0.7,"bbox":[1,1,2,2]}]"#;
1204        let json = Value::Json(detections.to_vec());
1205        assert!(value_contains(&json, "person"));
1206        assert!(value_contains(&json, "car"));
1207        assert!(!value_contains(&json, "bicycle"));
1208        // Keys are not matched — only the data.
1209        assert!(!value_contains(&json, "label"));
1210    }
1211
1212    #[test]
1213    fn integer_addition_overflow_surfaces_as_eval_error() {
1214        let expr = binop(
1215            BinOp::Add,
1216            lit(Value::Integer(i64::MAX)),
1217            lit(Value::Integer(1)),
1218        );
1219        let err = evaluate(&expr, &empty_row()).unwrap_err();
1220        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Add });
1221    }
1222
1223    #[test]
1224    fn integer_multiplication_overflow_surfaces_as_eval_error() {
1225        let expr = binop(
1226            BinOp::Mul,
1227            lit(Value::Integer(i64::MAX)),
1228            lit(Value::Integer(2)),
1229        );
1230        let err = evaluate(&expr, &empty_row()).unwrap_err();
1231        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Mul });
1232    }
1233
1234    #[test]
1235    fn integer_subtraction_overflow_surfaces_as_eval_error() {
1236        let expr = binop(
1237            BinOp::Sub,
1238            lit(Value::Integer(i64::MIN)),
1239            lit(Value::Integer(1)),
1240        );
1241        let err = evaluate(&expr, &empty_row()).unwrap_err();
1242        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1243    }
1244
1245    #[test]
1246    fn unary_neg_overflow_on_min_int_surfaces_as_eval_error() {
1247        let expr = Expr::UnaryOp {
1248            op: UnaryOp::Neg,
1249            operand: Box::new(lit(Value::Integer(i64::MIN))),
1250            span: Span::synthetic(),
1251        };
1252        let err = evaluate(&expr, &empty_row()).unwrap_err();
1253        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1254    }
1255
1256    #[test]
1257    fn null_propagates_through_arithmetic() {
1258        let expr = binop(BinOp::Add, lit(Value::Null), lit(Value::Integer(7)));
1259        let v = evaluate(&expr, &empty_row()).unwrap();
1260        assert_eq!(v, Value::Null);
1261    }
1262
1263    #[test]
1264    fn null_propagates_through_comparison() {
1265        let expr = binop(BinOp::Lt, lit(Value::Integer(1)), lit(Value::Null));
1266        let v = evaluate(&expr, &empty_row()).unwrap();
1267        assert_eq!(v, Value::Null);
1268    }
1269
1270    #[test]
1271    fn null_propagates_through_concat() {
1272        let expr = binop(
1273            BinOp::Concat,
1274            lit(Value::Text(Arc::from("hi"))),
1275            lit(Value::Null),
1276        );
1277        let v = evaluate(&expr, &empty_row()).unwrap();
1278        assert_eq!(v, Value::Null);
1279    }
1280
1281    #[test]
1282    fn three_valued_and_returns_false_when_one_side_false_even_with_null() {
1283        let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(false)));
1284        let v = evaluate(&expr, &empty_row()).unwrap();
1285        assert_eq!(v, Value::Boolean(false));
1286    }
1287
1288    #[test]
1289    fn three_valued_or_returns_true_when_one_side_true_even_with_null() {
1290        let expr = binop(BinOp::Or, lit(Value::Null), lit(Value::Boolean(true)));
1291        let v = evaluate(&expr, &empty_row()).unwrap();
1292        assert_eq!(v, Value::Boolean(true));
1293    }
1294
1295    #[test]
1296    fn three_valued_and_returns_null_for_null_and_true() {
1297        let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(true)));
1298        let v = evaluate(&expr, &empty_row()).unwrap();
1299        assert_eq!(v, Value::Null);
1300    }
1301
1302    #[test]
1303    fn implicit_cast_triggers_for_decimal_plus_integer() {
1304        // Operator catalog has +(Decimal, Decimal) -> Decimal as
1305        // the only overload that survives coercion (no Decimal ->
1306        // numeric implicit casts exist in the cast catalog). The
1307        // spine therefore inserts an Integer -> Decimal cast on
1308        // the rhs and dispatches the Decimal addition.
1309        // parse_decimal scales by 10^4, so Integer(2) coerces to
1310        // Decimal(20000) and Decimal(10000) + Decimal(20000) =
1311        // Decimal(30000) (fixed-point 3.0000).
1312        let expr = binop(
1313            BinOp::Add,
1314            lit(Value::Decimal(10000)),
1315            lit(Value::Integer(2)),
1316        );
1317        let v = evaluate(&expr, &empty_row()).unwrap();
1318        assert_eq!(v, Value::Decimal(30000));
1319    }
1320
1321    #[test]
1322    fn integer_plus_bigint_resolves_to_preferred_float_overload() {
1323        // (Integer, BigInt) has no exact match. The spine ties
1324        // between +(Integer, Float, Float) (rhs cast BigInt->Float)
1325        // and +(BigInt, BigInt, BigInt) (lhs cast Integer->BigInt).
1326        // Float wins on the preferred-return-type tie-break, so the
1327        // BigInt operand is coerced to Float and the result is Float.
1328        let expr = binop(
1329            BinOp::Add,
1330            lit(Value::Integer(5)),
1331            lit(Value::BigInt(40_000_000_000)),
1332        );
1333        let v = evaluate(&expr, &empty_row()).unwrap();
1334        assert_eq!(v, Value::Float(40_000_000_005.0));
1335    }
1336
1337    #[test]
1338    fn integer_predicates_preserve_precision_across_signed_and_unsigned_boundaries() {
1339        let mut values = Vec::new();
1340        for number in [
1341            i64::MIN,
1342            -1,
1343            0,
1344            9_007_199_254_740_992,
1345            9_007_199_254_740_993,
1346            i64::MAX,
1347        ] {
1348            values.push((Value::Integer(number), i128::from(number)));
1349            values.push((Value::BigInt(number), i128::from(number)));
1350        }
1351        for number in [
1352            0,
1353            9_007_199_254_740_992,
1354            9_007_199_254_740_993,
1355            i64::MAX as u64,
1356            u64::MAX - 1,
1357            u64::MAX,
1358        ] {
1359            values.push((Value::UnsignedInteger(number), i128::from(number)));
1360        }
1361        for (left, left_exact) in &values {
1362            for (right, right_exact) in &values {
1363                for (operator, expected) in [
1364                    (BinOp::Eq, left_exact == right_exact),
1365                    (BinOp::Ne, left_exact != right_exact),
1366                    (BinOp::Lt, left_exact < right_exact),
1367                    (BinOp::Le, left_exact <= right_exact),
1368                    (BinOp::Gt, left_exact > right_exact),
1369                    (BinOp::Ge, left_exact >= right_exact),
1370                ] {
1371                    let expression = binop(operator, lit(left.clone()), lit(right.clone()));
1372                    assert_eq!(
1373                        evaluate(&expression, &empty_row())
1374                            .expect("integer comparison is supported"),
1375                        Value::Boolean(expected),
1376                        "{left:?} {operator:?} {right:?}"
1377                    );
1378                }
1379                for negated in [false, true] {
1380                    let expected = (*left_exact == *right_exact) != negated;
1381                    let membership = Expr::InList {
1382                        target: Box::new(lit(left.clone())),
1383                        values: vec![lit(right.clone())],
1384                        negated,
1385                        span: Span::synthetic(),
1386                    };
1387                    let interval = Expr::Between {
1388                        target: Box::new(lit(left.clone())),
1389                        low: Box::new(lit(right.clone())),
1390                        high: Box::new(lit(right.clone())),
1391                        negated,
1392                        span: Span::synthetic(),
1393                    };
1394                    for expression in [membership, interval] {
1395                        assert_eq!(
1396                            evaluate(&expression, &empty_row())
1397                                .expect("integer predicate is supported"),
1398                            Value::Boolean(expected),
1399                            "{expression:?}"
1400                        );
1401                    }
1402                }
1403            }
1404        }
1405    }
1406
1407    #[test]
1408    fn implicit_cast_promotes_integer_to_float_for_float_addition() {
1409        // The catalog has +(Integer, Float) -> Float as a direct
1410        // entry, so no actual coercion is inserted, but the result
1411        // must still be Float.
1412        let expr = binop(BinOp::Add, lit(Value::Integer(2)), lit(Value::Float(0.5)));
1413        let v = evaluate(&expr, &empty_row()).unwrap();
1414        assert_eq!(v, Value::Float(2.5));
1415    }
1416
1417    #[test]
1418    fn integer_division_promotes_to_float() {
1419        let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(2)));
1420        let v = evaluate(&expr, &empty_row()).unwrap();
1421        assert_eq!(v, Value::Float(3.5));
1422    }
1423
1424    #[test]
1425    fn division_by_zero_is_eval_error() {
1426        let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(0)));
1427        let err = evaluate(&expr, &empty_row()).unwrap_err();
1428        assert_eq!(err, EvalError::DivisionByZero);
1429    }
1430
1431    #[test]
1432    fn unknown_function_surfaces_as_eval_error() {
1433        let expr = Expr::FunctionCall {
1434            name: "no_such_fn".to_string(),
1435            args: vec![lit(Value::Integer(1))],
1436            span: Span::synthetic(),
1437        };
1438        let err = evaluate(&expr, &empty_row()).unwrap_err();
1439        match err {
1440            EvalError::UnknownFunction { name, args } => {
1441                assert_eq!(name, "no_such_fn");
1442                assert_eq!(args, vec![DataType::Integer]);
1443            }
1444            other => panic!("expected UnknownFunction, got {other:?}"),
1445        }
1446    }
1447
1448    #[test]
1449    fn coalesce_returns_first_non_null() {
1450        let expr = Expr::FunctionCall {
1451            name: "COALESCE".to_string(),
1452            args: vec![
1453                lit(Value::Null),
1454                lit(Value::Null),
1455                lit(Value::Integer(42)),
1456                lit(Value::Integer(99)),
1457            ],
1458            span: Span::synthetic(),
1459        };
1460        let v = evaluate(&expr, &empty_row()).unwrap();
1461        assert_eq!(v, Value::Integer(42));
1462    }
1463
1464    #[test]
1465    fn coalesce_all_null_returns_null() {
1466        let expr = Expr::FunctionCall {
1467            name: "COALESCE".to_string(),
1468            args: vec![lit(Value::Null), lit(Value::Null)],
1469            span: Span::synthetic(),
1470        };
1471        let v = evaluate(&expr, &empty_row()).unwrap();
1472        assert_eq!(v, Value::Null);
1473    }
1474
1475    #[test]
1476    fn upper_lower_dispatch_through_function_catalog() {
1477        let expr = Expr::FunctionCall {
1478            name: "UPPER".to_string(),
1479            args: vec![lit(Value::Text(Arc::from("hello")))],
1480            span: Span::synthetic(),
1481        };
1482        let v = evaluate(&expr, &empty_row()).unwrap();
1483        assert_eq!(v, Value::Text(Arc::from("HELLO")));
1484    }
1485
1486    #[test]
1487    fn length_of_null_propagates() {
1488        let expr = Expr::FunctionCall {
1489            name: "LENGTH".to_string(),
1490            args: vec![lit(Value::Null)],
1491            span: Span::synthetic(),
1492        };
1493        let v = evaluate(&expr, &empty_row()).unwrap();
1494        assert_eq!(v, Value::Null);
1495    }
1496
1497    #[test]
1498    fn case_when_picks_first_true_branch() {
1499        let expr = Expr::Case {
1500            branches: vec![
1501                (lit(Value::Boolean(false)), lit(Value::Integer(1))),
1502                (lit(Value::Boolean(true)), lit(Value::Integer(2))),
1503                (lit(Value::Boolean(true)), lit(Value::Integer(3))),
1504            ],
1505            else_: Some(Box::new(lit(Value::Integer(99)))),
1506            span: Span::synthetic(),
1507        };
1508        let v = evaluate(&expr, &empty_row()).unwrap();
1509        assert_eq!(v, Value::Integer(2));
1510    }
1511
1512    #[test]
1513    fn case_falls_through_to_else_when_no_branch_matches() {
1514        let expr = Expr::Case {
1515            branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1516            else_: Some(Box::new(lit(Value::Integer(99)))),
1517            span: Span::synthetic(),
1518        };
1519        let v = evaluate(&expr, &empty_row()).unwrap();
1520        assert_eq!(v, Value::Integer(99));
1521    }
1522
1523    #[test]
1524    fn case_returns_null_when_no_branch_matches_and_no_else() {
1525        let expr = Expr::Case {
1526            branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1527            else_: None,
1528            span: Span::synthetic(),
1529        };
1530        let v = evaluate(&expr, &empty_row()).unwrap();
1531        assert_eq!(v, Value::Null);
1532    }
1533
1534    #[test]
1535    fn is_null_handles_null_and_non_null() {
1536        let null_expr = Expr::IsNull {
1537            operand: Box::new(lit(Value::Null)),
1538            negated: false,
1539            span: Span::synthetic(),
1540        };
1541        assert_eq!(
1542            evaluate(&null_expr, &empty_row()).unwrap(),
1543            Value::Boolean(true)
1544        );
1545
1546        let non_null_expr = Expr::IsNull {
1547            operand: Box::new(lit(Value::Integer(7))),
1548            negated: false,
1549            span: Span::synthetic(),
1550        };
1551        assert_eq!(
1552            evaluate(&non_null_expr, &empty_row()).unwrap(),
1553            Value::Boolean(false)
1554        );
1555    }
1556
1557    #[test]
1558    fn between_inclusive_bounds() {
1559        let expr = Expr::Between {
1560            target: Box::new(lit(Value::Integer(5))),
1561            low: Box::new(lit(Value::Integer(1))),
1562            high: Box::new(lit(Value::Integer(10))),
1563            negated: false,
1564            span: Span::synthetic(),
1565        };
1566        assert_eq!(evaluate(&expr, &empty_row()).unwrap(), Value::Boolean(true));
1567    }
1568
1569    #[test]
1570    fn in_list_match_and_miss() {
1571        let hit = Expr::InList {
1572            target: Box::new(lit(Value::Integer(2))),
1573            values: vec![
1574                lit(Value::Integer(1)),
1575                lit(Value::Integer(2)),
1576                lit(Value::Integer(3)),
1577            ],
1578            negated: false,
1579            span: Span::synthetic(),
1580        };
1581        assert_eq!(evaluate(&hit, &empty_row()).unwrap(), Value::Boolean(true));
1582
1583        let miss = Expr::InList {
1584            target: Box::new(lit(Value::Integer(99))),
1585            values: vec![lit(Value::Integer(1)), lit(Value::Integer(2))],
1586            negated: false,
1587            span: Span::synthetic(),
1588        };
1589        assert_eq!(
1590            evaluate(&miss, &empty_row()).unwrap(),
1591            Value::Boolean(false)
1592        );
1593    }
1594
1595    #[test]
1596    fn column_lookup_walks_field_ref() {
1597        let row = |field: &FieldRef| -> Option<Value> {
1598            match field {
1599                FieldRef::TableColumn { table, column } if table == "t" && column == "x" => {
1600                    Some(Value::Integer(11))
1601                }
1602                _ => None,
1603            }
1604        };
1605        let expr = Expr::Column {
1606            field: FieldRef::TableColumn {
1607                table: "t".to_string(),
1608                column: "x".to_string(),
1609            },
1610            span: Span::synthetic(),
1611        };
1612        assert_eq!(evaluate(&expr, &row).unwrap(), Value::Integer(11));
1613    }
1614
1615    #[test]
1616    fn missing_column_surfaces_unknown_column() {
1617        let row = |_: &FieldRef| -> Option<Value> { None };
1618        let expr = Expr::Column {
1619            field: FieldRef::TableColumn {
1620                table: "t".to_string(),
1621                column: "missing".to_string(),
1622            },
1623            span: Span::synthetic(),
1624        };
1625        let err = evaluate(&expr, &row).unwrap_err();
1626        match err {
1627            EvalError::UnknownColumn(_) => {}
1628            other => panic!("expected UnknownColumn, got {other:?}"),
1629        }
1630    }
1631
1632    #[test]
1633    fn parameter_without_bind_is_eval_error() {
1634        let expr = Expr::Parameter {
1635            index: 1,
1636            span: Span::synthetic(),
1637        };
1638        let err = evaluate(&expr, &empty_row()).unwrap_err();
1639        assert_eq!(err, EvalError::UnboundParameter(1));
1640    }
1641
1642    #[test]
1643    fn cast_integer_to_text_uses_explicit_path() {
1644        let expr = Expr::Cast {
1645            inner: Box::new(lit(Value::Integer(42))),
1646            target: DataType::Text,
1647            span: Span::synthetic(),
1648        };
1649        assert_eq!(
1650            evaluate(&expr, &empty_row()).unwrap(),
1651            Value::Text(Arc::from("42"))
1652        );
1653    }
1654
1655    #[test]
1656    fn concat_returns_text() {
1657        let expr = binop(
1658            BinOp::Concat,
1659            lit(Value::Text(Arc::from("foo"))),
1660            lit(Value::Text(Arc::from("bar"))),
1661        );
1662        assert_eq!(
1663            evaluate(&expr, &empty_row()).unwrap(),
1664            Value::Text(Arc::from("foobar"))
1665        );
1666    }
1667}