use super::*;
use crate::bind::BoundFrameBound;
pub(super) fn drop_unread_columns(select: &mut BoundSelect) {
if !select
.sources
.iter()
.any(|source| matches!(source.rows, SourceRows::Subquery(_)))
{
return;
}
for position in 0..select.sources.len() {
let Some(source) = select.sources.get(position) else {
continue;
};
let SourceRows::Subquery(block) = &source.rows else {
continue;
};
if !may_drop_columns(block) {
continue;
}
let Some(read) = columns_read(select, source.id, block.columns.len()) else {
continue;
};
if read.iter().all(|held| *held) {
continue;
}
let Some(SourceRows::Subquery(block)) = select
.sources
.get_mut(position)
.map(|source| &mut source.rows)
else {
continue;
};
null_unread(block, &read);
}
}
fn may_drop_columns(block: &BoundSelect) -> bool {
let arms = core::iter::once(block).chain(block.compounds.iter().map(|(_, arm)| arm));
let plain = |arm: &BoundSelect| {
!arm.distinct
&& arm.aggregates.is_empty()
&& arm.group_by.is_empty()
&& arm.having.is_none()
&& arm.windows.is_empty()
&& arm.values.is_empty()
};
arms.into_iter().all(plain)
&& block
.compounds
.iter()
.all(|(op, _)| *op == CompoundOp::UnionAll)
}
fn null_unread(block: &mut BoundSelect, read: &[bool]) {
let unread = |position: usize| !read.get(position).copied().unwrap_or(true);
for (position, column) in block.columns.iter_mut().enumerate() {
if unread(position) {
column.expr = BoundExpr::Null;
}
}
for (_, arm) in &mut block.compounds {
for (position, column) in arm.columns.iter_mut().enumerate() {
if unread(position) {
column.expr = BoundExpr::Null;
}
}
}
}
fn columns_read(select: &BoundSelect, id: usize, width: usize) -> Option<Vec<bool>> {
let mut read = vec![false; width];
let mut opaque = false;
each_expression(select, &mut |expr| mark(expr, id, &mut read, &mut opaque));
for source in &select.sources {
if let SourceRows::Subquery(block) = &source.rows {
opaque |= block.correlations.contains(&id);
}
if let SourceRows::Recursive(body) = &source.rows {
opaque |= body
.seeds
.iter()
.chain(body.steps.iter())
.any(|(_, arm)| arm.correlations.contains(&id));
}
}
(!opaque).then_some(read)
}
fn mark(expr: &BoundExpr, id: usize, read: &mut [bool], opaque: &mut bool) {
match expr {
BoundExpr::Column { source, column, .. } if *source == id => {
if let Some(flag) = read.get_mut(usize::from(*column)) {
*flag = true;
}
}
BoundExpr::Rowid { source } if *source == id => *opaque = true,
BoundExpr::VirtualFunction { source, .. } if *source == id => *opaque = true,
BoundExpr::Subquery { block, .. } if block.correlations.contains(&id) => *opaque = true,
_ => {}
}
for child in expr.children() {
mark(child, id, read, opaque);
}
}
fn each_expression(select: &BoundSelect, visit: &mut impl FnMut(&BoundExpr)) {
for term in &select.sources {
if let Some(constraint) = &term.constraint {
visit(constraint);
}
}
let singles = select
.filter
.iter()
.chain(select.having.iter())
.chain(select.group_by.iter())
.chain(select.limit.iter())
.chain(select.offset.iter());
for expr in singles {
visit(expr);
}
for column in &select.columns {
visit(&column.expr);
}
for term in &select.order_by {
visit(&term.expr);
}
for aggregate in &select.aggregates {
aggregate.arguments.iter().for_each(&mut *visit);
aggregate.filter.iter().for_each(&mut *visit);
for term in &aggregate.order_by {
visit(&term.expr);
}
}
for window in &select.windows {
window.arguments.iter().for_each(&mut *visit);
window.filter.iter().for_each(&mut *visit);
window.partition_by.iter().for_each(&mut *visit);
for term in &window.order_by {
visit(&term.expr);
}
for bound in [&window.start, &window.end] {
if let BoundFrameBound::Preceding(expr) | BoundFrameBound::Following(expr) = bound {
visit(expr);
}
}
}
for row in &select.values {
row.iter().for_each(&mut *visit);
}
for (_, arm) in &select.compounds {
each_expression(arm, visit);
}
}