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