vibesql-executor 0.2.0

Query execution engine for vibesql SQL database
Documentation
//! Single-table expression evaluator
//!
//! This module provides the ExpressionEvaluator for evaluating expressions
//! in the context of a single table schema.

use std::{cell::RefCell, rc::Rc};

use lru::LruCache;

use crate::errors::ExecutorError;

/// Evaluates expressions in the context of a row
pub struct ExpressionEvaluator<'a> {
    pub(super) schema: &'a vibesql_catalog::TableSchema,
    pub(super) outer_row: Option<&'a vibesql_storage::Row>,
    pub(super) outer_schema: Option<&'a vibesql_catalog::TableSchema>,
    pub(super) database: Option<&'a vibesql_storage::Database>,
    /// Trigger context for OLD/NEW pseudo-variable resolution
    pub(super) trigger_context: Option<&'a crate::trigger_execution::TriggerContext<'a>>,
    /// Procedural context for stored procedure/function variable resolution
    pub(super) procedural_context: Option<&'a crate::procedural::ExecutionContext>,
    /// CTE context for WITH clause support in UPDATE/DELETE subqueries
    pub(super) cte_context:
        Option<&'a std::collections::HashMap<String, crate::select::cte::CteResult>>,
    /// Current depth in expression tree (for preventing stack overflow)
    pub(super) depth: usize,
    /// CSE cache for common sub-expression elimination with LRU eviction (shared via Rc across
    /// depth levels)
    pub(super) cse_cache: Rc<RefCell<LruCache<u64, vibesql_types::SqlValue>>>,
    /// Whether CSE is enabled (can be disabled for debugging)
    pub(super) enable_cse: bool,
    /// Cache for non-correlated subquery results with LRU eviction (key = subquery hash, value =
    /// result rows) Shared via Rc across child evaluators within a single statement execution.
    pub(super) subquery_cache: Rc<RefCell<LruCache<u64, Vec<vibesql_storage::Row>>>>,
    /// Row index for ROWID pseudo-column support (SQLite compatibility)
    /// When set, ROWID/_rowid_/oid column references will return this value
    pub(super) row_index: Option<u64>,
    /// Table alias for UPDATE/DELETE statements (SQLite extension: UPDATE t1 AS xyz)
    /// When set, column references qualified with this alias will resolve to the schema
    pub(super) table_alias: Option<String>,
    /// Schema-attached expression context (CHECK constraint, generated
    /// column, index expression). Non-deterministic date/time function uses
    /// are rejected at evaluation time in these contexts (SQLite semantics).
    pub(super) schema_context: super::SchemaExprContext,
}

impl<'a> ExpressionEvaluator<'a> {
    /// Create a new expression evaluator for a given schema
    pub fn new(schema: &'a vibesql_catalog::TableSchema) -> Self {
        ExpressionEvaluator {
            schema,
            outer_row: None,
            outer_schema: None,
            database: None,
            trigger_context: None,
            procedural_context: None,
            cte_context: None,
            depth: 0,
            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
            enable_cse: super::caching::is_cse_enabled(),
            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
            row_index: None,
            table_alias: None,
            schema_context: super::SchemaExprContext::None,
        }
    }

    /// Create a new expression evaluator with outer query context for correlated subqueries
    pub fn with_outer_context(
        schema: &'a vibesql_catalog::TableSchema,
        outer_row: &'a vibesql_storage::Row,
        outer_schema: &'a vibesql_catalog::TableSchema,
    ) -> Self {
        ExpressionEvaluator {
            schema,
            outer_row: Some(outer_row),
            outer_schema: Some(outer_schema),
            database: None,
            trigger_context: None,
            procedural_context: None,
            cte_context: None,
            depth: 0,
            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
            enable_cse: super::caching::is_cse_enabled(),
            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
            row_index: None,
            table_alias: None,
            schema_context: super::SchemaExprContext::None,
        }
    }

    /// Create a new expression evaluator with database reference for subqueries
    pub fn with_database(
        schema: &'a vibesql_catalog::TableSchema,
        database: &'a vibesql_storage::Database,
    ) -> Self {
        ExpressionEvaluator {
            schema,
            outer_row: None,
            outer_schema: None,
            database: Some(database),
            trigger_context: None,
            procedural_context: None,
            cte_context: None,
            depth: 0,
            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
            enable_cse: super::caching::is_cse_enabled(),
            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
            row_index: None,
            table_alias: None,
            schema_context: super::SchemaExprContext::None,
        }
    }

    /// Create a new expression evaluator with trigger context for OLD/NEW pseudo-variables
    pub fn with_trigger_context(
        schema: &'a vibesql_catalog::TableSchema,
        database: &'a vibesql_storage::Database,
        trigger_context: &'a crate::trigger_execution::TriggerContext<'a>,
    ) -> Self {
        ExpressionEvaluator {
            schema,
            outer_row: None,
            outer_schema: None,
            database: Some(database),
            trigger_context: Some(trigger_context),
            procedural_context: None,
            cte_context: None,
            depth: 0,
            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
            enable_cse: super::caching::is_cse_enabled(),
            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
            row_index: None,
            table_alias: None,
            schema_context: super::SchemaExprContext::None,
        }
    }

    /// Create a new expression evaluator with database and outer context (for correlated
    /// subqueries)
    pub fn with_database_and_outer_context(
        schema: &'a vibesql_catalog::TableSchema,
        database: &'a vibesql_storage::Database,
        outer_row: &'a vibesql_storage::Row,
        outer_schema: &'a vibesql_catalog::TableSchema,
    ) -> Self {
        ExpressionEvaluator {
            schema,
            outer_row: Some(outer_row),
            outer_schema: Some(outer_schema),
            database: Some(database),
            trigger_context: None,
            procedural_context: None,
            cte_context: None,
            depth: 0,
            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
            enable_cse: super::caching::is_cse_enabled(),
            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
            row_index: None,
            table_alias: None,
            schema_context: super::SchemaExprContext::None,
        }
    }

    /// Create a new expression evaluator with procedural context for stored procedure/function
    /// execution
    pub fn with_procedural_context(
        schema: &'a vibesql_catalog::TableSchema,
        database: &'a vibesql_storage::Database,
        procedural_context: &'a crate::procedural::ExecutionContext,
    ) -> Self {
        ExpressionEvaluator {
            schema,
            outer_row: None,
            outer_schema: None,
            database: Some(database),
            trigger_context: None,
            procedural_context: Some(procedural_context),
            cte_context: None,
            depth: 0,
            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
            enable_cse: super::caching::is_cse_enabled(),
            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
            row_index: None,
            table_alias: None,
            schema_context: super::SchemaExprContext::None,
        }
    }

    /// Create a new expression evaluator with database and CTE context
    /// Used for UPDATE/DELETE statements with WITH clauses
    pub fn with_database_and_cte(
        schema: &'a vibesql_catalog::TableSchema,
        database: &'a vibesql_storage::Database,
        cte_context: &'a std::collections::HashMap<String, crate::select::cte::CteResult>,
    ) -> Self {
        ExpressionEvaluator {
            schema,
            outer_row: None,
            outer_schema: None,
            database: Some(database),
            trigger_context: None,
            procedural_context: None,
            cte_context: Some(cte_context),
            depth: 0,
            cse_cache: Rc::new(RefCell::new(super::caching::create_cse_cache())),
            enable_cse: super::caching::is_cse_enabled(),
            subquery_cache: Rc::new(RefCell::new(super::caching::create_subquery_cache())),
            row_index: None,
            table_alias: None,
            schema_context: super::SchemaExprContext::None,
        }
    }

    /// Attach an outer CTE context to this evaluator (builder style).
    ///
    /// Ensures subqueries evaluated in expression position resolve names
    /// bound by an enclosing WITH clause before falling back to catalog
    /// tables/views (issue #5350).
    pub fn with_cte_context(
        mut self,
        cte_context: &'a std::collections::HashMap<String, crate::select::cte::CteResult>,
    ) -> Self {
        self.cte_context = Some(cte_context);
        self
    }

    /// Set the row index for ROWID pseudo-column support
    ///
    /// When set, references to ROWID, _rowid_, or oid columns will return
    /// this value as a Bigint. This provides SQLite compatibility.
    pub fn set_row_index(&mut self, index: u64) {
        self.row_index = Some(index);
    }

    /// Clear the row index (typically between row evaluations)
    pub fn clear_row_index(&mut self) {
        self.row_index = None;
    }

    /// Set the table alias for UPDATE/DELETE statements
    ///
    /// When set, column references qualified with this alias will resolve to the schema.
    /// SQLite extension: UPDATE t1 AS xyz SET ... WHERE xyz.column = ...
    pub fn set_table_alias(&mut self, alias: String) {
        self.table_alias = Some(alias);
    }

    /// Mark this evaluator as evaluating a schema-attached expression
    /// (CHECK constraint, generated column, or index expression).
    ///
    /// In these contexts SQLite rejects non-deterministic uses of the
    /// date/time functions ('now' / zero-argument defaults / 'localtime' /
    /// 'utc') at evaluation time with
    /// `non-deterministic use of <fn>() in <context>`.
    pub fn with_schema_context(mut self, schema_context: super::SchemaExprContext) -> Self {
        self.schema_context = schema_context;
        self
    }

    /// Evaluate a binary operation
    pub(crate) fn eval_binary_op(
        &self,
        left: &vibesql_types::SqlValue,
        op: &vibesql_ast::BinaryOperator,
        right: &vibesql_types::SqlValue,
    ) -> Result<vibesql_types::SqlValue, ExecutorError> {
        // Extract SQL mode from database, default to MySQL if not available
        let sql_mode = self.database.map(|db| db.sql_mode()).unwrap_or_default();

        super::core::eval_binary_op_static(left, op, right, sql_mode)
    }

    /// Clear the CSE cache
    /// Should be called before evaluating expressions for a new row in multi-row contexts
    pub fn clear_cse_cache(&self) {
        self.cse_cache.borrow_mut().clear();
    }

    /// Static version of eval_binary_op for shared logic
    ///
    /// Delegates to the core module's implementation.
    pub(crate) fn eval_binary_op_static(
        left: &vibesql_types::SqlValue,
        op: &vibesql_ast::BinaryOperator,
        right: &vibesql_types::SqlValue,
        sql_mode: vibesql_types::SqlMode,
    ) -> Result<vibesql_types::SqlValue, ExecutorError> {
        super::core::eval_binary_op_static(left, op, right, sql_mode)
    }

    /// Static version of eval_between for constant folding during optimization
    ///
    /// Delegates to the core module's implementation.
    pub(crate) fn eval_between_static(
        expr_val: &vibesql_types::SqlValue,
        low_val: &vibesql_types::SqlValue,
        high_val: &vibesql_types::SqlValue,
        negated: bool,
        symmetric: bool,
        sql_mode: vibesql_types::SqlMode,
    ) -> Result<vibesql_types::SqlValue, ExecutorError> {
        super::core::eval_between_static(expr_val, low_val, high_val, negated, symmetric, sql_mode)
    }

    /// Compare two SQL values for equality in simple CASE expressions
    ///
    /// Delegates to the core module's implementation.
    pub(crate) fn values_are_equal(
        left: &vibesql_types::SqlValue,
        right: &vibesql_types::SqlValue,
    ) -> bool {
        super::core::values_are_equal(left, right)
    }

    /// Compare two SQL values using IS DISTINCT FROM semantics (SQL:1999)
    ///
    /// Delegates to the core module's implementation.
    pub(crate) fn values_are_distinct(
        left: &vibesql_types::SqlValue,
        right: &vibesql_types::SqlValue,
    ) -> bool {
        super::core::values_are_distinct(left, right)
    }

    /// Helper to execute a closure with incremented depth
    pub(super) fn with_incremented_depth<F, T>(&self, f: F) -> Result<T, ExecutorError>
    where
        F: FnOnce(&Self) -> Result<T, ExecutorError>,
    {
        // Create a new evaluator with incremented depth
        // Share the CSE cache and subquery cache across depth levels for consistent caching
        let evaluator = ExpressionEvaluator {
            schema: self.schema,
            outer_row: self.outer_row,
            outer_schema: self.outer_schema,
            database: self.database,
            trigger_context: self.trigger_context,
            procedural_context: self.procedural_context,
            cte_context: self.cte_context,
            depth: self.depth + 1,
            cse_cache: self.cse_cache.clone(),
            enable_cse: self.enable_cse,
            subquery_cache: self.subquery_cache.clone(),
            row_index: self.row_index,
            table_alias: self.table_alias.clone(),
            schema_context: self.schema_context,
        };
        f(&evaluator)
    }
}