vibesql-executor 0.2.0

Query execution engine for vibesql SQL database
Documentation
//! Compiled CASE expression evaluation for fast-path aggregate processing
//!
//! This module provides optimized evaluation for simple CASE expressions commonly
//! found in TPC-DS queries like Q2, which have patterns like:
//! ```sql
//! SUM(CASE WHEN d_day_name = 'Sunday' THEN sales_price ELSE NULL END)
//! ```
//!
//! For Q2's 7 SUM(CASE...) aggregates over ~14K grouped rows, this optimization
//! avoids per-row CSE cache clearing and expression tree traversal, providing
//! ~5-10% improvement in aggregate-heavy GROUP BY queries.

use vibesql_ast::Expression;
use vibesql_types::SqlValue;

use crate::schema::CombinedSchema;

/// Compiled representation of simple CASE expressions for fast evaluation
#[derive(Debug)]
pub enum CompiledCaseExpression {
    /// CASE WHEN col = literal THEN result_col ELSE NULL END
    WhenEqualsThenColumn {
        condition_col_idx: usize,
        condition_value: SqlValue,
        result_col_idx: usize,
    },
    /// CASE WHEN col = literal THEN literal_result ELSE NULL END
    WhenEqualsThenLiteral {
        condition_col_idx: usize,
        condition_value: SqlValue,
        result_value: SqlValue,
    },
}

impl CompiledCaseExpression {
    /// Try to compile a CASE expression into a fast-path representation
    ///
    /// Returns Some(compiled) for simple patterns, None for complex expressions
    pub fn try_compile(expr: &Expression, schema: &CombinedSchema) -> Option<Self> {
        match expr {
            Expression::Case { operand, when_clauses, else_result } => {
                // We only handle simple searched CASE (no operand)
                if operand.is_some() {
                    return None;
                }

                // Must have exactly one WHEN clause
                if when_clauses.len() != 1 {
                    return None;
                }

                // ELSE must be NULL or absent
                match else_result.as_deref() {
                    None => {}
                    Some(Expression::Literal(SqlValue::Null)) => {}
                    _ => return None,
                }

                let when_clause = &when_clauses[0];

                // CaseWhen has conditions (Vec<Expression>) and result (Expression)
                // For searched CASE, conditions contains the boolean expression(s)
                // We only handle single condition case
                if when_clause.conditions.len() != 1 {
                    return None;
                }

                let condition = &when_clause.conditions[0];
                let result = &when_clause.result;

                // Condition must be: column = literal
                if let Expression::BinaryOp {
                    op: vibesql_ast::BinaryOperator::Equal,
                    left,
                    right,
                } = condition
                {
                    // Try left = column, right = literal
                    if let Some((col_idx, lit_val)) =
                        Self::extract_column_equals_literal(left, right, schema)
                    {
                        return Self::compile_result(col_idx, lit_val, result, schema);
                    }
                    // Try right = column, left = literal
                    if let Some((col_idx, lit_val)) =
                        Self::extract_column_equals_literal(right, left, schema)
                    {
                        return Self::compile_result(col_idx, lit_val, result, schema);
                    }
                }

                None
            }
            _ => None,
        }
    }

    /// Extract column index and literal value from column = literal pattern
    fn extract_column_equals_literal(
        col_expr: &Expression,
        lit_expr: &Expression,
        schema: &CombinedSchema,
    ) -> Option<(usize, SqlValue)> {
        let col_idx = match col_expr {
            Expression::ColumnRef(col_id) => {
                schema.get_column_index(col_id.table_canonical(), col_id.column_canonical())?
            }
            _ => return None,
        };

        let lit_val = match lit_expr {
            // Expression::Literal contains SqlValue directly
            Expression::Literal(val) => val.clone(),
            _ => return None,
        };

        Some((col_idx, lit_val))
    }

    /// Compile the THEN result expression
    fn compile_result(
        condition_col_idx: usize,
        condition_value: SqlValue,
        result: &Expression,
        schema: &CombinedSchema,
    ) -> Option<Self> {
        match result {
            Expression::ColumnRef(col_id) => {
                let result_col_idx =
                    schema.get_column_index(col_id.table_canonical(), col_id.column_canonical())?;
                Some(CompiledCaseExpression::WhenEqualsThenColumn {
                    condition_col_idx,
                    condition_value,
                    result_col_idx,
                })
            }
            Expression::Literal(val) => Some(CompiledCaseExpression::WhenEqualsThenLiteral {
                condition_col_idx,
                condition_value,
                result_value: val.clone(),
            }),
            _ => None,
        }
    }

    /// Evaluate the compiled CASE expression against a row
    ///
    /// This is the fast-path: no expression tree traversal, no CSE cache,
    /// just direct column access and comparison.
    #[inline]
    pub fn evaluate(&self, row: &vibesql_storage::Row) -> SqlValue {
        match self {
            CompiledCaseExpression::WhenEqualsThenColumn {
                condition_col_idx,
                condition_value,
                result_col_idx,
            } => {
                if &row.values[*condition_col_idx] == condition_value {
                    row.values[*result_col_idx].clone()
                } else {
                    SqlValue::Null
                }
            }
            CompiledCaseExpression::WhenEqualsThenLiteral {
                condition_col_idx,
                condition_value,
                result_value,
            } => {
                if &row.values[*condition_col_idx] == condition_value {
                    result_value.clone()
                } else {
                    SqlValue::Null
                }
            }
        }
    }
}