use std::collections::HashMap;
use crate::{errors::ExecutorError, select::cte::CteResult};
pub(super) fn build_merged_outer_schema<'a>(
current_schema: &'a crate::schema::CombinedSchema,
outer_schema: Option<&'a crate::schema::CombinedSchema>,
) -> std::borrow::Cow<'a, crate::schema::CombinedSchema> {
if let Some(outer) = outer_schema {
let mut new_schema = current_schema.clone();
new_schema.outer_schema = Some(Box::new(outer.clone()));
std::borrow::Cow::Owned(new_schema)
} else {
std::borrow::Cow::Borrowed(current_schema)
}
}
pub(super) fn build_merged_outer_row<'a>(
current_row: &'a vibesql_storage::Row,
outer_row: Option<&'a vibesql_storage::Row>,
) -> std::borrow::Cow<'a, vibesql_storage::Row> {
if let Some(outer) = outer_row {
let mut merged_values = current_row.values.clone();
merged_values.extend(outer.values.iter().cloned());
std::borrow::Cow::Owned(vibesql_storage::Row::new(merged_values))
} else {
std::borrow::Cow::Borrowed(current_row)
}
}
pub fn compute_select_list_column_count(
stmt: &vibesql_ast::SelectStmt,
database: &vibesql_storage::Database,
cte_results: Option<&HashMap<String, CteResult>>,
) -> Result<usize, ExecutorError> {
let left_count =
compute_single_select_column_count(&stmt.select_list, &stmt.from, database, cte_results)?;
if let Some(set_op) = &stmt.set_operation {
validate_set_operation_column_counts(left_count, set_op, database, cte_results)?;
}
Ok(left_count)
}
fn validate_set_operation_column_counts(
left_count: usize,
set_op: &vibesql_ast::SetOperation,
database: &vibesql_storage::Database,
cte_results: Option<&HashMap<String, CteResult>>,
) -> Result<(), ExecutorError> {
let right_stmt = &set_op.right;
let right_count = compute_single_select_column_count(
&right_stmt.select_list,
&right_stmt.from,
database,
cte_results,
)?;
if left_count != right_count {
let operator = match (&set_op.op, set_op.all) {
(vibesql_ast::SetOperator::Union, true) => "UNION ALL",
(vibesql_ast::SetOperator::Union, false) => "UNION",
(vibesql_ast::SetOperator::Intersect, true) => "INTERSECT ALL",
(vibesql_ast::SetOperator::Intersect, false) => "INTERSECT",
(vibesql_ast::SetOperator::Except, true) => "EXCEPT ALL",
(vibesql_ast::SetOperator::Except, false) => "EXCEPT",
};
return Err(ExecutorError::SetOperationColumnMismatch { operator: operator.to_string() });
}
if let Some(next_set_op) = &right_stmt.set_operation {
validate_set_operation_column_counts(right_count, next_set_op, database, cte_results)?;
}
Ok(())
}
fn compute_single_select_column_count(
select_list: &[vibesql_ast::SelectItem],
from: &Option<vibesql_ast::FromClause>,
database: &vibesql_storage::Database,
cte_results: Option<&HashMap<String, CteResult>>,
) -> Result<usize, ExecutorError> {
let mut count = 0;
for item in select_list {
match item {
vibesql_ast::SelectItem::Wildcard { .. } => {
if let Some(from_clause) = from {
count += count_columns_in_from_clause(from_clause, database, cte_results)?;
} else {
return Err(ExecutorError::UnsupportedFeature(
"SELECT * requires FROM clause".to_string(),
));
}
}
vibesql_ast::SelectItem::QualifiedWildcard { qualifier, .. } => {
if let Some(cte_ctx) = cte_results {
if let Some((schema, _)) = cte_ctx.get(qualifier).or_else(|| {
cte_ctx
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(qualifier))
.map(|(_, v)| v)
}) {
count += schema.columns.len();
continue;
}
}
let tbl = database
.get_table(qualifier)
.ok_or_else(|| ExecutorError::TableNotFound(qualifier.clone()))?;
count += tbl.schema.columns.len();
}
vibesql_ast::SelectItem::Expression { .. } => {
count += 1;
}
}
}
Ok(count)
}
pub fn get_subquery_first_column_affinity(
stmt: &vibesql_ast::SelectStmt,
database: &vibesql_storage::Database,
) -> Option<vibesql_types::TypeAffinity> {
let first_item = stmt.select_list.first()?;
let expr = match first_item {
vibesql_ast::SelectItem::Expression { expr, .. } => expr,
_ => return None, };
get_expression_affinity_from_from_clause(expr, &stmt.from, database)
}
fn get_expression_affinity_from_from_clause(
expr: &vibesql_ast::Expression,
from: &Option<vibesql_ast::FromClause>,
database: &vibesql_storage::Database,
) -> Option<vibesql_types::TypeAffinity> {
match expr {
vibesql_ast::Expression::ColumnRef(col_id) => {
let table_name = col_id.table_canonical();
let column_name = col_id.column_canonical();
if let Some(table) = table_name {
if let Some(tbl) = database.get_table(&table) {
if let Some(col_idx) = tbl.schema.get_column_index(&column_name) {
return Some(tbl.schema.columns[col_idx].data_type.sqlite_affinity());
}
}
return None;
}
if let Some(from_clause) = from {
return find_column_affinity_in_from_clause(&column_name, from_clause, database);
}
None
}
vibesql_ast::Expression::Collate { expr: inner, .. } => {
get_expression_affinity_from_from_clause(inner, from, database)
}
vibesql_ast::Expression::Cast { data_type, .. } => Some(data_type.sqlite_affinity()),
_ => None,
}
}
fn find_column_affinity_in_from_clause(
column_name: &str,
from: &vibesql_ast::FromClause,
database: &vibesql_storage::Database,
) -> Option<vibesql_types::TypeAffinity> {
match from {
vibesql_ast::FromClause::Table { name, .. } => {
let table = database.get_table(name)?;
let col_idx = table.schema.get_column_index(column_name)?;
Some(table.schema.columns[col_idx].data_type.sqlite_affinity())
}
vibesql_ast::FromClause::Join { left, right, .. } => {
find_column_affinity_in_from_clause(column_name, left, database)
.or_else(|| find_column_affinity_in_from_clause(column_name, right, database))
}
vibesql_ast::FromClause::Subquery { .. } | vibesql_ast::FromClause::Values { .. } => {
None
}
}
}
fn count_columns_in_from_clause(
from: &vibesql_ast::FromClause,
database: &vibesql_storage::Database,
cte_results: Option<&HashMap<String, CteResult>>,
) -> Result<usize, ExecutorError> {
match from {
vibesql_ast::FromClause::Table { name, .. } => {
if let Some(cte_ctx) = cte_results {
if let Some((schema, _)) = cte_ctx.get(name).or_else(|| {
cte_ctx.iter().find(|(k, _)| k.eq_ignore_ascii_case(name)).map(|(_, v)| v)
}) {
return Ok(schema.columns.len());
}
}
let table = database
.get_table(name)
.ok_or_else(|| ExecutorError::TableNotFound(name.clone()))?;
Ok(table.schema.columns.len())
}
vibesql_ast::FromClause::Join { left, right, .. } => {
let left_count = count_columns_in_from_clause(left, database, cte_results)?;
let right_count = count_columns_in_from_clause(right, database, cte_results)?;
Ok(left_count + right_count)
}
vibesql_ast::FromClause::Subquery { query, column_aliases, .. } => {
if let Some(aliases) = column_aliases {
Ok(aliases.len())
} else {
compute_select_list_column_count(query, database, cte_results)
}
}
vibesql_ast::FromClause::Values { rows, column_aliases, .. } => {
if let Some(aliases) = column_aliases {
Ok(aliases.len())
} else if let Some(first_row) = rows.first() {
Ok(first_row.len())
} else {
Ok(0) }
}
}
}