Skip to main content

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