#![allow(clippy::ptr_arg)]
#![allow(dead_code)]
mod cse;
mod group_by;
mod having;
mod join;
mod join_helpers;
use std::collections::HashMap;
use vibesql_ast::Expression;
use super::builder::SelectExecutor;
use crate::{
errors::ExecutorError,
optimizer::adaptive::{choose_execution_model, ExecutionModel},
schema::CombinedSchema,
select::{columnar, cte::CteResult},
};
fn collect_aggregates_from_expr(expr: &Expression) -> Vec<Expression> {
let mut aggregates = vec![];
collect_aggregates_recursive(expr, &mut aggregates);
aggregates
}
fn collect_aggregates_recursive(expr: &Expression, aggregates: &mut Vec<Expression>) {
match expr {
Expression::AggregateFunction { .. } => {
aggregates.push(expr.clone());
}
Expression::BinaryOp { left, right, .. } => {
collect_aggregates_recursive(left, aggregates);
collect_aggregates_recursive(right, aggregates);
}
Expression::UnaryOp { expr: inner, .. } => {
collect_aggregates_recursive(inner, aggregates);
}
Expression::Case { operand, when_clauses, else_result } => {
if let Some(op) = operand {
collect_aggregates_recursive(op, aggregates);
}
for case_when in when_clauses {
for cond in &case_when.conditions {
collect_aggregates_recursive(cond, aggregates);
}
collect_aggregates_recursive(&case_when.result, aggregates);
}
if let Some(else_expr) = else_result {
collect_aggregates_recursive(else_expr, aggregates);
}
}
Expression::Function { args, .. } => {
for arg in args {
collect_aggregates_recursive(arg, aggregates);
}
}
Expression::InList { expr: inner, values, .. } => {
collect_aggregates_recursive(inner, aggregates);
for item in values {
collect_aggregates_recursive(item, aggregates);
}
}
Expression::Between { expr: inner, low, high, .. } => {
collect_aggregates_recursive(inner, aggregates);
collect_aggregates_recursive(low, aggregates);
collect_aggregates_recursive(high, aggregates);
}
_ => {}
}
}
fn contains_aggregate(expr: &Expression) -> bool {
match expr {
Expression::AggregateFunction { .. } => true,
Expression::BinaryOp { left, right, .. } => {
contains_aggregate(left) || contains_aggregate(right)
}
Expression::UnaryOp { expr: inner, .. } => contains_aggregate(inner),
Expression::Function { args, .. } => args.iter().any(contains_aggregate),
Expression::Case { operand, when_clauses, else_result, .. } => {
operand.as_ref().is_some_and(|e| contains_aggregate(e))
|| when_clauses.iter().any(|clause| {
clause.conditions.iter().any(contains_aggregate)
|| contains_aggregate(&clause.result)
})
|| else_result.as_ref().is_some_and(|e| contains_aggregate(e))
}
Expression::InList { expr: inner, values, .. } => {
contains_aggregate(inner) || values.iter().any(contains_aggregate)
}
Expression::Between { expr: inner, low, high, .. } => {
contains_aggregate(inner) || contains_aggregate(low) || contains_aggregate(high)
}
Expression::Cast { expr: inner, .. } => contains_aggregate(inner),
Expression::IsNull { expr: inner, .. } => contains_aggregate(inner),
_ => false,
}
}
fn contains_subquery(expr: &Expression) -> bool {
match expr {
Expression::ScalarSubquery(_) => true,
Expression::In { .. } => true,
Expression::Exists { .. } => true,
Expression::QuantifiedComparison { .. } => true,
Expression::BinaryOp { left, right, .. } => {
contains_subquery(left) || contains_subquery(right)
}
Expression::UnaryOp { expr: inner, .. } => contains_subquery(inner),
Expression::AggregateFunction { args, .. } => args.iter().any(contains_subquery),
Expression::Function { args, .. } => args.iter().any(contains_subquery),
Expression::Conjunction(exprs) | Expression::Disjunction(exprs) => {
exprs.iter().any(contains_subquery)
}
Expression::Case { operand, when_clauses, else_result } => {
operand.as_ref().is_some_and(|o| contains_subquery(o))
|| when_clauses.iter().any(|w| {
w.conditions.iter().any(contains_subquery) || contains_subquery(&w.result)
})
|| else_result.as_ref().is_some_and(|e| contains_subquery(e))
}
Expression::InList { expr: inner, values, .. } => {
contains_subquery(inner) || values.iter().any(contains_subquery)
}
Expression::Between { expr: inner, low, high, .. } => {
contains_subquery(inner) || contains_subquery(low) || contains_subquery(high)
}
Expression::IsNull { expr: inner, .. } => contains_subquery(inner),
Expression::Cast { expr: inner, .. } => contains_subquery(inner),
_ => false,
}
}
impl SelectExecutor<'_> {
pub(in crate::select::executor) fn try_columnar_execution(
&self,
stmt: &vibesql_ast::SelectStmt,
cte_results: &HashMap<String, CteResult>,
) -> Result<Option<Vec<vibesql_storage::Row>>, ExecutorError> {
if stmt.group_by.is_some() {
log::debug!(" Columnar execution: Not supported - has GROUP BY");
return Ok(None);
}
match choose_execution_model(stmt) {
ExecutionModel::RowOriented => {
log::debug!(" Columnar execution: Query not compatible (adaptive execution selected row-oriented)");
#[cfg(feature = "profile-q6")]
eprintln!("[PROFILE-Q6] Reason: Adaptive execution selected ROW-ORIENTED model");
return Ok(None);
}
ExecutionModel::Columnar => {
log::debug!(
" Columnar execution: Query eligible (adaptive execution selected columnar)"
);
#[cfg(feature = "profile-q6")]
eprintln!("[PROFILE-Q6] ✓ Adaptive execution selected COLUMNAR model");
}
}
if !cte_results.is_empty() || stmt.set_operation.is_some() {
log::debug!(" Columnar execution: Not supported - has CTEs or set operations");
#[cfg(feature = "profile-q6")]
eprintln!("[PROFILE-Q6] Reason: Has CTEs or set operations (not supported yet)");
return Ok(None);
}
let from_clause = match &stmt.from {
Some(from) => from,
None => {
log::debug!(" Columnar execution: Not supported - no FROM clause");
#[cfg(feature = "profile-q6")]
eprintln!("[PROFILE-Q6] Reason: No FROM clause");
return Ok(None);
}
};
let from_result = self.execute_from_with_where(
from_clause,
cte_results,
None, None, None, None, )?;
let schema = from_result.schema.clone();
super::validation::validate_select_columns_with_context(
&stmt.select_list,
stmt.where_clause.as_ref(),
&schema,
self.procedural_context,
self.outer_schema,
)?;
let select_exprs: Vec<_> = stmt
.select_list
.iter()
.filter_map(|item| match item {
vibesql_ast::SelectItem::Expression { expr, .. } => Some(expr.clone()),
_ => None, })
.collect();
let rows_slice = from_result.data.as_slice();
#[cfg(feature = "profile-q6")]
eprintln!("[PROFILE-Q6] Attempting columnar execution on {} rows...", rows_slice.len());
match columnar::execute_columnar(
rows_slice,
stmt.where_clause.as_ref(), &select_exprs,
&schema,
self.database.case_sensitive_like(),
) {
Some(result) => {
#[cfg(feature = "profile-q6")]
eprintln!("[PROFILE-Q6] ✓ Columnar execution succeeded");
result.map(Some)
}
None => {
#[cfg(feature = "profile-q6")]
eprintln!("[PROFILE-Q6] Reason: execute_columnar returned None (predicates or aggregates too complex)");
Ok(None)
} }
}
pub(in crate::select::executor) fn try_native_columnar_execution(
&self,
stmt: &vibesql_ast::SelectStmt,
cte_results: &HashMap<String, CteResult>,
) -> Result<Option<Vec<vibesql_storage::Row>>, ExecutorError> {
if std::env::var("VIBESQL_DISABLE_COLUMNAR").is_ok() {
log::debug!("Native columnar: disabled via VIBESQL_DISABLE_COLUMNAR");
return Ok(None);
}
if !cte_results.is_empty() || stmt.set_operation.is_some() {
log::debug!("Native columnar: skipping - has CTEs or set operations");
return Ok(None);
}
if self.outer_schema.is_some() {
log::debug!("Native columnar: skipping - correlated subquery with outer schema");
return Ok(None);
}
let from_clause = match &stmt.from {
Some(from) => from,
None => return Ok(None),
};
let (table_name, table_alias) =
match join_helpers::extract_table_name_and_alias(from_clause) {
Some((name, alias)) => (name, alias),
None => {
log::debug!("Native columnar: skipping - not a simple single-table query");
return Ok(None);
}
};
match choose_execution_model(stmt) {
ExecutionModel::RowOriented => {
log::debug!("Native columnar: skipping - adaptive model selected row-oriented");
return Ok(None);
}
ExecutionModel::Columnar => {} }
let table = match self.database.get_table(&table_name) {
Some(t) => t,
None => return Ok(None),
};
let schema_key = table_alias.unwrap_or_else(|| table_name.clone());
let schema = CombinedSchema::from_table(schema_key, table.schema.clone());
super::validation::validate_select_columns_with_context(
&stmt.select_list,
stmt.where_clause.as_ref(),
&schema,
self.procedural_context,
self.outer_schema,
)?;
#[cfg(feature = "profile-q6")]
let scan_start = std::time::Instant::now();
let columnar_arc = match self.database.get_columnar(&table_name) {
Ok(Some(ct)) => ct,
Ok(None) => {
log::debug!("Native columnar: table not found in cache or storage");
return Ok(None);
}
Err(e) => {
log::debug!("Native columnar: get_columnar failed: {:?}", e);
return Ok(None);
}
};
#[cfg(feature = "profile-q6")]
{
let scan_time = scan_start.elapsed();
let cache_stats = self.database.columnar_cache_stats();
eprintln!(
"[PROFILE-Q6] Native columnar scan: {:?} ({} rows, cache hits: {}, misses: {})",
scan_time,
columnar_arc.row_count(),
cache_stats.hits,
cache_stats.misses
);
}
log::info!(
"Native columnar execution: table={}, rows={}",
table_name,
columnar_arc.row_count()
);
if columnar_arc.row_count() == 0 {
log::debug!("Native columnar: skipping empty table");
return Ok(None);
}
let batch = columnar::ColumnarBatch::from_storage_columnar(&columnar_arc)?;
let predicates = match stmt.where_clause.as_ref() {
Some(where_expr) => {
match columnar::extract_column_predicates(
where_expr,
&schema,
self.database.case_sensitive_like(),
) {
Some(preds) => preds,
None => {
log::debug!(
"Native columnar: skipping - WHERE clause contains unsupported predicates"
);
return Ok(None);
}
}
}
None => vec![],
};
let has_group_by = stmt.group_by.is_some();
let select_exprs: Vec<_> = stmt
.select_list
.iter()
.filter_map(|item| match item {
vibesql_ast::SelectItem::Expression { expr, .. } => {
if has_group_by && !matches!(expr, Expression::AggregateFunction { .. }) {
None
} else {
Some(expr.clone())
}
}
_ => None,
})
.collect();
let having_aggregates = if let Some(having_expr) = &stmt.having {
collect_aggregates_from_expr(having_expr)
} else {
vec![]
};
let select_aggregates = columnar::extract_aggregates(&select_exprs, &schema);
let has_unsupported_select_aggregates = select_aggregates.is_none();
let mut aggregates = select_aggregates.unwrap_or_default();
let mut has_unsupported_having_aggregates = false;
if !having_aggregates.is_empty() {
let having_aggs = columnar::extract_aggregates(&having_aggregates, &schema);
if having_aggs.is_none() {
has_unsupported_having_aggregates = true;
}
if let Some(having_aggs) = having_aggs {
for agg in having_aggs {
if !aggregates.iter().any(|a| a.source == agg.source && a.op == agg.op) {
aggregates.push(agg);
}
}
}
}
if aggregates.is_empty() && !has_group_by {
log::debug!("Native columnar: skipping - no aggregates");
return Ok(None);
}
if has_unsupported_select_aggregates && has_group_by {
log::debug!(
"Native columnar: skipping - GROUP BY with unsupported aggregate expressions in SELECT"
);
return Ok(None);
}
if has_unsupported_having_aggregates {
log::debug!("Native columnar: skipping - unsupported aggregate expressions in HAVING");
return Ok(None);
}
if let Some(having_expr) = &stmt.having {
if contains_subquery(having_expr) {
log::debug!("Native columnar: skipping - HAVING contains subquery");
return Ok(None);
}
}
if let Some(ref group_by) = stmt.group_by {
if group_by.as_simple().is_none() {
log::debug!("Native columnar: skipping - ROLLUP/CUBE/GROUPING SETS not supported");
return Ok(None);
}
}
if has_group_by && stmt.order_by.is_some() {
log::debug!("Native columnar: skipping - GROUP BY with ORDER BY not supported");
return Ok(None);
}
if has_group_by {
for item in &stmt.select_list {
if let vibesql_ast::SelectItem::Expression { expr, .. } = item {
if contains_aggregate(expr)
&& !matches!(expr, Expression::AggregateFunction { .. })
{
log::debug!(
"Native columnar: skipping - SELECT contains expression wrapping aggregate"
);
return Ok(None);
}
}
}
let group_by_exprs_list = stmt
.group_by
.as_ref()
.and_then(|g| g.as_simple())
.map(|exprs| exprs.to_vec())
.unwrap_or_default();
let mut select_non_agg_exprs: Vec<&Expression> = Vec::new();
let mut select_agg_count = 0;
for item in &stmt.select_list {
if let vibesql_ast::SelectItem::Expression { expr, .. } = item {
if matches!(expr, Expression::AggregateFunction { .. }) {
select_agg_count += 1;
} else {
select_non_agg_exprs.push(expr);
}
}
}
if select_non_agg_exprs.len() != group_by_exprs_list.len() {
log::debug!(
"Native columnar: skipping GROUP BY - SELECT list ({} non-aggs, {} aggs) doesn't match GROUP BY ({} keys)",
select_non_agg_exprs.len(),
select_agg_count,
group_by_exprs_list.len()
);
return Ok(None);
}
use crate::select::grouping::expressions_equal;
for select_expr in &select_non_agg_exprs {
let matches_group_by = group_by_exprs_list
.iter()
.any(|group_expr| expressions_equal(select_expr, group_expr));
if !matches_group_by {
log::debug!(
"Native columnar: skipping GROUP BY - SELECT expression {:?} is not a GROUP BY key",
select_expr
);
return Ok(None);
}
}
let mut first_agg_pos = None;
let mut last_non_agg_pos = None;
for (i, item) in stmt.select_list.iter().enumerate() {
if let vibesql_ast::SelectItem::Expression { expr, .. } = item {
if matches!(expr, Expression::AggregateFunction { .. }) {
if first_agg_pos.is_none() {
first_agg_pos = Some(i);
}
} else {
last_non_agg_pos = Some(i);
}
}
}
if let (Some(first_agg), Some(last_non_agg)) = (first_agg_pos, last_non_agg_pos) {
if first_agg < last_non_agg {
log::debug!(
"Native columnar: skipping GROUP BY - aggregate at position {} before non-aggregate at {}",
first_agg,
last_non_agg
);
return Ok(None);
}
}
}
#[cfg(feature = "profile-q6")]
let exec_start = std::time::Instant::now();
let result = if has_group_by {
let group_by_result = match self.execute_columnar_group_by(
stmt,
&batch,
&predicates,
&aggregates,
&schema,
) {
Ok(rows) => rows,
Err(ExecutorError::Other(msg))
if msg.contains("not supported in columnar path") =>
{
log::debug!("Native columnar: GROUP BY falling back to row-oriented: {}", msg);
return Ok(None);
}
Err(e) => return Err(e),
};
if let Some(having_expr) = &stmt.having {
let group_col_count = stmt
.group_by
.as_ref()
.and_then(|g| g.as_simple())
.map(|exprs| exprs.len())
.unwrap_or(0);
log::debug!(
"Applying columnar HAVING filter: {} groups, {} group cols, {} aggregates",
group_by_result.len(),
group_col_count,
aggregates.len()
);
match having::apply_having_filter(
group_by_result,
having_expr,
group_col_count,
&aggregates,
&schema,
) {
Ok(rows) => rows,
Err(ExecutorError::UnsupportedFeature(msg))
if msg.contains("not supported in columnar")
|| msg.contains("not found in computed aggregates") =>
{
log::debug!(
"Native columnar: HAVING falling back to row-oriented: {}",
msg
);
return Ok(None);
}
Err(ExecutorError::Other(msg))
if msg.contains("not supported in columnar path")
|| msg.contains("not found in computed aggregates") =>
{
log::debug!(
"Native columnar: HAVING falling back to row-oriented: {}",
msg
);
return Ok(None);
}
Err(e) => return Err(e),
}
} else {
group_by_result
}
} else {
let agg_result =
columnar::execute_columnar_batch(&batch, &predicates, &aggregates, Some(&schema))?;
if let Some(having_expr) = &stmt.having {
log::debug!(
"Applying columnar HAVING filter (no GROUP BY): {} rows, {} aggregates",
agg_result.len(),
aggregates.len()
);
match having::apply_having_filter(
agg_result,
having_expr,
0, &aggregates,
&schema,
) {
Ok(rows) => rows,
Err(ExecutorError::UnsupportedFeature(msg))
if msg.contains("not supported in columnar")
|| msg.contains("not found in computed aggregates") =>
{
log::debug!(
"Native columnar: HAVING (no GROUP BY) falling back to row-oriented: {}",
msg
);
return Ok(None);
}
Err(ExecutorError::Other(msg))
if msg.contains("not supported in columnar path")
|| msg.contains("not found in computed aggregates") =>
{
log::debug!(
"Native columnar: HAVING (no GROUP BY) falling back to row-oriented: {}",
msg
);
return Ok(None);
}
Err(e) => return Err(e),
}
} else {
agg_result
}
};
#[cfg(feature = "profile-q6")]
{
let exec_time = exec_start.elapsed();
eprintln!("[PROFILE-Q6] Native columnar execution: {:?}", exec_time);
}
log::info!(
"Native columnar execution completed: {} predicates, {} aggregates, group_by={}, having={}",
predicates.len(),
aggregates.len(),
has_group_by,
stmt.having.is_some()
);
let limit = crate::select::helpers::evaluate_limit(&stmt.limit, self.database)?;
let offset = crate::select::helpers::evaluate_offset(&stmt.offset, self.database)?;
let final_result = crate::select::helpers::apply_limit_offset(result, limit, offset);
Ok(Some(final_result))
}
}