use std::collections::{HashMap, HashSet};
use vibesql_ast::{BinaryOperator, Expression};
use super::select_analysis::{
collect_unqualified_columns_from_expr, extract_tables_from_expr, has_unqualified_column_ref,
};
pub(super) fn extract_local_predicates(
expr: &Expression,
table_names: &HashSet<String>,
table_prefixes: &HashMap<String, String>,
) -> HashMap<String, Expression> {
let mut predicates: HashMap<String, Vec<Expression>> = HashMap::new();
collect_local_predicates(expr, &mut predicates, table_names, table_prefixes);
predicates.into_iter().map(|(table, preds)| (table, combine_predicates(preds))).collect()
}
fn collect_local_predicates(
expr: &Expression,
predicates: &mut HashMap<String, Vec<Expression>>,
_table_names: &HashSet<String>,
table_prefixes: &HashMap<String, String>,
) {
match expr {
Expression::BinaryOp { op: BinaryOperator::And, left, right } => {
collect_local_predicates(left, predicates, _table_names, table_prefixes);
collect_local_predicates(right, predicates, _table_names, table_prefixes);
}
_ => {
let mut qualified_refs = HashSet::new();
extract_tables_from_expr(expr, &mut qualified_refs);
let mut unqualified_cols = HashSet::new();
collect_unqualified_columns_from_expr(expr, &mut unqualified_cols);
let mut all_refs = qualified_refs.clone();
for col in &unqualified_cols {
let col_lower = col.to_lowercase();
for (table, prefix) in table_prefixes {
if col_lower.starts_with(prefix) {
all_refs.insert(table.clone());
}
}
}
if is_potential_join_condition(expr) {
return;
}
if all_refs.len() == 1 {
let table = all_refs.into_iter().next().unwrap();
predicates.entry(table).or_default().push(expr.clone());
}
}
}
}
fn is_potential_join_condition(expr: &Expression) -> bool {
if let Expression::BinaryOp { op: BinaryOperator::Equal, left, right } = expr {
let mut left_tables = HashSet::new();
let mut right_tables = HashSet::new();
extract_tables_from_expr(left, &mut left_tables);
extract_tables_from_expr(right, &mut right_tables);
let left_unqualified = has_unqualified_column_ref(left);
let right_unqualified = has_unqualified_column_ref(right);
if !left_tables.is_empty() && right_unqualified && right_tables.is_empty() {
return true;
}
if !right_tables.is_empty() && left_unqualified && left_tables.is_empty() {
return true;
}
}
false
}
pub(super) fn combine_predicates(predicates: Vec<Expression>) -> Expression {
if predicates.is_empty() {
return Expression::Literal(vibesql_types::SqlValue::Boolean(true));
}
let mut iter = predicates.into_iter();
let mut result = iter.next().unwrap();
for pred in iter {
result = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(result),
right: Box::new(pred),
};
}
result
}
pub(super) fn flatten_and_chain(expr: &Expression) -> Vec<Expression> {
match expr {
Expression::BinaryOp { op: BinaryOperator::And, left, right } => {
let mut result = flatten_and_chain(left);
result.extend(flatten_and_chain(right));
result
}
_ => vec![expr.clone()],
}
}