Skip to main content

reddb_engine/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::runtime::values::runtime_exact_integer_cmp;
66use reddb_rql::ast::{BinOp, Expr, FieldRef, UnaryOp};
67use reddb_types::coerce::coerce_via_catalog;
68use reddb_types::coercion_spine;
69use reddb_types::function_catalog::FUNCTION_CATALOG;
70use reddb_types::operator_catalog::OperatorEntry;
71use reddb_types::{DataType, Value};
72
73/// Pluggable row-binding lookup. The evaluator stays agnostic of
74/// whether the caller has a slot vector indexed by planner-assigned
75/// position, a flat `HashMap<String, Value>`, or a graph binding —
76/// every consumer just provides a `Row` impl that resolves a
77/// `FieldRef` to a `Value`.
78pub trait Row {
79    /// Resolve a column / property reference. Returns `None` when the
80    /// reference is unknown (the evaluator surfaces this as
81    /// [`EvalError::UnknownColumn`]) or `Some(Value::Null)` when the
82    /// column exists but the row's value is null.
83    fn get(&self, field: &FieldRef) -> Option<Value>;
84
85    /// Hosts which decode stored values can report operating failures instead
86    /// of treating a denied read as an unknown or NULL column.
87    fn try_get(&self, field: &FieldRef) -> Result<Option<Value>, EvalError> {
88        Ok(self.get(field))
89    }
90}
91
92/// Trivial `Row` impl over a flat `(table, column) -> Value` closure
93/// — useful for tests and for callers that only have a column-name
94/// map.
95impl<F> Row for F
96where
97    F: Fn(&FieldRef) -> Option<Value>,
98{
99    fn get(&self, field: &FieldRef) -> Option<Value> {
100        self(field)
101    }
102}
103
104/// Errors surfaced by [`evaluate`]. Every variant is a runtime
105/// failure shape — type-resolution failures live in
106/// `expr_typing::TypeError`, not here.
107#[derive(Debug, Clone, PartialEq)]
108pub enum EvalError {
109    /// Column / property not present in the row binding.
110    UnknownColumn(FieldRef),
111    /// Query parameter placeholders are not resolved at this layer
112    /// — the bind phase substitutes a concrete value before
113    /// evaluation.
114    UnboundParameter(usize),
115    /// Operator catalog has no overload that accepts these operand
116    /// types, even after considering implicit coercions.
117    OperatorMismatch {
118        op: BinOp,
119        lhs: DataType,
120        rhs: DataType,
121    },
122    /// Unary operator doesn't accept the operand type.
123    UnaryMismatch { op: UnaryOp, operand: DataType },
124    /// Function catalog has no overload matching this call's
125    /// argument types. Includes user-defined functions because
126    /// today's catalog is the static built-in table only.
127    UnknownFunction { name: String, args: Vec<DataType> },
128    /// Implicit cast required by the spine failed at runtime — the
129    /// catalog said the conversion was legal at `Implicit` context
130    /// but the value's bytes couldn't be converted (e.g. overflow on
131    /// `BigInt` → `Integer`).
132    ImplicitCastFailed {
133        from: DataType,
134        to: DataType,
135        reason: String,
136    },
137    /// Explicit `CAST(x AS T)` failed at runtime.
138    CastFailed {
139        from: DataType,
140        to: DataType,
141        reason: String,
142    },
143    /// Signed-integer overflow during arithmetic.
144    ArithmeticOverflow { op: BinOp },
145    /// `n / 0` or `n % 0`.
146    DivisionByZero,
147    /// Numeric scalar evaluation produced an undefined or non-finite
148    /// float result (domain error, overflow, NaN, or infinity).
149    InvalidNumericResult { function: String, reason: String },
150    /// JSON_PARSE input was not valid JSON text.
151    JsonParseFailed { reason: String },
152    /// Decoding an expression input would exceed the host's read allowance.
153    ReadLimitExceeded {
154        required_bytes: usize,
155        limit_bytes: usize,
156    },
157    /// `IN (...)` against an empty value list — preserves the legacy
158    /// "always false" semantic but recorded explicitly so the
159    /// optimiser can fold it.
160    EmptyInList,
161}
162
163impl std::fmt::Display for EvalError {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        match self {
166            EvalError::UnknownColumn(field) => write!(f, "unknown column: {field:?}"),
167            EvalError::UnboundParameter(idx) => {
168                write!(f, "unbound query parameter: ${idx}")
169            }
170            EvalError::OperatorMismatch { op, lhs, rhs } => {
171                write!(f, "operator {op:?} not defined for ({lhs:?}, {rhs:?})")
172            }
173            EvalError::UnaryMismatch { op, operand } => {
174                write!(f, "unary {op:?} not defined for {operand:?}")
175            }
176            EvalError::UnknownFunction { name, args } => {
177                write!(f, "unknown function: {name}({args:?})")
178            }
179            EvalError::ImplicitCastFailed { from, to, reason } => {
180                write!(f, "implicit cast {from:?} -> {to:?} failed: {reason}")
181            }
182            EvalError::CastFailed { from, to, reason } => {
183                write!(f, "cast {from:?} -> {to:?} failed: {reason}")
184            }
185            EvalError::ArithmeticOverflow { op } => {
186                write!(f, "arithmetic overflow in {op:?}")
187            }
188            EvalError::DivisionByZero => write!(f, "division by zero"),
189            EvalError::InvalidNumericResult { function, reason } => {
190                write!(f, "invalid numeric result in {function}: {reason}")
191            }
192            EvalError::JsonParseFailed { reason } => {
193                write!(f, "JSON_PARSE failed to parse JSON: {reason}")
194            }
195            EvalError::ReadLimitExceeded {
196                required_bytes,
197                limit_bytes,
198            } => write!(
199                f,
200                "read memory limit exceeded: requires {required_bytes} bytes, limit {limit_bytes} bytes"
201            ),
202            EvalError::EmptyInList => write!(f, "IN list is empty"),
203        }
204    }
205}
206
207impl std::error::Error for EvalError {}
208
209/// Evaluate a scalar `Expr` against a row binding. Single entry
210/// point for the deep evaluator interface — every recursive call
211/// folds back through this function so the resolution surface stays
212/// uniform.
213pub fn evaluate(expr: &Expr, row: &dyn Row) -> Result<Value, EvalError> {
214    evaluate_with_literal_decoder(expr, row, &|value| Ok(value.clone()))
215}
216
217/// Decode only literals reached by the typed walk. Hosts retain resource and
218/// secret policy without cloning the AST or eagerly expanding unused branches.
219pub fn evaluate_with_literal_decoder(
220    expr: &Expr,
221    row: &dyn Row,
222    decode_literal: &impl Fn(&Value) -> Result<Value, EvalError>,
223) -> Result<Value, EvalError> {
224    match expr {
225        Expr::Literal { value, .. } => decode_literal(value),
226        Expr::Column { field, .. } => row
227            .try_get(field)?
228            .ok_or_else(|| EvalError::UnknownColumn(field.clone())),
229        Expr::Parameter { index, .. } => Err(EvalError::UnboundParameter(*index)),
230        Expr::UnaryOp { op, operand, .. } => eval_unary(*op, operand, row, decode_literal),
231        Expr::BinaryOp { op, lhs, rhs, .. } => eval_binary(*op, lhs, rhs, row, decode_literal),
232        Expr::Cast { inner, target, .. } => eval_cast(inner, *target, row, decode_literal),
233        Expr::FunctionCall { name, args, .. } => eval_function(name, args, row, decode_literal),
234        Expr::Case {
235            branches, else_, ..
236        } => eval_case(branches, else_.as_deref(), row, decode_literal),
237        Expr::IsNull {
238            operand, negated, ..
239        } => {
240            let v = evaluate_with_literal_decoder(operand, row, decode_literal)?;
241            let is_null = v.is_null();
242            Ok(Value::Boolean(if *negated { !is_null } else { is_null }))
243        }
244        Expr::InList {
245            target,
246            values,
247            negated,
248            ..
249        } => eval_in_list(target, values, *negated, row, decode_literal),
250        Expr::Between {
251            target,
252            low,
253            high,
254            negated,
255            ..
256        } => eval_between(target, low, high, *negated, row, decode_literal),
257        Expr::Subquery { .. } => Err(EvalError::UnknownFunction {
258            name: "SUBQUERY".to_string(),
259            args: Vec::new(),
260        }),
261        Expr::WindowFunctionCall { name, .. } => Err(EvalError::UnknownFunction {
262            name: format!("{name} OVER (...)"),
263            args: Vec::new(),
264        }),
265    }
266}
267
268fn eval_unary(
269    op: UnaryOp,
270    operand: &Expr,
271    row: &dyn Row,
272    decode_literal: &impl Fn(&Value) -> Result<Value, EvalError>,
273) -> Result<Value, EvalError> {
274    let v = evaluate_with_literal_decoder(operand, row, decode_literal)?;
275    if v.is_null() {
276        return Ok(Value::Null);
277    }
278    match op {
279        UnaryOp::Neg => match &v {
280            Value::Integer(n) => n
281                .checked_neg()
282                .map(Value::Integer)
283                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
284            Value::BigInt(n) => n
285                .checked_neg()
286                .map(Value::BigInt)
287                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
288            Value::Float(n) => Ok(Value::Float(-*n)),
289            Value::Decimal(n) => n
290                .checked_neg()
291                .map(Value::Decimal)
292                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
293            other => Err(EvalError::UnaryMismatch {
294                op,
295                operand: other.data_type(),
296            }),
297        },
298        UnaryOp::Not => match &v {
299            Value::Boolean(b) => Ok(Value::Boolean(!b)),
300            other => Err(EvalError::UnaryMismatch {
301                op,
302                operand: other.data_type(),
303            }),
304        },
305    }
306}
307
308fn eval_binary(
309    op: BinOp,
310    lhs: &Expr,
311    rhs: &Expr,
312    row: &dyn Row,
313    decode_literal: &impl Fn(&Value) -> Result<Value, EvalError>,
314) -> Result<Value, EvalError> {
315    // Logical ops use SQL three-valued logic so we eval both sides
316    // and short-circuit on Null *after* type-checking; arithmetic /
317    // comparison / concat short-circuit before dispatch.
318    let l = evaluate_with_literal_decoder(lhs, row, decode_literal)?;
319    let r = evaluate_with_literal_decoder(rhs, row, decode_literal)?;
320
321    match op {
322        BinOp::And => return three_valued_and(&l, &r),
323        BinOp::Or => return three_valued_or(&l, &r),
324        _ => {}
325    }
326
327    if l.is_null() || r.is_null() {
328        return Ok(Value::Null);
329    }
330
331    // Compare integer representations before numeric promotion can round large
332    // values or reject a u64 that exceeds the signed range.
333    if matches!(
334        op,
335        BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge
336    ) {
337        if let Some(order) = runtime_exact_integer_cmp(&l, &r) {
338            return Ok(Value::Boolean(match op {
339                BinOp::Eq => order.is_eq(),
340                BinOp::Ne => !order.is_eq(),
341                BinOp::Lt => order.is_lt(),
342                BinOp::Le => !order.is_gt(),
343                BinOp::Gt => order.is_gt(),
344                BinOp::Ge => !order.is_lt(),
345                _ => unreachable!("integer comparison operator was admitted"),
346            }));
347        }
348    }
349
350    let lhs_dt = l.data_type();
351    let rhs_dt = r.data_type();
352    let (entry, coercions) =
353        coercion_spine::resolve_binop(op, lhs_dt, rhs_dt).ok_or(EvalError::OperatorMismatch {
354            op,
355            lhs: lhs_dt,
356            rhs: rhs_dt,
357        })?;
358
359    let l = match coercions.at(0) {
360        Some(target) => apply_implicit_cast(&l, lhs_dt, target)?,
361        None => l,
362    };
363    let r = match coercions.at(1) {
364        Some(target) => apply_implicit_cast(&r, rhs_dt, target)?,
365        None => r,
366    };
367
368    dispatch_binop(op, entry, l, r)
369}
370
371fn dispatch_binop(
372    op: BinOp,
373    entry: &OperatorEntry,
374    l: Value,
375    r: Value,
376) -> Result<Value, EvalError> {
377    match op {
378        BinOp::Add => arith_add(entry, l, r),
379        BinOp::Sub => arith_sub(entry, l, r),
380        BinOp::Mul => arith_mul(entry, l, r),
381        BinOp::Div => arith_div(entry, l, r),
382        BinOp::Mod => arith_mod(entry, l, r),
383        BinOp::Concat => match (&l, &r) {
384            (Value::Text(a), Value::Text(b)) => {
385                let mut s = String::with_capacity(a.len() + b.len());
386                s.push_str(a);
387                s.push_str(b);
388                Ok(Value::Text(Arc::from(s)))
389            }
390            _ => Err(EvalError::OperatorMismatch {
391                op,
392                lhs: l.data_type(),
393                rhs: r.data_type(),
394            }),
395        },
396        BinOp::Eq => Ok(Value::Boolean(values_equal(&l, &r))),
397        BinOp::Ne => Ok(Value::Boolean(!values_equal(&l, &r))),
398        BinOp::Lt => cmp_op(op, l, r, |o| o == std::cmp::Ordering::Less),
399        BinOp::Le => cmp_op(op, l, r, |o| o != std::cmp::Ordering::Greater),
400        BinOp::Gt => cmp_op(op, l, r, |o| o == std::cmp::Ordering::Greater),
401        BinOp::Ge => cmp_op(op, l, r, |o| o != std::cmp::Ordering::Less),
402        BinOp::And | BinOp::Or => unreachable!("handled before dispatch"),
403    }
404}
405
406fn arith_add(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
407    match entry.return_type {
408        DataType::Integer => match (l, r) {
409            (Value::Integer(a), Value::Integer(b)) => a
410                .checked_add(b)
411                .map(Value::Integer)
412                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
413            _ => unreachable_after_coercion("Add", DataType::Integer),
414        },
415        DataType::BigInt => match (l, r) {
416            (Value::BigInt(a), Value::BigInt(b)) => a
417                .checked_add(b)
418                .map(Value::BigInt)
419                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
420            _ => unreachable_after_coercion("Add", DataType::BigInt),
421        },
422        DataType::Float => checked_float_binop(BinOp::Add, as_f64(&l) + as_f64(&r)),
423        DataType::Decimal => match (l, r) {
424            (Value::Decimal(a), Value::Decimal(b)) => a
425                .checked_add(b)
426                .map(Value::Decimal)
427                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
428            _ => unreachable_after_coercion("Add", DataType::Decimal),
429        },
430        other => Err(EvalError::OperatorMismatch {
431            op: BinOp::Add,
432            lhs: other,
433            rhs: other,
434        }),
435    }
436}
437
438fn arith_sub(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
439    match entry.return_type {
440        DataType::Integer => match (l, r) {
441            (Value::Integer(a), Value::Integer(b)) => a
442                .checked_sub(b)
443                .map(Value::Integer)
444                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
445            _ => unreachable_after_coercion("Sub", DataType::Integer),
446        },
447        DataType::BigInt => match (l, r) {
448            (Value::BigInt(a), Value::BigInt(b)) => a
449                .checked_sub(b)
450                .map(Value::BigInt)
451                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
452            _ => unreachable_after_coercion("Sub", DataType::BigInt),
453        },
454        DataType::Float => checked_float_binop(BinOp::Sub, as_f64(&l) - as_f64(&r)),
455        DataType::Decimal => match (l, r) {
456            (Value::Decimal(a), Value::Decimal(b)) => a
457                .checked_sub(b)
458                .map(Value::Decimal)
459                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
460            _ => unreachable_after_coercion("Sub", DataType::Decimal),
461        },
462        other => Err(EvalError::OperatorMismatch {
463            op: BinOp::Sub,
464            lhs: other,
465            rhs: other,
466        }),
467    }
468}
469
470fn arith_mul(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
471    match entry.return_type {
472        DataType::Integer => match (l, r) {
473            (Value::Integer(a), Value::Integer(b)) => a
474                .checked_mul(b)
475                .map(Value::Integer)
476                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mul }),
477            _ => unreachable_after_coercion("Mul", DataType::Integer),
478        },
479        DataType::BigInt => match (l, r) {
480            (Value::BigInt(a), Value::BigInt(b)) => a
481                .checked_mul(b)
482                .map(Value::BigInt)
483                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mul }),
484            _ => unreachable_after_coercion("Mul", DataType::BigInt),
485        },
486        DataType::Float => checked_float_binop(BinOp::Mul, as_f64(&l) * as_f64(&r)),
487        other => Err(EvalError::OperatorMismatch {
488            op: BinOp::Mul,
489            lhs: other,
490            rhs: other,
491        }),
492    }
493}
494
495fn arith_div(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
496    // Operator catalog promotes integer / integer to Float —
497    // mirror that here so behavior stays identical to the typer.
498    match entry.return_type {
499        DataType::Float => {
500            let denom = as_f64(&r);
501            if denom == 0.0 {
502                return Err(EvalError::DivisionByZero);
503            }
504            checked_float_binop(BinOp::Div, as_f64(&l) / denom)
505        }
506        other => Err(EvalError::OperatorMismatch {
507            op: BinOp::Div,
508            lhs: other,
509            rhs: other,
510        }),
511    }
512}
513
514fn arith_mod(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
515    match entry.return_type {
516        DataType::Integer => match (l, r) {
517            (Value::Integer(_), Value::Integer(0)) => Err(EvalError::DivisionByZero),
518            (Value::Integer(a), Value::Integer(b)) => a
519                .checked_rem(b)
520                .map(Value::Integer)
521                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mod }),
522            _ => unreachable_after_coercion("Mod", DataType::Integer),
523        },
524        DataType::BigInt => match (l, r) {
525            (Value::BigInt(_), Value::BigInt(0)) => Err(EvalError::DivisionByZero),
526            (Value::BigInt(a), Value::BigInt(b)) => a
527                .checked_rem(b)
528                .map(Value::BigInt)
529                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mod }),
530            _ => unreachable_after_coercion("Mod", DataType::BigInt),
531        },
532        other => Err(EvalError::OperatorMismatch {
533            op: BinOp::Mod,
534            lhs: other,
535            rhs: other,
536        }),
537    }
538}
539
540fn unreachable_after_coercion(op: &'static str, expected: DataType) -> Result<Value, EvalError> {
541    Err(EvalError::OperatorMismatch {
542        op: match op {
543            "Add" => BinOp::Add,
544            "Sub" => BinOp::Sub,
545            "Mul" => BinOp::Mul,
546            "Div" => BinOp::Div,
547            "Mod" => BinOp::Mod,
548            _ => BinOp::Add,
549        },
550        lhs: expected,
551        rhs: expected,
552    })
553}
554
555fn checked_float_binop(op: BinOp, value: f64) -> Result<Value, EvalError> {
556    if value.is_finite() {
557        Ok(Value::Float(value))
558    } else {
559        Err(EvalError::InvalidNumericResult {
560            function: format!("{op:?}"),
561            reason: "result is NaN or infinite".to_string(),
562        })
563    }
564}
565
566fn as_f64(v: &Value) -> f64 {
567    match v {
568        Value::Float(x) => *x,
569        Value::Integer(x) => *x as f64,
570        Value::BigInt(x) => *x as f64,
571        Value::UnsignedInteger(x) => *x as f64,
572        Value::Decimal(x) => reddb_types::types::decimal_to_f64(*x),
573        _ => 0.0,
574    }
575}
576
577fn cmp_op<F>(op: BinOp, l: Value, r: Value, pick: F) -> Result<Value, EvalError>
578where
579    F: Fn(std::cmp::Ordering) -> bool,
580{
581    let ord = compare_values(&l, &r).ok_or(EvalError::OperatorMismatch {
582        op,
583        lhs: l.data_type(),
584        rhs: r.data_type(),
585    })?;
586    Ok(Value::Boolean(pick(ord)))
587}
588
589/// Total ordering for the numeric and text families that the
590/// catalog declares comparison overloads for. Returns `None` when
591/// the values aren't comparable — callers surface this as
592/// [`EvalError::OperatorMismatch`].
593fn compare_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
594    if let Some(order) = runtime_exact_integer_cmp(a, b) {
595        return Some(order);
596    }
597    match (a, b) {
598        (Value::Temperature(x), Value::Temperature(y)) => Some(x.cmp(y)),
599        (Value::Integer(x), Value::Integer(y)) => Some(x.cmp(y)),
600        (Value::BigInt(x), Value::BigInt(y)) => Some(x.cmp(y)),
601        (Value::Float(x), Value::Float(y)) => x.partial_cmp(y),
602        (Value::Text(x), Value::Text(y)) => Some(x.as_ref().cmp(y.as_ref())),
603        (Value::Boolean(x), Value::Boolean(y)) => Some(x.cmp(y)),
604        (Value::Timestamp(x), Value::Timestamp(y)) => Some(x.cmp(y)),
605        (Value::TimestampMs(x), Value::TimestampMs(y)) => Some(x.cmp(y)),
606        (Value::Date(x), Value::Date(y)) => Some(x.cmp(y)),
607        (Value::Time(x), Value::Time(y)) => Some(x.cmp(y)),
608        (Value::Uuid(x), Value::Uuid(y)) => Some(x.cmp(y)),
609        (Value::Decimal(x), Value::Decimal(y)) => Some(x.cmp(y)),
610        // Cross-numeric — operand coercion should have homogenised
611        // these but if a caller invokes the evaluator with mixed
612        // numerics directly, fall back to f64 ordering.
613        (Value::Integer(_) | Value::Float(_) | Value::BigInt(_), _) => {
614            let l = as_f64(a);
615            let r = as_f64(b);
616            l.partial_cmp(&r)
617        }
618        _ => None,
619    }
620}
621
622fn values_equal(a: &Value, b: &Value) -> bool {
623    if let Some(order) = runtime_exact_integer_cmp(a, b) {
624        return order.is_eq();
625    }
626    match (a, b) {
627        (Value::Float(x), Value::Float(y)) => x == y,
628        _ => a == b,
629    }
630}
631
632fn three_valued_and(l: &Value, r: &Value) -> Result<Value, EvalError> {
633    match (l, r) {
634        (Value::Boolean(false), _) | (_, Value::Boolean(false)) => Ok(Value::Boolean(false)),
635        (Value::Boolean(true), Value::Boolean(true)) => Ok(Value::Boolean(true)),
636        (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
637        _ => Err(EvalError::OperatorMismatch {
638            op: BinOp::And,
639            lhs: l.data_type(),
640            rhs: r.data_type(),
641        }),
642    }
643}
644
645fn three_valued_or(l: &Value, r: &Value) -> Result<Value, EvalError> {
646    match (l, r) {
647        (Value::Boolean(true), _) | (_, Value::Boolean(true)) => Ok(Value::Boolean(true)),
648        (Value::Boolean(false), Value::Boolean(false)) => Ok(Value::Boolean(false)),
649        (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
650        _ => Err(EvalError::OperatorMismatch {
651            op: BinOp::Or,
652            lhs: l.data_type(),
653            rhs: r.data_type(),
654        }),
655    }
656}
657
658fn apply_implicit_cast(value: &Value, src: DataType, target: DataType) -> Result<Value, EvalError> {
659    if src == target {
660        return Ok(value.clone());
661    }
662    coerce_via_catalog(value, src, target, None).map_err(|reason| EvalError::ImplicitCastFailed {
663        from: src,
664        to: target,
665        reason,
666    })
667}
668
669fn eval_cast(
670    inner: &Expr,
671    target: DataType,
672    row: &dyn Row,
673    decode_literal: &impl Fn(&Value) -> Result<Value, EvalError>,
674) -> Result<Value, EvalError> {
675    let v = evaluate_with_literal_decoder(inner, row, decode_literal)?;
676    if v.is_null() {
677        return Ok(Value::Null);
678    }
679    let src = v.data_type();
680    if src == target {
681        return Ok(v);
682    }
683    coerce_via_catalog(&v, src, target, None).map_err(|reason| EvalError::CastFailed {
684        from: src,
685        to: target,
686        reason,
687    })
688}
689
690fn eval_function(
691    name: &str,
692    args: &[Expr],
693    row: &dyn Row,
694    decode_literal: &impl Fn(&Value) -> Result<Value, EvalError>,
695) -> Result<Value, EvalError> {
696    // COALESCE has SQL-special semantics that the catalog can't
697    // express (variadic + uniform arg type unifies poorly with
698    // first-non-null). Handle it before catalog dispatch so we
699    // preserve `COALESCE(int, int) -> int` rather than coercing
700    // every argument to Text.
701    if name.eq_ignore_ascii_case("COALESCE") {
702        for arg in args {
703            let v = evaluate_with_literal_decoder(arg, row, decode_literal)?;
704            if !v.is_null() {
705                return Ok(v);
706            }
707        }
708        return Ok(Value::Null);
709    }
710
711    let arg_values: Vec<Value> = args
712        .iter()
713        .map(|a| evaluate_with_literal_decoder(a, row, decode_literal))
714        .collect::<Result<Vec<_>, _>>()?;
715    let arg_types: Vec<DataType> = arg_values.iter().map(|v| v.data_type()).collect();
716
717    // Strict NULL propagation applies only to non-variadic built-ins.
718    // Variadic functions such as CONCAT own their NULL semantics.
719    // Unknown functions with NULL arguments must still surface as
720    // `UnknownFunction` rather than silently returning NULL.
721    if arg_values.iter().any(Value::is_null)
722        && FUNCTION_CATALOG
723            .iter()
724            .any(|entry| entry.name.eq_ignore_ascii_case(name))
725        && FUNCTION_CATALOG
726            .iter()
727            .filter(|entry| entry.name.eq_ignore_ascii_case(name))
728            .all(|entry| !entry.variadic)
729    {
730        return Ok(Value::Null);
731    }
732
733    let (entry, coercions) =
734        coercion_spine::resolve_function(name, &arg_types).ok_or_else(|| {
735            EvalError::UnknownFunction {
736                name: name.to_string(),
737                args: arg_types.clone(),
738            }
739        })?;
740
741    // Apply per-arg implicit casts.
742    let mut coerced: Vec<Value> = Vec::with_capacity(arg_values.len());
743    for (idx, value) in arg_values.into_iter().enumerate() {
744        let src = arg_types[idx];
745        match coercions.at(idx) {
746            Some(target) if src != target => {
747                coerced.push(apply_implicit_cast(&value, src, target)?);
748            }
749            _ => coerced.push(value),
750        }
751    }
752
753    // NULL propagation for built-ins: if any non-variadic argument
754    // is null, return null. Variadic / aggregate semantics handle
755    // null differently and aren't in scope for this slice.
756    if !entry.variadic && coerced.iter().any(|v| v.is_null()) {
757        return Ok(Value::Null);
758    }
759
760    dispatch_function(entry.name, &coerced)
761}
762
763fn dispatch_function(name: &str, args: &[Value]) -> Result<Value, EvalError> {
764    match name {
765        "TEMPERATURE" | "TEMPERATURE_UNIT" | "TEMPERATURE_VALUE" => {
766            super::runtime::value_functions::temperature_function(name, args).ok_or_else(|| {
767                EvalError::UnknownFunction {
768                    name: name.into(),
769                    args: args.iter().map(Value::data_type).collect(),
770                }
771            })
772        }
773        "UPPER" => match &args[0] {
774            Value::Text(s) => Ok(Value::Text(Arc::from(s.to_uppercase()))),
775            other => Err(EvalError::UnknownFunction {
776                name: name.to_string(),
777                args: vec![other.data_type()],
778            }),
779        },
780        "LOWER" => match &args[0] {
781            Value::Text(s) => Ok(Value::Text(Arc::from(s.to_lowercase()))),
782            other => Err(EvalError::UnknownFunction {
783                name: name.to_string(),
784                args: vec![other.data_type()],
785            }),
786        },
787        "TRIM" => trim_function(name, args, true, true),
788        "LTRIM" => trim_function(name, args, true, false),
789        "RTRIM" => trim_function(name, args, false, true),
790        "LENGTH" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => match &args[0] {
791            Value::Text(s) => Ok(Value::Integer(s.chars().count() as i64)),
792            other => Err(EvalError::UnknownFunction {
793                name: name.to_string(),
794                args: vec![other.data_type()],
795            }),
796        },
797        "OCTET_LENGTH" => match &args[0] {
798            Value::Text(s) => Ok(Value::Integer(s.len() as i64)),
799            Value::Blob(b) => Ok(Value::Integer(b.len() as i64)),
800            other => Err(EvalError::UnknownFunction {
801                name: name.to_string(),
802                args: vec![other.data_type()],
803            }),
804        },
805        "CONCAT" => {
806            let mut out = String::new();
807            for arg in args {
808                match arg {
809                    Value::Null => {}
810                    Value::Text(value) => out.push_str(value),
811                    _ => {
812                        return Err(EvalError::UnknownFunction {
813                            name: name.to_string(),
814                            args: args.iter().map(Value::data_type).collect(),
815                        })
816                    }
817                }
818            }
819            Ok(Value::Text(Arc::from(out)))
820        }
821        "JSON_PARSE" => json_parse_value(&args[0]),
822        "JSON_EXTRACT" => Ok(json_extract_value(&args[0], &args[1], false)),
823        "JSON_EXTRACT_TEXT" => Ok(json_extract_value(&args[0], &args[1], true)),
824        "CONTAINS" => Ok(contains_value(&args[0], &args[1])),
825        "ABS" => match &args[0] {
826            Value::Integer(n) => n
827                .checked_abs()
828                .map(Value::Integer)
829                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
830            Value::BigInt(n) => n
831                .checked_abs()
832                .map(Value::BigInt)
833                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
834            Value::Float(n) => Ok(Value::Float(n.abs())),
835            Value::Decimal(n) => n
836                .checked_abs()
837                .map(Value::Decimal)
838                .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
839            other => Err(EvalError::UnknownFunction {
840                name: name.to_string(),
841                args: vec![other.data_type()],
842            }),
843        },
844        "SQRT" => unary_math(name, args, |x| {
845            if x < 0.0 {
846                return Err("input must be greater than or equal to zero");
847            }
848            Ok(x.sqrt())
849        }),
850        "POWER" | "POW" => binary_math(name, args, |base, exp| Ok(base.powf(exp))),
851        "EXP" => unary_math(name, args, |x| Ok(x.exp())),
852        "LN" => unary_math(name, args, |x| {
853            if x <= 0.0 {
854                return Err("input must be greater than zero");
855            }
856            Ok(x.ln())
857        }),
858        "LOG" if args.len() == 1 => unary_math(name, args, |x| {
859            if x <= 0.0 {
860                return Err("input must be greater than zero");
861            }
862            Ok(x.log10())
863        }),
864        "LOG" => binary_math(name, args, |base, x| {
865            if base <= 0.0 {
866                return Err("base must be greater than zero");
867            }
868            if base == 1.0 {
869                return Err("base must not equal one");
870            }
871            if x <= 0.0 {
872                return Err("input must be greater than zero");
873            }
874            Ok(x.log(base))
875        }),
876        "LOG10" => unary_math(name, args, |x| {
877            if x <= 0.0 {
878                return Err("input must be greater than zero");
879            }
880            Ok(x.log10())
881        }),
882        "SIN" => unary_math(name, args, |x| Ok(x.sin())),
883        "COS" => unary_math(name, args, |x| Ok(x.cos())),
884        "TAN" => unary_math(name, args, |x| Ok(x.tan())),
885        "ASIN" | "ARCSIN" => unary_math(name, args, |x| {
886            if !(-1.0..=1.0).contains(&x) {
887                return Err("input must be between -1 and 1");
888            }
889            Ok(x.asin())
890        }),
891        "ACOS" | "ARCCOS" => unary_math(name, args, |x| {
892            if !(-1.0..=1.0).contains(&x) {
893                return Err("input must be between -1 and 1");
894            }
895            Ok(x.acos())
896        }),
897        "ATAN" | "ARCTAN" => unary_math(name, args, |x| Ok(x.atan())),
898        "ATAN2" => binary_math(name, args, |y, x| Ok(y.atan2(x))),
899        "COT" => unary_math(name, args, |x| {
900            let tan = x.tan();
901            if tan == 0.0 {
902                return Err("input must not produce zero tangent");
903            }
904            Ok(1.0 / tan)
905        }),
906        "DEGREES" => unary_math(name, args, |x| Ok(x.to_degrees())),
907        "RADIANS" => unary_math(name, args, |x| Ok(x.to_radians())),
908        "PI" => checked_math_result(name, std::f64::consts::PI),
909        // Functions whose runtime body the slice doesn't yet cover
910        // surface as UnknownFunction with the resolved arg types so
911        // callers can see the catalog matched but the dispatch
912        // didn't. Subsequent slices fill in CONCAT, time functions, …
913        other => Err(EvalError::UnknownFunction {
914            name: other.to_string(),
915            args: args.iter().map(|v| v.data_type()).collect(),
916        }),
917    }
918}
919
920fn trim_function(name: &str, args: &[Value], left: bool, right: bool) -> Result<Value, EvalError> {
921    if args.len() != 1 {
922        return Err(EvalError::UnknownFunction {
923            name: name.to_string(),
924            args: args.iter().map(|v| v.data_type()).collect(),
925        });
926    }
927
928    match &args[0] {
929        Value::Text(s) => {
930            let trimmed = match (left, right) {
931                (true, true) => s.trim(),
932                (true, false) => s.trim_start(),
933                (false, true) => s.trim_end(),
934                (false, false) => s,
935            };
936            Ok(Value::Text(Arc::from(trimmed)))
937        }
938        other => Err(EvalError::UnknownFunction {
939            name: name.to_string(),
940            args: vec![other.data_type()],
941        }),
942    }
943}
944
945fn unary_math<F>(name: &str, args: &[Value], op: F) -> Result<Value, EvalError>
946where
947    F: FnOnce(f64) -> Result<f64, &'static str>,
948{
949    let input = math_arg(name, args.first(), 0)?;
950    let value = op(input).map_err(|reason| EvalError::InvalidNumericResult {
951        function: name.to_string(),
952        reason: reason.to_string(),
953    })?;
954    checked_math_result(name, value)
955}
956
957fn binary_math<F>(name: &str, args: &[Value], op: F) -> Result<Value, EvalError>
958where
959    F: FnOnce(f64, f64) -> Result<f64, &'static str>,
960{
961    let left = math_arg(name, args.first(), 0)?;
962    let right = math_arg(name, args.get(1), 1)?;
963    let value = op(left, right).map_err(|reason| EvalError::InvalidNumericResult {
964        function: name.to_string(),
965        reason: reason.to_string(),
966    })?;
967    checked_math_result(name, value)
968}
969
970fn math_arg(name: &str, value: Option<&Value>, index: usize) -> Result<f64, EvalError> {
971    let value = value.ok_or_else(|| EvalError::UnknownFunction {
972        name: name.to_string(),
973        args: Vec::new(),
974    })?;
975    let numeric = as_f64(value);
976    if numeric.is_finite() {
977        Ok(numeric)
978    } else {
979        Err(EvalError::InvalidNumericResult {
980            function: name.to_string(),
981            reason: format!("argument {index} is NaN or infinite"),
982        })
983    }
984}
985
986fn checked_math_result(name: &str, value: f64) -> Result<Value, EvalError> {
987    if value.is_finite() {
988        Ok(Value::Float(value))
989    } else {
990        Err(EvalError::InvalidNumericResult {
991            function: name.to_string(),
992            reason: "result is NaN or infinite".to_string(),
993        })
994    }
995}
996
997fn json_extract_value(input: &Value, path: &Value, as_text: bool) -> Value {
998    let Value::Text(path) = path else {
999        return Value::Null;
1000    };
1001    let Some(json) = value_to_json(input) else {
1002        return Value::Null;
1003    };
1004    let Some(steps) = parse_json_path(path) else {
1005        return Value::Null;
1006    };
1007    let Some(target) = json_path_get(&json, &steps) else {
1008        return Value::Null;
1009    };
1010
1011    if as_text {
1012        match target {
1013            crate::serde_json::Value::String(value) => Value::text(value.clone()),
1014            crate::serde_json::Value::Null => Value::Null,
1015            crate::serde_json::Value::Bool(value) => Value::text(value.to_string()),
1016            crate::serde_json::Value::Integer(value) => Value::text(value.to_string()),
1017            crate::serde_json::Value::Number(value) => Value::text(value.to_string()),
1018            other => Value::text(other.to_string_compact()),
1019        }
1020    } else {
1021        Value::text(target.to_string_compact())
1022    }
1023}
1024
1025fn json_parse_value(input: &Value) -> Result<Value, EvalError> {
1026    let Value::Text(text) = input else {
1027        return Err(EvalError::UnknownFunction {
1028            name: "JSON_PARSE".to_string(),
1029            args: vec![input.data_type()],
1030        });
1031    };
1032    crate::serde_json::from_str::<crate::serde_json::Value>(text)
1033        .map(|json| Value::Json(json.to_string_compact().into_bytes()))
1034        .map_err(|err| EvalError::JsonParseFailed {
1035            reason: err.to_string(),
1036        })
1037}
1038
1039fn contains_value(input: &Value, needle: &Value) -> Value {
1040    let Value::Text(needle) = needle else {
1041        return Value::Null;
1042    };
1043    Value::Boolean(value_contains(input, needle))
1044}
1045
1046fn value_contains(value: &Value, needle: &str) -> bool {
1047    match value {
1048        Value::Array(values) => values.iter().any(|value| value_contains(value, needle)),
1049        Value::Json(_) => value_to_json(value)
1050            .as_ref()
1051            .is_some_and(|json| json_value_contains(json, needle)),
1052        Value::Text(value) => value.contains(needle),
1053        other => other.display_string().contains(needle),
1054    }
1055}
1056
1057fn json_value_contains(value: &crate::serde_json::Value, needle: &str) -> bool {
1058    match value {
1059        crate::serde_json::Value::Array(values) => values
1060            .iter()
1061            .any(|value| json_value_contains(value, needle)),
1062        // Recurse into object *values* so a needle nested inside a JSON
1063        // object matches — e.g. `CONTAINS(vision_detections, 'person')`
1064        // over a derived `[{"label":"person",...}]` array (#1275). Keys
1065        // are deliberately not matched: containment is about the data, not
1066        // the field names.
1067        crate::serde_json::Value::Object(map) => {
1068            map.values().any(|value| json_value_contains(value, needle))
1069        }
1070        crate::serde_json::Value::String(value) => value == needle,
1071        crate::serde_json::Value::Integer(value) => value.to_string() == needle,
1072        crate::serde_json::Value::Number(value) => value.to_string() == needle,
1073        crate::serde_json::Value::Decimal(value) => value == needle,
1074        crate::serde_json::Value::Bool(value) => value.to_string() == needle,
1075        crate::serde_json::Value::Null => false,
1076    }
1077}
1078
1079fn value_to_json(value: &Value) -> Option<crate::serde_json::Value> {
1080    match value {
1081        Value::Null => Some(crate::serde_json::Value::Null),
1082        // A document body may be the native binary container (PRD-1398); decode
1083        // it to JSON so json_extract / CONTAINS see the same shape as a plain
1084        // JSON body.
1085        Value::Json(bytes) => reddb_types::document_body_codec::decode_container_to_json(bytes)
1086            .or_else(|| crate::serde_json::from_slice(bytes).ok()),
1087        Value::Text(value) => crate::serde_json::from_str(value).ok(),
1088        _ => None,
1089    }
1090}
1091
1092enum JsonPathStep<'a> {
1093    Field(&'a str),
1094    Index(usize),
1095}
1096
1097fn parse_json_path(path: &str) -> Option<Vec<JsonPathStep<'_>>> {
1098    let path = path.trim();
1099    let rest = path.strip_prefix('$').unwrap_or(path);
1100    let mut steps = Vec::new();
1101    let bytes = rest.as_bytes();
1102    let mut index = 0;
1103    while index < bytes.len() {
1104        match bytes[index] {
1105            b'.' => {
1106                index += 1;
1107                let start = index;
1108                while index < bytes.len() && bytes[index] != b'.' && bytes[index] != b'[' {
1109                    index += 1;
1110                }
1111                if start == index {
1112                    return None;
1113                }
1114                steps.push(JsonPathStep::Field(
1115                    std::str::from_utf8(&bytes[start..index]).ok()?,
1116                ));
1117            }
1118            b'[' => {
1119                index += 1;
1120                let start = index;
1121                while index < bytes.len() && bytes[index] != b']' {
1122                    index += 1;
1123                }
1124                if index >= bytes.len() {
1125                    return None;
1126                }
1127                steps.push(JsonPathStep::Index(
1128                    std::str::from_utf8(&bytes[start..index])
1129                        .ok()?
1130                        .parse()
1131                        .ok()?,
1132                ));
1133                index += 1;
1134            }
1135            _ => return None,
1136        }
1137    }
1138    Some(steps)
1139}
1140
1141fn json_path_get<'a>(
1142    root: &'a crate::serde_json::Value,
1143    steps: &[JsonPathStep<'_>],
1144) -> Option<&'a crate::serde_json::Value> {
1145    let mut current = root;
1146    for step in steps {
1147        current = match (step, current) {
1148            (JsonPathStep::Field(name), crate::serde_json::Value::Object(map)) => map.get(*name)?,
1149            (JsonPathStep::Index(index), crate::serde_json::Value::Array(values)) => {
1150                values.get(*index)?
1151            }
1152            _ => return None,
1153        };
1154    }
1155    Some(current)
1156}
1157
1158fn eval_case(
1159    branches: &[(Expr, Expr)],
1160    else_: Option<&Expr>,
1161    row: &dyn Row,
1162    decode_literal: &impl Fn(&Value) -> Result<Value, EvalError>,
1163) -> Result<Value, EvalError> {
1164    for (cond, value) in branches {
1165        let c = evaluate_with_literal_decoder(cond, row, decode_literal)?;
1166        if matches!(c, Value::Boolean(true)) {
1167            return evaluate_with_literal_decoder(value, row, decode_literal);
1168        }
1169    }
1170    match else_ {
1171        Some(e) => evaluate_with_literal_decoder(e, row, decode_literal),
1172        None => Ok(Value::Null),
1173    }
1174}
1175
1176fn eval_in_list(
1177    target: &Expr,
1178    values: &[Expr],
1179    negated: bool,
1180    row: &dyn Row,
1181    decode_literal: &impl Fn(&Value) -> Result<Value, EvalError>,
1182) -> Result<Value, EvalError> {
1183    if values.is_empty() {
1184        return Err(EvalError::EmptyInList);
1185    }
1186    let needle = evaluate_with_literal_decoder(target, row, decode_literal)?;
1187    if needle.is_null() {
1188        return Ok(Value::Null);
1189    }
1190    let mut saw_null = false;
1191    for v in values {
1192        let candidate = evaluate_with_literal_decoder(v, row, decode_literal)?;
1193        if candidate.is_null() {
1194            saw_null = true;
1195            continue;
1196        }
1197        if values_equal(&needle, &candidate) {
1198            return Ok(Value::Boolean(!negated));
1199        }
1200    }
1201    if saw_null {
1202        return Ok(Value::Null);
1203    }
1204    Ok(Value::Boolean(negated))
1205}
1206
1207fn eval_between(
1208    target: &Expr,
1209    low: &Expr,
1210    high: &Expr,
1211    negated: bool,
1212    row: &dyn Row,
1213    decode_literal: &impl Fn(&Value) -> Result<Value, EvalError>,
1214) -> Result<Value, EvalError> {
1215    let v = evaluate_with_literal_decoder(target, row, decode_literal)?;
1216    let lo = evaluate_with_literal_decoder(low, row, decode_literal)?;
1217    let hi = evaluate_with_literal_decoder(high, row, decode_literal)?;
1218    if v.is_null() || lo.is_null() || hi.is_null() {
1219        return Ok(Value::Null);
1220    }
1221    let lo_ok = compare_values(&v, &lo)
1222        .ok_or(EvalError::OperatorMismatch {
1223            op: BinOp::Ge,
1224            lhs: v.data_type(),
1225            rhs: lo.data_type(),
1226        })
1227        .map(|o| o != std::cmp::Ordering::Less)?;
1228    let hi_ok = compare_values(&v, &hi)
1229        .ok_or(EvalError::OperatorMismatch {
1230            op: BinOp::Le,
1231            lhs: v.data_type(),
1232            rhs: hi.data_type(),
1233        })
1234        .map(|o| o != std::cmp::Ordering::Greater)?;
1235    let inside = lo_ok && hi_ok;
1236    Ok(Value::Boolean(if negated { !inside } else { inside }))
1237}
1238
1239#[cfg(test)]
1240mod tests {
1241    use super::*;
1242    use reddb_rql::ast::Span;
1243
1244    fn lit(v: Value) -> Expr {
1245        Expr::Literal {
1246            value: v,
1247            span: Span::synthetic(),
1248        }
1249    }
1250
1251    fn binop(op: BinOp, l: Expr, r: Expr) -> Expr {
1252        Expr::BinaryOp {
1253            op,
1254            lhs: Box::new(l),
1255            rhs: Box::new(r),
1256            span: Span::synthetic(),
1257        }
1258    }
1259
1260    fn empty_row() -> impl Row {
1261        |_field: &FieldRef| -> Option<Value> { None }
1262    }
1263
1264    #[test]
1265    fn contains_descends_into_json_object_values() {
1266        // A derived component-detections array (#1275): CONTAINS must find
1267        // a label nested inside the object elements, not just top-level
1268        // array strings.
1269        let detections = br#"[{"label":"person","confidence":0.9,"bbox":[0,0,1,1]},
1270                              {"label":"car","confidence":0.7,"bbox":[1,1,2,2]}]"#;
1271        let json = Value::Json(detections.to_vec());
1272        assert!(value_contains(&json, "person"));
1273        assert!(value_contains(&json, "car"));
1274        assert!(!value_contains(&json, "bicycle"));
1275        // Keys are not matched — only the data.
1276        assert!(!value_contains(&json, "label"));
1277    }
1278
1279    #[test]
1280    fn integer_addition_overflow_surfaces_as_eval_error() {
1281        let expr = binop(
1282            BinOp::Add,
1283            lit(Value::Integer(i64::MAX)),
1284            lit(Value::Integer(1)),
1285        );
1286        let err = evaluate(&expr, &empty_row()).unwrap_err();
1287        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Add });
1288    }
1289
1290    #[test]
1291    fn integer_multiplication_overflow_surfaces_as_eval_error() {
1292        let expr = binop(
1293            BinOp::Mul,
1294            lit(Value::Integer(i64::MAX)),
1295            lit(Value::Integer(2)),
1296        );
1297        let err = evaluate(&expr, &empty_row()).unwrap_err();
1298        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Mul });
1299    }
1300
1301    #[test]
1302    fn integer_subtraction_overflow_surfaces_as_eval_error() {
1303        let expr = binop(
1304            BinOp::Sub,
1305            lit(Value::Integer(i64::MIN)),
1306            lit(Value::Integer(1)),
1307        );
1308        let err = evaluate(&expr, &empty_row()).unwrap_err();
1309        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1310    }
1311
1312    #[test]
1313    fn unary_neg_overflow_on_min_int_surfaces_as_eval_error() {
1314        let expr = Expr::UnaryOp {
1315            op: UnaryOp::Neg,
1316            operand: Box::new(lit(Value::Integer(i64::MIN))),
1317            span: Span::synthetic(),
1318        };
1319        let err = evaluate(&expr, &empty_row()).unwrap_err();
1320        assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1321    }
1322
1323    #[test]
1324    fn null_propagates_through_arithmetic() {
1325        let expr = binop(BinOp::Add, lit(Value::Null), lit(Value::Integer(7)));
1326        let v = evaluate(&expr, &empty_row()).unwrap();
1327        assert_eq!(v, Value::Null);
1328    }
1329
1330    #[test]
1331    fn null_propagates_through_comparison() {
1332        let expr = binop(BinOp::Lt, lit(Value::Integer(1)), lit(Value::Null));
1333        let v = evaluate(&expr, &empty_row()).unwrap();
1334        assert_eq!(v, Value::Null);
1335    }
1336
1337    #[test]
1338    fn null_propagates_through_concat() {
1339        let expr = binop(
1340            BinOp::Concat,
1341            lit(Value::Text(Arc::from("hi"))),
1342            lit(Value::Null),
1343        );
1344        let v = evaluate(&expr, &empty_row()).unwrap();
1345        assert_eq!(v, Value::Null);
1346    }
1347
1348    #[test]
1349    fn three_valued_and_returns_false_when_one_side_false_even_with_null() {
1350        let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(false)));
1351        let v = evaluate(&expr, &empty_row()).unwrap();
1352        assert_eq!(v, Value::Boolean(false));
1353    }
1354
1355    #[test]
1356    fn three_valued_or_returns_true_when_one_side_true_even_with_null() {
1357        let expr = binop(BinOp::Or, lit(Value::Null), lit(Value::Boolean(true)));
1358        let v = evaluate(&expr, &empty_row()).unwrap();
1359        assert_eq!(v, Value::Boolean(true));
1360    }
1361
1362    #[test]
1363    fn three_valued_and_returns_null_for_null_and_true() {
1364        let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(true)));
1365        let v = evaluate(&expr, &empty_row()).unwrap();
1366        assert_eq!(v, Value::Null);
1367    }
1368
1369    #[test]
1370    fn implicit_cast_triggers_for_decimal_plus_integer() {
1371        // Operator catalog has +(Decimal, Decimal) -> Decimal as
1372        // the only overload that survives coercion (no Decimal ->
1373        // numeric implicit casts exist in the cast catalog). The
1374        // spine therefore inserts an Integer -> Decimal cast on
1375        // the rhs and dispatches the Decimal addition.
1376        // parse_decimal scales by 10^4, so Integer(2) coerces to
1377        // Decimal(20000) and Decimal(10000) + Decimal(20000) =
1378        // Decimal(30000) (fixed-point 3.0000).
1379        let expr = binop(
1380            BinOp::Add,
1381            lit(Value::Decimal(10000)),
1382            lit(Value::Integer(2)),
1383        );
1384        let v = evaluate(&expr, &empty_row()).unwrap();
1385        assert_eq!(v, Value::Decimal(30000));
1386    }
1387
1388    #[test]
1389    fn integer_plus_bigint_resolves_to_preferred_float_overload() {
1390        // (Integer, BigInt) has no exact match. The spine ties
1391        // between +(Integer, Float, Float) (rhs cast BigInt->Float)
1392        // and +(BigInt, BigInt, BigInt) (lhs cast Integer->BigInt).
1393        // Float wins on the preferred-return-type tie-break, so the
1394        // BigInt operand is coerced to Float and the result is Float.
1395        let expr = binop(
1396            BinOp::Add,
1397            lit(Value::Integer(5)),
1398            lit(Value::BigInt(40_000_000_000)),
1399        );
1400        let v = evaluate(&expr, &empty_row()).unwrap();
1401        assert_eq!(v, Value::Float(40_000_000_005.0));
1402    }
1403
1404    #[test]
1405    fn integer_predicates_preserve_precision_across_signed_and_unsigned_boundaries() {
1406        let mut values = Vec::new();
1407        for number in [
1408            i64::MIN,
1409            -1,
1410            0,
1411            9_007_199_254_740_992,
1412            9_007_199_254_740_993,
1413            i64::MAX,
1414        ] {
1415            values.push((Value::Integer(number), i128::from(number)));
1416            values.push((Value::BigInt(number), i128::from(number)));
1417        }
1418        for number in [
1419            0,
1420            9_007_199_254_740_992,
1421            9_007_199_254_740_993,
1422            i64::MAX as u64,
1423            u64::MAX - 1,
1424            u64::MAX,
1425        ] {
1426            values.push((Value::UnsignedInteger(number), i128::from(number)));
1427        }
1428        for (left, left_exact) in &values {
1429            for (right, right_exact) in &values {
1430                for (operator, expected) in [
1431                    (BinOp::Eq, left_exact == right_exact),
1432                    (BinOp::Ne, left_exact != right_exact),
1433                    (BinOp::Lt, left_exact < right_exact),
1434                    (BinOp::Le, left_exact <= right_exact),
1435                    (BinOp::Gt, left_exact > right_exact),
1436                    (BinOp::Ge, left_exact >= right_exact),
1437                ] {
1438                    let expression = binop(operator, lit(left.clone()), lit(right.clone()));
1439                    assert_eq!(
1440                        evaluate(&expression, &empty_row())
1441                            .expect("integer comparison is supported"),
1442                        Value::Boolean(expected),
1443                        "{left:?} {operator:?} {right:?}"
1444                    );
1445                }
1446                for negated in [false, true] {
1447                    let expected = (*left_exact == *right_exact) != negated;
1448                    let membership = Expr::InList {
1449                        target: Box::new(lit(left.clone())),
1450                        values: vec![lit(right.clone())],
1451                        negated,
1452                        span: Span::synthetic(),
1453                    };
1454                    let interval = Expr::Between {
1455                        target: Box::new(lit(left.clone())),
1456                        low: Box::new(lit(right.clone())),
1457                        high: Box::new(lit(right.clone())),
1458                        negated,
1459                        span: Span::synthetic(),
1460                    };
1461                    for expression in [membership, interval] {
1462                        assert_eq!(
1463                            evaluate(&expression, &empty_row())
1464                                .expect("integer predicate is supported"),
1465                            Value::Boolean(expected),
1466                            "{expression:?}"
1467                        );
1468                    }
1469                }
1470            }
1471        }
1472    }
1473
1474    #[test]
1475    fn implicit_cast_promotes_integer_to_float_for_float_addition() {
1476        // The catalog has +(Integer, Float) -> Float as a direct
1477        // entry, so no actual coercion is inserted, but the result
1478        // must still be Float.
1479        let expr = binop(BinOp::Add, lit(Value::Integer(2)), lit(Value::Float(0.5)));
1480        let v = evaluate(&expr, &empty_row()).unwrap();
1481        assert_eq!(v, Value::Float(2.5));
1482    }
1483
1484    #[test]
1485    fn integer_division_promotes_to_float() {
1486        let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(2)));
1487        let v = evaluate(&expr, &empty_row()).unwrap();
1488        assert_eq!(v, Value::Float(3.5));
1489    }
1490
1491    #[test]
1492    fn division_by_zero_is_eval_error() {
1493        let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(0)));
1494        let err = evaluate(&expr, &empty_row()).unwrap_err();
1495        assert_eq!(err, EvalError::DivisionByZero);
1496    }
1497
1498    #[test]
1499    fn unknown_function_surfaces_as_eval_error() {
1500        let expr = Expr::FunctionCall {
1501            name: "no_such_fn".to_string(),
1502            args: vec![lit(Value::Integer(1))],
1503            span: Span::synthetic(),
1504        };
1505        let err = evaluate(&expr, &empty_row()).unwrap_err();
1506        match err {
1507            EvalError::UnknownFunction { name, args } => {
1508                assert_eq!(name, "no_such_fn");
1509                assert_eq!(args, vec![DataType::Integer]);
1510            }
1511            other => panic!("expected UnknownFunction, got {other:?}"),
1512        }
1513    }
1514
1515    #[test]
1516    fn coalesce_returns_first_non_null() {
1517        let expr = Expr::FunctionCall {
1518            name: "COALESCE".to_string(),
1519            args: vec![
1520                lit(Value::Null),
1521                lit(Value::Null),
1522                lit(Value::Integer(42)),
1523                lit(Value::Integer(99)),
1524            ],
1525            span: Span::synthetic(),
1526        };
1527        let v = evaluate(&expr, &empty_row()).unwrap();
1528        assert_eq!(v, Value::Integer(42));
1529    }
1530
1531    #[test]
1532    fn coalesce_all_null_returns_null() {
1533        let expr = Expr::FunctionCall {
1534            name: "COALESCE".to_string(),
1535            args: vec![lit(Value::Null), lit(Value::Null)],
1536            span: Span::synthetic(),
1537        };
1538        let v = evaluate(&expr, &empty_row()).unwrap();
1539        assert_eq!(v, Value::Null);
1540    }
1541
1542    #[test]
1543    fn variadic_concat_owns_null_semantics() {
1544        let expr = Expr::FunctionCall {
1545            name: "CONCAT".to_string(),
1546            args: vec![
1547                lit(Value::Text(Arc::from("red"))),
1548                lit(Value::Null),
1549                lit(Value::Text(Arc::from("db"))),
1550            ],
1551            span: Span::synthetic(),
1552        };
1553        let value = evaluate(&expr, &empty_row()).unwrap();
1554        assert_eq!(value, Value::Text(Arc::from("reddb")));
1555    }
1556
1557    #[test]
1558    fn upper_lower_dispatch_through_function_catalog() {
1559        let expr = Expr::FunctionCall {
1560            name: "UPPER".to_string(),
1561            args: vec![lit(Value::Text(Arc::from("hello")))],
1562            span: Span::synthetic(),
1563        };
1564        let v = evaluate(&expr, &empty_row()).unwrap();
1565        assert_eq!(v, Value::Text(Arc::from("HELLO")));
1566    }
1567
1568    #[test]
1569    fn length_of_null_propagates() {
1570        let expr = Expr::FunctionCall {
1571            name: "LENGTH".to_string(),
1572            args: vec![lit(Value::Null)],
1573            span: Span::synthetic(),
1574        };
1575        let v = evaluate(&expr, &empty_row()).unwrap();
1576        assert_eq!(v, Value::Null);
1577    }
1578
1579    #[test]
1580    fn case_when_picks_first_true_branch() {
1581        let expr = Expr::Case {
1582            branches: vec![
1583                (lit(Value::Boolean(false)), lit(Value::Integer(1))),
1584                (lit(Value::Boolean(true)), lit(Value::Integer(2))),
1585                (lit(Value::Boolean(true)), lit(Value::Integer(3))),
1586            ],
1587            else_: Some(Box::new(lit(Value::Integer(99)))),
1588            span: Span::synthetic(),
1589        };
1590        let v = evaluate(&expr, &empty_row()).unwrap();
1591        assert_eq!(v, Value::Integer(2));
1592    }
1593
1594    #[test]
1595    fn case_falls_through_to_else_when_no_branch_matches() {
1596        let expr = Expr::Case {
1597            branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1598            else_: Some(Box::new(lit(Value::Integer(99)))),
1599            span: Span::synthetic(),
1600        };
1601        let v = evaluate(&expr, &empty_row()).unwrap();
1602        assert_eq!(v, Value::Integer(99));
1603    }
1604
1605    #[test]
1606    fn case_returns_null_when_no_branch_matches_and_no_else() {
1607        let expr = Expr::Case {
1608            branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1609            else_: None,
1610            span: Span::synthetic(),
1611        };
1612        let v = evaluate(&expr, &empty_row()).unwrap();
1613        assert_eq!(v, Value::Null);
1614    }
1615
1616    #[test]
1617    fn is_null_handles_null_and_non_null() {
1618        let null_expr = Expr::IsNull {
1619            operand: Box::new(lit(Value::Null)),
1620            negated: false,
1621            span: Span::synthetic(),
1622        };
1623        assert_eq!(
1624            evaluate(&null_expr, &empty_row()).unwrap(),
1625            Value::Boolean(true)
1626        );
1627
1628        let non_null_expr = Expr::IsNull {
1629            operand: Box::new(lit(Value::Integer(7))),
1630            negated: false,
1631            span: Span::synthetic(),
1632        };
1633        assert_eq!(
1634            evaluate(&non_null_expr, &empty_row()).unwrap(),
1635            Value::Boolean(false)
1636        );
1637    }
1638
1639    #[test]
1640    fn between_inclusive_bounds() {
1641        let expr = Expr::Between {
1642            target: Box::new(lit(Value::Integer(5))),
1643            low: Box::new(lit(Value::Integer(1))),
1644            high: Box::new(lit(Value::Integer(10))),
1645            negated: false,
1646            span: Span::synthetic(),
1647        };
1648        assert_eq!(evaluate(&expr, &empty_row()).unwrap(), Value::Boolean(true));
1649    }
1650
1651    #[test]
1652    fn in_list_match_and_miss() {
1653        let hit = Expr::InList {
1654            target: Box::new(lit(Value::Integer(2))),
1655            values: vec![
1656                lit(Value::Integer(1)),
1657                lit(Value::Integer(2)),
1658                lit(Value::Integer(3)),
1659            ],
1660            negated: false,
1661            span: Span::synthetic(),
1662        };
1663        assert_eq!(evaluate(&hit, &empty_row()).unwrap(), Value::Boolean(true));
1664
1665        let miss = Expr::InList {
1666            target: Box::new(lit(Value::Integer(99))),
1667            values: vec![lit(Value::Integer(1)), lit(Value::Integer(2))],
1668            negated: false,
1669            span: Span::synthetic(),
1670        };
1671        assert_eq!(
1672            evaluate(&miss, &empty_row()).unwrap(),
1673            Value::Boolean(false)
1674        );
1675    }
1676
1677    #[test]
1678    fn column_lookup_walks_field_ref() {
1679        let row = |field: &FieldRef| -> Option<Value> {
1680            match field {
1681                FieldRef::TableColumn { table, column } if table == "t" && column == "x" => {
1682                    Some(Value::Integer(11))
1683                }
1684                _ => None,
1685            }
1686        };
1687        let expr = Expr::Column {
1688            field: FieldRef::TableColumn {
1689                table: "t".to_string(),
1690                column: "x".to_string(),
1691            },
1692            span: Span::synthetic(),
1693        };
1694        assert_eq!(evaluate(&expr, &row).unwrap(), Value::Integer(11));
1695    }
1696
1697    #[test]
1698    fn missing_column_surfaces_unknown_column() {
1699        let row = |_: &FieldRef| -> Option<Value> { None };
1700        let expr = Expr::Column {
1701            field: FieldRef::TableColumn {
1702                table: "t".to_string(),
1703                column: "missing".to_string(),
1704            },
1705            span: Span::synthetic(),
1706        };
1707        let err = evaluate(&expr, &row).unwrap_err();
1708        match err {
1709            EvalError::UnknownColumn(_) => {}
1710            other => panic!("expected UnknownColumn, got {other:?}"),
1711        }
1712    }
1713
1714    #[test]
1715    fn parameter_without_bind_is_eval_error() {
1716        let expr = Expr::Parameter {
1717            index: 1,
1718            span: Span::synthetic(),
1719        };
1720        let err = evaluate(&expr, &empty_row()).unwrap_err();
1721        assert_eq!(err, EvalError::UnboundParameter(1));
1722    }
1723
1724    #[test]
1725    fn cast_integer_to_text_uses_explicit_path() {
1726        let expr = Expr::Cast {
1727            inner: Box::new(lit(Value::Integer(42))),
1728            target: DataType::Text,
1729            span: Span::synthetic(),
1730        };
1731        assert_eq!(
1732            evaluate(&expr, &empty_row()).unwrap(),
1733            Value::Text(Arc::from("42"))
1734        );
1735    }
1736
1737    #[test]
1738    fn concat_returns_text() {
1739        let expr = binop(
1740            BinOp::Concat,
1741            lit(Value::Text(Arc::from("foo"))),
1742            lit(Value::Text(Arc::from("bar"))),
1743        );
1744        assert_eq!(
1745            evaluate(&expr, &empty_row()).unwrap(),
1746            Value::Text(Arc::from("foobar"))
1747        );
1748    }
1749}