use vibesql_ast::{BinaryOperator, Expression, FromClause, JoinType, SelectItem, SelectStmt};
use super::helpers::{
get_outer_table_name, is_self_join, is_simple_single_table_self_join,
rewrite_column_refs_with_alias,
};
pub(super) struct InToJoinResult {
pub from: FromClause,
}
pub(super) fn try_convert_in_to_join(
from: &FromClause,
expr: &Expression,
subquery: &SelectStmt,
negated: bool,
) -> Option<InToJoinResult> {
if subquery.select_list.len() != 1 {
return None;
}
let subquery_column = match &subquery.select_list[0] {
SelectItem::Expression { expr, .. } => expr.clone(),
_ => return None,
};
if crate::select::window::expression_has_window_function(&subquery_column) {
return None;
}
if subquery.limit.is_some() || subquery.offset.is_some() || subquery.set_operation.is_some() {
return None;
}
let is_aggregate_subquery = subquery.group_by.is_some() || subquery.having.is_some();
if is_aggregate_subquery {
return try_convert_aggregate_in_to_join(from, expr, subquery, &subquery_column, negated);
}
let (table_name, table_alias) = match &subquery.from {
Some(FromClause::Table { name, alias, .. }) => (name.clone(), alias.clone()),
_ => return None, };
let needs_alias = is_self_join(from, &table_name, &table_alias);
if needs_alias {
if let Expression::ColumnRef(col_id) = &subquery_column {
if col_id.schema_canonical().is_none() && col_id.table_canonical().is_none() {
let is_simple_self_join =
is_simple_single_table_self_join(from, &table_name, &table_alias);
if !is_simple_self_join {
return None;
}
}
}
}
let (
effective_alias,
outer_expr_qualified,
subquery_column_rewritten,
subquery_where_rewritten,
) = if needs_alias {
let new_alias = format!("__subquery_{}", table_name);
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!(
"[SUBQUERY_TRANSFORM] Self-join detected: table={}, new_alias={}",
table_name, new_alias
);
}
let qualified_expr = if let Some(outer_table) = get_outer_table_name(from) {
rewrite_column_refs_with_alias(expr, &outer_table, &outer_table)
} else {
expr.clone()
};
let old_table_ref = table_alias.as_ref().unwrap_or(&table_name);
let rewritten_col =
rewrite_column_refs_with_alias(&subquery_column, old_table_ref, &new_alias);
let rewritten_where = subquery
.where_clause
.as_ref()
.map(|w| rewrite_column_refs_with_alias(w, old_table_ref, &new_alias));
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!("[SUBQUERY_TRANSFORM] qualified_expr={:?}", qualified_expr);
eprintln!("[SUBQUERY_TRANSFORM] rewritten_col={:?}", rewritten_col);
eprintln!("[SUBQUERY_TRANSFORM] rewritten_where={:?}", rewritten_where);
}
(Some(new_alias), qualified_expr, rewritten_col, rewritten_where)
} else {
let effective_table = table_alias.as_deref().unwrap_or(&table_name);
let qualified_subquery_column =
rewrite_column_refs_with_alias(&subquery_column, effective_table, effective_table);
let qualified_subquery_where = subquery
.where_clause
.as_ref()
.map(|w| rewrite_column_refs_with_alias(w, effective_table, effective_table));
(table_alias.clone(), expr.clone(), qualified_subquery_column, qualified_subquery_where)
};
let join_condition = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(outer_expr_qualified),
right: Box::new(subquery_column_rewritten),
};
let final_condition = if let Some(subquery_where) = subquery_where_rewritten {
Some(Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(join_condition),
right: Box::new(subquery_where),
})
} else {
Some(join_condition)
};
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: final_condition.clone(),
using_columns: None,
natural: false,
alias: None,
};
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!("[SUBQUERY_TRANSFORM] Final condition: {:?}", final_condition);
eprintln!("[SUBQUERY_TRANSFORM] New FROM: {:?}", new_from);
}
Some(InToJoinResult { from: new_from })
}
fn try_convert_aggregate_in_to_join(
from: &FromClause,
outer_expr: &Expression,
subquery: &SelectStmt,
subquery_column: &Expression,
negated: bool,
) -> Option<InToJoinResult> {
let column_name = match subquery_column {
Expression::ColumnRef(col_id) => col_id.column_canonical().to_string(),
_ => return None,
};
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let alias = format!("__in_agg_{}", COUNTER.fetch_add(1, Ordering::Relaxed));
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!(
"[SUBQUERY_TRANSFORM] Converting aggregate IN subquery to derived table semi-join"
);
eprintln!("[SUBQUERY_TRANSFORM] Derived table alias: {}", alias);
eprintln!("[SUBQUERY_TRANSFORM] Column for join: {}", column_name);
}
let right_from = FromClause::Subquery {
query: Box::new(subquery.clone()),
alias: alias.clone(),
column_aliases: None,
};
let join_condition = Expression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(outer_expr.clone()),
right: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
&alias,
false,
&column_name,
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(join_condition.clone()),
using_columns: None,
natural: false,
alias: None,
};
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!("[SUBQUERY_TRANSFORM] Final condition: {:?}", join_condition);
eprintln!("[SUBQUERY_TRANSFORM] New FROM: {:?}", new_from);
}
Some(InToJoinResult { from: new_from })
}