use crate::{errors::ExecutorError, schema::CombinedSchema, select::SelectResult};
fn derive_column_name_from_expr(expr: &vibesql_ast::Expression) -> String {
match expr {
vibesql_ast::Expression::ColumnRef(col_id) => col_id.column_canonical().to_string(),
vibesql_ast::Expression::Function { name, args, character_unit: _ } => {
let args_str = if args.is_empty() {
"*".to_string()
} else {
args.iter().map(derive_column_name_from_expr).collect::<Vec<_>>().join(", ")
};
format!("{}({})", name, args_str)
}
vibesql_ast::Expression::AggregateFunction { name, distinct, args, .. } => {
let distinct_str = if *distinct { "DISTINCT " } else { "" };
let args_str = if args.is_empty() {
"*".to_string()
} else {
args.iter().map(derive_column_name_from_expr).collect::<Vec<_>>().join(", ")
};
format!("{}({}{})", name, distinct_str, args_str)
}
vibesql_ast::Expression::BinaryOp { left, op, right } => {
format!(
"({} {} {})",
derive_column_name_from_expr(left),
match op {
vibesql_ast::BinaryOperator::Plus => "+",
vibesql_ast::BinaryOperator::Minus => "-",
vibesql_ast::BinaryOperator::Multiply => "*",
vibesql_ast::BinaryOperator::Divide => "/",
vibesql_ast::BinaryOperator::Equal => "=",
vibesql_ast::BinaryOperator::NotEqual => "!=",
vibesql_ast::BinaryOperator::LessThan => "<",
vibesql_ast::BinaryOperator::LessThanOrEqual => "<=",
vibesql_ast::BinaryOperator::GreaterThan => ">",
vibesql_ast::BinaryOperator::GreaterThanOrEqual => ">=",
vibesql_ast::BinaryOperator::And => "AND",
vibesql_ast::BinaryOperator::Or => "OR",
vibesql_ast::BinaryOperator::Concat => "||",
_ => "?",
},
derive_column_name_from_expr(right)
)
}
vibesql_ast::Expression::Literal(val) => match val {
vibesql_types::SqlValue::Integer(n) => n.to_string(),
vibesql_types::SqlValue::Smallint(n) => n.to_string(),
vibesql_types::SqlValue::Bigint(n) => n.to_string(),
vibesql_types::SqlValue::Unsigned(n) => n.to_string(),
vibesql_types::SqlValue::Double(f) => f.to_string(),
vibesql_types::SqlValue::Float(f) => f.to_string(),
vibesql_types::SqlValue::Real(f) => f.to_string(),
vibesql_types::SqlValue::Numeric(f) => f.to_string(),
vibesql_types::SqlValue::Varchar(s) | vibesql_types::SqlValue::Character(s) => {
format!("'{}'", s)
}
vibesql_types::SqlValue::Boolean(b) => b.to_string(),
vibesql_types::SqlValue::Date(d) => format!("'{}'", d),
vibesql_types::SqlValue::Time(t) => format!("'{}'", t),
vibesql_types::SqlValue::Timestamp(ts) => format!("'{}'", ts),
vibesql_types::SqlValue::Interval(i) => format!("INTERVAL '{}'", i),
vibesql_types::SqlValue::Vector(v) => {
let formatted: Vec<String> = v.iter().map(|f| f.to_string()).collect();
format!("[{}]", formatted.join(", "))
}
vibesql_types::SqlValue::Blob(b) => {
let hex: String = b.iter().map(|byte| format!("{:02X}", byte)).collect();
format!("x'{}'", hex)
}
vibesql_types::SqlValue::Null => "NULL".to_string(),
},
vibesql_ast::Expression::Wildcard => "*".to_string(),
vibesql_ast::Expression::Collate { expr, .. } => derive_column_name_from_expr(expr),
_ => "?column?".to_string(),
}
}
pub(crate) fn execute_derived_table<F>(
query: &vibesql_ast::SelectStmt,
alias: &str,
column_aliases: Option<&Vec<String>>,
execute_subquery: F,
) -> Result<super::FromResult, ExecutorError>
where
F: Fn(&vibesql_ast::SelectStmt) -> Result<SelectResult, ExecutorError>,
{
let subquery_result = execute_subquery(query)?;
let rows = subquery_result.rows;
let subquery_columns = subquery_result.columns;
let mut column_names = Vec::new();
let mut column_types = Vec::new();
let mut col_index = 0;
for item in &query.select_list {
match item {
vibesql_ast::SelectItem::Wildcard { .. }
| vibesql_ast::SelectItem::QualifiedWildcard { .. } => {
if let Some(first_row) = rows.first() {
for (j, value) in first_row.values.iter().enumerate() {
let col_name = subquery_columns
.get(col_index + j)
.cloned()
.unwrap_or_else(|| format!("column{}", col_index + j + 1));
column_names.push(col_name);
column_types.push(value.get_type());
}
col_index += first_row.values.len();
} else {
for col_name in &subquery_columns {
column_names.push(col_name.to_string());
column_types.push(vibesql_types::DataType::Null);
}
}
}
vibesql_ast::SelectItem::Expression { expr, alias: col_alias, .. } => {
let col_name = if let Some(a) = col_alias {
a.clone()
} else {
derive_column_name_from_expr(expr)
};
column_names.push(col_name);
let col_type = if let Some(first_row) = rows.first() {
if col_index < first_row.values.len() {
first_row.values[col_index].get_type()
} else {
vibesql_types::DataType::Null
}
} else {
vibesql_types::DataType::Null
};
column_types.push(col_type);
col_index += 1;
}
}
}
if let Some(aliases) = column_aliases {
if aliases.len() != column_names.len() {
return Err(ExecutorError::ColumnCountMismatch {
expected: column_names.len(),
provided: aliases.len(),
});
}
column_names = aliases.clone();
}
let schema = CombinedSchema::from_derived_table(alias.to_string(), column_names, column_types);
Ok(super::FromResult::from_rows(schema, rows))
}