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    /// `IN (...)` against an empty value list — preserves the legacy
141    /// "always false" semantic but recorded explicitly so the
142    /// optimiser can fold it.
143    EmptyInList,
144}
145
146impl std::fmt::Display for EvalError {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        match self {
149            EvalError::UnknownColumn(field) => write!(f, "unknown column: {field:?}"),
150            EvalError::UnboundParameter(idx) => {
151                write!(f, "unbound query parameter: ${idx}")
152            }
153            EvalError::OperatorMismatch { op, lhs, rhs } => {
154                write!(f, "operator {op:?} not defined for ({lhs:?}, {rhs:?})")
155            }
156            EvalError::UnaryMismatch { op, operand } => {
157                write!(f, "unary {op:?} not defined for {operand:?}")
158            }
159            EvalError::UnknownFunction { name, args } => {
160                write!(f, "unknown function: {name}({args:?})")
161            }
162            EvalError::ImplicitCastFailed { from, to, reason } => {
163                write!(f, "implicit cast {from:?} -> {to:?} failed: {reason}")
164            }
165            EvalError::CastFailed { from, to, reason } => {
166                write!(f, "cast {from:?} -> {to:?} failed: {reason}")
167            }
168            EvalError::ArithmeticOverflow { op } => {
169                write!(f, "arithmetic overflow in {op:?}")
170            }
171            EvalError::DivisionByZero => write!(f, "division by zero"),
172            EvalError::EmptyInList => write!(f, "IN list is empty"),
173        }
174    }
175}
176
177impl std::error::Error for EvalError {}
178
179/// Evaluate a scalar `Expr` against a row binding. Single entry
180/// point for the deep evaluator interface — every recursive call
181/// folds back through this function so the resolution surface stays
182/// uniform.
183pub fn evaluate(expr: &Expr, row: &dyn Row) -> Result<Value, EvalError> {
184    match expr {
185        Expr::Literal { value, .. } => Ok(value.clone()),
186        Expr::Column { field, .. } => row
187            .get(field)
188            .ok_or_else(|| EvalError::UnknownColumn(field.clone())),
189        Expr::Parameter { index, .. } => Err(EvalError::UnboundParameter(*index)),
190        Expr::UnaryOp { op, operand, .. } => eval_unary(*op, operand, row),
191        Expr::BinaryOp { op, lhs, rhs, .. } => eval_binary(*op, lhs, rhs, row),
192        Expr::Cast { inner, target, .. } => eval_cast(inner, *target, row),
193        Expr::FunctionCall { name, args, .. } => eval_function(name, args, row),
194        Expr::Case {
195            branches, else_, ..
196        } => eval_case(branches, else_.as_deref(), row),
197        Expr::IsNull {
198            operand, negated, ..
199        } => {
200            let v = evaluate(operand, row)?;
201            let is_null = v.is_null();
202            Ok(Value::Boolean(if *negated { !is_null } else { is_null }))
203        }
204        Expr::InList {
205            target,
206            values,
207            negated,
208            ..
209        } => eval_in_list(target, values, *negated, row),
210        Expr::Between {
211            target,
212            low,
213            high,
214            negated,
215            ..
216        } => eval_between(target, low, high, *negated, row),
217        Expr::Subquery { .. } => Err(EvalError::UnknownFunction {
218            name: "SUBQUERY".to_string(),
219            args: Vec::new(),
220        }),
221    }
222}
223
224fn eval_unary(op: UnaryOp, operand: &Expr, row: &dyn Row) -> Result<Value, EvalError> {
225    let v = evaluate(operand, row)?;
226    if v.is_null() {
227        return Ok(Value::Null);
228    }
229    match op {
230        UnaryOp::Neg => match &v {
231            Value::Integer(n) => n
232                .checked_neg()
233                .map(Value::Integer)
234                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
235            Value::BigInt(n) => n
236                .checked_neg()
237                .map(Value::BigInt)
238                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
239            Value::Float(n) => Ok(Value::Float(-*n)),
240            Value::Decimal(n) => n
241                .checked_neg()
242                .map(Value::Decimal)
243                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
244            other => Err(EvalError::UnaryMismatch {
245                op,
246                operand: other.data_type(),
247            }),
248        },
249        UnaryOp::Not => match &v {
250            Value::Boolean(b) => Ok(Value::Boolean(!b)),
251            other => Err(EvalError::UnaryMismatch {
252                op,
253                operand: other.data_type(),
254            }),
255        },
256    }
257}
258
259fn eval_binary(op: BinOp, lhs: &Expr, rhs: &Expr, row: &dyn Row) -> Result<Value, EvalError> {
260    // Logical ops use SQL three-valued logic so we eval both sides
261    // and short-circuit on Null *after* type-checking; arithmetic /
262    // comparison / concat short-circuit before dispatch.
263    let l = evaluate(lhs, row)?;
264    let r = evaluate(rhs, row)?;
265
266    match op {
267        BinOp::And => return three_valued_and(&l, &r),
268        BinOp::Or => return three_valued_or(&l, &r),
269        _ => {}
270    }
271
272    if l.is_null() || r.is_null() {
273        return Ok(Value::Null);
274    }
275
276    let lhs_dt = l.data_type();
277    let rhs_dt = r.data_type();
278    let (entry, coercions) =
279        coercion_spine::resolve_binop(op, lhs_dt, rhs_dt).ok_or(EvalError::OperatorMismatch {
280            op,
281            lhs: lhs_dt,
282            rhs: rhs_dt,
283        })?;
284
285    let l = match coercions.at(0) {
286        Some(target) => apply_implicit_cast(&l, lhs_dt, target)?,
287        None => l,
288    };
289    let r = match coercions.at(1) {
290        Some(target) => apply_implicit_cast(&r, rhs_dt, target)?,
291        None => r,
292    };
293
294    dispatch_binop(op, entry, l, r)
295}
296
297fn dispatch_binop(
298    op: BinOp,
299    entry: &OperatorEntry,
300    l: Value,
301    r: Value,
302) -> Result<Value, EvalError> {
303    match op {
304        BinOp::Add => arith_add(entry, l, r),
305        BinOp::Sub => arith_sub(entry, l, r),
306        BinOp::Mul => arith_mul(entry, l, r),
307        BinOp::Div => arith_div(entry, l, r),
308        BinOp::Mod => arith_mod(entry, l, r),
309        BinOp::Concat => match (&l, &r) {
310            (Value::Text(a), Value::Text(b)) => {
311                let mut s = String::with_capacity(a.len() + b.len());
312                s.push_str(a);
313                s.push_str(b);
314                Ok(Value::Text(Arc::from(s)))
315            }
316            _ => Err(EvalError::OperatorMismatch {
317                op,
318                lhs: l.data_type(),
319                rhs: r.data_type(),
320            }),
321        },
322        BinOp::Eq => Ok(Value::Boolean(values_equal(&l, &r))),
323        BinOp::Ne => Ok(Value::Boolean(!values_equal(&l, &r))),
324        BinOp::Lt => cmp_op(op, l, r, |o| o == std::cmp::Ordering::Less),
325        BinOp::Le => cmp_op(op, l, r, |o| o != std::cmp::Ordering::Greater),
326        BinOp::Gt => cmp_op(op, l, r, |o| o == std::cmp::Ordering::Greater),
327        BinOp::Ge => cmp_op(op, l, r, |o| o != std::cmp::Ordering::Less),
328        BinOp::And | BinOp::Or => unreachable!("handled before dispatch"),
329    }
330}
331
332fn arith_add(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
333    match entry.return_type {
334        DataType::Integer => match (l, r) {
335            (Value::Integer(a), Value::Integer(b)) => a
336                .checked_add(b)
337                .map(Value::Integer)
338                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
339            _ => unreachable_after_coercion("Add", DataType::Integer),
340        },
341        DataType::BigInt => match (l, r) {
342            (Value::BigInt(a), Value::BigInt(b)) => a
343                .checked_add(b)
344                .map(Value::BigInt)
345                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
346            _ => unreachable_after_coercion("Add", DataType::BigInt),
347        },
348        DataType::Float => Ok(Value::Float(as_f64(&l) + as_f64(&r))),
349        DataType::Decimal => match (l, r) {
350            (Value::Decimal(a), Value::Decimal(b)) => a
351                .checked_add(b)
352                .map(Value::Decimal)
353                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
354            _ => unreachable_after_coercion("Add", DataType::Decimal),
355        },
356        other => Err(EvalError::OperatorMismatch {
357            op: BinOp::Add,
358            lhs: other,
359            rhs: other,
360        }),
361    }
362}
363
364fn arith_sub(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
365    match entry.return_type {
366        DataType::Integer => match (l, r) {
367            (Value::Integer(a), Value::Integer(b)) => a
368                .checked_sub(b)
369                .map(Value::Integer)
370                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
371            _ => unreachable_after_coercion("Sub", DataType::Integer),
372        },
373        DataType::BigInt => match (l, r) {
374            (Value::BigInt(a), Value::BigInt(b)) => a
375                .checked_sub(b)
376                .map(Value::BigInt)
377                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
378            _ => unreachable_after_coercion("Sub", DataType::BigInt),
379        },
380        DataType::Float => Ok(Value::Float(as_f64(&l) - as_f64(&r))),
381        DataType::Decimal => match (l, r) {
382            (Value::Decimal(a), Value::Decimal(b)) => a
383                .checked_sub(b)
384                .map(Value::Decimal)
385                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
386            _ => unreachable_after_coercion("Sub", DataType::Decimal),
387        },
388        other => Err(EvalError::OperatorMismatch {
389            op: BinOp::Sub,
390            lhs: other,
391            rhs: other,
392        }),
393    }
394}
395
396fn arith_mul(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
397    match entry.return_type {
398        DataType::Integer => match (l, r) {
399            (Value::Integer(a), Value::Integer(b)) => a
400                .checked_mul(b)
401                .map(Value::Integer)
402                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mul }),
403            _ => unreachable_after_coercion("Mul", DataType::Integer),
404        },
405        DataType::BigInt => match (l, r) {
406            (Value::BigInt(a), Value::BigInt(b)) => a
407                .checked_mul(b)
408                .map(Value::BigInt)
409                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mul }),
410            _ => unreachable_after_coercion("Mul", DataType::BigInt),
411        },
412        DataType::Float => Ok(Value::Float(as_f64(&l) * as_f64(&r))),
413        other => Err(EvalError::OperatorMismatch {
414            op: BinOp::Mul,
415            lhs: other,
416            rhs: other,
417        }),
418    }
419}
420
421fn arith_div(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
422    // Operator catalog promotes integer / integer to Float —
423    // mirror that here so behavior stays identical to the typer.
424    match entry.return_type {
425        DataType::Float => {
426            let denom = as_f64(&r);
427            if denom == 0.0 {
428                return Err(EvalError::DivisionByZero);
429            }
430            Ok(Value::Float(as_f64(&l) / denom))
431        }
432        other => Err(EvalError::OperatorMismatch {
433            op: BinOp::Div,
434            lhs: other,
435            rhs: other,
436        }),
437    }
438}
439
440fn arith_mod(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
441    match entry.return_type {
442        DataType::Integer => match (l, r) {
443            (Value::Integer(_), Value::Integer(0)) => Err(EvalError::DivisionByZero),
444            (Value::Integer(a), Value::Integer(b)) => a
445                .checked_rem(b)
446                .map(Value::Integer)
447                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mod }),
448            _ => unreachable_after_coercion("Mod", DataType::Integer),
449        },
450        DataType::BigInt => match (l, r) {
451            (Value::BigInt(_), Value::BigInt(0)) => Err(EvalError::DivisionByZero),
452            (Value::BigInt(a), Value::BigInt(b)) => a
453                .checked_rem(b)
454                .map(Value::BigInt)
455                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mod }),
456            _ => unreachable_after_coercion("Mod", DataType::BigInt),
457        },
458        other => Err(EvalError::OperatorMismatch {
459            op: BinOp::Mod,
460            lhs: other,
461            rhs: other,
462        }),
463    }
464}
465
466fn unreachable_after_coercion(op: &'static str, expected: DataType) -> Result<Value, EvalError> {
467    Err(EvalError::OperatorMismatch {
468        op: match op {
469            "Add" => BinOp::Add,
470            "Sub" => BinOp::Sub,
471            "Mul" => BinOp::Mul,
472            "Div" => BinOp::Div,
473            "Mod" => BinOp::Mod,
474            _ => BinOp::Add,
475        },
476        lhs: expected,
477        rhs: expected,
478    })
479}
480
481fn as_f64(v: &Value) -> f64 {
482    match v {
483        Value::Float(x) => *x,
484        Value::Integer(x) => *x as f64,
485        Value::BigInt(x) => *x as f64,
486        Value::UnsignedInteger(x) => *x as f64,
487        Value::Decimal(x) => *x as f64,
488        _ => 0.0,
489    }
490}
491
492fn cmp_op<F>(op: BinOp, l: Value, r: Value, pick: F) -> Result<Value, EvalError>
493where
494    F: Fn(std::cmp::Ordering) -> bool,
495{
496    let ord = compare_values(&l, &r).ok_or(EvalError::OperatorMismatch {
497        op,
498        lhs: l.data_type(),
499        rhs: r.data_type(),
500    })?;
501    Ok(Value::Boolean(pick(ord)))
502}
503
504/// Total ordering for the numeric and text families that the
505/// catalog declares comparison overloads for. Returns `None` when
506/// the values aren't comparable — callers surface this as
507/// [`EvalError::OperatorMismatch`].
508fn compare_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
509    use std::cmp::Ordering;
510    match (a, b) {
511        (Value::Integer(x), Value::Integer(y)) => Some(x.cmp(y)),
512        (Value::BigInt(x), Value::BigInt(y)) => Some(x.cmp(y)),
513        (Value::Float(x), Value::Float(y)) => x.partial_cmp(y),
514        (Value::Text(x), Value::Text(y)) => Some(x.as_ref().cmp(y.as_ref())),
515        (Value::Boolean(x), Value::Boolean(y)) => Some(x.cmp(y)),
516        (Value::Timestamp(x), Value::Timestamp(y)) => Some(x.cmp(y)),
517        (Value::TimestampMs(x), Value::TimestampMs(y)) => Some(x.cmp(y)),
518        (Value::Date(x), Value::Date(y)) => Some(x.cmp(y)),
519        (Value::Time(x), Value::Time(y)) => Some(x.cmp(y)),
520        (Value::Uuid(x), Value::Uuid(y)) => Some(x.cmp(y)),
521        (Value::Decimal(x), Value::Decimal(y)) => Some(x.cmp(y)),
522        // Cross-numeric — operand coercion should have homogenised
523        // these but if a caller invokes the evaluator with mixed
524        // numerics directly, fall back to f64 ordering.
525        (Value::Integer(_) | Value::Float(_) | Value::BigInt(_), _) => {
526            let l = as_f64(a);
527            let r = as_f64(b);
528            l.partial_cmp(&r)
529        }
530        _ => None,
531    }
532}
533
534fn values_equal(a: &Value, b: &Value) -> bool {
535    match (a, b) {
536        (Value::Float(x), Value::Float(y)) => x == y,
537        _ => a == b,
538    }
539}
540
541fn three_valued_and(l: &Value, r: &Value) -> Result<Value, EvalError> {
542    match (l, r) {
543        (Value::Boolean(false), _) | (_, Value::Boolean(false)) => Ok(Value::Boolean(false)),
544        (Value::Boolean(true), Value::Boolean(true)) => Ok(Value::Boolean(true)),
545        (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
546        _ => Err(EvalError::OperatorMismatch {
547            op: BinOp::And,
548            lhs: l.data_type(),
549            rhs: r.data_type(),
550        }),
551    }
552}
553
554fn three_valued_or(l: &Value, r: &Value) -> Result<Value, EvalError> {
555    match (l, r) {
556        (Value::Boolean(true), _) | (_, Value::Boolean(true)) => Ok(Value::Boolean(true)),
557        (Value::Boolean(false), Value::Boolean(false)) => Ok(Value::Boolean(false)),
558        (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
559        _ => Err(EvalError::OperatorMismatch {
560            op: BinOp::Or,
561            lhs: l.data_type(),
562            rhs: r.data_type(),
563        }),
564    }
565}
566
567fn apply_implicit_cast(value: &Value, src: DataType, target: DataType) -> Result<Value, EvalError> {
568    if src == target {
569        return Ok(value.clone());
570    }
571    coerce_via_catalog(value, src, target, None).map_err(|reason| EvalError::ImplicitCastFailed {
572        from: src,
573        to: target,
574        reason,
575    })
576}
577
578fn eval_cast(inner: &Expr, target: DataType, row: &dyn Row) -> Result<Value, EvalError> {
579    let v = evaluate(inner, row)?;
580    if v.is_null() {
581        return Ok(Value::Null);
582    }
583    let src = v.data_type();
584    if src == target {
585        return Ok(v);
586    }
587    coerce_via_catalog(&v, src, target, None).map_err(|reason| EvalError::CastFailed {
588        from: src,
589        to: target,
590        reason,
591    })
592}
593
594fn eval_function(name: &str, args: &[Expr], row: &dyn Row) -> Result<Value, EvalError> {
595    // COALESCE has SQL-special semantics that the catalog can't
596    // express (variadic + uniform arg type unifies poorly with
597    // first-non-null). Handle it before catalog dispatch so we
598    // preserve `COALESCE(int, int) -> int` rather than coercing
599    // every argument to Text.
600    if name.eq_ignore_ascii_case("COALESCE") {
601        for arg in args {
602            let v = evaluate(arg, row)?;
603            if !v.is_null() {
604                return Ok(v);
605            }
606        }
607        return Ok(Value::Null);
608    }
609
610    let arg_values: Vec<Value> = args
611        .iter()
612        .map(|a| evaluate(a, row))
613        .collect::<Result<Vec<_>, _>>()?;
614    let arg_types: Vec<DataType> = arg_values.iter().map(|v| v.data_type()).collect();
615
616    // Strict NULL propagation for built-in scalar functions: any
617    // null arg short-circuits the call to NULL. Only applies when
618    // the function name exists in the catalog so unknown functions
619    // with null args still surface as `UnknownFunction` rather than
620    // silently returning null.
621    if arg_values.iter().any(Value::is_null)
622        && FUNCTION_CATALOG
623            .iter()
624            .any(|e| e.name.eq_ignore_ascii_case(name))
625    {
626        return Ok(Value::Null);
627    }
628
629    let (entry, coercions) =
630        coercion_spine::resolve_function(name, &arg_types).ok_or_else(|| {
631            EvalError::UnknownFunction {
632                name: name.to_string(),
633                args: arg_types.clone(),
634            }
635        })?;
636
637    // Apply per-arg implicit casts.
638    let mut coerced: Vec<Value> = Vec::with_capacity(arg_values.len());
639    for (idx, value) in arg_values.into_iter().enumerate() {
640        let src = arg_types[idx];
641        match coercions.at(idx) {
642            Some(target) if src != target => {
643                coerced.push(apply_implicit_cast(&value, src, target)?);
644            }
645            _ => coerced.push(value),
646        }
647    }
648
649    // NULL propagation for built-ins: if any non-variadic argument
650    // is null, return null. Variadic / aggregate semantics handle
651    // null differently and aren't in scope for this slice.
652    if !entry.variadic && coerced.iter().any(|v| v.is_null()) {
653        return Ok(Value::Null);
654    }
655
656    dispatch_function(entry.name, &coerced)
657}
658
659fn dispatch_function(name: &str, args: &[Value]) -> Result<Value, EvalError> {
660    match name {
661        "UPPER" => match &args[0] {
662            Value::Text(s) => Ok(Value::Text(Arc::from(s.to_uppercase()))),
663            other => Err(EvalError::UnknownFunction {
664                name: name.to_string(),
665                args: vec![other.data_type()],
666            }),
667        },
668        "LOWER" => match &args[0] {
669            Value::Text(s) => Ok(Value::Text(Arc::from(s.to_lowercase()))),
670            other => Err(EvalError::UnknownFunction {
671                name: name.to_string(),
672                args: vec![other.data_type()],
673            }),
674        },
675        "LENGTH" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => match &args[0] {
676            Value::Text(s) => Ok(Value::Integer(s.chars().count() as i64)),
677            other => Err(EvalError::UnknownFunction {
678                name: name.to_string(),
679                args: vec![other.data_type()],
680            }),
681        },
682        "OCTET_LENGTH" => match &args[0] {
683            Value::Text(s) => Ok(Value::Integer(s.len() as i64)),
684            Value::Blob(b) => Ok(Value::Integer(b.len() as i64)),
685            other => Err(EvalError::UnknownFunction {
686                name: name.to_string(),
687                args: vec![other.data_type()],
688            }),
689        },
690        "ABS" => match &args[0] {
691            Value::Integer(n) => n
692                .checked_abs()
693                .map(Value::Integer)
694                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
695            Value::BigInt(n) => n
696                .checked_abs()
697                .map(Value::BigInt)
698                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
699            Value::Float(n) => Ok(Value::Float(n.abs())),
700            Value::Decimal(n) => n
701                .checked_abs()
702                .map(Value::Decimal)
703                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
704            other => Err(EvalError::UnknownFunction {
705                name: name.to_string(),
706                args: vec![other.data_type()],
707            }),
708        },
709        // Functions whose runtime body the slice doesn't yet cover
710        // surface as UnknownFunction with the resolved arg types so
711        // callers can see the catalog matched but the dispatch
712        // didn't. Subsequent slices fill in ROUND, FLOOR, CEIL,
713        // CONCAT, UPPER/LOWER edge cases, time functions, …
714        other => Err(EvalError::UnknownFunction {
715            name: other.to_string(),
716            args: args.iter().map(|v| v.data_type()).collect(),
717        }),
718    }
719}
720
721fn eval_case(
722    branches: &[(Expr, Expr)],
723    else_: Option<&Expr>,
724    row: &dyn Row,
725) -> Result<Value, EvalError> {
726    for (cond, value) in branches {
727        let c = evaluate(cond, row)?;
728        if matches!(c, Value::Boolean(true)) {
729            return evaluate(value, row);
730        }
731    }
732    match else_ {
733        Some(e) => evaluate(e, row),
734        None => Ok(Value::Null),
735    }
736}
737
738fn eval_in_list(
739    target: &Expr,
740    values: &[Expr],
741    negated: bool,
742    row: &dyn Row,
743) -> Result<Value, EvalError> {
744    if values.is_empty() {
745        return Err(EvalError::EmptyInList);
746    }
747    let needle = evaluate(target, row)?;
748    if needle.is_null() {
749        return Ok(Value::Null);
750    }
751    let mut saw_null = false;
752    for v in values {
753        let candidate = evaluate(v, row)?;
754        if candidate.is_null() {
755            saw_null = true;
756            continue;
757        }
758        if values_equal(&needle, &candidate) {
759            return Ok(Value::Boolean(!negated));
760        }
761    }
762    if saw_null {
763        return Ok(Value::Null);
764    }
765    Ok(Value::Boolean(negated))
766}
767
768fn eval_between(
769    target: &Expr,
770    low: &Expr,
771    high: &Expr,
772    negated: bool,
773    row: &dyn Row,
774) -> Result<Value, EvalError> {
775    let v = evaluate(target, row)?;
776    let lo = evaluate(low, row)?;
777    let hi = evaluate(high, row)?;
778    if v.is_null() || lo.is_null() || hi.is_null() {
779        return Ok(Value::Null);
780    }
781    let lo_ok = compare_values(&v, &lo)
782        .ok_or(EvalError::OperatorMismatch {
783            op: BinOp::Ge,
784            lhs: v.data_type(),
785            rhs: lo.data_type(),
786        })
787        .map(|o| o != std::cmp::Ordering::Less)?;
788    let hi_ok = compare_values(&v, &hi)
789        .ok_or(EvalError::OperatorMismatch {
790            op: BinOp::Le,
791            lhs: v.data_type(),
792            rhs: hi.data_type(),
793        })
794        .map(|o| o != std::cmp::Ordering::Greater)?;
795    let inside = lo_ok && hi_ok;
796    Ok(Value::Boolean(if negated { !inside } else { inside }))
797}
798
799#[cfg(test)]
800mod tests {
801    use super::*;
802    use crate::storage::query::ast::Span;
803
804    fn lit(v: Value) -> Expr {
805        Expr::Literal {
806            value: v,
807            span: Span::synthetic(),
808        }
809    }
810
811    fn binop(op: BinOp, l: Expr, r: Expr) -> Expr {
812        Expr::BinaryOp {
813            op,
814            lhs: Box::new(l),
815            rhs: Box::new(r),
816            span: Span::synthetic(),
817        }
818    }
819
820    fn empty_row() -> impl Row {
821        |_field: &FieldRef| -> Option<Value> { None }
822    }
823
824    #[test]
825    fn integer_addition_overflow_surfaces_as_eval_error() {
826        let expr = binop(
827            BinOp::Add,
828            lit(Value::Integer(i64::MAX)),
829            lit(Value::Integer(1)),
830        );
831        let err = evaluate(&expr, &empty_row()).unwrap_err();
832        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Add });
833    }
834
835    #[test]
836    fn integer_multiplication_overflow_surfaces_as_eval_error() {
837        let expr = binop(
838            BinOp::Mul,
839            lit(Value::Integer(i64::MAX)),
840            lit(Value::Integer(2)),
841        );
842        let err = evaluate(&expr, &empty_row()).unwrap_err();
843        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Mul });
844    }
845
846    #[test]
847    fn integer_subtraction_overflow_surfaces_as_eval_error() {
848        let expr = binop(
849            BinOp::Sub,
850            lit(Value::Integer(i64::MIN)),
851            lit(Value::Integer(1)),
852        );
853        let err = evaluate(&expr, &empty_row()).unwrap_err();
854        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
855    }
856
857    #[test]
858    fn unary_neg_overflow_on_min_int_surfaces_as_eval_error() {
859        let expr = Expr::UnaryOp {
860            op: UnaryOp::Neg,
861            operand: Box::new(lit(Value::Integer(i64::MIN))),
862            span: Span::synthetic(),
863        };
864        let err = evaluate(&expr, &empty_row()).unwrap_err();
865        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
866    }
867
868    #[test]
869    fn null_propagates_through_arithmetic() {
870        let expr = binop(BinOp::Add, lit(Value::Null), lit(Value::Integer(7)));
871        let v = evaluate(&expr, &empty_row()).unwrap();
872        assert_eq!(v, Value::Null);
873    }
874
875    #[test]
876    fn null_propagates_through_comparison() {
877        let expr = binop(BinOp::Lt, lit(Value::Integer(1)), lit(Value::Null));
878        let v = evaluate(&expr, &empty_row()).unwrap();
879        assert_eq!(v, Value::Null);
880    }
881
882    #[test]
883    fn null_propagates_through_concat() {
884        let expr = binop(
885            BinOp::Concat,
886            lit(Value::Text(Arc::from("hi"))),
887            lit(Value::Null),
888        );
889        let v = evaluate(&expr, &empty_row()).unwrap();
890        assert_eq!(v, Value::Null);
891    }
892
893    #[test]
894    fn three_valued_and_returns_false_when_one_side_false_even_with_null() {
895        let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(false)));
896        let v = evaluate(&expr, &empty_row()).unwrap();
897        assert_eq!(v, Value::Boolean(false));
898    }
899
900    #[test]
901    fn three_valued_or_returns_true_when_one_side_true_even_with_null() {
902        let expr = binop(BinOp::Or, lit(Value::Null), lit(Value::Boolean(true)));
903        let v = evaluate(&expr, &empty_row()).unwrap();
904        assert_eq!(v, Value::Boolean(true));
905    }
906
907    #[test]
908    fn three_valued_and_returns_null_for_null_and_true() {
909        let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(true)));
910        let v = evaluate(&expr, &empty_row()).unwrap();
911        assert_eq!(v, Value::Null);
912    }
913
914    #[test]
915    fn implicit_cast_triggers_for_decimal_plus_integer() {
916        // Operator catalog has +(Decimal, Decimal) -> Decimal as
917        // the only overload that survives coercion (no Decimal ->
918        // numeric implicit casts exist in the cast catalog). The
919        // spine therefore inserts an Integer -> Decimal cast on
920        // the rhs and dispatches the Decimal addition.
921        // parse_decimal scales by 10^4, so Integer(2) coerces to
922        // Decimal(20000) and Decimal(10000) + Decimal(20000) =
923        // Decimal(30000) (fixed-point 3.0000).
924        let expr = binop(
925            BinOp::Add,
926            lit(Value::Decimal(10000)),
927            lit(Value::Integer(2)),
928        );
929        let v = evaluate(&expr, &empty_row()).unwrap();
930        assert_eq!(v, Value::Decimal(30000));
931    }
932
933    #[test]
934    fn integer_plus_bigint_resolves_to_preferred_float_overload() {
935        // (Integer, BigInt) has no exact match. The spine ties
936        // between +(Integer, Float, Float) (rhs cast BigInt->Float)
937        // and +(BigInt, BigInt, BigInt) (lhs cast Integer->BigInt).
938        // Float wins on the preferred-return-type tie-break, so the
939        // BigInt operand is coerced to Float and the result is Float.
940        let expr = binop(
941            BinOp::Add,
942            lit(Value::Integer(5)),
943            lit(Value::BigInt(40_000_000_000)),
944        );
945        let v = evaluate(&expr, &empty_row()).unwrap();
946        assert_eq!(v, Value::Float(40_000_000_005.0));
947    }
948
949    #[test]
950    fn implicit_cast_promotes_integer_to_float_for_float_addition() {
951        // The catalog has +(Integer, Float) -> Float as a direct
952        // entry, so no actual coercion is inserted, but the result
953        // must still be Float.
954        let expr = binop(BinOp::Add, lit(Value::Integer(2)), lit(Value::Float(0.5)));
955        let v = evaluate(&expr, &empty_row()).unwrap();
956        assert_eq!(v, Value::Float(2.5));
957    }
958
959    #[test]
960    fn integer_division_promotes_to_float() {
961        let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(2)));
962        let v = evaluate(&expr, &empty_row()).unwrap();
963        assert_eq!(v, Value::Float(3.5));
964    }
965
966    #[test]
967    fn division_by_zero_is_eval_error() {
968        let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(0)));
969        let err = evaluate(&expr, &empty_row()).unwrap_err();
970        assert_eq!(err, EvalError::DivisionByZero);
971    }
972
973    #[test]
974    fn unknown_function_surfaces_as_eval_error() {
975        let expr = Expr::FunctionCall {
976            name: "no_such_fn".to_string(),
977            args: vec![lit(Value::Integer(1))],
978            span: Span::synthetic(),
979        };
980        let err = evaluate(&expr, &empty_row()).unwrap_err();
981        match err {
982            EvalError::UnknownFunction { name, args } => {
983                assert_eq!(name, "no_such_fn");
984                assert_eq!(args, vec![DataType::Integer]);
985            }
986            other => panic!("expected UnknownFunction, got {other:?}"),
987        }
988    }
989
990    #[test]
991    fn coalesce_returns_first_non_null() {
992        let expr = Expr::FunctionCall {
993            name: "COALESCE".to_string(),
994            args: vec![
995                lit(Value::Null),
996                lit(Value::Null),
997                lit(Value::Integer(42)),
998                lit(Value::Integer(99)),
999            ],
1000            span: Span::synthetic(),
1001        };
1002        let v = evaluate(&expr, &empty_row()).unwrap();
1003        assert_eq!(v, Value::Integer(42));
1004    }
1005
1006    #[test]
1007    fn coalesce_all_null_returns_null() {
1008        let expr = Expr::FunctionCall {
1009            name: "COALESCE".to_string(),
1010            args: vec![lit(Value::Null), lit(Value::Null)],
1011            span: Span::synthetic(),
1012        };
1013        let v = evaluate(&expr, &empty_row()).unwrap();
1014        assert_eq!(v, Value::Null);
1015    }
1016
1017    #[test]
1018    fn upper_lower_dispatch_through_function_catalog() {
1019        let expr = Expr::FunctionCall {
1020            name: "UPPER".to_string(),
1021            args: vec![lit(Value::Text(Arc::from("hello")))],
1022            span: Span::synthetic(),
1023        };
1024        let v = evaluate(&expr, &empty_row()).unwrap();
1025        assert_eq!(v, Value::Text(Arc::from("HELLO")));
1026    }
1027
1028    #[test]
1029    fn length_of_null_propagates() {
1030        let expr = Expr::FunctionCall {
1031            name: "LENGTH".to_string(),
1032            args: vec![lit(Value::Null)],
1033            span: Span::synthetic(),
1034        };
1035        let v = evaluate(&expr, &empty_row()).unwrap();
1036        assert_eq!(v, Value::Null);
1037    }
1038
1039    #[test]
1040    fn case_when_picks_first_true_branch() {
1041        let expr = Expr::Case {
1042            branches: vec![
1043                (lit(Value::Boolean(false)), lit(Value::Integer(1))),
1044                (lit(Value::Boolean(true)), lit(Value::Integer(2))),
1045                (lit(Value::Boolean(true)), lit(Value::Integer(3))),
1046            ],
1047            else_: Some(Box::new(lit(Value::Integer(99)))),
1048            span: Span::synthetic(),
1049        };
1050        let v = evaluate(&expr, &empty_row()).unwrap();
1051        assert_eq!(v, Value::Integer(2));
1052    }
1053
1054    #[test]
1055    fn case_falls_through_to_else_when_no_branch_matches() {
1056        let expr = Expr::Case {
1057            branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1058            else_: Some(Box::new(lit(Value::Integer(99)))),
1059            span: Span::synthetic(),
1060        };
1061        let v = evaluate(&expr, &empty_row()).unwrap();
1062        assert_eq!(v, Value::Integer(99));
1063    }
1064
1065    #[test]
1066    fn case_returns_null_when_no_branch_matches_and_no_else() {
1067        let expr = Expr::Case {
1068            branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1069            else_: None,
1070            span: Span::synthetic(),
1071        };
1072        let v = evaluate(&expr, &empty_row()).unwrap();
1073        assert_eq!(v, Value::Null);
1074    }
1075
1076    #[test]
1077    fn is_null_handles_null_and_non_null() {
1078        let null_expr = Expr::IsNull {
1079            operand: Box::new(lit(Value::Null)),
1080            negated: false,
1081            span: Span::synthetic(),
1082        };
1083        assert_eq!(
1084            evaluate(&null_expr, &empty_row()).unwrap(),
1085            Value::Boolean(true)
1086        );
1087
1088        let non_null_expr = Expr::IsNull {
1089            operand: Box::new(lit(Value::Integer(7))),
1090            negated: false,
1091            span: Span::synthetic(),
1092        };
1093        assert_eq!(
1094            evaluate(&non_null_expr, &empty_row()).unwrap(),
1095            Value::Boolean(false)
1096        );
1097    }
1098
1099    #[test]
1100    fn between_inclusive_bounds() {
1101        let expr = Expr::Between {
1102            target: Box::new(lit(Value::Integer(5))),
1103            low: Box::new(lit(Value::Integer(1))),
1104            high: Box::new(lit(Value::Integer(10))),
1105            negated: false,
1106            span: Span::synthetic(),
1107        };
1108        assert_eq!(evaluate(&expr, &empty_row()).unwrap(), Value::Boolean(true));
1109    }
1110
1111    #[test]
1112    fn in_list_match_and_miss() {
1113        let hit = Expr::InList {
1114            target: Box::new(lit(Value::Integer(2))),
1115            values: vec![
1116                lit(Value::Integer(1)),
1117                lit(Value::Integer(2)),
1118                lit(Value::Integer(3)),
1119            ],
1120            negated: false,
1121            span: Span::synthetic(),
1122        };
1123        assert_eq!(evaluate(&hit, &empty_row()).unwrap(), Value::Boolean(true));
1124
1125        let miss = Expr::InList {
1126            target: Box::new(lit(Value::Integer(99))),
1127            values: vec![lit(Value::Integer(1)), lit(Value::Integer(2))],
1128            negated: false,
1129            span: Span::synthetic(),
1130        };
1131        assert_eq!(
1132            evaluate(&miss, &empty_row()).unwrap(),
1133            Value::Boolean(false)
1134        );
1135    }
1136
1137    #[test]
1138    fn column_lookup_walks_field_ref() {
1139        let row = |field: &FieldRef| -> Option<Value> {
1140            match field {
1141                FieldRef::TableColumn { table, column } if table == "t" && column == "x" => {
1142                    Some(Value::Integer(11))
1143                }
1144                _ => None,
1145            }
1146        };
1147        let expr = Expr::Column {
1148            field: FieldRef::TableColumn {
1149                table: "t".to_string(),
1150                column: "x".to_string(),
1151            },
1152            span: Span::synthetic(),
1153        };
1154        assert_eq!(evaluate(&expr, &row).unwrap(), Value::Integer(11));
1155    }
1156
1157    #[test]
1158    fn missing_column_surfaces_unknown_column() {
1159        let row = |_: &FieldRef| -> Option<Value> { None };
1160        let expr = Expr::Column {
1161            field: FieldRef::TableColumn {
1162                table: "t".to_string(),
1163                column: "missing".to_string(),
1164            },
1165            span: Span::synthetic(),
1166        };
1167        let err = evaluate(&expr, &row).unwrap_err();
1168        match err {
1169            EvalError::UnknownColumn(_) => {}
1170            other => panic!("expected UnknownColumn, got {other:?}"),
1171        }
1172    }
1173
1174    #[test]
1175    fn parameter_without_bind_is_eval_error() {
1176        let expr = Expr::Parameter {
1177            index: 1,
1178            span: Span::synthetic(),
1179        };
1180        let err = evaluate(&expr, &empty_row()).unwrap_err();
1181        assert_eq!(err, EvalError::UnboundParameter(1));
1182    }
1183
1184    #[test]
1185    fn cast_integer_to_text_uses_explicit_path() {
1186        let expr = Expr::Cast {
1187            inner: Box::new(lit(Value::Integer(42))),
1188            target: DataType::Text,
1189            span: Span::synthetic(),
1190        };
1191        assert_eq!(
1192            evaluate(&expr, &empty_row()).unwrap(),
1193            Value::Text(Arc::from("42"))
1194        );
1195    }
1196
1197    #[test]
1198    fn concat_returns_text() {
1199        let expr = binop(
1200            BinOp::Concat,
1201            lit(Value::Text(Arc::from("foo"))),
1202            lit(Value::Text(Arc::from("bar"))),
1203        );
1204        assert_eq!(
1205            evaluate(&expr, &empty_row()).unwrap(),
1206            Value::Text(Arc::from("foobar"))
1207        );
1208    }
1209}