#![allow(clippy::clone_on_copy)]
mod exists;
mod helpers;
mod in_clause;
mod scalar_comparison;
use exists::try_convert_exists_to_join;
use in_clause::try_convert_in_to_join;
use scalar_comparison::try_convert_scalar_comparison_to_join;
use vibesql_ast::{BinaryOperator, Expression, FromClause, SelectStmt};
pub fn transform_subqueries_to_joins(stmt: &SelectStmt) -> SelectStmt {
let mut result = stmt.clone();
if let Some(where_clause) = &result.where_clause {
result.where_clause = Some(transform_subqueries_in_expression(where_clause));
}
if result.from.is_none() || result.where_clause.is_none() {
return result;
}
let max_iterations = 10; for iteration in 0..max_iterations {
let mut made_progress = false;
if let Some(where_clause) = &result.where_clause {
if let Some((new_from, new_where)) =
try_extract_subqueries_to_joins(result.from.as_ref().unwrap(), where_clause)
{
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() {
eprintln!(
"[SUBQUERY_TRANSFORM] Iteration {}: Converted subquery to join",
iteration + 1
);
}
result.from = Some(new_from);
result.where_clause = new_where;
made_progress = true;
}
}
if !made_progress {
if std::env::var("SUBQUERY_TRANSFORM_VERBOSE").is_ok() && iteration > 0 {
eprintln!("[SUBQUERY_TRANSFORM] Completed after {} iterations", iteration);
}
break;
}
}
result
}
fn transform_scalar_subqueries_in_stmt(stmt: &SelectStmt) -> SelectStmt {
let mut result = stmt.clone();
if result.from.is_none() || result.where_clause.is_none() {
return result;
}
let max_iterations = 5;
for _iteration in 0..max_iterations {
let mut made_progress = false;
if let Some(where_clause) = &result.where_clause {
if let Some((new_from, new_where)) =
try_extract_scalar_comparisons_only(result.from.as_ref().unwrap(), where_clause)
{
result.from = Some(new_from);
result.where_clause = new_where;
made_progress = true;
}
}
if !made_progress {
break;
}
}
result
}
fn try_extract_scalar_comparisons_only(
from: &FromClause,
where_clause: &Expression,
) -> Option<(FromClause, Option<Expression>)> {
match where_clause {
Expression::BinaryOp { op, left, right }
if matches!(right.as_ref(), Expression::ScalarSubquery(_)) =>
{
if let Some(result) = try_convert_scalar_comparison_to_join(from, op, left, right) {
return Some((result.from, Some(result.replacement_expr)));
}
None
}
Expression::BinaryOp { op: BinaryOperator::And, left, right } => {
if let Expression::BinaryOp { op, left: inner_left, right: inner_right } = left.as_ref()
{
if matches!(inner_right.as_ref(), Expression::ScalarSubquery(_)) {
if let Some(result) =
try_convert_scalar_comparison_to_join(from, op, inner_left, inner_right)
{
let new_where = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(result.replacement_expr),
right: right.clone(),
};
return Some((result.from, Some(new_where)));
}
}
}
if let Expression::BinaryOp { op, left: inner_left, right: inner_right } =
right.as_ref()
{
if matches!(inner_right.as_ref(), Expression::ScalarSubquery(_)) {
if let Some(result) =
try_convert_scalar_comparison_to_join(from, op, inner_left, inner_right)
{
let new_where = Expression::BinaryOp {
op: BinaryOperator::And,
left: left.clone(),
right: Box::new(result.replacement_expr),
};
return Some((result.from, Some(new_where)));
}
}
}
None
}
Expression::Conjunction(children) => {
for (i, child) in children.iter().enumerate() {
if let Expression::BinaryOp { op, left, right } = child {
if matches!(right.as_ref(), Expression::ScalarSubquery(_)) {
if let Some(result) =
try_convert_scalar_comparison_to_join(from, op, left, right)
{
let mut remaining: Vec<_> = children
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, c)| c.clone())
.collect();
remaining.push(result.replacement_expr);
let new_where = match remaining.len() {
0 => None,
1 => Some(remaining.into_iter().next().unwrap()),
_ => Some(Expression::Conjunction(remaining)),
};
return Some((result.from, new_where));
}
}
}
}
None
}
_ => None,
}
}
fn transform_subqueries_in_expression(expr: &Expression) -> Expression {
match expr {
Expression::In { expr: inner_expr, subquery, negated } => {
let transformed_inner = transform_subqueries_in_expression(inner_expr);
let transformed_subquery = transform_scalar_subqueries_in_stmt(subquery);
Expression::In {
expr: Box::new(transformed_inner),
subquery: Box::new(transformed_subquery),
negated: *negated,
}
}
Expression::Exists { subquery, negated } => {
let transformed_subquery = transform_scalar_subqueries_in_stmt(subquery);
Expression::Exists { subquery: Box::new(transformed_subquery), negated: *negated }
}
Expression::ScalarSubquery(subquery) => {
let transformed_subquery = transform_subqueries_to_joins(subquery);
Expression::ScalarSubquery(Box::new(transformed_subquery))
}
Expression::BinaryOp { op, left, right } => Expression::BinaryOp {
op: op.clone(),
left: Box::new(transform_subqueries_in_expression(left)),
right: Box::new(transform_subqueries_in_expression(right)),
},
Expression::Conjunction(children) => Expression::Conjunction(
children.iter().map(transform_subqueries_in_expression).collect(),
),
Expression::Disjunction(children) => Expression::Disjunction(
children.iter().map(transform_subqueries_in_expression).collect(),
),
Expression::UnaryOp { op, expr: inner } => Expression::UnaryOp {
op: op.clone(),
expr: Box::new(transform_subqueries_in_expression(inner)),
},
Expression::IsNull { expr: inner, negated } => Expression::IsNull {
expr: Box::new(transform_subqueries_in_expression(inner)),
negated: *negated,
},
Expression::Between { expr: inner, low, high, negated, symmetric } => Expression::Between {
expr: Box::new(transform_subqueries_in_expression(inner)),
low: Box::new(transform_subqueries_in_expression(low)),
high: Box::new(transform_subqueries_in_expression(high)),
negated: *negated,
symmetric: *symmetric,
},
Expression::Case { operand, when_clauses, else_result } => Expression::Case {
operand: operand.as_ref().map(|o| Box::new(transform_subqueries_in_expression(o))),
when_clauses: when_clauses
.iter()
.map(|w| vibesql_ast::CaseWhen {
conditions: w
.conditions
.iter()
.map(transform_subqueries_in_expression)
.collect(),
result: transform_subqueries_in_expression(&w.result),
})
.collect(),
else_result: else_result
.as_ref()
.map(|e| Box::new(transform_subqueries_in_expression(e))),
},
_ => expr.clone(),
}
}
fn try_extract_subqueries_to_joins(
from: &FromClause,
where_clause: &Expression,
) -> Option<(FromClause, Option<Expression>)> {
match where_clause {
Expression::In { expr, subquery, negated } => {
if let Some(result) = try_convert_in_to_join(from, expr, subquery, *negated) {
return Some((result.from, None)); }
None
}
Expression::BinaryOp { op: BinaryOperator::And, left, right } => {
match left.as_ref() {
Expression::In { expr, subquery, negated } => {
if let Some(result) = try_convert_in_to_join(from, expr, subquery, *negated) {
return Some((result.from, Some((**right).clone())));
}
}
Expression::Exists { subquery, negated } => {
if let Some((join, _)) = try_convert_exists_to_join(from, subquery, *negated) {
return Some((join, Some((**right).clone())));
}
}
Expression::BinaryOp { op, left: inner_left, right: inner_right }
if matches!(inner_right.as_ref(), Expression::ScalarSubquery(_)) =>
{
if let Some(result) =
try_convert_scalar_comparison_to_join(from, op, inner_left, inner_right)
{
let new_where = Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(result.replacement_expr),
right: right.clone(),
};
return Some((result.from, Some(new_where)));
}
}
_ => {}
}
match right.as_ref() {
Expression::In { expr, subquery, negated } => {
if let Some(result) = try_convert_in_to_join(from, expr, subquery, *negated) {
return Some((result.from, Some((**left).clone())));
}
}
Expression::Exists { subquery, negated } => {
if let Some((join, _)) = try_convert_exists_to_join(from, subquery, *negated) {
return Some((join, Some((**left).clone())));
}
}
Expression::BinaryOp { op, left: inner_left, right: inner_right }
if matches!(inner_right.as_ref(), Expression::ScalarSubquery(_)) =>
{
if let Some(result) =
try_convert_scalar_comparison_to_join(from, op, inner_left, inner_right)
{
let new_where = Expression::BinaryOp {
op: BinaryOperator::And,
left: left.clone(),
right: Box::new(result.replacement_expr),
};
return Some((result.from, Some(new_where)));
}
}
_ => {}
}
if let Some((new_left_from, new_left_where)) =
try_extract_subqueries_to_joins(from, left)
{
let combined_where = match new_left_where {
Some(new_left) => Some(Expression::BinaryOp {
op: BinaryOperator::And,
left: Box::new(new_left),
right: right.clone(),
}),
None => Some((**right).clone()),
};
return Some((new_left_from, combined_where));
}
if let Some((new_right_from, new_right_where)) =
try_extract_subqueries_to_joins(from, right)
{
let combined_where = match new_right_where {
Some(new_right) => Some(Expression::BinaryOp {
op: BinaryOperator::And,
left: left.clone(),
right: Box::new(new_right),
}),
None => Some((**left).clone()),
};
return Some((new_right_from, combined_where));
}
None
}
Expression::Exists { subquery, negated } => {
try_convert_exists_to_join(from, subquery, *negated)
}
Expression::BinaryOp { op, left, right }
if matches!(right.as_ref(), Expression::ScalarSubquery(_)) =>
{
if let Some(result) = try_convert_scalar_comparison_to_join(from, op, left, right) {
return Some((result.from, Some(result.replacement_expr)));
}
None
}
Expression::Conjunction(children) => {
for (i, child) in children.iter().enumerate() {
match child {
Expression::In { expr, subquery, negated } => {
if let Some(result) = try_convert_in_to_join(from, expr, subquery, *negated)
{
let remaining: Vec<_> = children
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, c)| c.clone())
.collect();
let new_where = match remaining.len() {
0 => None,
1 => Some(remaining.into_iter().next().unwrap()),
_ => Some(Expression::Conjunction(remaining)),
};
return Some((result.from, new_where));
}
}
Expression::Exists { subquery, negated } => {
if let Some((join, _)) =
try_convert_exists_to_join(from, subquery, *negated)
{
let remaining: Vec<_> = children
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, c)| c.clone())
.collect();
let new_where = match remaining.len() {
0 => None,
1 => Some(remaining.into_iter().next().unwrap()),
_ => Some(Expression::Conjunction(remaining)),
};
return Some((join, new_where));
}
}
Expression::BinaryOp { op, left, right }
if matches!(right.as_ref(), Expression::ScalarSubquery(_)) =>
{
if let Some(result) =
try_convert_scalar_comparison_to_join(from, op, left, right)
{
let mut remaining: Vec<_> = children
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, c)| c.clone())
.collect();
remaining.push(result.replacement_expr);
let new_where = match remaining.len() {
0 => None,
1 => Some(remaining.into_iter().next().unwrap()),
_ => Some(Expression::Conjunction(remaining)),
};
return Some((result.from, new_where));
}
}
_ => {}
}
}
for (i, child) in children.iter().enumerate() {
if let Some((new_from, new_child_where)) =
try_extract_subqueries_to_joins(from, child)
{
let mut new_children: Vec<_> = children
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, c)| c.clone())
.collect();
if let Some(residual) = new_child_where {
new_children.push(residual);
}
let combined_where = match new_children.len() {
0 => None,
1 => Some(new_children.into_iter().next().unwrap()),
_ => Some(Expression::Conjunction(new_children)),
};
return Some((new_from, combined_where));
}
}
None
}
_ => None,
}
}
#[cfg(test)]
mod tests;