use vibesql_ast::{Expression, SelectItem, SelectStmt};
use crate::errors::ExecutorError;
pub fn validate_in_subquery_column_counts(
stmt: &SelectStmt,
database: &vibesql_storage::Database,
) -> Result<(), ExecutorError> {
for item in &stmt.select_list {
if let SelectItem::Expression { expr, .. } = item {
validate_expr(expr, database)?;
}
}
if let Some(where_clause) = &stmt.where_clause {
validate_expr(where_clause, database)?;
}
if let Some(having) = &stmt.having {
validate_expr(having, database)?;
}
if let Some(order_by) = stmt.order_by.as_deref() {
for item in order_by {
validate_expr(&item.expr, database)?;
}
}
if let Some(set_op) = &stmt.set_operation {
validate_in_subquery_column_counts(&set_op.right, database)?;
}
Ok(())
}
fn validate_expr(
expr: &Expression,
database: &vibesql_storage::Database,
) -> Result<(), ExecutorError> {
match expr {
Expression::In { expr: lhs, subquery, .. } => {
let expected = match lhs.as_ref() {
Expression::RowValueConstructor(items) => items.len(),
_ => 1,
};
if let Ok(actual) =
crate::evaluator::compute_select_list_column_count(subquery, database, None)
{
if actual != expected {
return Err(ExecutorError::SubqueryColumnCountMismatch { expected, actual });
}
}
validate_expr(lhs, database)
}
Expression::BinaryOp { left, right, .. } => {
validate_expr(left, database)?;
validate_expr(right, database)
}
Expression::UnaryOp { expr: inner, .. }
| Expression::Cast { expr: inner, .. }
| Expression::Collate { expr: inner, .. }
| Expression::IsNull { expr: inner, .. }
| Expression::IsTruthValue { expr: inner, .. }
| Expression::Extract { expr: inner, .. } => validate_expr(inner, database),
Expression::IsDistinctFrom { left, right, .. } => {
validate_expr(left, database)?;
validate_expr(right, database)
}
Expression::Function { args, .. } => {
for arg in args {
validate_expr(arg, database)?;
}
Ok(())
}
Expression::AggregateFunction { args, filter, .. } => {
for arg in args {
validate_expr(arg, database)?;
}
if let Some(f) = filter {
validate_expr(f, database)?;
}
Ok(())
}
Expression::WindowFunction { function, over } => {
let args = match function {
vibesql_ast::WindowFunctionSpec::Aggregate { args, .. }
| vibesql_ast::WindowFunctionSpec::Ranking { args, .. }
| vibesql_ast::WindowFunctionSpec::Value { args, .. } => args,
};
for arg in args {
validate_expr(arg, database)?;
}
if let Some(partition_by) = &over.partition_by {
for p in partition_by {
validate_expr(p, database)?;
}
}
if let Some(order_by) = &over.order_by {
for item in order_by {
validate_expr(&item.expr, database)?;
}
}
Ok(())
}
Expression::Case { operand, when_clauses, else_result } => {
if let Some(op) = operand {
validate_expr(op, database)?;
}
for clause in when_clauses {
for cond in &clause.conditions {
validate_expr(cond, database)?;
}
validate_expr(&clause.result, database)?;
}
if let Some(e) = else_result {
validate_expr(e, database)?;
}
Ok(())
}
Expression::Between { expr: inner, low, high, .. } => {
validate_expr(inner, database)?;
validate_expr(low, database)?;
validate_expr(high, database)
}
Expression::InList { expr: inner, values, .. } => {
validate_expr(inner, database)?;
for v in values {
validate_expr(v, database)?;
}
Ok(())
}
Expression::Like { expr: inner, pattern, .. }
| Expression::Glob { expr: inner, pattern, .. } => {
validate_expr(inner, database)?;
validate_expr(pattern, database)
}
Expression::Conjunction(children)
| Expression::Disjunction(children)
| Expression::RowValueConstructor(children) => {
for c in children {
validate_expr(c, database)?;
}
Ok(())
}
Expression::QuantifiedComparison { expr: inner, .. } => validate_expr(inner, database),
_ => Ok(()),
}
}