use std::collections::HashSet;
use crate::schema::CombinedSchema;
pub(crate) fn flatten_conjuncts(expr: &vibesql_ast::Expression) -> Vec<vibesql_ast::Expression> {
match expr {
vibesql_ast::Expression::BinaryOp { left, op: vibesql_ast::BinaryOperator::And, right } => {
let mut conjuncts = flatten_conjuncts(left);
conjuncts.extend(flatten_conjuncts(right));
conjuncts
}
other => vec![other.clone()],
}
}
pub(crate) fn extract_referenced_tables_branch(
expr: &vibesql_ast::Expression,
schema: &CombinedSchema,
) -> Option<HashSet<String>> {
let mut tables = HashSet::new();
let success = extract_tables_recursive_branch(expr, schema, &mut tables);
if success {
Some(tables)
} else {
None
}
}
fn extract_tables_recursive_branch(
expr: &vibesql_ast::Expression,
schema: &CombinedSchema,
tables: &mut HashSet<String>,
) -> bool {
match expr {
vibesql_ast::Expression::ColumnRef(col_id) if col_id.schema_canonical().is_none() => {
match col_id.table_canonical() {
Some(table_name) => {
let normalized = table_name.to_lowercase();
if schema.contains_table(&normalized) {
tables.insert(normalized);
true
} else {
false
}
}
None => {
let column_lower = col_id.column_canonical().to_lowercase();
let mut found = false;
for (table_name, (_start_idx, table_schema)) in &schema.table_schemas {
if table_schema
.columns
.iter()
.any(|col| col.name.to_lowercase() == column_lower)
{
tables.insert(table_name.to_string());
found = true;
}
}
found }
}
}
vibesql_ast::Expression::BinaryOp { left, op: _, right } => {
extract_tables_recursive_branch(left, schema, tables)
&& extract_tables_recursive_branch(right, schema, tables)
}
vibesql_ast::Expression::UnaryOp { expr: inner, .. } => {
extract_tables_recursive_branch(inner, schema, tables)
}
vibesql_ast::Expression::Function { args, .. } => {
args.iter().all(|arg| extract_tables_recursive_branch(arg, schema, tables))
}
vibesql_ast::Expression::Between { expr, low, high, .. } => {
extract_tables_recursive_branch(expr, schema, tables)
&& extract_tables_recursive_branch(low, schema, tables)
&& extract_tables_recursive_branch(high, schema, tables)
}
vibesql_ast::Expression::InList { expr, values, .. } => {
extract_tables_recursive_branch(expr, schema, tables)
&& values.iter().all(|val| extract_tables_recursive_branch(val, schema, tables))
}
vibesql_ast::Expression::Case { operand, when_clauses, else_result } => {
let op_ok = operand
.as_ref()
.is_none_or(|op| extract_tables_recursive_branch(op, schema, tables));
let when_ok = when_clauses.iter().all(|when_clause| {
when_clause
.conditions
.iter()
.all(|condition| extract_tables_recursive_branch(condition, schema, tables))
&& extract_tables_recursive_branch(&when_clause.result, schema, tables)
});
let else_ok = else_result
.as_ref()
.is_none_or(|else_res| extract_tables_recursive_branch(else_res, schema, tables));
op_ok && when_ok && else_ok
}
vibesql_ast::Expression::In { expr, subquery, .. } => {
if !crate::correlation::is_correlated(subquery, schema) {
extract_tables_recursive_branch(expr, schema, tables)
} else {
false
}
}
vibesql_ast::Expression::ScalarSubquery(_) => {
false
}
vibesql_ast::Expression::Exists { .. } => {
false
}
vibesql_ast::Expression::QuantifiedComparison { .. } => {
false
}
vibesql_ast::Expression::Like { expr, pattern, .. } => {
extract_tables_recursive_branch(expr, schema, tables)
&& extract_tables_recursive_branch(pattern, schema, tables)
}
vibesql_ast::Expression::IsNull { expr, .. } => {
extract_tables_recursive_branch(expr, schema, tables)
}
_ => {
true
}
}
}
pub(crate) fn try_extract_equijoin_branch(
expr: &vibesql_ast::Expression,
schema: &CombinedSchema,
) -> Option<(String, String, String, String)> {
if let vibesql_ast::Expression::BinaryOp {
left,
op: vibesql_ast::BinaryOperator::Equal,
right,
} = expr
{
let (left_table, left_col) = extract_column_reference_branch(left, schema)?;
let (right_table, right_col) = extract_column_reference_branch(right, schema)?;
if left_table != right_table {
return Some((left_table, left_col, right_table, right_col));
}
}
None
}
fn extract_column_reference_branch(
expr: &vibesql_ast::Expression,
schema: &CombinedSchema,
) -> Option<(String, String)> {
match expr {
vibesql_ast::Expression::ColumnRef(col_id) => {
if let Some(table_name) = col_id.table_canonical() {
let normalized_table = table_name.to_lowercase();
if schema.contains_table(&normalized_table) {
return Some((normalized_table, col_id.column_canonical().to_lowercase()));
}
}
None
}
_ => None,
}
}