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 block.shared.is_some() {
continue;
}
if block.compounds.is_empty() {
if accepts_a_pushed_filter(block) {
push_into_arm(block, id, &conjuncts);
}
} else if accepts_a_compound_filter(block) {
push_into_compound(block, id, &conjuncts);
}
}
}
fn push_into_arm(arm: &mut BoundSelect, id: usize, conjuncts: &[BoundExpr]) {
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, arm) else {
continue;
};
arm.filter = Some(match arm.filter.take() {
Some(existing) => BoundExpr::And(Box::new(existing), Box::new(inner)),
None => inner,
});
}
}
fn push_into_compound(block: &mut BoundSelect, id: usize, conjuncts: &[BoundExpr]) {
if accepts_an_arm_filter(block) {
push_into_arm(block, id, conjuncts);
}
for (_, arm) in &mut block.compounds {
if accepts_an_arm_filter(arm) && arm.compounds.is_empty() {
push_into_arm(arm, id, conjuncts);
}
}
}
fn accepts_a_compound_filter(block: &BoundSelect) -> bool {
let all_union_all = block
.compounds
.iter()
.all(|(op, _)| *op == crate::ast::CompoundOp::UnionAll);
block.limit.is_none()
&& block.offset.is_none()
&& (all_union_all || block.order_by.is_empty())
&& block.windows.is_empty()
&& block
.compounds
.iter()
.all(|(_, arm)| arm.windows.is_empty())
}
fn accepts_an_arm_filter(arm: &BoundSelect) -> bool {
!arm.distinct
&& arm.group_by.is_empty()
&& arm.aggregates.is_empty()
&& arm.having.is_none()
&& arm.windows.is_empty()
&& arm.values.is_empty()
}
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))
}
pub fn is_repeatable_condition(expr: &BoundExpr) -> bool {
pushable(expr, usize::MAX)
}
pub fn calls_a_volatile_function(expr: &BoundExpr) -> bool {
let this = matches!(
expr,
BoundExpr::Function {
func: crate::function::ScalarFunc::Random
| crate::function::ScalarFunc::RandomBlob
| crate::function::ScalarFunc::Changes
| crate::function::ScalarFunc::TotalChanges
| crate::function::ScalarFunc::LastInsertRowid,
..
}
);
this || expr
.children()
.iter()
.any(|child| calls_a_volatile_function(child))
}
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;
}
let replaced = expr
.children_mut()
.into_iter()
.all(|child| replace_columns(child, id, block));
if replaced {
refresh_comparison_rules(expr);
}
replaced
}
fn refresh_comparison_rules(expr: &mut BoundExpr) {
use crate::bind::comparison_rules;
match expr {
BoundExpr::Compare {
left,
right,
affinity,
collation,
..
}
| BoundExpr::Is {
left,
right,
affinity,
collation,
..
} => (*affinity, *collation) = comparison_rules(left, right),
BoundExpr::Between {
operand,
low,
high,
low_affinity,
low_collation,
high_affinity,
high_collation,
..
} => {
(*low_affinity, *low_collation) = comparison_rules(operand, low);
(*high_affinity, *high_collation) = comparison_rules(operand, high);
}
BoundExpr::InList {
operand,
list,
affinity,
collation,
..
} => {
if let Some(first) = list.first() {
(*affinity, *collation) = comparison_rules(operand, first);
}
}
BoundExpr::Case {
operand: Some(operand),
branches,
comparisons,
..
} => {
*comparisons = branches
.iter()
.map(|(when, _)| comparison_rules(operand, when))
.collect();
}
_ => {}
}
}