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