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