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