use inillucent_value::{Affinity, Collation};
use crate::ast::BinaryOp;
use crate::bind::{BoundExpr, BoundSelect, BoundStatement, SubqueryKind};
pub fn lower(statement: &mut BoundStatement) {
let mut next = highest_subquery_id(statement).saturating_add(1);
let mut rewrite = |expr: &mut BoundExpr| lower_one(expr, &mut next);
match statement {
BoundStatement::Select(select) => crate::rewrite::rewrite_select(select, &mut rewrite),
BoundStatement::Insert(insert) => crate::rewrite::rewrite_insert(insert, &mut rewrite),
BoundStatement::Update(update) => crate::rewrite::rewrite_update(update, &mut rewrite),
BoundStatement::Delete(delete) => crate::rewrite::rewrite_delete(delete, &mut rewrite),
BoundStatement::Directive(_) | BoundStatement::Empty => {}
}
}
fn lower_one(expr: &mut BoundExpr, next: &mut usize) {
let BoundExpr::Subquery {
kind: SubqueryKind::In,
negated,
operand: Some(operand),
block,
affinity,
collation,
..
} = expr
else {
return;
};
if block.correlations.is_empty() || !liftable(block) {
return;
}
let listed = match block.columns.first() {
Some(column) => column.expr.clone(),
None => return,
};
let replacement = lowered(
&Lowering {
operand: (**operand).clone(),
listed,
negated: *negated,
affinity: *affinity,
collation: *collation,
},
block,
next,
);
*expr = replacement;
}
fn liftable(block: &BoundSelect) -> bool {
block.group_by.is_empty()
&& block.having.is_none()
&& !block.distinct
&& block.limit.is_none()
&& block.offset.is_none()
&& block.compounds.is_empty()
&& block.windows.is_empty()
&& block.aggregates.is_empty()
&& block.values.is_empty()
&& !block.sources.is_empty()
}
struct Lowering {
operand: BoundExpr,
listed: BoundExpr,
negated: bool,
affinity: Option<Affinity>,
collation: Collation,
}
fn lowered(about: &Lowering, block: &BoundSelect, next: &mut usize) -> BoundExpr {
let matched = exists(
block,
Some(BoundExpr::Compare {
op: BinaryOp::Equal,
left: Box::new(about.listed.clone()),
right: Box::new(about.operand.clone()),
affinity: about.affinity,
collation: about.collation,
}),
next,
);
let any_row = exists(block, None, next);
let any_null = exists(
block,
Some(BoundExpr::IsNull {
negated: false,
operand: Box::new(about.listed.clone()),
}),
next,
);
let (found, missing) = match about.negated {
true => (BoundExpr::Integer(0), BoundExpr::Integer(1)),
false => (BoundExpr::Integer(1), BoundExpr::Integer(0)),
};
BoundExpr::Case {
operand: None,
branches: vec![
(matched, found),
(
BoundExpr::IsNull {
negated: false,
operand: Box::new(about.operand.clone()),
},
BoundExpr::Case {
operand: None,
branches: vec![(any_row, BoundExpr::Null)],
otherwise: Some(Box::new(missing.clone())),
comparisons: Vec::new(),
},
),
(any_null, BoundExpr::Null),
],
otherwise: Some(Box::new(missing)),
comparisons: Vec::new(),
}
}
fn exists(block: &BoundSelect, extra: Option<BoundExpr>, next: &mut usize) -> BoundExpr {
let mut copy = block.clone();
copy.columns.truncate(1);
if let Some(first) = copy.columns.first_mut() {
first.expr = BoundExpr::Integer(1);
first.origin = None;
}
copy.order_by.clear();
if let Some(extra) = extra {
copy.filter = Some(match copy.filter.take() {
Some(held) => BoundExpr::And(Box::new(held), Box::new(extra)),
None => extra,
});
}
let id = *next;
*next = next.saturating_add(1);
BoundExpr::Subquery {
id,
kind: SubqueryKind::Exists,
negated: false,
operand: None,
block: Box::new(copy),
affinity: None,
collation: Collation::Binary,
}
}
fn highest_subquery_id(statement: &mut BoundStatement) -> usize {
let mut highest = 0usize;
let mut look = |expr: &mut BoundExpr| {
if let BoundExpr::Subquery { id, .. } = expr {
highest = highest.max(*id);
}
};
match statement {
BoundStatement::Select(select) => crate::rewrite::rewrite_select(select, &mut look),
BoundStatement::Insert(insert) => crate::rewrite::rewrite_insert(insert, &mut look),
BoundStatement::Update(update) => crate::rewrite::rewrite_update(update, &mut look),
BoundStatement::Delete(delete) => crate::rewrite::rewrite_delete(delete, &mut look),
BoundStatement::Directive(_) | BoundStatement::Empty => {}
}
highest
}