1use std::{
29 cmp::Ordering,
30 collections::{HashMap, HashSet},
31};
32
33use vibesql_ast::arena::{
34 ArenaInterner, Expression as ArenaExpression, ExtendedExpr as ArenaExtendedExpr,
35 SelectItem as ArenaSelectItem, SelectStmt as ArenaSelectStmt,
36};
37use vibesql_storage::Row;
38use vibesql_types::SqlValue;
39
40use super::builder::SelectExecutor;
41use crate::{
42 errors::ExecutorError,
43 evaluator::{window::compare_values, ArenaExpressionEvaluator},
44 schema::CombinedSchema,
45};
46
47impl SelectExecutor<'_> {
48 pub fn execute_select_arena<'arena>(
73 &self,
74 stmt: &ArenaSelectStmt<'arena>,
75 params: &[SqlValue],
76 interner: &'arena ArenaInterner<'arena>,
77 ) -> Result<Vec<Row>, ExecutorError> {
78 if stmt.with_clause.is_some() {
80 return Err(ExecutorError::UnsupportedExpression(
81 "Arena execution does not support WITH clause".to_string(),
82 ));
83 }
84
85 if stmt.set_operation.is_some() {
86 return Err(ExecutorError::UnsupportedExpression(
87 "Arena execution does not support set operations".to_string(),
88 ));
89 }
90
91 if stmt.group_by.is_some() || stmt.having.is_some() {
92 return Err(ExecutorError::UnsupportedExpression(
93 "Arena execution does not support GROUP BY/HAVING".to_string(),
94 ));
95 }
96
97 if stmt.distinct {
98 return Err(ExecutorError::UnsupportedExpression(
99 "Arena execution does not support DISTINCT".to_string(),
100 ));
101 }
102
103 if self.has_arena_aggregates(&stmt.select_list) {
105 return Err(ExecutorError::UnsupportedExpression(
106 "Arena execution does not support aggregate functions".to_string(),
107 ));
108 }
109
110 match &stmt.from {
112 Some(from) => self.execute_arena_with_from(stmt, from, params, interner),
113 None => self.execute_arena_without_from(stmt, params, interner),
114 }
115 }
116
117 fn execute_arena_without_from<'arena>(
119 &self,
120 stmt: &ArenaSelectStmt<'arena>,
121 params: &[SqlValue],
122 interner: &'arena ArenaInterner<'arena>,
123 ) -> Result<Vec<Row>, ExecutorError> {
124 let schema = CombinedSchema {
126 table_schemas: HashMap::new(),
127 total_columns: 0,
128 hidden_columns: HashSet::new(),
129 outer_schema: None,
130 duplicate_aliases: HashSet::new(),
131 joined_columns: HashSet::new(),
132 using_coalesce_indices: HashMap::new(),
133 column_replacement_map: HashMap::new(),
134 alias_tables: HashSet::new(),
135 shadowed_tables: HashMap::new(),
136 };
137 let empty_row = Row::new(vec![]);
138 let evaluator = ArenaExpressionEvaluator::new(&schema, params, interner);
139
140 let mut values = Vec::with_capacity(stmt.select_list.len());
142 for item in stmt.select_list.iter() {
143 match item {
144 ArenaSelectItem::Expression { expr, .. } => {
145 let value = evaluator.eval(expr, &empty_row)?;
146 values.push(value);
147 }
148 ArenaSelectItem::Wildcard { .. } | ArenaSelectItem::QualifiedWildcard { .. } => {
149 continue;
151 }
152 }
153 }
154
155 let rows = vec![Row::new(values)];
157 let limit = self.evaluate_arena_limit(&stmt.limit)?;
158 let offset = self.evaluate_arena_offset(&stmt.offset)?;
159 Ok(self.apply_arena_limit_offset(rows, limit, offset))
160 }
161
162 fn execute_arena_with_from<'arena>(
164 &self,
165 stmt: &ArenaSelectStmt<'arena>,
166 from: &vibesql_ast::arena::FromClause<'arena>,
167 params: &[SqlValue],
168 interner: &'arena ArenaInterner<'arena>,
169 ) -> Result<Vec<Row>, ExecutorError> {
170 use vibesql_ast::arena::FromClause;
171
172 let (table_name, alias) = match from {
174 FromClause::Table { name, alias, .. } => (*name, *alias),
175 FromClause::Join { .. } => {
176 return Err(ExecutorError::UnsupportedExpression(
177 "Arena execution does not support JOINs yet".to_string(),
178 ));
179 }
180 FromClause::Subquery { .. } => {
181 return Err(ExecutorError::UnsupportedExpression(
182 "Arena execution does not support subqueries in FROM".to_string(),
183 ));
184 }
185 };
186
187 let table_name_str = interner.resolve(table_name);
189
190 let table = self
192 .database
193 .get_table(table_name_str)
194 .ok_or_else(|| ExecutorError::TableNotFound(table_name_str.to_string()))?;
195
196 let schema_alias_str = alias.map(|a| interner.resolve(a)).unwrap_or(table_name_str);
198 let schema = CombinedSchema::from_table(schema_alias_str.to_string(), table.schema.clone());
199
200 let evaluator =
202 ArenaExpressionEvaluator::with_database(&schema, params, self.database, interner);
203
204 let mut results = Vec::new();
207 for (_, row) in table.scan_live() {
208 if let Some(where_clause) = &stmt.where_clause {
210 let filter_result = evaluator.eval(where_clause, row)?;
211 let is_truthy = match filter_result {
212 SqlValue::Boolean(b) => b,
213 SqlValue::Null => false,
214 SqlValue::Integer(n) => n != 0,
215 SqlValue::Smallint(n) => n != 0,
216 SqlValue::Bigint(n) => n != 0,
217 SqlValue::Float(f) => f != 0.0,
218 SqlValue::Real(f) => f != 0.0,
219 SqlValue::Double(f) => f != 0.0,
220 SqlValue::Numeric(f) => f != 0.0,
221 SqlValue::Varchar(ref s) | SqlValue::Character(ref s) => string_to_truthy(s),
223 other => {
224 return Err(ExecutorError::TypeError(format!(
225 "WHERE clause must evaluate to boolean, got {:?}",
226 other
227 )));
228 }
229 };
230 if !is_truthy {
231 continue;
232 }
233 }
234
235 let projected =
237 self.project_arena_row(&stmt.select_list, row, &schema, &evaluator, interner)?;
238 results.push(projected);
239
240 if results.len() % 1000 == 0 {
242 self.check_timeout()?;
243 }
244 }
245
246 if let Some(order_by) = &stmt.order_by {
248 self.sort_arena_results(&mut results, order_by.as_slice(), &schema, params, interner)?;
249 }
250
251 let limit = self.evaluate_arena_limit(&stmt.limit)?;
253 let offset = self.evaluate_arena_offset(&stmt.offset)?;
254 Ok(self.apply_arena_limit_offset(results, limit, offset))
255 }
256
257 fn project_arena_row<'arena>(
259 &self,
260 select_list: &[ArenaSelectItem<'arena>],
261 row: &Row,
262 schema: &CombinedSchema,
263 evaluator: &ArenaExpressionEvaluator<'_, 'arena>,
264 interner: &'arena ArenaInterner<'arena>,
265 ) -> Result<Row, ExecutorError> {
266 let mut values = Vec::with_capacity(select_list.len());
267
268 for item in select_list.iter() {
269 match item {
270 ArenaSelectItem::Expression { expr, .. } => {
271 let value = evaluator.eval(expr, row)?;
272 values.push(value);
273 }
274 ArenaSelectItem::Wildcard { .. } => {
275 values.extend(row.values.iter().cloned());
277 }
278 ArenaSelectItem::QualifiedWildcard { qualifier, .. } => {
279 let qualifier_str = interner.resolve(*qualifier);
281 if let Some(&(start, ref tbl_schema)) = schema.get_table(qualifier_str) {
282 for i in 0..tbl_schema.columns.len() {
283 if let Some(val) = row.get(start + i) {
284 values.push(val.clone());
285 }
286 }
287 } else {
288 values.extend(row.values.iter().cloned());
290 }
291 }
292 }
293 }
294
295 Ok(Row::new(values))
296 }
297
298 fn sort_arena_results<'arena>(
300 &self,
301 results: &mut Vec<Row>,
302 order_by: &[vibesql_ast::arena::OrderByItem<'arena>],
303 schema: &CombinedSchema,
304 params: &[SqlValue],
305 interner: &'arena ArenaInterner<'arena>,
306 ) -> Result<(), ExecutorError> {
307 use vibesql_ast::arena::OrderDirection;
308
309 let evaluator =
311 ArenaExpressionEvaluator::with_database(schema, params, self.database, interner);
312
313 let mut keyed_rows: Vec<(Vec<SqlValue>, Row)> = results
315 .drain(..)
316 .map(|row| {
317 let keys: Result<Vec<_>, _> =
318 order_by.iter().map(|item| evaluator.eval(&item.expr, &row)).collect();
319 keys.map(|k| (k, row))
320 })
321 .collect::<Result<_, _>>()?;
322
323 keyed_rows.sort_by(|(keys_a, _), (keys_b, _)| {
325 for (i, (key_a, key_b)) in keys_a.iter().zip(keys_b.iter()).enumerate() {
326 let order_item = order_by.get(i);
327 let asc = order_item.is_some_and(|o| matches!(o.direction, OrderDirection::Asc));
328
329 let nulls_first = match order_item.and_then(|o| o.nulls_order) {
335 Some(vibesql_ast::arena::NullsOrder::First) => true,
336 Some(vibesql_ast::arena::NullsOrder::Last) => false,
337 None => asc, };
339
340 let cmp = match (key_a.is_null(), key_b.is_null()) {
342 (true, true) => Ordering::Equal,
343 (true, false) => {
344 if nulls_first {
345 return Ordering::Less; } else {
347 return Ordering::Greater; }
349 }
350 (false, true) => {
351 if nulls_first {
352 return Ordering::Greater; } else {
354 return Ordering::Less; }
356 }
357 (false, false) => {
358 let cmp = compare_values(key_a, key_b);
360 if asc {
361 cmp
362 } else {
363 cmp.reverse()
364 }
365 }
366 };
367
368 if cmp != Ordering::Equal {
369 return cmp;
370 }
371 }
372 Ordering::Equal
373 });
374
375 results.extend(keyed_rows.into_iter().map(|(_, row)| row));
377
378 Ok(())
379 }
380
381 fn evaluate_arena_limit_offset_expr_raw(
384 &self,
385 expr: &vibesql_ast::arena::Expression,
386 ) -> Result<i64, ExecutorError> {
387 match expr {
388 vibesql_ast::arena::Expression::Literal(vibesql_types::SqlValue::Integer(n)) => Ok(*n),
389 _ => Err(ExecutorError::InvalidLimitOffset {
390 clause: "LIMIT/OFFSET".to_string(),
391 value: "<expression>".to_string(),
392 reason: "must be a constant integer".to_string(),
393 }),
394 }
395 }
396
397 fn evaluate_arena_limit(
400 &self,
401 limit: &Option<vibesql_ast::arena::Expression>,
402 ) -> Result<Option<usize>, ExecutorError> {
403 match limit.as_ref().map(|e| self.evaluate_arena_limit_offset_expr_raw(e)).transpose()? {
404 Some(n) if n < 0 => Ok(None), Some(n) => Ok(Some(n as usize)),
406 None => Ok(None),
407 }
408 }
409
410 fn evaluate_arena_offset(
413 &self,
414 offset: &Option<vibesql_ast::arena::Expression>,
415 ) -> Result<Option<usize>, ExecutorError> {
416 match offset.as_ref().map(|e| self.evaluate_arena_limit_offset_expr_raw(e)).transpose()? {
417 Some(n) if n < 0 => Ok(Some(0)), Some(n) => Ok(Some(n as usize)),
419 None => Ok(None),
420 }
421 }
422
423 fn apply_arena_limit_offset(
425 &self,
426 mut results: Vec<Row>,
427 limit: Option<usize>,
428 offset: Option<usize>,
429 ) -> Vec<Row> {
430 if let Some(off) = offset {
432 if off >= results.len() {
433 return vec![];
434 }
435 results = results.into_iter().skip(off).collect();
436 }
437
438 if let Some(lim) = limit {
440 results.truncate(lim);
441 }
442
443 results
444 }
445
446 fn has_arena_aggregates<'arena>(&self, select_list: &[ArenaSelectItem<'arena>]) -> bool {
448 for item in select_list.iter() {
449 if let ArenaSelectItem::Expression { expr, .. } = item {
450 if self.arena_expr_has_aggregate(expr) {
451 return true;
452 }
453 }
454 }
455 false
456 }
457
458 fn arena_expr_has_aggregate<'arena>(&self, expr: &ArenaExpression<'arena>) -> bool {
460 match expr {
461 ArenaExpression::BinaryOp { left, right, .. } => {
463 self.arena_expr_has_aggregate(left) || self.arena_expr_has_aggregate(right)
464 }
465 ArenaExpression::UnaryOp { expr, .. } => self.arena_expr_has_aggregate(expr),
466 ArenaExpression::IsNull { expr, .. } => self.arena_expr_has_aggregate(expr),
467 ArenaExpression::IsDistinctFrom { left, right, .. } => {
468 self.arena_expr_has_aggregate(left) || self.arena_expr_has_aggregate(right)
469 }
470 ArenaExpression::IsTruthValue { expr, .. } => self.arena_expr_has_aggregate(expr),
471 ArenaExpression::Conjunction(children) | ArenaExpression::Disjunction(children) => {
472 children.iter().any(|c| self.arena_expr_has_aggregate(c))
473 }
474 ArenaExpression::Literal(_)
475 | ArenaExpression::Placeholder(_)
476 | ArenaExpression::NumberedPlaceholder(_)
477 | ArenaExpression::NamedPlaceholder(_)
478 | ArenaExpression::ColumnRef { .. }
479 | ArenaExpression::Wildcard
480 | ArenaExpression::CurrentDate
481 | ArenaExpression::CurrentTime { .. }
482 | ArenaExpression::CurrentTimestamp { .. }
483 | ArenaExpression::Default => false,
484 ArenaExpression::Extended(ext) => self.arena_extended_has_aggregate(ext),
486 }
487 }
488
489 fn arena_extended_has_aggregate<'arena>(&self, ext: &ArenaExtendedExpr<'arena>) -> bool {
491 match ext {
492 ArenaExtendedExpr::AggregateFunction { .. } => true,
493 ArenaExtendedExpr::Function { args, .. } => {
494 args.iter().any(|a| self.arena_expr_has_aggregate(a))
495 }
496 ArenaExtendedExpr::Case { operand, when_clauses, else_result, .. } => {
497 operand.as_ref().is_some_and(|o| self.arena_expr_has_aggregate(o))
498 || when_clauses.iter().any(|w| {
499 w.conditions.iter().any(|c| self.arena_expr_has_aggregate(c))
500 || self.arena_expr_has_aggregate(&w.result)
501 })
502 || else_result.as_ref().is_some_and(|e| self.arena_expr_has_aggregate(e))
503 }
504 ArenaExtendedExpr::Between { expr, low, high, .. } => {
505 self.arena_expr_has_aggregate(expr)
506 || self.arena_expr_has_aggregate(low)
507 || self.arena_expr_has_aggregate(high)
508 }
509 ArenaExtendedExpr::InList { expr, values, .. } => {
510 self.arena_expr_has_aggregate(expr)
511 || values.iter().any(|v| self.arena_expr_has_aggregate(v))
512 }
513 ArenaExtendedExpr::Cast { expr, .. } => self.arena_expr_has_aggregate(expr),
514 ArenaExtendedExpr::Like { expr, pattern, .. } => {
515 self.arena_expr_has_aggregate(expr) || self.arena_expr_has_aggregate(pattern)
516 }
517 _ => false,
518 }
519 }
520}
521
522#[inline(always)]
524fn string_to_truthy(s: &str) -> bool {
525 if s.is_empty() {
526 return false;
527 }
528 let trimmed = s.trim();
529 if trimmed.is_empty() {
530 return false;
531 }
532 let mut end = 0;
534 let mut has_dot = false;
535 let mut has_digit = false;
536 let chars: Vec<char> = trimmed.chars().collect();
537 if !chars.is_empty() && (chars[0] == '-' || chars[0] == '+') {
538 end = 1;
539 }
540 while end < chars.len() {
541 let c = chars[end];
542 if c.is_ascii_digit() {
543 has_digit = true;
544 end += 1;
545 } else if c == '.' && !has_dot {
546 has_dot = true;
547 end += 1;
548 } else {
549 break;
550 }
551 }
552 if !has_digit {
553 return false;
554 }
555 let num_str: String = chars[..end].iter().collect();
556 num_str.parse::<f64>().map(|n| n != 0.0).unwrap_or(false)
557}