use std::collections::HashMap;
use vibesql_ast::{FromClause, SelectStmt};
use super::{
patterns::has_analytical_pattern,
query::{has_aggregate_functions, has_group_by, is_single_table},
};
use crate::select::cte::CteResult;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExecutionStrategy {
NativeColumnar {
table: String,
score: StrategyScore,
},
StandardColumnar {
score: StrategyScore,
},
RowOriented {
has_aggregation: bool,
has_group_by: bool,
score: StrategyScore,
},
ExpressionOnly {
score: StrategyScore,
},
}
impl ExecutionStrategy {
pub fn score(&self) -> &StrategyScore {
match self {
ExecutionStrategy::NativeColumnar { score, .. } => score,
ExecutionStrategy::StandardColumnar { score } => score,
ExecutionStrategy::RowOriented { score, .. } => score,
ExecutionStrategy::ExpressionOnly { score } => score,
}
}
pub fn name(&self) -> &'static str {
match self {
ExecutionStrategy::NativeColumnar { .. } => "NativeColumnar",
ExecutionStrategy::StandardColumnar { .. } => "StandardColumnar",
ExecutionStrategy::RowOriented { .. } => "RowOriented",
ExecutionStrategy::ExpressionOnly { .. } => "ExpressionOnly",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StrategyScore {
pub reason: StrategyReason,
pub details: Option<String>,
}
impl StrategyScore {
pub fn new(reason: StrategyReason) -> Self {
Self { reason, details: None }
}
pub fn with_details(mut self, details: impl Into<String>) -> Self {
self.details = Some(details.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)] pub enum StrategyReason {
QueryHint(String),
ColumnarStorageAvailable,
AnalyticalPattern,
SingleTableScan,
NoFromClause,
UnsupportedFeature(String),
Fallback,
}
impl std::fmt::Display for StrategyReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StrategyReason::QueryHint(hint) => write!(f, "Query hint: {}", hint),
StrategyReason::ColumnarStorageAvailable => write!(f, "Columnar storage available"),
StrategyReason::AnalyticalPattern => write!(f, "Analytical pattern detected"),
StrategyReason::SingleTableScan => write!(f, "Single table scan"),
StrategyReason::NoFromClause => write!(f, "No FROM clause"),
StrategyReason::UnsupportedFeature(feature) => {
write!(f, "Unsupported feature: {}", feature)
}
StrategyReason::Fallback => write!(f, "Default fallback"),
}
}
}
pub struct StrategyContext<'a> {
pub stmt: &'a SelectStmt,
pub cte_results: &'a HashMap<String, CteResult>,
pub native_columnar_enabled: bool,
}
impl<'a> StrategyContext<'a> {
pub fn new(
stmt: &'a SelectStmt,
cte_results: &'a HashMap<String, CteResult>,
native_columnar_enabled: bool,
) -> Self {
Self { stmt, cte_results, native_columnar_enabled }
}
}
pub fn choose_execution_strategy(ctx: &StrategyContext<'_>) -> ExecutionStrategy {
let stmt = ctx.stmt;
if stmt.from.is_none() {
return ExecutionStrategy::ExpressionOnly {
score: StrategyScore::new(StrategyReason::NoFromClause),
};
}
if !ctx.cte_results.is_empty() || stmt.set_operation.is_some() {
let reason = if !ctx.cte_results.is_empty() { "CTEs" } else { "set operations" };
return make_row_oriented(stmt, StrategyReason::UnsupportedFeature(reason.to_string()));
}
if let Some(strategy) = try_native_columnar_strategy(ctx) {
return strategy;
}
if let Some(strategy) = try_standard_columnar_strategy(ctx) {
return strategy;
}
make_row_oriented(stmt, StrategyReason::Fallback)
}
fn try_native_columnar_strategy(ctx: &StrategyContext<'_>) -> Option<ExecutionStrategy> {
if !ctx.native_columnar_enabled {
return None;
}
if !is_single_table(ctx.stmt) {
return None;
}
if !has_analytical_pattern(ctx.stmt) {
return None;
}
let table_name = match &ctx.stmt.from {
Some(FromClause::Table { name, .. }) => name.clone(),
_ => return None,
};
Some(ExecutionStrategy::NativeColumnar {
table: table_name,
score: StrategyScore::new(StrategyReason::ColumnarStorageAvailable)
.with_details("Zero-copy SIMD execution from columnar storage"),
})
}
fn try_standard_columnar_strategy(ctx: &StrategyContext<'_>) -> Option<ExecutionStrategy> {
if ctx.stmt.group_by.is_some() {
return None;
}
if !is_single_table(ctx.stmt) {
return None;
}
if !has_analytical_pattern(ctx.stmt) {
return None;
}
Some(ExecutionStrategy::StandardColumnar {
score: StrategyScore::new(StrategyReason::AnalyticalPattern)
.with_details("SIMD execution with row-to-batch conversion"),
})
}
fn make_row_oriented(stmt: &SelectStmt, reason: StrategyReason) -> ExecutionStrategy {
let has_agg = has_aggregate_functions(stmt) || stmt.having.is_some();
let has_gb = has_group_by(stmt);
ExecutionStrategy::RowOriented {
has_aggregation: has_agg,
has_group_by: has_gb,
score: StrategyScore::new(reason),
}
}
#[cfg(test)]
mod tests {
use vibesql_ast::{BinaryOperator, Expression, GroupByClause, SelectItem};
use vibesql_types::SqlValue;
use super::*;
fn make_simple_table_query(table: &str) -> SelectStmt {
SelectStmt {
with_clause: None,
distinct: false,
select_list: vec![SelectItem::Wildcard { alias: None }],
into_table: None,
into_variables: None,
from: Some(FromClause::Table {
index_hint: None,
name: table.to_string(),
alias: None,
column_aliases: None,
quoted: false,
}),
where_clause: None,
group_by: None,
having: None,
window_definitions: None,
order_by: None,
limit: None,
offset: None,
set_operation: None,
values: None,
}
}
fn make_aggregate_query(table: &str) -> SelectStmt {
SelectStmt {
with_clause: None,
distinct: false,
select_list: vec![SelectItem::Expression {
expr: Expression::AggregateFunction {
name: vibesql_ast::FunctionIdentifier::new("SUM"),
distinct: false,
args: vec![Expression::BinaryOp {
left: Box::new(Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::simple("price", false),
)),
op: BinaryOperator::Multiply,
right: Box::new(Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::simple("quantity", false),
)),
}],
order_by: None,
filter: None,
},
alias: None,
source_text: None,
}],
into_table: None,
into_variables: None,
from: Some(FromClause::Table {
index_hint: None,
name: table.to_string(),
alias: None,
column_aliases: None,
quoted: false,
}),
where_clause: None,
group_by: None,
having: None,
window_definitions: None,
order_by: None,
limit: None,
offset: None,
set_operation: None,
values: None,
}
}
#[test]
fn test_expression_only_for_no_from() {
let stmt = SelectStmt {
with_clause: None,
distinct: false,
select_list: vec![SelectItem::Expression {
expr: Expression::Literal(SqlValue::Integer(42)),
alias: None,
source_text: None,
}],
into_table: None,
into_variables: None,
from: None,
where_clause: None,
group_by: None,
having: None,
window_definitions: None,
order_by: None,
limit: None,
offset: None,
set_operation: None,
values: None,
};
let cte_results = HashMap::new();
let ctx = StrategyContext::new(&stmt, &cte_results, true);
let strategy = choose_execution_strategy(&ctx);
assert!(matches!(strategy, ExecutionStrategy::ExpressionOnly { .. }));
}
#[test]
fn test_row_oriented_for_simple_select() {
let stmt = make_simple_table_query("users");
let cte_results = HashMap::new();
let ctx = StrategyContext::new(&stmt, &cte_results, true);
let strategy = choose_execution_strategy(&ctx);
assert!(matches!(strategy, ExecutionStrategy::RowOriented { .. }));
}
#[test]
fn test_native_columnar_for_aggregate_when_enabled() {
let stmt = make_aggregate_query("orders");
let cte_results = HashMap::new();
let ctx = StrategyContext::new(&stmt, &cte_results, true);
let strategy = choose_execution_strategy(&ctx);
assert!(matches!(strategy, ExecutionStrategy::NativeColumnar { .. }));
}
#[test]
fn test_standard_columnar_for_aggregate_when_native_disabled() {
let stmt = make_aggregate_query("orders");
let cte_results = HashMap::new();
let ctx = StrategyContext::new(&stmt, &cte_results, false);
let strategy = choose_execution_strategy(&ctx);
assert!(matches!(strategy, ExecutionStrategy::StandardColumnar { .. }));
}
#[test]
fn test_row_oriented_for_group_by_when_native_disabled() {
let mut stmt = make_aggregate_query("orders");
stmt.group_by = Some(GroupByClause::Simple(vec![Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::simple("region", false),
)]));
let cte_results = HashMap::new();
let ctx = StrategyContext::new(&stmt, &cte_results, false);
let strategy = choose_execution_strategy(&ctx);
assert!(matches!(strategy, ExecutionStrategy::RowOriented { .. }));
}
#[test]
fn test_native_columnar_for_group_by_when_enabled() {
let mut stmt = make_aggregate_query("orders");
stmt.group_by = Some(GroupByClause::Simple(vec![Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::simple("region", false),
)]));
let cte_results = HashMap::new();
let ctx = StrategyContext::new(&stmt, &cte_results, true);
let strategy = choose_execution_strategy(&ctx);
assert!(matches!(strategy, ExecutionStrategy::NativeColumnar { .. }));
}
#[test]
fn test_strategy_name() {
let native = ExecutionStrategy::NativeColumnar {
table: "test".to_string(),
score: StrategyScore::new(StrategyReason::ColumnarStorageAvailable),
};
assert_eq!(native.name(), "NativeColumnar");
let standard = ExecutionStrategy::StandardColumnar {
score: StrategyScore::new(StrategyReason::AnalyticalPattern),
};
assert_eq!(standard.name(), "StandardColumnar");
let row = ExecutionStrategy::RowOriented {
has_aggregation: true,
has_group_by: false,
score: StrategyScore::new(StrategyReason::Fallback),
};
assert_eq!(row.name(), "RowOriented");
let expr = ExecutionStrategy::ExpressionOnly {
score: StrategyScore::new(StrategyReason::NoFromClause),
};
assert_eq!(expr.name(), "ExpressionOnly");
}
}