vibesql_executor/select/executor/
arena_execution.rs

1//! Arena-based SELECT execution for zero-allocation prepared statement execution.
2//!
3//! This module provides arena-based execution of SELECT statements, enabling
4//! zero-allocation query execution for prepared statements with inline
5//! placeholder resolution.
6//!
7//! # Usage
8//!
9//! ```text
10//! use bumpalo::Bump;
11//! use vibesql_parser::arena_parser::ArenaParser;
12//!
13//! let arena = Bump::new();
14//! let stmt = ArenaParser::parse_sql("SELECT * FROM users WHERE id = ?", &arena)?;
15//! let params = &[SqlValue::Integer(42)];
16//! let result = executor.execute_select_arena(stmt, params)?;
17//! ```
18//!
19//! # Performance
20//!
21//! Arena-based execution provides:
22//! - Zero malloc/free overhead per query execution
23//! - Inline placeholder resolution (no AST cloning)
24//! - Direct evaluation without intermediate allocations
25//!
26//! This is particularly beneficial for OLTP workloads with high query rates.
27
28use std::cmp::Ordering;
29use std::collections::HashMap;
30
31use vibesql_ast::arena::{
32    ArenaInterner, Expression as ArenaExpression, ExtendedExpr as ArenaExtendedExpr,
33    SelectItem as ArenaSelectItem, SelectStmt as ArenaSelectStmt,
34};
35use vibesql_storage::Row;
36use vibesql_types::SqlValue;
37
38use super::builder::SelectExecutor;
39use crate::errors::ExecutorError;
40use crate::evaluator::window::compare_values;
41use crate::evaluator::ArenaExpressionEvaluator;
42use crate::schema::CombinedSchema;
43
44impl SelectExecutor<'_> {
45    /// Execute an arena-allocated SELECT statement with inline placeholder resolution.
46    ///
47    /// This method provides zero-allocation query execution for prepared statements.
48    /// Parameters are resolved inline during evaluation, avoiding AST cloning.
49    ///
50    /// # Arguments
51    ///
52    /// * `stmt` - Arena-allocated SELECT statement
53    /// * `params` - Parameter values for placeholder resolution
54    ///
55    /// # Returns
56    ///
57    /// Vector of result rows.
58    ///
59    /// # Limitations
60    ///
61    /// Currently supports simple SELECT queries without:
62    /// - JOINs (single table only)
63    /// - Subqueries
64    /// - Aggregates, GROUP BY, HAVING
65    /// - Window functions
66    /// - CTEs (WITH clause)
67    ///
68    /// For unsupported features, returns an error. Fall back to standard execution.
69    pub fn execute_select_arena<'arena>(
70        &self,
71        stmt: &ArenaSelectStmt<'arena>,
72        params: &[SqlValue],
73        interner: &'arena ArenaInterner<'arena>,
74    ) -> Result<Vec<Row>, ExecutorError> {
75        // Check for unsupported features
76        if stmt.with_clause.is_some() {
77            return Err(ExecutorError::UnsupportedExpression(
78                "Arena execution does not support WITH clause".to_string(),
79            ));
80        }
81
82        if stmt.set_operation.is_some() {
83            return Err(ExecutorError::UnsupportedExpression(
84                "Arena execution does not support set operations".to_string(),
85            ));
86        }
87
88        if stmt.group_by.is_some() || stmt.having.is_some() {
89            return Err(ExecutorError::UnsupportedExpression(
90                "Arena execution does not support GROUP BY/HAVING".to_string(),
91            ));
92        }
93
94        if stmt.distinct {
95            return Err(ExecutorError::UnsupportedExpression(
96                "Arena execution does not support DISTINCT".to_string(),
97            ));
98        }
99
100        // Check for aggregates in select list
101        if self.has_arena_aggregates(&stmt.select_list) {
102            return Err(ExecutorError::UnsupportedExpression(
103                "Arena execution does not support aggregate functions".to_string(),
104            ));
105        }
106
107        // Execute based on FROM clause
108        match &stmt.from {
109            Some(from) => self.execute_arena_with_from(stmt, from, params, interner),
110            None => self.execute_arena_without_from(stmt, params, interner),
111        }
112    }
113
114    /// Execute SELECT without FROM clause (expression-only).
115    fn execute_arena_without_from<'arena>(
116        &self,
117        stmt: &ArenaSelectStmt<'arena>,
118        params: &[SqlValue],
119        interner: &'arena ArenaInterner<'arena>,
120    ) -> Result<Vec<Row>, ExecutorError> {
121        // Create an empty schema and row for expression evaluation
122        let schema = CombinedSchema { table_schemas: HashMap::new(), total_columns: 0 };
123        let empty_row = Row::new(vec![]);
124        let evaluator = ArenaExpressionEvaluator::new(&schema, params, interner);
125
126        // Evaluate each select item
127        let mut values = Vec::with_capacity(stmt.select_list.len());
128        for item in stmt.select_list.iter() {
129            match item {
130                ArenaSelectItem::Expression { expr, .. } => {
131                    let value = evaluator.eval(expr, &empty_row)?;
132                    values.push(value);
133                }
134                ArenaSelectItem::Wildcard { .. } | ArenaSelectItem::QualifiedWildcard { .. } => {
135                    // Wildcard without FROM is typically an error, but we'll skip it
136                    continue;
137                }
138            }
139        }
140
141        // Apply LIMIT/OFFSET (for expression-only, limit defaults to 1 row)
142        let rows = vec![Row::new(values)];
143        Ok(self.apply_arena_limit_offset(rows, stmt.limit, stmt.offset))
144    }
145
146    /// Execute SELECT with FROM clause.
147    fn execute_arena_with_from<'arena>(
148        &self,
149        stmt: &ArenaSelectStmt<'arena>,
150        from: &vibesql_ast::arena::FromClause<'arena>,
151        params: &[SqlValue],
152        interner: &'arena ArenaInterner<'arena>,
153    ) -> Result<Vec<Row>, ExecutorError> {
154        use vibesql_ast::arena::FromClause;
155
156        // Currently only support simple table reference
157        let (table_name, alias) = match from {
158            FromClause::Table { name, alias, .. } => (*name, *alias),
159            FromClause::Join { .. } => {
160                return Err(ExecutorError::UnsupportedExpression(
161                    "Arena execution does not support JOINs yet".to_string(),
162                ));
163            }
164            FromClause::Subquery { .. } => {
165                return Err(ExecutorError::UnsupportedExpression(
166                    "Arena execution does not support subqueries in FROM".to_string(),
167                ));
168            }
169        };
170
171        // Resolve table name symbol to string
172        let table_name_str = interner.resolve(table_name);
173
174        // Get the table
175        let table = self
176            .database
177            .get_table(table_name_str)
178            .ok_or_else(|| ExecutorError::TableNotFound(table_name_str.to_string()))?;
179
180        // Build schema - use alias if provided, otherwise table name
181        let schema_alias_str = alias.map(|a| interner.resolve(a)).unwrap_or(table_name_str);
182        let schema = CombinedSchema::from_table(schema_alias_str.to_string(), table.schema.clone());
183
184        // Create evaluator
185        let evaluator =
186            ArenaExpressionEvaluator::with_database(&schema, params, self.database, interner);
187
188        // Scan table and filter (only live rows)
189        // Issue #3790: Use scan_live() to filter out deleted rows
190        let mut results = Vec::new();
191        for (_, row) in table.scan_live() {
192            // Apply WHERE clause filter
193            if let Some(where_clause) = &stmt.where_clause {
194                let filter_result = evaluator.eval(where_clause, row)?;
195                match filter_result {
196                    SqlValue::Boolean(true) => {}
197                    SqlValue::Boolean(false) | SqlValue::Null => continue,
198                    _ => {
199                        return Err(ExecutorError::TypeError(format!(
200                            "WHERE clause must evaluate to boolean, got {:?}",
201                            filter_result
202                        )));
203                    }
204                }
205            }
206
207            // Project columns
208            let projected =
209                self.project_arena_row(&stmt.select_list, row, &schema, &evaluator, interner)?;
210            results.push(projected);
211
212            // Check timeout periodically
213            if results.len() % 1000 == 0 {
214                self.check_timeout()?;
215            }
216        }
217
218        // Apply ORDER BY if present
219        if let Some(order_by) = &stmt.order_by {
220            self.sort_arena_results(&mut results, order_by.as_slice(), &schema, params, interner)?;
221        }
222
223        // Apply LIMIT/OFFSET
224        Ok(self.apply_arena_limit_offset(results, stmt.limit, stmt.offset))
225    }
226
227    /// Project a row according to the SELECT list.
228    fn project_arena_row<'arena>(
229        &self,
230        select_list: &[ArenaSelectItem<'arena>],
231        row: &Row,
232        schema: &CombinedSchema,
233        evaluator: &ArenaExpressionEvaluator<'_, 'arena>,
234        interner: &'arena ArenaInterner<'arena>,
235    ) -> Result<Row, ExecutorError> {
236        let mut values = Vec::with_capacity(select_list.len());
237
238        for item in select_list.iter() {
239            match item {
240                ArenaSelectItem::Expression { expr, .. } => {
241                    let value = evaluator.eval(expr, row)?;
242                    values.push(value);
243                }
244                ArenaSelectItem::Wildcard { .. } => {
245                    // Unqualified wildcard (*) - expand all columns
246                    values.extend(row.values.iter().cloned());
247                }
248                ArenaSelectItem::QualifiedWildcard { qualifier, .. } => {
249                    // Qualified wildcard (table.*) - expand columns from specific table
250                    let qualifier_str = interner.resolve(*qualifier);
251                    if let Some(&(start, ref tbl_schema)) = schema.get_table(qualifier_str) {
252                        for i in 0..tbl_schema.columns.len() {
253                            if let Some(val) = row.get(start + i) {
254                                values.push(val.clone());
255                            }
256                        }
257                    } else {
258                        // Table not found - expand all as fallback
259                        values.extend(row.values.iter().cloned());
260                    }
261                }
262            }
263        }
264
265        Ok(Row::new(values))
266    }
267
268    /// Sort results according to ORDER BY clause.
269    fn sort_arena_results<'arena>(
270        &self,
271        results: &mut Vec<Row>,
272        order_by: &[vibesql_ast::arena::OrderByItem<'arena>],
273        schema: &CombinedSchema,
274        params: &[SqlValue],
275        interner: &'arena ArenaInterner<'arena>,
276    ) -> Result<(), ExecutorError> {
277        use vibesql_ast::arena::OrderDirection;
278
279        // Create evaluator for order by expressions
280        let evaluator =
281            ArenaExpressionEvaluator::with_database(schema, params, self.database, interner);
282
283        // Pre-compute order by values for each row to avoid repeated evaluation
284        let mut keyed_rows: Vec<(Vec<SqlValue>, Row)> = results
285            .drain(..)
286            .map(|row| {
287                let keys: Result<Vec<_>, _> =
288                    order_by.iter().map(|item| evaluator.eval(&item.expr, &row)).collect();
289                keys.map(|k| (k, row))
290            })
291            .collect::<Result<_, _>>()?;
292
293        // Sort by keys
294        keyed_rows.sort_by(|(keys_a, _), (keys_b, _)| {
295            for (i, (key_a, key_b)) in keys_a.iter().zip(keys_b.iter()).enumerate() {
296                let cmp = compare_values(key_a, key_b);
297                if cmp != Ordering::Equal {
298                    // Apply ASC/DESC
299                    let asc =
300                        order_by.get(i).is_some_and(|o| matches!(o.direction, OrderDirection::Asc));
301                    return if asc { cmp } else { cmp.reverse() };
302                }
303            }
304            Ordering::Equal
305        });
306
307        // Move sorted rows back to results
308        results.extend(keyed_rows.into_iter().map(|(_, row)| row));
309
310        Ok(())
311    }
312
313    /// Apply LIMIT and OFFSET to results.
314    fn apply_arena_limit_offset(
315        &self,
316        mut results: Vec<Row>,
317        limit: Option<usize>,
318        offset: Option<usize>,
319    ) -> Vec<Row> {
320        // Apply offset first
321        if let Some(off) = offset {
322            if off >= results.len() {
323                return vec![];
324            }
325            results = results.into_iter().skip(off).collect();
326        }
327
328        // Apply limit
329        if let Some(lim) = limit {
330            results.truncate(lim);
331        }
332
333        results
334    }
335
336    /// Check if select list contains aggregate functions.
337    fn has_arena_aggregates<'arena>(&self, select_list: &[ArenaSelectItem<'arena>]) -> bool {
338        for item in select_list.iter() {
339            if let ArenaSelectItem::Expression { expr, .. } = item {
340                if self.arena_expr_has_aggregate(expr) {
341                    return true;
342                }
343            }
344        }
345        false
346    }
347
348    /// Check if an arena expression contains an aggregate function.
349    fn arena_expr_has_aggregate<'arena>(&self, expr: &ArenaExpression<'arena>) -> bool {
350        match expr {
351            // Hot-path inline variants
352            ArenaExpression::BinaryOp { left, right, .. } => {
353                self.arena_expr_has_aggregate(left) || self.arena_expr_has_aggregate(right)
354            }
355            ArenaExpression::UnaryOp { expr, .. } => self.arena_expr_has_aggregate(expr),
356            ArenaExpression::IsNull { expr, .. } => self.arena_expr_has_aggregate(expr),
357            ArenaExpression::Conjunction(children) | ArenaExpression::Disjunction(children) => {
358                children.iter().any(|c| self.arena_expr_has_aggregate(c))
359            }
360            ArenaExpression::Literal(_)
361            | ArenaExpression::Placeholder(_)
362            | ArenaExpression::NumberedPlaceholder(_)
363            | ArenaExpression::NamedPlaceholder(_)
364            | ArenaExpression::ColumnRef { .. }
365            | ArenaExpression::Wildcard
366            | ArenaExpression::CurrentDate
367            | ArenaExpression::CurrentTime { .. }
368            | ArenaExpression::CurrentTimestamp { .. }
369            | ArenaExpression::Default => false,
370            // Cold-path extended variants
371            ArenaExpression::Extended(ext) => self.arena_extended_has_aggregate(ext),
372        }
373    }
374
375    /// Check if an extended expression contains an aggregate function.
376    fn arena_extended_has_aggregate<'arena>(&self, ext: &ArenaExtendedExpr<'arena>) -> bool {
377        match ext {
378            ArenaExtendedExpr::AggregateFunction { .. } => true,
379            ArenaExtendedExpr::Function { args, .. } => {
380                args.iter().any(|a| self.arena_expr_has_aggregate(a))
381            }
382            ArenaExtendedExpr::Case { operand, when_clauses, else_result, .. } => {
383                operand.as_ref().is_some_and(|o| self.arena_expr_has_aggregate(o))
384                    || when_clauses.iter().any(|w| {
385                        w.conditions.iter().any(|c| self.arena_expr_has_aggregate(c))
386                            || self.arena_expr_has_aggregate(&w.result)
387                    })
388                    || else_result.as_ref().is_some_and(|e| self.arena_expr_has_aggregate(e))
389            }
390            ArenaExtendedExpr::Between { expr, low, high, .. } => {
391                self.arena_expr_has_aggregate(expr)
392                    || self.arena_expr_has_aggregate(low)
393                    || self.arena_expr_has_aggregate(high)
394            }
395            ArenaExtendedExpr::InList { expr, values, .. } => {
396                self.arena_expr_has_aggregate(expr)
397                    || values.iter().any(|v| self.arena_expr_has_aggregate(v))
398            }
399            ArenaExtendedExpr::Cast { expr, .. } => self.arena_expr_has_aggregate(expr),
400            ArenaExtendedExpr::Like { expr, pattern, .. } => {
401                self.arena_expr_has_aggregate(expr) || self.arena_expr_has_aggregate(pattern)
402            }
403            _ => false,
404        }
405    }
406}