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        "TRIM" => trim_function(name, args, true, true),
697        "LTRIM" => trim_function(name, args, true, false),
698        "RTRIM" => trim_function(name, args, false, true),
699        "LENGTH" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => match &args[0] {
700            Value::Text(s) => Ok(Value::Integer(s.chars().count() as i64)),
701            other => Err(EvalError::UnknownFunction {
702                name: name.to_string(),
703                args: vec![other.data_type()],
704            }),
705        },
706        "OCTET_LENGTH" => match &args[0] {
707            Value::Text(s) => Ok(Value::Integer(s.len() as i64)),
708            Value::Blob(b) => Ok(Value::Integer(b.len() as i64)),
709            other => Err(EvalError::UnknownFunction {
710                name: name.to_string(),
711                args: vec![other.data_type()],
712            }),
713        },
714        "JSON_EXTRACT" => Ok(json_extract_value(&args[0], &args[1], false)),
715        "JSON_EXTRACT_TEXT" => Ok(json_extract_value(&args[0], &args[1], true)),
716        "CONTAINS" => Ok(contains_value(&args[0], &args[1])),
717        "ABS" => match &args[0] {
718            Value::Integer(n) => n
719                .checked_abs()
720                .map(Value::Integer)
721                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
722            Value::BigInt(n) => n
723                .checked_abs()
724                .map(Value::BigInt)
725                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
726            Value::Float(n) => Ok(Value::Float(n.abs())),
727            Value::Decimal(n) => n
728                .checked_abs()
729                .map(Value::Decimal)
730                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
731            other => Err(EvalError::UnknownFunction {
732                name: name.to_string(),
733                args: vec![other.data_type()],
734            }),
735        },
736        "SQRT" => unary_math(name, args, |x| {
737            if x < 0.0 {
738                return Err("input must be greater than or equal to zero");
739            }
740            Ok(x.sqrt())
741        }),
742        "POWER" | "POW" => binary_math(name, args, |base, exp| Ok(base.powf(exp))),
743        "EXP" => unary_math(name, args, |x| Ok(x.exp())),
744        "LN" => unary_math(name, args, |x| {
745            if x <= 0.0 {
746                return Err("input must be greater than zero");
747            }
748            Ok(x.ln())
749        }),
750        "LOG" if args.len() == 1 => unary_math(name, args, |x| {
751            if x <= 0.0 {
752                return Err("input must be greater than zero");
753            }
754            Ok(x.log10())
755        }),
756        "LOG" => binary_math(name, args, |base, x| {
757            if base <= 0.0 {
758                return Err("base must be greater than zero");
759            }
760            if base == 1.0 {
761                return Err("base must not equal one");
762            }
763            if x <= 0.0 {
764                return Err("input must be greater than zero");
765            }
766            Ok(x.log(base))
767        }),
768        "LOG10" => unary_math(name, args, |x| {
769            if x <= 0.0 {
770                return Err("input must be greater than zero");
771            }
772            Ok(x.log10())
773        }),
774        "SIN" => unary_math(name, args, |x| Ok(x.sin())),
775        "COS" => unary_math(name, args, |x| Ok(x.cos())),
776        "TAN" => unary_math(name, args, |x| Ok(x.tan())),
777        "ASIN" | "ARCSIN" => unary_math(name, args, |x| {
778            if !(-1.0..=1.0).contains(&x) {
779                return Err("input must be between -1 and 1");
780            }
781            Ok(x.asin())
782        }),
783        "ACOS" | "ARCCOS" => unary_math(name, args, |x| {
784            if !(-1.0..=1.0).contains(&x) {
785                return Err("input must be between -1 and 1");
786            }
787            Ok(x.acos())
788        }),
789        "ATAN" | "ARCTAN" => unary_math(name, args, |x| Ok(x.atan())),
790        "ATAN2" => binary_math(name, args, |y, x| Ok(y.atan2(x))),
791        "COT" => unary_math(name, args, |x| {
792            let tan = x.tan();
793            if tan == 0.0 {
794                return Err("input must not produce zero tangent");
795            }
796            Ok(1.0 / tan)
797        }),
798        "DEGREES" => unary_math(name, args, |x| Ok(x.to_degrees())),
799        "RADIANS" => unary_math(name, args, |x| Ok(x.to_radians())),
800        "PI" => checked_math_result(name, std::f64::consts::PI),
801        // Functions whose runtime body the slice doesn't yet cover
802        // surface as UnknownFunction with the resolved arg types so
803        // callers can see the catalog matched but the dispatch
804        // didn't. Subsequent slices fill in CONCAT, time functions, …
805        other => Err(EvalError::UnknownFunction {
806            name: other.to_string(),
807            args: args.iter().map(|v| v.data_type()).collect(),
808        }),
809    }
810}
811
812fn trim_function(name: &str, args: &[Value], left: bool, right: bool) -> Result<Value, EvalError> {
813    if args.len() != 1 {
814        return Err(EvalError::UnknownFunction {
815            name: name.to_string(),
816            args: args.iter().map(|v| v.data_type()).collect(),
817        });
818    }
819
820    match &args[0] {
821        Value::Text(s) => {
822            let trimmed = match (left, right) {
823                (true, true) => s.trim(),
824                (true, false) => s.trim_start(),
825                (false, true) => s.trim_end(),
826                (false, false) => s,
827            };
828            Ok(Value::Text(Arc::from(trimmed)))
829        }
830        other => Err(EvalError::UnknownFunction {
831            name: name.to_string(),
832            args: vec![other.data_type()],
833        }),
834    }
835}
836
837fn unary_math<F>(name: &str, args: &[Value], op: F) -> Result<Value, EvalError>
838where
839    F: FnOnce(f64) -> Result<f64, &'static str>,
840{
841    let input = math_arg(name, args.first(), 0)?;
842    let value = op(input).map_err(|reason| EvalError::InvalidNumericResult {
843        function: name.to_string(),
844        reason: reason.to_string(),
845    })?;
846    checked_math_result(name, value)
847}
848
849fn binary_math<F>(name: &str, args: &[Value], op: F) -> Result<Value, EvalError>
850where
851    F: FnOnce(f64, f64) -> Result<f64, &'static str>,
852{
853    let left = math_arg(name, args.first(), 0)?;
854    let right = math_arg(name, args.get(1), 1)?;
855    let value = op(left, right).map_err(|reason| EvalError::InvalidNumericResult {
856        function: name.to_string(),
857        reason: reason.to_string(),
858    })?;
859    checked_math_result(name, value)
860}
861
862fn math_arg(name: &str, value: Option<&Value>, index: usize) -> Result<f64, EvalError> {
863    let value = value.ok_or_else(|| EvalError::UnknownFunction {
864        name: name.to_string(),
865        args: Vec::new(),
866    })?;
867    let numeric = as_f64(value);
868    if numeric.is_finite() {
869        Ok(numeric)
870    } else {
871        Err(EvalError::InvalidNumericResult {
872            function: name.to_string(),
873            reason: format!("argument {index} is NaN or infinite"),
874        })
875    }
876}
877
878fn checked_math_result(name: &str, value: f64) -> Result<Value, EvalError> {
879    if value.is_finite() {
880        Ok(Value::Float(value))
881    } else {
882        Err(EvalError::InvalidNumericResult {
883            function: name.to_string(),
884            reason: "result is NaN or infinite".to_string(),
885        })
886    }
887}
888
889fn json_extract_value(input: &Value, path: &Value, as_text: bool) -> Value {
890    let Value::Text(path) = path else {
891        return Value::Null;
892    };
893    let Some(json) = value_to_json(input) else {
894        return Value::Null;
895    };
896    let Some(steps) = parse_json_path(path) else {
897        return Value::Null;
898    };
899    let Some(target) = json_path_get(&json, &steps) else {
900        return Value::Null;
901    };
902
903    if as_text {
904        match target {
905            crate::serde_json::Value::String(value) => Value::text(value.clone()),
906            crate::serde_json::Value::Null => Value::Null,
907            crate::serde_json::Value::Bool(value) => Value::text(value.to_string()),
908            crate::serde_json::Value::Number(value) => Value::text(value.to_string()),
909            other => Value::text(other.to_string_compact()),
910        }
911    } else {
912        Value::text(target.to_string_compact())
913    }
914}
915
916fn contains_value(input: &Value, needle: &Value) -> Value {
917    let Value::Text(needle) = needle else {
918        return Value::Null;
919    };
920    Value::Boolean(value_contains(input, needle))
921}
922
923fn value_contains(value: &Value, needle: &str) -> bool {
924    match value {
925        Value::Array(values) => values.iter().any(|value| value_contains(value, needle)),
926        Value::Json(_) => value_to_json(value)
927            .as_ref()
928            .is_some_and(|json| json_value_contains(json, needle)),
929        Value::Text(value) => value.contains(needle),
930        other => other.display_string().contains(needle),
931    }
932}
933
934fn json_value_contains(value: &crate::serde_json::Value, needle: &str) -> bool {
935    match value {
936        crate::serde_json::Value::Array(values) => values
937            .iter()
938            .any(|value| json_value_contains(value, needle)),
939        // Recurse into object *values* so a needle nested inside a JSON
940        // object matches — e.g. `CONTAINS(vision_detections, 'person')`
941        // over a derived `[{"label":"person",...}]` array (#1275). Keys
942        // are deliberately not matched: containment is about the data, not
943        // the field names.
944        crate::serde_json::Value::Object(map) => {
945            map.values().any(|value| json_value_contains(value, needle))
946        }
947        crate::serde_json::Value::String(value) => value == needle,
948        crate::serde_json::Value::Number(value) => value.to_string() == needle,
949        crate::serde_json::Value::Bool(value) => value.to_string() == needle,
950        crate::serde_json::Value::Null => false,
951    }
952}
953
954fn value_to_json(value: &Value) -> Option<crate::serde_json::Value> {
955    match value {
956        Value::Null => Some(crate::serde_json::Value::Null),
957        Value::Json(bytes) => crate::serde_json::from_slice(bytes).ok(),
958        Value::Text(value) => crate::serde_json::from_str(value).ok(),
959        _ => None,
960    }
961}
962
963enum JsonPathStep<'a> {
964    Field(&'a str),
965    Index(usize),
966}
967
968fn parse_json_path(path: &str) -> Option<Vec<JsonPathStep<'_>>> {
969    let path = path.trim();
970    let rest = path.strip_prefix('$').unwrap_or(path);
971    let mut steps = Vec::new();
972    let bytes = rest.as_bytes();
973    let mut index = 0;
974    while index < bytes.len() {
975        match bytes[index] {
976            b'.' => {
977                index += 1;
978                let start = index;
979                while index < bytes.len() && bytes[index] != b'.' && bytes[index] != b'[' {
980                    index += 1;
981                }
982                if start == index {
983                    return None;
984                }
985                steps.push(JsonPathStep::Field(
986                    std::str::from_utf8(&bytes[start..index]).ok()?,
987                ));
988            }
989            b'[' => {
990                index += 1;
991                let start = index;
992                while index < bytes.len() && bytes[index] != b']' {
993                    index += 1;
994                }
995                if index >= bytes.len() {
996                    return None;
997                }
998                steps.push(JsonPathStep::Index(
999                    std::str::from_utf8(&bytes[start..index])
1000                        .ok()?
1001                        .parse()
1002                        .ok()?,
1003                ));
1004                index += 1;
1005            }
1006            _ => return None,
1007        }
1008    }
1009    Some(steps)
1010}
1011
1012fn json_path_get<'a>(
1013    root: &'a crate::serde_json::Value,
1014    steps: &[JsonPathStep<'_>],
1015) -> Option<&'a crate::serde_json::Value> {
1016    let mut current = root;
1017    for step in steps {
1018        current = match (step, current) {
1019            (JsonPathStep::Field(name), crate::serde_json::Value::Object(map)) => map.get(*name)?,
1020            (JsonPathStep::Index(index), crate::serde_json::Value::Array(values)) => {
1021                values.get(*index)?
1022            }
1023            _ => return None,
1024        };
1025    }
1026    Some(current)
1027}
1028
1029fn eval_case(
1030    branches: &[(Expr, Expr)],
1031    else_: Option<&Expr>,
1032    row: &dyn Row,
1033) -> Result<Value, EvalError> {
1034    for (cond, value) in branches {
1035        let c = evaluate(cond, row)?;
1036        if matches!(c, Value::Boolean(true)) {
1037            return evaluate(value, row);
1038        }
1039    }
1040    match else_ {
1041        Some(e) => evaluate(e, row),
1042        None => Ok(Value::Null),
1043    }
1044}
1045
1046fn eval_in_list(
1047    target: &Expr,
1048    values: &[Expr],
1049    negated: bool,
1050    row: &dyn Row,
1051) -> Result<Value, EvalError> {
1052    if values.is_empty() {
1053        return Err(EvalError::EmptyInList);
1054    }
1055    let needle = evaluate(target, row)?;
1056    if needle.is_null() {
1057        return Ok(Value::Null);
1058    }
1059    let mut saw_null = false;
1060    for v in values {
1061        let candidate = evaluate(v, row)?;
1062        if candidate.is_null() {
1063            saw_null = true;
1064            continue;
1065        }
1066        if values_equal(&needle, &candidate) {
1067            return Ok(Value::Boolean(!negated));
1068        }
1069    }
1070    if saw_null {
1071        return Ok(Value::Null);
1072    }
1073    Ok(Value::Boolean(negated))
1074}
1075
1076fn eval_between(
1077    target: &Expr,
1078    low: &Expr,
1079    high: &Expr,
1080    negated: bool,
1081    row: &dyn Row,
1082) -> Result<Value, EvalError> {
1083    let v = evaluate(target, row)?;
1084    let lo = evaluate(low, row)?;
1085    let hi = evaluate(high, row)?;
1086    if v.is_null() || lo.is_null() || hi.is_null() {
1087        return Ok(Value::Null);
1088    }
1089    let lo_ok = compare_values(&v, &lo)
1090        .ok_or(EvalError::OperatorMismatch {
1091            op: BinOp::Ge,
1092            lhs: v.data_type(),
1093            rhs: lo.data_type(),
1094        })
1095        .map(|o| o != std::cmp::Ordering::Less)?;
1096    let hi_ok = compare_values(&v, &hi)
1097        .ok_or(EvalError::OperatorMismatch {
1098            op: BinOp::Le,
1099            lhs: v.data_type(),
1100            rhs: hi.data_type(),
1101        })
1102        .map(|o| o != std::cmp::Ordering::Greater)?;
1103    let inside = lo_ok && hi_ok;
1104    Ok(Value::Boolean(if negated { !inside } else { inside }))
1105}
1106
1107#[cfg(test)]
1108mod tests {
1109    use super::*;
1110    use crate::storage::query::ast::Span;
1111
1112    fn lit(v: Value) -> Expr {
1113        Expr::Literal {
1114            value: v,
1115            span: Span::synthetic(),
1116        }
1117    }
1118
1119    fn binop(op: BinOp, l: Expr, r: Expr) -> Expr {
1120        Expr::BinaryOp {
1121            op,
1122            lhs: Box::new(l),
1123            rhs: Box::new(r),
1124            span: Span::synthetic(),
1125        }
1126    }
1127
1128    fn empty_row() -> impl Row {
1129        |_field: &FieldRef| -> Option<Value> { None }
1130    }
1131
1132    #[test]
1133    fn contains_descends_into_json_object_values() {
1134        // A derived component-detections array (#1275): CONTAINS must find
1135        // a label nested inside the object elements, not just top-level
1136        // array strings.
1137        let detections = br#"[{"label":"person","confidence":0.9,"bbox":[0,0,1,1]},
1138                              {"label":"car","confidence":0.7,"bbox":[1,1,2,2]}]"#;
1139        let json = Value::Json(detections.to_vec());
1140        assert!(value_contains(&json, "person"));
1141        assert!(value_contains(&json, "car"));
1142        assert!(!value_contains(&json, "bicycle"));
1143        // Keys are not matched — only the data.
1144        assert!(!value_contains(&json, "label"));
1145    }
1146
1147    #[test]
1148    fn integer_addition_overflow_surfaces_as_eval_error() {
1149        let expr = binop(
1150            BinOp::Add,
1151            lit(Value::Integer(i64::MAX)),
1152            lit(Value::Integer(1)),
1153        );
1154        let err = evaluate(&expr, &empty_row()).unwrap_err();
1155        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Add });
1156    }
1157
1158    #[test]
1159    fn integer_multiplication_overflow_surfaces_as_eval_error() {
1160        let expr = binop(
1161            BinOp::Mul,
1162            lit(Value::Integer(i64::MAX)),
1163            lit(Value::Integer(2)),
1164        );
1165        let err = evaluate(&expr, &empty_row()).unwrap_err();
1166        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Mul });
1167    }
1168
1169    #[test]
1170    fn integer_subtraction_overflow_surfaces_as_eval_error() {
1171        let expr = binop(
1172            BinOp::Sub,
1173            lit(Value::Integer(i64::MIN)),
1174            lit(Value::Integer(1)),
1175        );
1176        let err = evaluate(&expr, &empty_row()).unwrap_err();
1177        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1178    }
1179
1180    #[test]
1181    fn unary_neg_overflow_on_min_int_surfaces_as_eval_error() {
1182        let expr = Expr::UnaryOp {
1183            op: UnaryOp::Neg,
1184            operand: Box::new(lit(Value::Integer(i64::MIN))),
1185            span: Span::synthetic(),
1186        };
1187        let err = evaluate(&expr, &empty_row()).unwrap_err();
1188        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1189    }
1190
1191    #[test]
1192    fn null_propagates_through_arithmetic() {
1193        let expr = binop(BinOp::Add, lit(Value::Null), lit(Value::Integer(7)));
1194        let v = evaluate(&expr, &empty_row()).unwrap();
1195        assert_eq!(v, Value::Null);
1196    }
1197
1198    #[test]
1199    fn null_propagates_through_comparison() {
1200        let expr = binop(BinOp::Lt, lit(Value::Integer(1)), lit(Value::Null));
1201        let v = evaluate(&expr, &empty_row()).unwrap();
1202        assert_eq!(v, Value::Null);
1203    }
1204
1205    #[test]
1206    fn null_propagates_through_concat() {
1207        let expr = binop(
1208            BinOp::Concat,
1209            lit(Value::Text(Arc::from("hi"))),
1210            lit(Value::Null),
1211        );
1212        let v = evaluate(&expr, &empty_row()).unwrap();
1213        assert_eq!(v, Value::Null);
1214    }
1215
1216    #[test]
1217    fn three_valued_and_returns_false_when_one_side_false_even_with_null() {
1218        let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(false)));
1219        let v = evaluate(&expr, &empty_row()).unwrap();
1220        assert_eq!(v, Value::Boolean(false));
1221    }
1222
1223    #[test]
1224    fn three_valued_or_returns_true_when_one_side_true_even_with_null() {
1225        let expr = binop(BinOp::Or, lit(Value::Null), lit(Value::Boolean(true)));
1226        let v = evaluate(&expr, &empty_row()).unwrap();
1227        assert_eq!(v, Value::Boolean(true));
1228    }
1229
1230    #[test]
1231    fn three_valued_and_returns_null_for_null_and_true() {
1232        let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(true)));
1233        let v = evaluate(&expr, &empty_row()).unwrap();
1234        assert_eq!(v, Value::Null);
1235    }
1236
1237    #[test]
1238    fn implicit_cast_triggers_for_decimal_plus_integer() {
1239        // Operator catalog has +(Decimal, Decimal) -> Decimal as
1240        // the only overload that survives coercion (no Decimal ->
1241        // numeric implicit casts exist in the cast catalog). The
1242        // spine therefore inserts an Integer -> Decimal cast on
1243        // the rhs and dispatches the Decimal addition.
1244        // parse_decimal scales by 10^4, so Integer(2) coerces to
1245        // Decimal(20000) and Decimal(10000) + Decimal(20000) =
1246        // Decimal(30000) (fixed-point 3.0000).
1247        let expr = binop(
1248            BinOp::Add,
1249            lit(Value::Decimal(10000)),
1250            lit(Value::Integer(2)),
1251        );
1252        let v = evaluate(&expr, &empty_row()).unwrap();
1253        assert_eq!(v, Value::Decimal(30000));
1254    }
1255
1256    #[test]
1257    fn integer_plus_bigint_resolves_to_preferred_float_overload() {
1258        // (Integer, BigInt) has no exact match. The spine ties
1259        // between +(Integer, Float, Float) (rhs cast BigInt->Float)
1260        // and +(BigInt, BigInt, BigInt) (lhs cast Integer->BigInt).
1261        // Float wins on the preferred-return-type tie-break, so the
1262        // BigInt operand is coerced to Float and the result is Float.
1263        let expr = binop(
1264            BinOp::Add,
1265            lit(Value::Integer(5)),
1266            lit(Value::BigInt(40_000_000_000)),
1267        );
1268        let v = evaluate(&expr, &empty_row()).unwrap();
1269        assert_eq!(v, Value::Float(40_000_000_005.0));
1270    }
1271
1272    #[test]
1273    fn implicit_cast_promotes_integer_to_float_for_float_addition() {
1274        // The catalog has +(Integer, Float) -> Float as a direct
1275        // entry, so no actual coercion is inserted, but the result
1276        // must still be Float.
1277        let expr = binop(BinOp::Add, lit(Value::Integer(2)), lit(Value::Float(0.5)));
1278        let v = evaluate(&expr, &empty_row()).unwrap();
1279        assert_eq!(v, Value::Float(2.5));
1280    }
1281
1282    #[test]
1283    fn integer_division_promotes_to_float() {
1284        let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(2)));
1285        let v = evaluate(&expr, &empty_row()).unwrap();
1286        assert_eq!(v, Value::Float(3.5));
1287    }
1288
1289    #[test]
1290    fn division_by_zero_is_eval_error() {
1291        let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(0)));
1292        let err = evaluate(&expr, &empty_row()).unwrap_err();
1293        assert_eq!(err, EvalError::DivisionByZero);
1294    }
1295
1296    #[test]
1297    fn unknown_function_surfaces_as_eval_error() {
1298        let expr = Expr::FunctionCall {
1299            name: "no_such_fn".to_string(),
1300            args: vec![lit(Value::Integer(1))],
1301            span: Span::synthetic(),
1302        };
1303        let err = evaluate(&expr, &empty_row()).unwrap_err();
1304        match err {
1305            EvalError::UnknownFunction { name, args } => {
1306                assert_eq!(name, "no_such_fn");
1307                assert_eq!(args, vec![DataType::Integer]);
1308            }
1309            other => panic!("expected UnknownFunction, got {other:?}"),
1310        }
1311    }
1312
1313    #[test]
1314    fn coalesce_returns_first_non_null() {
1315        let expr = Expr::FunctionCall {
1316            name: "COALESCE".to_string(),
1317            args: vec![
1318                lit(Value::Null),
1319                lit(Value::Null),
1320                lit(Value::Integer(42)),
1321                lit(Value::Integer(99)),
1322            ],
1323            span: Span::synthetic(),
1324        };
1325        let v = evaluate(&expr, &empty_row()).unwrap();
1326        assert_eq!(v, Value::Integer(42));
1327    }
1328
1329    #[test]
1330    fn coalesce_all_null_returns_null() {
1331        let expr = Expr::FunctionCall {
1332            name: "COALESCE".to_string(),
1333            args: vec![lit(Value::Null), lit(Value::Null)],
1334            span: Span::synthetic(),
1335        };
1336        let v = evaluate(&expr, &empty_row()).unwrap();
1337        assert_eq!(v, Value::Null);
1338    }
1339
1340    #[test]
1341    fn upper_lower_dispatch_through_function_catalog() {
1342        let expr = Expr::FunctionCall {
1343            name: "UPPER".to_string(),
1344            args: vec![lit(Value::Text(Arc::from("hello")))],
1345            span: Span::synthetic(),
1346        };
1347        let v = evaluate(&expr, &empty_row()).unwrap();
1348        assert_eq!(v, Value::Text(Arc::from("HELLO")));
1349    }
1350
1351    #[test]
1352    fn length_of_null_propagates() {
1353        let expr = Expr::FunctionCall {
1354            name: "LENGTH".to_string(),
1355            args: vec![lit(Value::Null)],
1356            span: Span::synthetic(),
1357        };
1358        let v = evaluate(&expr, &empty_row()).unwrap();
1359        assert_eq!(v, Value::Null);
1360    }
1361
1362    #[test]
1363    fn case_when_picks_first_true_branch() {
1364        let expr = Expr::Case {
1365            branches: vec![
1366                (lit(Value::Boolean(false)), lit(Value::Integer(1))),
1367                (lit(Value::Boolean(true)), lit(Value::Integer(2))),
1368                (lit(Value::Boolean(true)), lit(Value::Integer(3))),
1369            ],
1370            else_: Some(Box::new(lit(Value::Integer(99)))),
1371            span: Span::synthetic(),
1372        };
1373        let v = evaluate(&expr, &empty_row()).unwrap();
1374        assert_eq!(v, Value::Integer(2));
1375    }
1376
1377    #[test]
1378    fn case_falls_through_to_else_when_no_branch_matches() {
1379        let expr = Expr::Case {
1380            branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1381            else_: Some(Box::new(lit(Value::Integer(99)))),
1382            span: Span::synthetic(),
1383        };
1384        let v = evaluate(&expr, &empty_row()).unwrap();
1385        assert_eq!(v, Value::Integer(99));
1386    }
1387
1388    #[test]
1389    fn case_returns_null_when_no_branch_matches_and_no_else() {
1390        let expr = Expr::Case {
1391            branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1392            else_: None,
1393            span: Span::synthetic(),
1394        };
1395        let v = evaluate(&expr, &empty_row()).unwrap();
1396        assert_eq!(v, Value::Null);
1397    }
1398
1399    #[test]
1400    fn is_null_handles_null_and_non_null() {
1401        let null_expr = Expr::IsNull {
1402            operand: Box::new(lit(Value::Null)),
1403            negated: false,
1404            span: Span::synthetic(),
1405        };
1406        assert_eq!(
1407            evaluate(&null_expr, &empty_row()).unwrap(),
1408            Value::Boolean(true)
1409        );
1410
1411        let non_null_expr = Expr::IsNull {
1412            operand: Box::new(lit(Value::Integer(7))),
1413            negated: false,
1414            span: Span::synthetic(),
1415        };
1416        assert_eq!(
1417            evaluate(&non_null_expr, &empty_row()).unwrap(),
1418            Value::Boolean(false)
1419        );
1420    }
1421
1422    #[test]
1423    fn between_inclusive_bounds() {
1424        let expr = Expr::Between {
1425            target: Box::new(lit(Value::Integer(5))),
1426            low: Box::new(lit(Value::Integer(1))),
1427            high: Box::new(lit(Value::Integer(10))),
1428            negated: false,
1429            span: Span::synthetic(),
1430        };
1431        assert_eq!(evaluate(&expr, &empty_row()).unwrap(), Value::Boolean(true));
1432    }
1433
1434    #[test]
1435    fn in_list_match_and_miss() {
1436        let hit = Expr::InList {
1437            target: Box::new(lit(Value::Integer(2))),
1438            values: vec![
1439                lit(Value::Integer(1)),
1440                lit(Value::Integer(2)),
1441                lit(Value::Integer(3)),
1442            ],
1443            negated: false,
1444            span: Span::synthetic(),
1445        };
1446        assert_eq!(evaluate(&hit, &empty_row()).unwrap(), Value::Boolean(true));
1447
1448        let miss = Expr::InList {
1449            target: Box::new(lit(Value::Integer(99))),
1450            values: vec![lit(Value::Integer(1)), lit(Value::Integer(2))],
1451            negated: false,
1452            span: Span::synthetic(),
1453        };
1454        assert_eq!(
1455            evaluate(&miss, &empty_row()).unwrap(),
1456            Value::Boolean(false)
1457        );
1458    }
1459
1460    #[test]
1461    fn column_lookup_walks_field_ref() {
1462        let row = |field: &FieldRef| -> Option<Value> {
1463            match field {
1464                FieldRef::TableColumn { table, column } if table == "t" && column == "x" => {
1465                    Some(Value::Integer(11))
1466                }
1467                _ => None,
1468            }
1469        };
1470        let expr = Expr::Column {
1471            field: FieldRef::TableColumn {
1472                table: "t".to_string(),
1473                column: "x".to_string(),
1474            },
1475            span: Span::synthetic(),
1476        };
1477        assert_eq!(evaluate(&expr, &row).unwrap(), Value::Integer(11));
1478    }
1479
1480    #[test]
1481    fn missing_column_surfaces_unknown_column() {
1482        let row = |_: &FieldRef| -> Option<Value> { None };
1483        let expr = Expr::Column {
1484            field: FieldRef::TableColumn {
1485                table: "t".to_string(),
1486                column: "missing".to_string(),
1487            },
1488            span: Span::synthetic(),
1489        };
1490        let err = evaluate(&expr, &row).unwrap_err();
1491        match err {
1492            EvalError::UnknownColumn(_) => {}
1493            other => panic!("expected UnknownColumn, got {other:?}"),
1494        }
1495    }
1496
1497    #[test]
1498    fn parameter_without_bind_is_eval_error() {
1499        let expr = Expr::Parameter {
1500            index: 1,
1501            span: Span::synthetic(),
1502        };
1503        let err = evaluate(&expr, &empty_row()).unwrap_err();
1504        assert_eq!(err, EvalError::UnboundParameter(1));
1505    }
1506
1507    #[test]
1508    fn cast_integer_to_text_uses_explicit_path() {
1509        let expr = Expr::Cast {
1510            inner: Box::new(lit(Value::Integer(42))),
1511            target: DataType::Text,
1512            span: Span::synthetic(),
1513        };
1514        assert_eq!(
1515            evaluate(&expr, &empty_row()).unwrap(),
1516            Value::Text(Arc::from("42"))
1517        );
1518    }
1519
1520    #[test]
1521    fn concat_returns_text() {
1522        let expr = binop(
1523            BinOp::Concat,
1524            lit(Value::Text(Arc::from("foo"))),
1525            lit(Value::Text(Arc::from("bar"))),
1526        );
1527        assert_eq!(
1528            evaluate(&expr, &empty_row()).unwrap(),
1529            Value::Text(Arc::from("foobar"))
1530        );
1531    }
1532}