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