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