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        // A document body may be the native binary container (PRD-1398); decode
958        // it to JSON so json_extract / CONTAINS see the same shape as a plain
959        // JSON body.
960        Value::Json(bytes) => crate::document_body::decode_container_to_json(bytes)
961            .or_else(|| crate::serde_json::from_slice(bytes).ok()),
962        Value::Text(value) => crate::serde_json::from_str(value).ok(),
963        _ => None,
964    }
965}
966
967enum JsonPathStep<'a> {
968    Field(&'a str),
969    Index(usize),
970}
971
972fn parse_json_path(path: &str) -> Option<Vec<JsonPathStep<'_>>> {
973    let path = path.trim();
974    let rest = path.strip_prefix('$').unwrap_or(path);
975    let mut steps = Vec::new();
976    let bytes = rest.as_bytes();
977    let mut index = 0;
978    while index < bytes.len() {
979        match bytes[index] {
980            b'.' => {
981                index += 1;
982                let start = index;
983                while index < bytes.len() && bytes[index] != b'.' && bytes[index] != b'[' {
984                    index += 1;
985                }
986                if start == index {
987                    return None;
988                }
989                steps.push(JsonPathStep::Field(
990                    std::str::from_utf8(&bytes[start..index]).ok()?,
991                ));
992            }
993            b'[' => {
994                index += 1;
995                let start = index;
996                while index < bytes.len() && bytes[index] != b']' {
997                    index += 1;
998                }
999                if index >= bytes.len() {
1000                    return None;
1001                }
1002                steps.push(JsonPathStep::Index(
1003                    std::str::from_utf8(&bytes[start..index])
1004                        .ok()?
1005                        .parse()
1006                        .ok()?,
1007                ));
1008                index += 1;
1009            }
1010            _ => return None,
1011        }
1012    }
1013    Some(steps)
1014}
1015
1016fn json_path_get<'a>(
1017    root: &'a crate::serde_json::Value,
1018    steps: &[JsonPathStep<'_>],
1019) -> Option<&'a crate::serde_json::Value> {
1020    let mut current = root;
1021    for step in steps {
1022        current = match (step, current) {
1023            (JsonPathStep::Field(name), crate::serde_json::Value::Object(map)) => map.get(*name)?,
1024            (JsonPathStep::Index(index), crate::serde_json::Value::Array(values)) => {
1025                values.get(*index)?
1026            }
1027            _ => return None,
1028        };
1029    }
1030    Some(current)
1031}
1032
1033fn eval_case(
1034    branches: &[(Expr, Expr)],
1035    else_: Option<&Expr>,
1036    row: &dyn Row,
1037) -> Result<Value, EvalError> {
1038    for (cond, value) in branches {
1039        let c = evaluate(cond, row)?;
1040        if matches!(c, Value::Boolean(true)) {
1041            return evaluate(value, row);
1042        }
1043    }
1044    match else_ {
1045        Some(e) => evaluate(e, row),
1046        None => Ok(Value::Null),
1047    }
1048}
1049
1050fn eval_in_list(
1051    target: &Expr,
1052    values: &[Expr],
1053    negated: bool,
1054    row: &dyn Row,
1055) -> Result<Value, EvalError> {
1056    if values.is_empty() {
1057        return Err(EvalError::EmptyInList);
1058    }
1059    let needle = evaluate(target, row)?;
1060    if needle.is_null() {
1061        return Ok(Value::Null);
1062    }
1063    let mut saw_null = false;
1064    for v in values {
1065        let candidate = evaluate(v, row)?;
1066        if candidate.is_null() {
1067            saw_null = true;
1068            continue;
1069        }
1070        if values_equal(&needle, &candidate) {
1071            return Ok(Value::Boolean(!negated));
1072        }
1073    }
1074    if saw_null {
1075        return Ok(Value::Null);
1076    }
1077    Ok(Value::Boolean(negated))
1078}
1079
1080fn eval_between(
1081    target: &Expr,
1082    low: &Expr,
1083    high: &Expr,
1084    negated: bool,
1085    row: &dyn Row,
1086) -> Result<Value, EvalError> {
1087    let v = evaluate(target, row)?;
1088    let lo = evaluate(low, row)?;
1089    let hi = evaluate(high, row)?;
1090    if v.is_null() || lo.is_null() || hi.is_null() {
1091        return Ok(Value::Null);
1092    }
1093    let lo_ok = compare_values(&v, &lo)
1094        .ok_or(EvalError::OperatorMismatch {
1095            op: BinOp::Ge,
1096            lhs: v.data_type(),
1097            rhs: lo.data_type(),
1098        })
1099        .map(|o| o != std::cmp::Ordering::Less)?;
1100    let hi_ok = compare_values(&v, &hi)
1101        .ok_or(EvalError::OperatorMismatch {
1102            op: BinOp::Le,
1103            lhs: v.data_type(),
1104            rhs: hi.data_type(),
1105        })
1106        .map(|o| o != std::cmp::Ordering::Greater)?;
1107    let inside = lo_ok && hi_ok;
1108    Ok(Value::Boolean(if negated { !inside } else { inside }))
1109}
1110
1111#[cfg(test)]
1112mod tests {
1113    use super::*;
1114    use crate::storage::query::ast::Span;
1115
1116    fn lit(v: Value) -> Expr {
1117        Expr::Literal {
1118            value: v,
1119            span: Span::synthetic(),
1120        }
1121    }
1122
1123    fn binop(op: BinOp, l: Expr, r: Expr) -> Expr {
1124        Expr::BinaryOp {
1125            op,
1126            lhs: Box::new(l),
1127            rhs: Box::new(r),
1128            span: Span::synthetic(),
1129        }
1130    }
1131
1132    fn empty_row() -> impl Row {
1133        |_field: &FieldRef| -> Option<Value> { None }
1134    }
1135
1136    #[test]
1137    fn contains_descends_into_json_object_values() {
1138        // A derived component-detections array (#1275): CONTAINS must find
1139        // a label nested inside the object elements, not just top-level
1140        // array strings.
1141        let detections = br#"[{"label":"person","confidence":0.9,"bbox":[0,0,1,1]},
1142                              {"label":"car","confidence":0.7,"bbox":[1,1,2,2]}]"#;
1143        let json = Value::Json(detections.to_vec());
1144        assert!(value_contains(&json, "person"));
1145        assert!(value_contains(&json, "car"));
1146        assert!(!value_contains(&json, "bicycle"));
1147        // Keys are not matched — only the data.
1148        assert!(!value_contains(&json, "label"));
1149    }
1150
1151    #[test]
1152    fn integer_addition_overflow_surfaces_as_eval_error() {
1153        let expr = binop(
1154            BinOp::Add,
1155            lit(Value::Integer(i64::MAX)),
1156            lit(Value::Integer(1)),
1157        );
1158        let err = evaluate(&expr, &empty_row()).unwrap_err();
1159        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Add });
1160    }
1161
1162    #[test]
1163    fn integer_multiplication_overflow_surfaces_as_eval_error() {
1164        let expr = binop(
1165            BinOp::Mul,
1166            lit(Value::Integer(i64::MAX)),
1167            lit(Value::Integer(2)),
1168        );
1169        let err = evaluate(&expr, &empty_row()).unwrap_err();
1170        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Mul });
1171    }
1172
1173    #[test]
1174    fn integer_subtraction_overflow_surfaces_as_eval_error() {
1175        let expr = binop(
1176            BinOp::Sub,
1177            lit(Value::Integer(i64::MIN)),
1178            lit(Value::Integer(1)),
1179        );
1180        let err = evaluate(&expr, &empty_row()).unwrap_err();
1181        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1182    }
1183
1184    #[test]
1185    fn unary_neg_overflow_on_min_int_surfaces_as_eval_error() {
1186        let expr = Expr::UnaryOp {
1187            op: UnaryOp::Neg,
1188            operand: Box::new(lit(Value::Integer(i64::MIN))),
1189            span: Span::synthetic(),
1190        };
1191        let err = evaluate(&expr, &empty_row()).unwrap_err();
1192        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1193    }
1194
1195    #[test]
1196    fn null_propagates_through_arithmetic() {
1197        let expr = binop(BinOp::Add, lit(Value::Null), lit(Value::Integer(7)));
1198        let v = evaluate(&expr, &empty_row()).unwrap();
1199        assert_eq!(v, Value::Null);
1200    }
1201
1202    #[test]
1203    fn null_propagates_through_comparison() {
1204        let expr = binop(BinOp::Lt, lit(Value::Integer(1)), lit(Value::Null));
1205        let v = evaluate(&expr, &empty_row()).unwrap();
1206        assert_eq!(v, Value::Null);
1207    }
1208
1209    #[test]
1210    fn null_propagates_through_concat() {
1211        let expr = binop(
1212            BinOp::Concat,
1213            lit(Value::Text(Arc::from("hi"))),
1214            lit(Value::Null),
1215        );
1216        let v = evaluate(&expr, &empty_row()).unwrap();
1217        assert_eq!(v, Value::Null);
1218    }
1219
1220    #[test]
1221    fn three_valued_and_returns_false_when_one_side_false_even_with_null() {
1222        let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(false)));
1223        let v = evaluate(&expr, &empty_row()).unwrap();
1224        assert_eq!(v, Value::Boolean(false));
1225    }
1226
1227    #[test]
1228    fn three_valued_or_returns_true_when_one_side_true_even_with_null() {
1229        let expr = binop(BinOp::Or, lit(Value::Null), lit(Value::Boolean(true)));
1230        let v = evaluate(&expr, &empty_row()).unwrap();
1231        assert_eq!(v, Value::Boolean(true));
1232    }
1233
1234    #[test]
1235    fn three_valued_and_returns_null_for_null_and_true() {
1236        let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(true)));
1237        let v = evaluate(&expr, &empty_row()).unwrap();
1238        assert_eq!(v, Value::Null);
1239    }
1240
1241    #[test]
1242    fn implicit_cast_triggers_for_decimal_plus_integer() {
1243        // Operator catalog has +(Decimal, Decimal) -> Decimal as
1244        // the only overload that survives coercion (no Decimal ->
1245        // numeric implicit casts exist in the cast catalog). The
1246        // spine therefore inserts an Integer -> Decimal cast on
1247        // the rhs and dispatches the Decimal addition.
1248        // parse_decimal scales by 10^4, so Integer(2) coerces to
1249        // Decimal(20000) and Decimal(10000) + Decimal(20000) =
1250        // Decimal(30000) (fixed-point 3.0000).
1251        let expr = binop(
1252            BinOp::Add,
1253            lit(Value::Decimal(10000)),
1254            lit(Value::Integer(2)),
1255        );
1256        let v = evaluate(&expr, &empty_row()).unwrap();
1257        assert_eq!(v, Value::Decimal(30000));
1258    }
1259
1260    #[test]
1261    fn integer_plus_bigint_resolves_to_preferred_float_overload() {
1262        // (Integer, BigInt) has no exact match. The spine ties
1263        // between +(Integer, Float, Float) (rhs cast BigInt->Float)
1264        // and +(BigInt, BigInt, BigInt) (lhs cast Integer->BigInt).
1265        // Float wins on the preferred-return-type tie-break, so the
1266        // BigInt operand is coerced to Float and the result is Float.
1267        let expr = binop(
1268            BinOp::Add,
1269            lit(Value::Integer(5)),
1270            lit(Value::BigInt(40_000_000_000)),
1271        );
1272        let v = evaluate(&expr, &empty_row()).unwrap();
1273        assert_eq!(v, Value::Float(40_000_000_005.0));
1274    }
1275
1276    #[test]
1277    fn implicit_cast_promotes_integer_to_float_for_float_addition() {
1278        // The catalog has +(Integer, Float) -> Float as a direct
1279        // entry, so no actual coercion is inserted, but the result
1280        // must still be Float.
1281        let expr = binop(BinOp::Add, lit(Value::Integer(2)), lit(Value::Float(0.5)));
1282        let v = evaluate(&expr, &empty_row()).unwrap();
1283        assert_eq!(v, Value::Float(2.5));
1284    }
1285
1286    #[test]
1287    fn integer_division_promotes_to_float() {
1288        let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(2)));
1289        let v = evaluate(&expr, &empty_row()).unwrap();
1290        assert_eq!(v, Value::Float(3.5));
1291    }
1292
1293    #[test]
1294    fn division_by_zero_is_eval_error() {
1295        let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(0)));
1296        let err = evaluate(&expr, &empty_row()).unwrap_err();
1297        assert_eq!(err, EvalError::DivisionByZero);
1298    }
1299
1300    #[test]
1301    fn unknown_function_surfaces_as_eval_error() {
1302        let expr = Expr::FunctionCall {
1303            name: "no_such_fn".to_string(),
1304            args: vec![lit(Value::Integer(1))],
1305            span: Span::synthetic(),
1306        };
1307        let err = evaluate(&expr, &empty_row()).unwrap_err();
1308        match err {
1309            EvalError::UnknownFunction { name, args } => {
1310                assert_eq!(name, "no_such_fn");
1311                assert_eq!(args, vec![DataType::Integer]);
1312            }
1313            other => panic!("expected UnknownFunction, got {other:?}"),
1314        }
1315    }
1316
1317    #[test]
1318    fn coalesce_returns_first_non_null() {
1319        let expr = Expr::FunctionCall {
1320            name: "COALESCE".to_string(),
1321            args: vec![
1322                lit(Value::Null),
1323                lit(Value::Null),
1324                lit(Value::Integer(42)),
1325                lit(Value::Integer(99)),
1326            ],
1327            span: Span::synthetic(),
1328        };
1329        let v = evaluate(&expr, &empty_row()).unwrap();
1330        assert_eq!(v, Value::Integer(42));
1331    }
1332
1333    #[test]
1334    fn coalesce_all_null_returns_null() {
1335        let expr = Expr::FunctionCall {
1336            name: "COALESCE".to_string(),
1337            args: vec![lit(Value::Null), lit(Value::Null)],
1338            span: Span::synthetic(),
1339        };
1340        let v = evaluate(&expr, &empty_row()).unwrap();
1341        assert_eq!(v, Value::Null);
1342    }
1343
1344    #[test]
1345    fn upper_lower_dispatch_through_function_catalog() {
1346        let expr = Expr::FunctionCall {
1347            name: "UPPER".to_string(),
1348            args: vec![lit(Value::Text(Arc::from("hello")))],
1349            span: Span::synthetic(),
1350        };
1351        let v = evaluate(&expr, &empty_row()).unwrap();
1352        assert_eq!(v, Value::Text(Arc::from("HELLO")));
1353    }
1354
1355    #[test]
1356    fn length_of_null_propagates() {
1357        let expr = Expr::FunctionCall {
1358            name: "LENGTH".to_string(),
1359            args: vec![lit(Value::Null)],
1360            span: Span::synthetic(),
1361        };
1362        let v = evaluate(&expr, &empty_row()).unwrap();
1363        assert_eq!(v, Value::Null);
1364    }
1365
1366    #[test]
1367    fn case_when_picks_first_true_branch() {
1368        let expr = Expr::Case {
1369            branches: vec![
1370                (lit(Value::Boolean(false)), lit(Value::Integer(1))),
1371                (lit(Value::Boolean(true)), lit(Value::Integer(2))),
1372                (lit(Value::Boolean(true)), lit(Value::Integer(3))),
1373            ],
1374            else_: Some(Box::new(lit(Value::Integer(99)))),
1375            span: Span::synthetic(),
1376        };
1377        let v = evaluate(&expr, &empty_row()).unwrap();
1378        assert_eq!(v, Value::Integer(2));
1379    }
1380
1381    #[test]
1382    fn case_falls_through_to_else_when_no_branch_matches() {
1383        let expr = Expr::Case {
1384            branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1385            else_: Some(Box::new(lit(Value::Integer(99)))),
1386            span: Span::synthetic(),
1387        };
1388        let v = evaluate(&expr, &empty_row()).unwrap();
1389        assert_eq!(v, Value::Integer(99));
1390    }
1391
1392    #[test]
1393    fn case_returns_null_when_no_branch_matches_and_no_else() {
1394        let expr = Expr::Case {
1395            branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1396            else_: None,
1397            span: Span::synthetic(),
1398        };
1399        let v = evaluate(&expr, &empty_row()).unwrap();
1400        assert_eq!(v, Value::Null);
1401    }
1402
1403    #[test]
1404    fn is_null_handles_null_and_non_null() {
1405        let null_expr = Expr::IsNull {
1406            operand: Box::new(lit(Value::Null)),
1407            negated: false,
1408            span: Span::synthetic(),
1409        };
1410        assert_eq!(
1411            evaluate(&null_expr, &empty_row()).unwrap(),
1412            Value::Boolean(true)
1413        );
1414
1415        let non_null_expr = Expr::IsNull {
1416            operand: Box::new(lit(Value::Integer(7))),
1417            negated: false,
1418            span: Span::synthetic(),
1419        };
1420        assert_eq!(
1421            evaluate(&non_null_expr, &empty_row()).unwrap(),
1422            Value::Boolean(false)
1423        );
1424    }
1425
1426    #[test]
1427    fn between_inclusive_bounds() {
1428        let expr = Expr::Between {
1429            target: Box::new(lit(Value::Integer(5))),
1430            low: Box::new(lit(Value::Integer(1))),
1431            high: Box::new(lit(Value::Integer(10))),
1432            negated: false,
1433            span: Span::synthetic(),
1434        };
1435        assert_eq!(evaluate(&expr, &empty_row()).unwrap(), Value::Boolean(true));
1436    }
1437
1438    #[test]
1439    fn in_list_match_and_miss() {
1440        let hit = Expr::InList {
1441            target: Box::new(lit(Value::Integer(2))),
1442            values: vec![
1443                lit(Value::Integer(1)),
1444                lit(Value::Integer(2)),
1445                lit(Value::Integer(3)),
1446            ],
1447            negated: false,
1448            span: Span::synthetic(),
1449        };
1450        assert_eq!(evaluate(&hit, &empty_row()).unwrap(), Value::Boolean(true));
1451
1452        let miss = Expr::InList {
1453            target: Box::new(lit(Value::Integer(99))),
1454            values: vec![lit(Value::Integer(1)), lit(Value::Integer(2))],
1455            negated: false,
1456            span: Span::synthetic(),
1457        };
1458        assert_eq!(
1459            evaluate(&miss, &empty_row()).unwrap(),
1460            Value::Boolean(false)
1461        );
1462    }
1463
1464    #[test]
1465    fn column_lookup_walks_field_ref() {
1466        let row = |field: &FieldRef| -> Option<Value> {
1467            match field {
1468                FieldRef::TableColumn { table, column } if table == "t" && column == "x" => {
1469                    Some(Value::Integer(11))
1470                }
1471                _ => None,
1472            }
1473        };
1474        let expr = Expr::Column {
1475            field: FieldRef::TableColumn {
1476                table: "t".to_string(),
1477                column: "x".to_string(),
1478            },
1479            span: Span::synthetic(),
1480        };
1481        assert_eq!(evaluate(&expr, &row).unwrap(), Value::Integer(11));
1482    }
1483
1484    #[test]
1485    fn missing_column_surfaces_unknown_column() {
1486        let row = |_: &FieldRef| -> Option<Value> { None };
1487        let expr = Expr::Column {
1488            field: FieldRef::TableColumn {
1489                table: "t".to_string(),
1490                column: "missing".to_string(),
1491            },
1492            span: Span::synthetic(),
1493        };
1494        let err = evaluate(&expr, &row).unwrap_err();
1495        match err {
1496            EvalError::UnknownColumn(_) => {}
1497            other => panic!("expected UnknownColumn, got {other:?}"),
1498        }
1499    }
1500
1501    #[test]
1502    fn parameter_without_bind_is_eval_error() {
1503        let expr = Expr::Parameter {
1504            index: 1,
1505            span: Span::synthetic(),
1506        };
1507        let err = evaluate(&expr, &empty_row()).unwrap_err();
1508        assert_eq!(err, EvalError::UnboundParameter(1));
1509    }
1510
1511    #[test]
1512    fn cast_integer_to_text_uses_explicit_path() {
1513        let expr = Expr::Cast {
1514            inner: Box::new(lit(Value::Integer(42))),
1515            target: DataType::Text,
1516            span: Span::synthetic(),
1517        };
1518        assert_eq!(
1519            evaluate(&expr, &empty_row()).unwrap(),
1520            Value::Text(Arc::from("42"))
1521        );
1522    }
1523
1524    #[test]
1525    fn concat_returns_text() {
1526        let expr = binop(
1527            BinOp::Concat,
1528            lit(Value::Text(Arc::from("foo"))),
1529            lit(Value::Text(Arc::from("bar"))),
1530        );
1531        assert_eq!(
1532            evaluate(&expr, &empty_row()).unwrap(),
1533            Value::Text(Arc::from("foobar"))
1534        );
1535    }
1536}