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
189        let mut results = Vec::new();
190        for row in table.scan() {
191            // Apply WHERE clause filter
192            if let Some(where_clause) = &stmt.where_clause {
193                let filter_result = evaluator.eval(where_clause, row)?;
194                match filter_result {
195                    SqlValue::Boolean(true) => {}
196                    SqlValue::Boolean(false) | SqlValue::Null => continue,
197                    _ => {
198                        return Err(ExecutorError::TypeError(format!(
199                            "WHERE clause must evaluate to boolean, got {:?}",
200                            filter_result
201                        )));
202                    }
203                }
204            }
205
206            // Project columns
207            let projected =
208                self.project_arena_row(&stmt.select_list, row, &schema, &evaluator, interner)?;
209            results.push(projected);
210
211            // Check timeout periodically
212            if results.len() % 1000 == 0 {
213                self.check_timeout()?;
214            }
215        }
216
217        // Apply ORDER BY if present
218        if let Some(order_by) = &stmt.order_by {
219            self.sort_arena_results(&mut results, order_by.as_slice(), &schema, params, interner)?;
220        }
221
222        // Apply LIMIT/OFFSET
223        Ok(self.apply_arena_limit_offset(results, stmt.limit, stmt.offset))
224    }
225
226    /// Project a row according to the SELECT list.
227    fn project_arena_row<'arena>(
228        &self,
229        select_list: &[ArenaSelectItem<'arena>],
230        row: &Row,
231        schema: &CombinedSchema,
232        evaluator: &ArenaExpressionEvaluator<'_, 'arena>,
233        interner: &'arena ArenaInterner<'arena>,
234    ) -> Result<Row, ExecutorError> {
235        let mut values = Vec::with_capacity(select_list.len());
236
237        for item in select_list.iter() {
238            match item {
239                ArenaSelectItem::Expression { expr, .. } => {
240                    let value = evaluator.eval(expr, row)?;
241                    values.push(value);
242                }
243                ArenaSelectItem::Wildcard { .. } => {
244                    // Unqualified wildcard (*) - expand all columns
245                    values.extend(row.values.iter().cloned());
246                }
247                ArenaSelectItem::QualifiedWildcard { qualifier, .. } => {
248                    // Qualified wildcard (table.*) - expand columns from specific table
249                    let qualifier_str = interner.resolve(*qualifier);
250                    if let Some(&(start, ref tbl_schema)) =
251                        schema.table_schemas.get(&qualifier_str.to_lowercase())
252                    {
253                        for i in 0..tbl_schema.columns.len() {
254                            if let Some(val) = row.get(start + i) {
255                                values.push(val.clone());
256                            }
257                        }
258                    } else {
259                        // Table not found - expand all as fallback
260                        values.extend(row.values.iter().cloned());
261                    }
262                }
263            }
264        }
265
266        Ok(Row::new(values))
267    }
268
269    /// Sort results according to ORDER BY clause.
270    fn sort_arena_results<'arena>(
271        &self,
272        results: &mut Vec<Row>,
273        order_by: &[vibesql_ast::arena::OrderByItem<'arena>],
274        schema: &CombinedSchema,
275        params: &[SqlValue],
276        interner: &'arena ArenaInterner<'arena>,
277    ) -> Result<(), ExecutorError> {
278        use vibesql_ast::arena::OrderDirection;
279
280        // Create evaluator for order by expressions
281        let evaluator =
282            ArenaExpressionEvaluator::with_database(schema, params, self.database, interner);
283
284        // Pre-compute order by values for each row to avoid repeated evaluation
285        let mut keyed_rows: Vec<(Vec<SqlValue>, Row)> = results
286            .drain(..)
287            .map(|row| {
288                let keys: Result<Vec<_>, _> =
289                    order_by.iter().map(|item| evaluator.eval(&item.expr, &row)).collect();
290                keys.map(|k| (k, row))
291            })
292            .collect::<Result<_, _>>()?;
293
294        // Sort by keys
295        keyed_rows.sort_by(|(keys_a, _), (keys_b, _)| {
296            for (i, (key_a, key_b)) in keys_a.iter().zip(keys_b.iter()).enumerate() {
297                let cmp = compare_values(key_a, key_b);
298                if cmp != Ordering::Equal {
299                    // Apply ASC/DESC
300                    let asc =
301                        order_by.get(i).is_some_and(|o| matches!(o.direction, OrderDirection::Asc));
302                    return if asc { cmp } else { cmp.reverse() };
303                }
304            }
305            Ordering::Equal
306        });
307
308        // Move sorted rows back to results
309        results.extend(keyed_rows.into_iter().map(|(_, row)| row));
310
311        Ok(())
312    }
313
314    /// Apply LIMIT and OFFSET to results.
315    fn apply_arena_limit_offset(
316        &self,
317        mut results: Vec<Row>,
318        limit: Option<usize>,
319        offset: Option<usize>,
320    ) -> Vec<Row> {
321        // Apply offset first
322        if let Some(off) = offset {
323            if off >= results.len() {
324                return vec![];
325            }
326            results = results.into_iter().skip(off).collect();
327        }
328
329        // Apply limit
330        if let Some(lim) = limit {
331            results.truncate(lim);
332        }
333
334        results
335    }
336
337    /// Check if select list contains aggregate functions.
338    fn has_arena_aggregates<'arena>(&self, select_list: &[ArenaSelectItem<'arena>]) -> bool {
339        for item in select_list.iter() {
340            if let ArenaSelectItem::Expression { expr, .. } = item {
341                if self.arena_expr_has_aggregate(expr) {
342                    return true;
343                }
344            }
345        }
346        false
347    }
348
349    /// Check if an arena expression contains an aggregate function.
350    fn arena_expr_has_aggregate<'arena>(&self, expr: &ArenaExpression<'arena>) -> bool {
351        match expr {
352            // Hot-path inline variants
353            ArenaExpression::BinaryOp { left, right, .. } => {
354                self.arena_expr_has_aggregate(left) || self.arena_expr_has_aggregate(right)
355            }
356            ArenaExpression::UnaryOp { expr, .. } => self.arena_expr_has_aggregate(expr),
357            ArenaExpression::IsNull { expr, .. } => self.arena_expr_has_aggregate(expr),
358            ArenaExpression::Conjunction(children) | ArenaExpression::Disjunction(children) => {
359                children.iter().any(|c| self.arena_expr_has_aggregate(c))
360            }
361            ArenaExpression::Literal(_)
362            | ArenaExpression::Placeholder(_)
363            | ArenaExpression::NumberedPlaceholder(_)
364            | ArenaExpression::NamedPlaceholder(_)
365            | ArenaExpression::ColumnRef { .. }
366            | ArenaExpression::Wildcard
367            | ArenaExpression::CurrentDate
368            | ArenaExpression::CurrentTime { .. }
369            | ArenaExpression::CurrentTimestamp { .. }
370            | ArenaExpression::Default => false,
371            // Cold-path extended variants
372            ArenaExpression::Extended(ext) => self.arena_extended_has_aggregate(ext),
373        }
374    }
375
376    /// Check if an extended expression contains an aggregate function.
377    fn arena_extended_has_aggregate<'arena>(&self, ext: &ArenaExtendedExpr<'arena>) -> bool {
378        match ext {
379            ArenaExtendedExpr::AggregateFunction { .. } => true,
380            ArenaExtendedExpr::Function { args, .. } => {
381                args.iter().any(|a| self.arena_expr_has_aggregate(a))
382            }
383            ArenaExtendedExpr::Case { operand, when_clauses, else_result, .. } => {
384                operand.as_ref().is_some_and(|o| self.arena_expr_has_aggregate(o))
385                    || when_clauses.iter().any(|w| {
386                        w.conditions.iter().any(|c| self.arena_expr_has_aggregate(c))
387                            || self.arena_expr_has_aggregate(&w.result)
388                    })
389                    || else_result.as_ref().is_some_and(|e| self.arena_expr_has_aggregate(e))
390            }
391            ArenaExtendedExpr::Between { expr, low, high, .. } => {
392                self.arena_expr_has_aggregate(expr)
393                    || self.arena_expr_has_aggregate(low)
394                    || self.arena_expr_has_aggregate(high)
395            }
396            ArenaExtendedExpr::InList { expr, values, .. } => {
397                self.arena_expr_has_aggregate(expr)
398                    || values.iter().any(|v| self.arena_expr_has_aggregate(v))
399            }
400            ArenaExtendedExpr::Cast { expr, .. } => self.arena_expr_has_aggregate(expr),
401            ArenaExtendedExpr::Like { expr, pattern, .. } => {
402                self.arena_expr_has_aggregate(expr) || self.arena_expr_has_aggregate(pattern)
403            }
404            _ => false,
405        }
406    }
407}