Skip to main content

vibesql_executor/evaluator/
single.rs

1//! Single-table expression evaluator
2//!
3//! This module provides the ExpressionEvaluator for evaluating expressions
4//! in the context of a single table schema.
5
6use std::{cell::RefCell, rc::Rc};
7
8use lru::LruCache;
9
10use crate::errors::ExecutorError;
11
12/// Evaluates expressions in the context of a row
13pub struct ExpressionEvaluator<'a> {
14    pub(super) schema: &'a vibesql_catalog::TableSchema,
15    pub(super) outer_row: Option<&'a vibesql_storage::Row>,
16    pub(super) outer_schema: Option<&'a vibesql_catalog::TableSchema>,
17    pub(super) database: Option<&'a vibesql_storage::Database>,
18    /// Trigger context for OLD/NEW pseudo-variable resolution
19    pub(super) trigger_context: Option<&'a crate::trigger_execution::TriggerContext<'a>>,
20    /// Procedural context for stored procedure/function variable resolution
21    pub(super) procedural_context: Option<&'a crate::procedural::ExecutionContext>,
22    /// CTE context for WITH clause support in UPDATE/DELETE subqueries
23    pub(super) cte_context:
24        Option<&'a std::collections::HashMap<String, crate::select::cte::CteResult>>,
25    /// Current depth in expression tree (for preventing stack overflow)
26    pub(super) depth: usize,
27    /// CSE cache for common sub-expression elimination with LRU eviction (shared via Rc across
28    /// depth levels)
29    pub(super) cse_cache: Rc<RefCell<LruCache<u64, vibesql_types::SqlValue>>>,
30    /// Whether CSE is enabled (can be disabled for debugging)
31    pub(super) enable_cse: bool,
32    /// Cache for non-correlated subquery results with LRU eviction (key = subquery hash, value =
33    /// result rows) Shared via Rc across child evaluators within a single statement execution.
34    pub(super) subquery_cache: Rc<RefCell<LruCache<u64, Vec<vibesql_storage::Row>>>>,
35    /// Row index for ROWID pseudo-column support (SQLite compatibility)
36    /// When set, ROWID/_rowid_/oid column references will return this value
37    pub(super) row_index: Option<u64>,
38    /// Table alias for UPDATE/DELETE statements (SQLite extension: UPDATE t1 AS xyz)
39    /// When set, column references qualified with this alias will resolve to the schema
40    pub(super) table_alias: Option<String>,
41    /// Schema-attached expression context (CHECK constraint, generated
42    /// column, index expression). Non-deterministic date/time function uses
43    /// are rejected at evaluation time in these contexts (SQLite semantics).
44    pub(super) schema_context: super::SchemaExprContext,
45}
46
47impl<'a> ExpressionEvaluator<'a> {
48    /// Create a new expression evaluator for a given schema
49    pub fn new(schema: &'a vibesql_catalog::TableSchema) -> Self {
50        ExpressionEvaluator {
51            schema,
52            outer_row: None,
53            outer_schema: None,
54            database: None,
55            trigger_context: None,
56            procedural_context: None,
57            cte_context: None,
58            depth: 0,
59            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
60            enable_cse: super::caching::is_cse_enabled(),
61            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
62            row_index: None,
63            table_alias: None,
64            schema_context: super::SchemaExprContext::None,
65        }
66    }
67
68    /// Create a new expression evaluator with outer query context for correlated subqueries
69    pub fn with_outer_context(
70        schema: &'a vibesql_catalog::TableSchema,
71        outer_row: &'a vibesql_storage::Row,
72        outer_schema: &'a vibesql_catalog::TableSchema,
73    ) -> Self {
74        ExpressionEvaluator {
75            schema,
76            outer_row: Some(outer_row),
77            outer_schema: Some(outer_schema),
78            database: None,
79            trigger_context: None,
80            procedural_context: None,
81            cte_context: None,
82            depth: 0,
83            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
84            enable_cse: super::caching::is_cse_enabled(),
85            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
86            row_index: None,
87            table_alias: None,
88            schema_context: super::SchemaExprContext::None,
89        }
90    }
91
92    /// Create a new expression evaluator with database reference for subqueries
93    pub fn with_database(
94        schema: &'a vibesql_catalog::TableSchema,
95        database: &'a vibesql_storage::Database,
96    ) -> Self {
97        ExpressionEvaluator {
98            schema,
99            outer_row: None,
100            outer_schema: None,
101            database: Some(database),
102            trigger_context: None,
103            procedural_context: None,
104            cte_context: None,
105            depth: 0,
106            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
107            enable_cse: super::caching::is_cse_enabled(),
108            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
109            row_index: None,
110            table_alias: None,
111            schema_context: super::SchemaExprContext::None,
112        }
113    }
114
115    /// Create a new expression evaluator with trigger context for OLD/NEW pseudo-variables
116    pub fn with_trigger_context(
117        schema: &'a vibesql_catalog::TableSchema,
118        database: &'a vibesql_storage::Database,
119        trigger_context: &'a crate::trigger_execution::TriggerContext<'a>,
120    ) -> Self {
121        ExpressionEvaluator {
122            schema,
123            outer_row: None,
124            outer_schema: None,
125            database: Some(database),
126            trigger_context: Some(trigger_context),
127            procedural_context: None,
128            cte_context: None,
129            depth: 0,
130            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
131            enable_cse: super::caching::is_cse_enabled(),
132            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
133            row_index: None,
134            table_alias: None,
135            schema_context: super::SchemaExprContext::None,
136        }
137    }
138
139    /// Create a new expression evaluator with database and outer context (for correlated
140    /// subqueries)
141    pub fn with_database_and_outer_context(
142        schema: &'a vibesql_catalog::TableSchema,
143        database: &'a vibesql_storage::Database,
144        outer_row: &'a vibesql_storage::Row,
145        outer_schema: &'a vibesql_catalog::TableSchema,
146    ) -> Self {
147        ExpressionEvaluator {
148            schema,
149            outer_row: Some(outer_row),
150            outer_schema: Some(outer_schema),
151            database: Some(database),
152            trigger_context: None,
153            procedural_context: None,
154            cte_context: None,
155            depth: 0,
156            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
157            enable_cse: super::caching::is_cse_enabled(),
158            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
159            row_index: None,
160            table_alias: None,
161            schema_context: super::SchemaExprContext::None,
162        }
163    }
164
165    /// Create a new expression evaluator with procedural context for stored procedure/function
166    /// execution
167    pub fn with_procedural_context(
168        schema: &'a vibesql_catalog::TableSchema,
169        database: &'a vibesql_storage::Database,
170        procedural_context: &'a crate::procedural::ExecutionContext,
171    ) -> Self {
172        ExpressionEvaluator {
173            schema,
174            outer_row: None,
175            outer_schema: None,
176            database: Some(database),
177            trigger_context: None,
178            procedural_context: Some(procedural_context),
179            cte_context: None,
180            depth: 0,
181            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
182            enable_cse: super::caching::is_cse_enabled(),
183            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
184            row_index: None,
185            table_alias: None,
186            schema_context: super::SchemaExprContext::None,
187        }
188    }
189
190    /// Create a new expression evaluator with database and CTE context
191    /// Used for UPDATE/DELETE statements with WITH clauses
192    pub fn with_database_and_cte(
193        schema: &'a vibesql_catalog::TableSchema,
194        database: &'a vibesql_storage::Database,
195        cte_context: &'a std::collections::HashMap<String, crate::select::cte::CteResult>,
196    ) -> Self {
197        ExpressionEvaluator {
198            schema,
199            outer_row: None,
200            outer_schema: None,
201            database: Some(database),
202            trigger_context: None,
203            procedural_context: None,
204            cte_context: Some(cte_context),
205            depth: 0,
206            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
207            enable_cse: super::caching::is_cse_enabled(),
208            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
209            row_index: None,
210            table_alias: None,
211            schema_context: super::SchemaExprContext::None,
212        }
213    }
214
215    /// Attach an outer CTE context to this evaluator (builder style).
216    ///
217    /// Ensures subqueries evaluated in expression position resolve names
218    /// bound by an enclosing WITH clause before falling back to catalog
219    /// tables/views (issue #5350).
220    pub fn with_cte_context(
221        mut self,
222        cte_context: &'a std::collections::HashMap<String, crate::select::cte::CteResult>,
223    ) -> Self {
224        self.cte_context = Some(cte_context);
225        self
226    }
227
228    /// Set the row index for ROWID pseudo-column support
229    ///
230    /// When set, references to ROWID, _rowid_, or oid columns will return
231    /// this value as a Bigint. This provides SQLite compatibility.
232    pub fn set_row_index(&mut self, index: u64) {
233        self.row_index = Some(index);
234    }
235
236    /// Clear the row index (typically between row evaluations)
237    pub fn clear_row_index(&mut self) {
238        self.row_index = None;
239    }
240
241    /// Set the table alias for UPDATE/DELETE statements
242    ///
243    /// When set, column references qualified with this alias will resolve to the schema.
244    /// SQLite extension: UPDATE t1 AS xyz SET ... WHERE xyz.column = ...
245    pub fn set_table_alias(&mut self, alias: String) {
246        self.table_alias = Some(alias);
247    }
248
249    /// Mark this evaluator as evaluating a schema-attached expression
250    /// (CHECK constraint, generated column, or index expression).
251    ///
252    /// In these contexts SQLite rejects non-deterministic uses of the
253    /// date/time functions ('now' / zero-argument defaults / 'localtime' /
254    /// 'utc') at evaluation time with
255    /// `non-deterministic use of <fn>() in <context>`.
256    pub fn with_schema_context(mut self, schema_context: super::SchemaExprContext) -> Self {
257        self.schema_context = schema_context;
258        self
259    }
260
261    /// Evaluate a binary operation
262    pub(crate) fn eval_binary_op(
263        &self,
264        left: &vibesql_types::SqlValue,
265        op: &vibesql_ast::BinaryOperator,
266        right: &vibesql_types::SqlValue,
267    ) -> Result<vibesql_types::SqlValue, ExecutorError> {
268        // Extract SQL mode from database, default to MySQL if not available
269        let sql_mode = self.database.map(|db| db.sql_mode()).unwrap_or_default();
270
271        super::core::eval_binary_op_static(left, op, right, sql_mode)
272    }
273
274    /// Clear the CSE cache
275    /// Should be called before evaluating expressions for a new row in multi-row contexts
276    pub fn clear_cse_cache(&self) {
277        self.cse_cache.borrow_mut().clear();
278    }
279
280    /// Static version of eval_binary_op for shared logic
281    ///
282    /// Delegates to the core module's implementation.
283    pub(crate) fn eval_binary_op_static(
284        left: &vibesql_types::SqlValue,
285        op: &vibesql_ast::BinaryOperator,
286        right: &vibesql_types::SqlValue,
287        sql_mode: vibesql_types::SqlMode,
288    ) -> Result<vibesql_types::SqlValue, ExecutorError> {
289        super::core::eval_binary_op_static(left, op, right, sql_mode)
290    }
291
292    /// Static version of eval_between for constant folding during optimization
293    ///
294    /// Delegates to the core module's implementation.
295    pub(crate) fn eval_between_static(
296        expr_val: &vibesql_types::SqlValue,
297        low_val: &vibesql_types::SqlValue,
298        high_val: &vibesql_types::SqlValue,
299        negated: bool,
300        symmetric: bool,
301        sql_mode: vibesql_types::SqlMode,
302    ) -> Result<vibesql_types::SqlValue, ExecutorError> {
303        super::core::eval_between_static(expr_val, low_val, high_val, negated, symmetric, sql_mode)
304    }
305
306    /// Compare two SQL values for equality in simple CASE expressions
307    ///
308    /// Delegates to the core module's implementation.
309    pub(crate) fn values_are_equal(
310        left: &vibesql_types::SqlValue,
311        right: &vibesql_types::SqlValue,
312    ) -> bool {
313        super::core::values_are_equal(left, right)
314    }
315
316    /// Compare two SQL values using IS DISTINCT FROM semantics (SQL:1999)
317    ///
318    /// Delegates to the core module's implementation.
319    pub(crate) fn values_are_distinct(
320        left: &vibesql_types::SqlValue,
321        right: &vibesql_types::SqlValue,
322    ) -> bool {
323        super::core::values_are_distinct(left, right)
324    }
325
326    /// Helper to execute a closure with incremented depth
327    pub(super) fn with_incremented_depth<F, T>(&self, f: F) -> Result<T, ExecutorError>
328    where
329        F: FnOnce(&Self) -> Result<T, ExecutorError>,
330    {
331        // Create a new evaluator with incremented depth
332        // Share the CSE cache and subquery cache across depth levels for consistent caching
333        let evaluator = ExpressionEvaluator {
334            schema: self.schema,
335            outer_row: self.outer_row,
336            outer_schema: self.outer_schema,
337            database: self.database,
338            trigger_context: self.trigger_context,
339            procedural_context: self.procedural_context,
340            cte_context: self.cte_context,
341            depth: self.depth + 1,
342            cse_cache: self.cse_cache.clone(),
343            enable_cse: self.enable_cse,
344            subquery_cache: self.subquery_cache.clone(),
345            row_index: self.row_index,
346            table_alias: self.table_alias.clone(),
347            schema_context: self.schema_context,
348        };
349        f(&evaluator)
350    }
351}