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 crate::errors::ExecutorError;
7use lru::LruCache;
8use std::{cell::RefCell, rc::Rc};
9
10/// Evaluates expressions in the context of a row
11pub struct ExpressionEvaluator<'a> {
12    pub(super) schema: &'a vibesql_catalog::TableSchema,
13    pub(super) outer_row: Option<&'a vibesql_storage::Row>,
14    pub(super) outer_schema: Option<&'a vibesql_catalog::TableSchema>,
15    pub(super) database: Option<&'a vibesql_storage::Database>,
16    /// Trigger context for OLD/NEW pseudo-variable resolution
17    pub(super) trigger_context: Option<&'a crate::trigger_execution::TriggerContext<'a>>,
18    /// Procedural context for stored procedure/function variable resolution
19    pub(super) procedural_context: Option<&'a crate::procedural::ExecutionContext>,
20    /// Current depth in expression tree (for preventing stack overflow)
21    pub(super) depth: usize,
22    /// CSE cache for common sub-expression elimination with LRU eviction (shared via Rc across depth levels)
23    pub(super) cse_cache: Rc<RefCell<LruCache<u64, vibesql_types::SqlValue>>>,
24    /// Whether CSE is enabled (can be disabled for debugging)
25    pub(super) enable_cse: bool,
26    /// Cache for non-correlated subquery results with LRU eviction (key = subquery hash, value = result rows)
27    /// Shared via Rc across child evaluators within a single statement execution.
28    pub(super) subquery_cache: Rc<RefCell<LruCache<u64, Vec<vibesql_storage::Row>>>>,
29}
30
31impl<'a> ExpressionEvaluator<'a> {
32    /// Create a new expression evaluator for a given schema
33    pub fn new(schema: &'a vibesql_catalog::TableSchema) -> Self {
34        ExpressionEvaluator {
35            schema,
36            outer_row: None,
37            outer_schema: None,
38            database: None,
39            trigger_context: None,
40            procedural_context: None,
41            depth: 0,
42            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
43            enable_cse: super::caching::is_cse_enabled(),
44            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
45        }
46    }
47
48    /// Create a new expression evaluator with outer query context for correlated subqueries
49    pub fn with_outer_context(
50        schema: &'a vibesql_catalog::TableSchema,
51        outer_row: &'a vibesql_storage::Row,
52        outer_schema: &'a vibesql_catalog::TableSchema,
53    ) -> Self {
54        ExpressionEvaluator {
55            schema,
56            outer_row: Some(outer_row),
57            outer_schema: Some(outer_schema),
58            database: None,
59            trigger_context: None,
60            procedural_context: None,
61            depth: 0,
62            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
63            enable_cse: super::caching::is_cse_enabled(),
64            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
65        }
66    }
67
68    /// Create a new expression evaluator with database reference for subqueries
69    pub fn with_database(
70        schema: &'a vibesql_catalog::TableSchema,
71        database: &'a vibesql_storage::Database,
72    ) -> Self {
73        ExpressionEvaluator {
74            schema,
75            outer_row: None,
76            outer_schema: None,
77            database: Some(database),
78            trigger_context: None,
79            procedural_context: None,
80            depth: 0,
81            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
82            enable_cse: super::caching::is_cse_enabled(),
83            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
84        }
85    }
86
87    /// Create a new expression evaluator with trigger context for OLD/NEW pseudo-variables
88    pub fn with_trigger_context(
89        schema: &'a vibesql_catalog::TableSchema,
90        database: &'a vibesql_storage::Database,
91        trigger_context: &'a crate::trigger_execution::TriggerContext<'a>,
92    ) -> Self {
93        ExpressionEvaluator {
94            schema,
95            outer_row: None,
96            outer_schema: None,
97            database: Some(database),
98            trigger_context: Some(trigger_context),
99            procedural_context: None,
100            depth: 0,
101            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
102            enable_cse: super::caching::is_cse_enabled(),
103            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
104        }
105    }
106
107    /// Create a new expression evaluator with database and outer context (for correlated
108    /// subqueries)
109    pub fn with_database_and_outer_context(
110        schema: &'a vibesql_catalog::TableSchema,
111        database: &'a vibesql_storage::Database,
112        outer_row: &'a vibesql_storage::Row,
113        outer_schema: &'a vibesql_catalog::TableSchema,
114    ) -> Self {
115        ExpressionEvaluator {
116            schema,
117            outer_row: Some(outer_row),
118            outer_schema: Some(outer_schema),
119            database: Some(database),
120            trigger_context: None,
121            procedural_context: None,
122            depth: 0,
123            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
124            enable_cse: super::caching::is_cse_enabled(),
125            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
126        }
127    }
128
129    /// Create a new expression evaluator with procedural context for stored procedure/function execution
130    pub fn with_procedural_context(
131        schema: &'a vibesql_catalog::TableSchema,
132        database: &'a vibesql_storage::Database,
133        procedural_context: &'a crate::procedural::ExecutionContext,
134    ) -> Self {
135        ExpressionEvaluator {
136            schema,
137            outer_row: None,
138            outer_schema: None,
139            database: Some(database),
140            trigger_context: None,
141            procedural_context: Some(procedural_context),
142            depth: 0,
143            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
144            enable_cse: super::caching::is_cse_enabled(),
145            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
146        }
147    }
148
149    /// Evaluate a binary operation
150    pub(crate) fn eval_binary_op(
151        &self,
152        left: &vibesql_types::SqlValue,
153        op: &vibesql_ast::BinaryOperator,
154        right: &vibesql_types::SqlValue,
155    ) -> Result<vibesql_types::SqlValue, ExecutorError> {
156        // Extract SQL mode from database, default to MySQL if not available
157        let sql_mode = self.database.map(|db| db.sql_mode()).unwrap_or_default();
158
159        super::core::eval_binary_op_static(left, op, right, sql_mode)
160    }
161
162    /// Clear the CSE cache
163    /// Should be called before evaluating expressions for a new row in multi-row contexts
164    pub fn clear_cse_cache(&self) {
165        self.cse_cache.borrow_mut().clear();
166    }
167
168    /// Static version of eval_binary_op for shared logic
169    ///
170    /// Delegates to the core module's implementation.
171    pub(crate) fn eval_binary_op_static(
172        left: &vibesql_types::SqlValue,
173        op: &vibesql_ast::BinaryOperator,
174        right: &vibesql_types::SqlValue,
175        sql_mode: vibesql_types::SqlMode,
176    ) -> Result<vibesql_types::SqlValue, ExecutorError> {
177        super::core::eval_binary_op_static(left, op, right, sql_mode)
178    }
179
180    /// Static version of eval_between for constant folding during optimization
181    ///
182    /// Delegates to the core module's implementation.
183    pub(crate) fn eval_between_static(
184        expr_val: &vibesql_types::SqlValue,
185        low_val: &vibesql_types::SqlValue,
186        high_val: &vibesql_types::SqlValue,
187        negated: bool,
188        symmetric: bool,
189        sql_mode: vibesql_types::SqlMode,
190    ) -> Result<vibesql_types::SqlValue, ExecutorError> {
191        super::core::eval_between_static(expr_val, low_val, high_val, negated, symmetric, sql_mode)
192    }
193
194    /// Compare two SQL values for equality in simple CASE expressions
195    ///
196    /// Delegates to the core module's implementation.
197    pub(crate) fn values_are_equal(
198        left: &vibesql_types::SqlValue,
199        right: &vibesql_types::SqlValue,
200    ) -> bool {
201        super::core::values_are_equal(left, right)
202    }
203
204    /// Helper to execute a closure with incremented depth
205    pub(super) fn with_incremented_depth<F, T>(&self, f: F) -> Result<T, ExecutorError>
206    where
207        F: FnOnce(&Self) -> Result<T, ExecutorError>,
208    {
209        // Create a new evaluator with incremented depth
210        // Share the CSE cache and subquery cache across depth levels for consistent caching
211        let evaluator = ExpressionEvaluator {
212            schema: self.schema,
213            outer_row: self.outer_row,
214            outer_schema: self.outer_schema,
215            database: self.database,
216            trigger_context: self.trigger_context,
217            procedural_context: self.procedural_context,
218            depth: self.depth + 1,
219            cse_cache: self.cse_cache.clone(),
220            enable_cse: self.enable_cse,
221            subquery_cache: self.subquery_cache.clone(),
222        };
223        f(&evaluator)
224    }
225}