Skip to main content

alopex_sql/executor/evaluator/
mod.rs

1//! Expression evaluator for typed expressions.
2//!
3//! Provides a lightweight, zero-allocation evaluator over typed expressions
4//! emitted by the planner. The evaluator operates on a borrowed row slice
5//! via [`EvalContext`] and returns [`SqlValue`] results or [`ExecutorError`].
6
7pub(crate) mod binary_op;
8mod case_expr;
9mod column_ref;
10pub(crate) mod conditional;
11mod context;
12pub(crate) mod datetime;
13pub(crate) mod fts;
14mod function_call;
15pub(crate) mod hash;
16mod is_null;
17pub(crate) mod json;
18mod literal;
19pub(crate) mod nested;
20pub(crate) mod numeric;
21pub(crate) mod pattern;
22pub(crate) mod predicate;
23pub mod registry;
24pub(crate) mod string;
25mod timestamp;
26pub(crate) mod type_fn;
27mod unary_op;
28pub mod vector_ops;
29
30pub use vector_ops::{VectorError, VectorMetric, vector_distance, vector_similarity};
31
32pub use context::EvalContext;
33pub(crate) use context::begin_statement;
34pub(crate) use timestamp::{coerce_value, try_coerce_value};
35
36use crate::executor::{EvaluationError, ExecutorError, Result};
37use crate::planner::typed_expr::TypedExpr;
38use crate::planner::typed_expr::TypedExprKind;
39use crate::storage::SqlValue;
40
41/// Evaluate a typed expression against the provided evaluation context.
42pub fn evaluate(expr: &TypedExpr, ctx: &EvalContext<'_>) -> Result<SqlValue> {
43    match &expr.kind {
44        TypedExprKind::Literal(lit) => literal::eval_literal(lit, &expr.resolved_type),
45        TypedExprKind::ColumnRef { column_index, .. } => {
46            column_ref::eval_column_ref(*column_index, ctx)
47        }
48        TypedExprKind::BinaryOp { left, op, right } => {
49            binary_op::eval_binary_op(op, left, right, &expr.resolved_type, ctx)
50        }
51        TypedExprKind::UnaryOp { op, operand } => unary_op::eval_unary_op(op, operand, ctx),
52        TypedExprKind::Case {
53            operand,
54            branches,
55            else_expr,
56        } => case_expr::evaluate_case(operand.as_deref(), branches, else_expr.as_deref(), ctx),
57        TypedExprKind::IsNull { expr, negated } => is_null::eval_is_null(expr, *negated, ctx),
58        TypedExprKind::VectorLiteral(values) => {
59            Ok(SqlValue::Vector(values.iter().map(|v| *v as f32).collect()))
60        }
61        TypedExprKind::FunctionCall {
62            name,
63            args,
64            distinct,
65            star,
66            filter,
67            order_by,
68            over,
69        } => {
70            if over.is_some() {
71                return Err(ExecutorError::InvalidOperation {
72                    operation: "evaluate window function as scalar expression".into(),
73                    reason: "window expressions must be evaluated by the Window operator".into(),
74                });
75            }
76            if filter.is_some() || !order_by.is_empty() {
77                return Err(ExecutorError::InvalidOperation {
78                    operation: "evaluate aggregate clause as scalar expression".into(),
79                    reason: "FILTER and aggregate ORDER BY must be evaluated by the \
80                             Aggregate operator"
81                        .into(),
82                });
83            }
84            let value = function_call::evaluate_function_call(name, args, *distinct, *star, ctx)?;
85            if matches!(
86                expr.resolved_type,
87                crate::planner::ResolvedType::Array(_)
88                    | crate::planner::ResolvedType::Map { .. }
89                    | crate::planner::ResolvedType::Struct(_)
90            ) {
91                timestamp::coerce_value(value, &expr.resolved_type)
92            } else {
93                Ok(value)
94            }
95        }
96        TypedExprKind::Cast { expr, target_type } => {
97            timestamp::evaluate_cast(expr, target_type, ctx)
98        }
99        TypedExprKind::TryCast { expr, target_type } => {
100            timestamp::evaluate_try_cast(expr, target_type, ctx)
101        }
102        TypedExprKind::Like {
103            expr,
104            pattern,
105            escape,
106            negated,
107            kind,
108        } => pattern::evaluate_pattern(expr, pattern, escape.as_deref(), *negated, *kind, ctx),
109        TypedExprKind::Between {
110            expr,
111            low,
112            high,
113            negated,
114        } => evaluate_between(expr, low, high, *negated, ctx),
115        TypedExprKind::InList {
116            expr,
117            list,
118            negated,
119        } => evaluate_in_list(expr, list, *negated, ctx),
120        // Unsupported expressions return a clear error message.
121        other => Err(ExecutorError::Evaluation(
122            EvaluationError::UnsupportedExpression(format!("{other:?}")),
123        )),
124    }
125}
126
127fn evaluate_between(
128    expr: &TypedExpr,
129    low: &TypedExpr,
130    high: &TypedExpr,
131    negated: bool,
132    ctx: &EvalContext<'_>,
133) -> Result<SqlValue> {
134    let value = evaluate(expr, ctx)?;
135    let lower = binary_op::eval_binary_values(
136        &crate::ast::expr::BinaryOp::GtEq,
137        value.clone(),
138        evaluate(low, ctx)?,
139    )?;
140    let upper = binary_op::eval_binary_values(
141        &crate::ast::expr::BinaryOp::LtEq,
142        value,
143        evaluate(high, ctx)?,
144    )?;
145    let result = binary_op::eval_binary_values(&crate::ast::expr::BinaryOp::And, lower, upper)?;
146    negate_predicate(result, negated)
147}
148
149fn evaluate_in_list(
150    expr: &TypedExpr,
151    list: &[TypedExpr],
152    negated: bool,
153    ctx: &EvalContext<'_>,
154) -> Result<SqlValue> {
155    let value = evaluate(expr, ctx)?;
156    let mut unknown = false;
157
158    for item in list {
159        match binary_op::eval_binary_values(
160            &crate::ast::expr::BinaryOp::Eq,
161            value.clone(),
162            evaluate(item, ctx)?,
163        )? {
164            SqlValue::Boolean(true) => return Ok(SqlValue::Boolean(!negated)),
165            SqlValue::Boolean(false) => {}
166            SqlValue::Null => unknown = true,
167            other => {
168                return Err(ExecutorError::Evaluation(EvaluationError::TypeMismatch {
169                    expected: "Boolean".into(),
170                    actual: other.type_name().into(),
171                }));
172            }
173        }
174    }
175
176    if unknown {
177        Ok(SqlValue::Null)
178    } else {
179        Ok(SqlValue::Boolean(negated))
180    }
181}
182
183fn negate_predicate(value: SqlValue, negated: bool) -> Result<SqlValue> {
184    if !negated {
185        return Ok(value);
186    }
187    match value {
188        SqlValue::Boolean(value) => Ok(SqlValue::Boolean(!value)),
189        SqlValue::Null => Ok(SqlValue::Null),
190        other => Err(ExecutorError::Evaluation(EvaluationError::TypeMismatch {
191            expected: "Boolean".into(),
192            actual: other.type_name().into(),
193        })),
194    }
195}