use super::*;
mod unused;
pub(super) fn push_into_derived_tables(select: &mut BoundSelect) {
push_filters(select);
unused::drop_unread_columns(select);
}
fn push_filters(select: &mut BoundSelect) {
let Some(filter) = select.filter.as_ref() else {
return;
};
if !select
.sources
.iter()
.any(|source| matches!(source.rows, SourceRows::Subquery(_)))
{
return;
}
if select
.sources
.iter()
.any(|source| matches!(source.join, JoinKind::Right | JoinKind::Full))
{
return;
}
let conjuncts = conjunction(filter);
for source in &mut select.sources {
if source.join == JoinKind::Left {
continue;
}
let id = source.id;
let SourceRows::Subquery(block) = &mut source.rows else {
continue;
};
if !accepts_a_pushed_filter(block) {
continue;
}
for conjunct in &conjuncts {
let mut used = Vec::new();
conjunct.sources_used(&mut used);
if used.as_slice() != [id] || !pushable(conjunct, id) {
continue;
}
let Some(inner) = substituted(conjunct, id, block) else {
continue;
};
block.filter = Some(match block.filter.take() {
Some(existing) => BoundExpr::And(Box::new(existing), Box::new(inner)),
None => inner,
});
}
}
}
fn accepts_a_pushed_filter(block: &BoundSelect) -> bool {
block.compounds.is_empty()
&& block.limit.is_none()
&& block.offset.is_none()
&& !block.distinct
&& block.group_by.is_empty()
&& block.aggregates.is_empty()
&& block.having.is_none()
&& block.windows.is_empty()
&& block.values.is_empty()
}
fn pushable(expr: &BoundExpr, id: usize) -> bool {
let this = match expr {
BoundExpr::Rowid { source } => *source != id,
BoundExpr::Subquery { .. }
| BoundExpr::Aggregate { .. }
| BoundExpr::WindowRef { .. }
| BoundExpr::SorterColumn { .. }
| BoundExpr::External { .. }
| BoundExpr::VirtualFunction { .. }
| BoundExpr::Raise { .. } => false,
BoundExpr::Function { func, .. } => !matches!(
func,
crate::function::ScalarFunc::Random
| crate::function::ScalarFunc::RandomBlob
| crate::function::ScalarFunc::Changes
| crate::function::ScalarFunc::TotalChanges
| crate::function::ScalarFunc::LastInsertRowid
),
_ => true,
};
this && expr.children().iter().all(|child| pushable(child, id))
}
fn substituted(conjunct: &BoundExpr, id: usize, block: &BoundSelect) -> Option<BoundExpr> {
let mut copy = conjunct.clone();
replace_columns(&mut copy, id, block).then_some(copy)
}
fn replace_columns(expr: &mut BoundExpr, id: usize, block: &BoundSelect) -> bool {
if let BoundExpr::Column { source, column, .. } = expr {
if *source != id {
return true;
}
let Some(inner) = block.columns.get(usize::from(*column)) else {
return false;
};
if !pushable(&inner.expr, usize::MAX) {
return false;
}
*expr = inner.expr.clone();
return true;
}
expr.children_mut()
.into_iter()
.all(|child| replace_columns(child, id, block))
}