use vibesql_ast::{BinaryOperator, Expression, FromClause, JoinType, SelectItem, SelectStmt};
use super::helpers::{is_self_join, rewrite_column_refs_with_alias};
pub(super) fn try_convert_exists_to_join(
from: &FromClause,
subquery: &SelectStmt,
negated: bool,
) -> Option<(FromClause, Option<Expression>)> {
let where_clause = subquery.where_clause.as_ref()?;
if !crate::optimizer::subquery_rewrite::correlation::is_correlated(subquery) {
return None;
}
if subquery.group_by.is_some() || subquery.having.is_some() || subquery.set_operation.is_some()
{
return None;
}
match &subquery.from {
Some(FromClause::Table { name, alias, .. }) => {
try_convert_simple_exists_to_join(
from,
subquery,
negated,
name.clone(),
alias.clone(),
where_clause,
)
}
Some(from_clause @ FromClause::Join { .. }) => {
if crate::optimizer::subquery_rewrite::correlation::from_clause_has_external_refs(
from_clause,
subquery,
) {
return None;
}
try_convert_complex_exists_to_join(from, subquery, negated, from_clause, where_clause)
}
_ => None, }
}
fn try_convert_simple_exists_to_join(
from: &FromClause,
_subquery: &SelectStmt,
negated: bool,
table_name: String,
table_alias: Option<String>,
where_clause: &Expression,
) -> Option<(FromClause, Option<Expression>)> {
let needs_alias = is_self_join(from, &table_name, &table_alias);
let (effective_alias, rewritten_where) = if needs_alias {
let new_alias = format!("__subquery_{}", table_alias.as_ref().unwrap_or(&table_name));
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!("[SUBQUERY_TRANSFORM] EXISTS self-join detected: table={}, alias={:?}, new_alias={}",
table_name, table_alias, new_alias);
}
let old_table_ref = table_alias.as_ref().unwrap_or(&table_name);
let rewritten = rewrite_column_refs_with_alias(where_clause, old_table_ref, &new_alias);
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!("[SUBQUERY_TRANSFORM] EXISTS rewritten_where={:?}", rewritten);
}
(Some(new_alias), rewritten)
} else {
(table_alias.clone(), where_clause.clone())
};
let right_from = FromClause::Table {
index_hint: None,
name: table_name,
alias: effective_alias,
column_aliases: None,
quoted: false, };
let join_type = if negated { JoinType::Anti } else { JoinType::Semi };
let new_from = FromClause::Join {
left: Box::new(from.clone()),
right: Box::new(right_from),
join_type,
condition: Some(rewritten_where),
using_columns: None,
natural: false,
alias: None,
};
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!("[SUBQUERY_TRANSFORM] EXISTS new_from={:?}", new_from);
}
Some((new_from, None))
}
fn try_convert_complex_exists_to_join(
from: &FromClause,
subquery: &SelectStmt,
negated: bool,
subquery_from: &FromClause,
where_clause: &Expression,
) -> Option<(FromClause, Option<Expression>)> {
let (correlation_preds, internal_preds) =
split_predicates_by_correlation(where_clause, subquery);
if correlation_preds.is_empty() {
return None;
}
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!(
"[SUBQUERY_TRANSFORM] Complex EXISTS: {} correlation preds, {} internal preds",
correlation_preds.len(),
internal_preds.len()
);
}
let (join_condition, subquery_columns) =
build_join_condition_and_select_columns(&correlation_preds, subquery)?;
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!("[SUBQUERY_TRANSFORM] Complex EXISTS: subquery_columns={:?}", subquery_columns);
}
let internal_where = combine_predicates(internal_preds);
static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let alias =
format!("__exists_subq_{}", COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed));
let derived_select = SelectStmt {
with_clause: None,
distinct: true, select_list: subquery_columns
.iter()
.map(|col| SelectItem::Expression {
expr: if let Some(ref t) = col.table {
Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
t,
false,
&col.column,
false,
))
} else {
Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(&col.column, false))
},
alias: Some(col.column.clone()),
source_text: None,
})
.collect(),
into_table: None,
into_variables: None,
from: Some(subquery_from.clone()),
where_clause: internal_where,
group_by: None,
having: None,
window_definitions: None,
order_by: None,
limit: None,
offset: None,
set_operation: None,
values: None,
};
let right_from = FromClause::Subquery {
query: Box::new(derived_select),
alias: alias.clone(),
column_aliases: None,
};
let rewritten_join_condition =
rewrite_join_condition_for_derived_table(&join_condition, &subquery_columns, &alias);
let join_type = if negated { JoinType::Anti } else { JoinType::Semi };
let new_from = FromClause::Join {
left: Box::new(from.clone()),
right: Box::new(right_from),
join_type,
condition: Some(rewritten_join_condition),
using_columns: None,
natural: false,
alias: None,
};
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!("[SUBQUERY_TRANSFORM] Complex EXISTS transformed successfully");
}
Some((new_from, None))
}
#[derive(Debug, Clone)]
struct ColumnRef {
table: Option<String>,
column: String,
}
fn split_predicates_by_correlation(
expr: &Expression,
subquery: &SelectStmt,
) -> (Vec<Expression>, Vec<Expression>) {
let mut correlation = Vec::new();
let mut internal = Vec::new();
let predicates = flatten_and_predicates(expr);
for pred in predicates {
if crate::optimizer::subquery_rewrite::correlation::has_external_column_refs(
&pred, subquery,
) {
correlation.push(pred);
} else {
internal.push(pred);
}
}
(correlation, internal)
}
fn flatten_and_predicates(expr: &Expression) -> Vec<Expression> {
match expr {
Expression::BinaryOp { op: BinaryOperator::And, left, right } => {
let mut result = flatten_and_predicates(left);
result.extend(flatten_and_predicates(right));
result
}
Expression::Conjunction(children) => {
children.iter().flat_map(flatten_and_predicates).collect()
}
_ => vec![expr.clone()],
}
}
fn combine_predicates(predicates: Vec<Expression>) -> Option<Expression> {
if predicates.is_empty() {
return None;
}
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),
};
}
Some(result)
}
fn build_join_condition_and_select_columns(
correlation_preds: &[Expression],
subquery: &SelectStmt,
) -> Option<(Expression, Vec<ColumnRef>)> {
let mut subquery_columns = Vec::new();
for pred in correlation_preds {
if let Some(col) = extract_subquery_column_from_correlation(pred, subquery) {
if !subquery_columns.iter().any(|c: &ColumnRef| c.column == col.column) {
subquery_columns.push(col);
}
}
}
if subquery_columns.is_empty() {
return None;
}
let join_condition = combine_predicates(correlation_preds.to_vec())?;
Some((join_condition, subquery_columns))
}
fn extract_subquery_column_from_correlation(
pred: &Expression,
subquery: &SelectStmt,
) -> Option<ColumnRef> {
match pred {
Expression::BinaryOp { op: BinaryOperator::Equal, left, right } => {
let left_external =
crate::optimizer::subquery_rewrite::correlation::has_external_column_refs(
left, subquery,
);
let right_external =
crate::optimizer::subquery_rewrite::correlation::has_external_column_refs(
right, subquery,
);
if left_external && !right_external {
extract_column_ref(right)
} else if !left_external && right_external {
extract_column_ref(left)
} else {
None
}
}
_ => None,
}
}
fn extract_column_ref(expr: &Expression) -> Option<ColumnRef> {
match expr {
Expression::ColumnRef(col_id) => Some(ColumnRef {
table: col_id.table_canonical().map(|t| t.to_string()),
column: col_id.column_canonical().to_string(),
}),
_ => None,
}
}
fn rewrite_join_condition_for_derived_table(
condition: &Expression,
subquery_columns: &[ColumnRef],
alias: &str,
) -> Expression {
match condition {
Expression::ColumnRef(col_id) => {
let table = col_id.table_canonical();
let column = col_id.column_canonical();
if subquery_columns
.iter()
.any(|c| c.column == column && (c.table.is_none() || c.table.as_deref() == table))
{
Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
alias, false, column, false,
))
} else {
condition.clone()
}
}
Expression::BinaryOp { op, left, right } => Expression::BinaryOp {
op: op.clone(),
left: Box::new(rewrite_join_condition_for_derived_table(left, subquery_columns, alias)),
right: Box::new(rewrite_join_condition_for_derived_table(
right,
subquery_columns,
alias,
)),
},
Expression::Conjunction(children) => Expression::Conjunction(
children
.iter()
.map(|c| rewrite_join_condition_for_derived_table(c, subquery_columns, alias))
.collect(),
),
_ => condition.clone(),
}
}