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