use std::rc::Rc;
use inillucent_value::{Affinity, Collation};
use crate::ast::{BinaryOp, JoinKind};
use crate::bind::{BoundExpr, BoundSelect, BoundStatement, SourceRows, SubqueryKind};
use crate::catalog_view::{TableInfo, TableKind};
pub fn lower(statement: &mut BoundStatement) {
let mut highest = 0usize;
let mut correlated_in = false;
walk_statement(statement, &mut |expr: &mut BoundExpr| {
if let BoundExpr::Subquery {
id, kind, block, ..
} = expr
{
highest = highest.max(*id);
correlated_in |= *kind == SubqueryKind::In && !block.correlations.is_empty();
}
});
if !correlated_in {
return;
}
let mut next = highest.saturating_add(1);
let tables = never_null_tables(statement);
walk_statement(statement, &mut |expr: &mut BoundExpr| {
lower_one(expr, &mut next, &tables)
});
}
fn never_null_tables(statement: &mut BoundStatement) -> Vec<(usize, Rc<TableInfo>)> {
let mut found = Vec::new();
if let BoundStatement::Select(select) = statement {
gather_tables(select, &mut found);
}
walk_statement(statement, &mut |expr: &mut BoundExpr| {
if let Some(block) = expr.block_mut() {
gather_tables(block, &mut found);
}
});
found
}
fn gather_tables(select: &BoundSelect, into: &mut Vec<(usize, Rc<TableInfo>)>) {
let outer = select.sources.iter().any(|source| {
matches!(
source.join,
JoinKind::Left | JoinKind::Right | JoinKind::Full
)
});
for source in &select.sources {
match &source.rows {
SourceRows::Table if !outer && source.table.kind == TableKind::Table => {
into.push((source.id, Rc::clone(&source.table)));
}
SourceRows::Subquery(block) => gather_tables(block, into),
SourceRows::Recursive(body) => {
for (_, arm) in body.seeds.iter().chain(body.steps.iter()) {
gather_tables(arm, into);
}
}
_ => {}
}
}
for (_, arm) in &select.compounds {
gather_tables(arm, into);
}
}
fn never_null(expr: &BoundExpr, tables: &[(usize, Rc<TableInfo>)]) -> bool {
let table_of = |wanted: usize| {
tables
.iter()
.find(|(id, _)| *id == wanted)
.map(|(_, table)| table)
};
match expr {
BoundExpr::Column { source, column, .. } => table_of(*source).is_some_and(|table| {
table.rowid_alias == Some(*column)
|| table
.columns
.get(usize::from(*column))
.is_some_and(|held| held.not_null)
}),
BoundExpr::Rowid { source } => table_of(*source).is_some_and(|table| !table.without_rowid),
_ => false,
}
}
fn walk_statement(statement: &mut BoundStatement, rewrite: crate::rewrite::Rewrite<'_>) {
match statement {
BoundStatement::Select(select) => crate::rewrite::rewrite_select(select, rewrite),
BoundStatement::Insert(insert) => {
crate::rewrite::rewrite_insert(insert, rewrite);
walk_triggers(&mut insert.triggers, rewrite);
for clause in &mut insert.upsert {
walk_triggers(&mut clause.triggers, rewrite);
}
}
BoundStatement::Update(update) => {
crate::rewrite::rewrite_update(update, rewrite);
walk_triggers(&mut update.triggers, rewrite);
}
BoundStatement::Delete(delete) => {
crate::rewrite::rewrite_delete(delete, rewrite);
walk_triggers(&mut delete.triggers, rewrite);
}
BoundStatement::Directive(_) | BoundStatement::Empty => {}
}
}
fn walk_triggers(triggers: &mut [crate::dml::BoundTrigger], rewrite: crate::rewrite::Rewrite<'_>) {
use crate::dml::BoundTriggerStatement;
for trigger in triggers {
for statement in &mut trigger.body {
match statement {
BoundTriggerStatement::Select(select) => {
crate::rewrite::rewrite_select(select, rewrite)
}
BoundTriggerStatement::Insert(insert) => {
crate::rewrite::rewrite_insert(insert, rewrite);
walk_triggers(&mut insert.triggers, rewrite);
for clause in &mut insert.upsert {
walk_triggers(&mut clause.triggers, rewrite);
}
}
BoundTriggerStatement::Update(update) => {
crate::rewrite::rewrite_update(update, rewrite);
walk_triggers(&mut update.triggers, rewrite);
}
BoundTriggerStatement::Delete(delete) => {
crate::rewrite::rewrite_delete(delete, rewrite);
walk_triggers(&mut delete.triggers, rewrite);
}
}
}
}
}
fn lower_one(expr: &mut BoundExpr, next: &mut usize, tables: &[(usize, Rc<TableInfo>)]) {
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_never_null: never_null(operand, tables),
listed_never_null: never_null(&listed, tables),
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,
operand_never_null: bool,
listed_never_null: bool,
}
fn lowered(about: &Lowering, block: &BoundSelect, next: &mut usize) -> BoundExpr {
let matched = exists(
block,
Some(BoundExpr::Compare {
op: BinaryOp::Equal,
left: Box::new(widened(about.listed.clone(), about.affinity)),
right: Box::new(widened(about.operand.clone(), about.affinity)),
affinity: about.affinity,
collation: about.collation,
}),
next,
);
if about.operand_never_null && about.listed_never_null {
let mut matched = matched;
if let BoundExpr::Subquery { negated, .. } = &mut matched {
*negated = about.negated;
}
return matched;
}
let (found, missing) = match about.negated {
true => (BoundExpr::Integer(0), BoundExpr::Integer(1)),
false => (BoundExpr::Integer(1), BoundExpr::Integer(0)),
};
let mut branches = vec![(matched, found)];
if !about.operand_never_null {
let any_row = exists(block, None, next);
branches.push((
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(),
},
));
}
if !about.listed_never_null {
let any_null = exists(
block,
Some(BoundExpr::IsNull {
negated: false,
operand: Box::new(about.listed.clone()),
}),
next,
);
branches.push((any_null, BoundExpr::Null));
}
BoundExpr::Case {
operand: None,
branches,
otherwise: Some(Box::new(missing)),
comparisons: Vec::new(),
}
}
fn widened(expr: BoundExpr, affinity: Option<Affinity>) -> BoundExpr {
if affinity != Some(Affinity::Real) {
return expr;
}
let is_integer = BoundExpr::Compare {
op: BinaryOp::Equal,
left: Box::new(BoundExpr::Function {
func: crate::function::ScalarFunc::TypeOf,
arguments: vec![expr.clone()],
collation: Collation::Binary,
}),
right: Box::new(BoundExpr::Text(b"integer".to_vec())),
affinity: None,
collation: Collation::Binary,
};
BoundExpr::Case {
operand: None,
branches: vec![(
is_integer,
BoundExpr::Cast {
operand: Box::new(expr.clone()),
affinity: Affinity::Real,
},
)],
otherwise: Some(Box::new(expr)),
comparisons: Vec::new(),
}
}
fn conjuncts(filter: &BoundExpr) -> Box<dyn Iterator<Item = &BoundExpr> + '_> {
match filter {
BoundExpr::And(left, right) => Box::new(conjuncts(left).chain(conjuncts(right))),
other => Box::new(std::iter::once(other)),
}
}
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();
let held_already = |extra: &BoundExpr| {
copy.filter
.as_ref()
.is_some_and(|filter| conjuncts(filter).any(|term| term == extra))
};
if let Some(extra) = extra.filter(|extra| !held_already(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,
}
}