use super::*;
pub(super) fn flatten_derived_tables(select: &mut BoundSelect, in_compound: bool) {
if !select
.sources
.iter()
.any(|source| matches!(source.rows, SourceRows::Subquery(_)) || is_outer(source.join))
{
return;
}
simplify_left_joins(select);
let mut position = 0;
let mut steps = 0usize;
while position < select.sources.len() && steps < 64 {
if can_flatten(select, position, in_compound) {
inline(select, position, in_compound);
position = 0;
steps += 1;
} else {
position += 1;
}
}
if steps > 0 {
simplify_left_joins(select);
}
}
fn simplify_left_joins(select: &mut BoundSelect) {
let Some(filter) = filter_without_table_arguments(select) else {
return;
};
for position in 0..select.sources.len() {
let Some(source) = select.sources.get(position) else {
continue;
};
let (id, join) = (source.id, source.join);
let before_a_right_join = select
.sources
.iter()
.skip(position.saturating_add(1))
.any(|later| matches!(later.join, JoinKind::Right | JoinKind::Full));
let extendable = matches!(join, JoinKind::Left | JoinKind::Full) || before_a_right_join;
if !extendable || !implies_non_null_row(&filter, id) {
continue;
}
if let Some(source) = select.sources.get_mut(position) {
source.join = match source.join {
JoinKind::Left => JoinKind::Inner,
JoinKind::Full => JoinKind::Right,
other => other,
};
}
if before_a_right_join {
for later in select.sources.iter_mut().skip(position.saturating_add(1)) {
later.join = match later.join {
JoinKind::Right => JoinKind::Inner,
JoinKind::Full => JoinKind::Left,
other => other,
};
}
}
}
}
fn filter_without_table_arguments(select: &BoundSelect) -> Option<BoundExpr> {
let filter = select.filter.as_ref()?;
let mut conjuncts = Vec::new();
split_conjunction(filter, &mut conjuncts);
conjuncts
.into_iter()
.filter(|conjunct| !is_table_argument(select, conjunct))
.reduce(|left, right| BoundExpr::And(Box::new(left), Box::new(right)))
}
pub(super) fn is_table_argument(select: &BoundSelect, conjunct: &BoundExpr) -> bool {
let BoundExpr::Compare {
op: BinaryOp::Equal,
left,
..
} = conjunct
else {
return false;
};
let BoundExpr::Column { source, column, .. } = left.as_ref() else {
return false;
};
select.sources.iter().any(|term| {
term.id == *source
&& term.table.kind == crate::catalog_view::TableKind::Virtual
&& term
.table
.columns
.get(usize::from(*column))
.is_some_and(|declared| declared.hidden)
})
}
pub(super) fn implies_non_null_row(filter: &BoundExpr, id: usize) -> bool {
let mut expr = filter;
while let BoundExpr::Collate { operand, .. } = expr {
expr = operand;
}
match expr {
BoundExpr::IsNull {
negated: true,
operand,
} => reads_the_term(operand, id),
BoundExpr::And(left, right) => {
implies_non_null_row(left, id) || implies_non_null_row(right, id)
}
other => reads_the_term(other, id),
}
}
fn reads_the_term(expr: &BoundExpr, id: usize) -> bool {
match expr {
BoundExpr::Column { source, .. } | BoundExpr::Rowid { source } => *source == id,
BoundExpr::IsNull { .. }
| BoundExpr::Is { .. }
| BoundExpr::Case { .. }
| BoundExpr::Function { .. }
| BoundExpr::Json { .. }
| BoundExpr::Math { .. }
| BoundExpr::Time { .. }
| BoundExpr::Pattern { .. }
| BoundExpr::External { .. }
| BoundExpr::VirtualFunction { .. }
| BoundExpr::Subquery { .. }
| BoundExpr::Aggregate { .. }
| BoundExpr::WindowRef { .. } => false,
BoundExpr::And(left, right) | BoundExpr::Or(left, right) => {
reads_the_term(left, id) && reads_the_term(right, id)
}
BoundExpr::InList { operand, list, .. } => !list.is_empty() && reads_the_term(operand, id),
BoundExpr::Between { operand, .. } => reads_the_term(operand, id),
other => other
.children()
.into_iter()
.any(|child| reads_the_term(child, id)),
}
}
fn can_flatten(select: &BoundSelect, position: usize, in_compound: bool) -> bool {
let Some(source) = select.sources.get(position) else {
return false;
};
let SourceRows::Subquery(block) = &source.rows else {
return false;
};
if source.derived.materialized == Some(true) || source.derived.pinned {
return false; }
let aggregate = !select.aggregates.is_empty() || !select.group_by.is_empty();
let block_aggregate =
!block.aggregates.is_empty() || !block.group_by.is_empty() || block.having.is_some();
if !select.windows.is_empty() || !block.windows.is_empty() || block_aggregate
|| block.distinct || block.sources.is_empty() || !block.values.is_empty()
|| !block.compounds.is_empty()
|| !block.correlations.is_empty()
{
return false;
}
if !limits_allow(select, block, position, in_compound, aggregate) {
return false;
}
if select
.sources
.iter()
.any(|held| matches!(held.join, JoinKind::Right | JoinKind::Full))
{
return false; }
if source.join == JoinKind::Left && !outer_join_allows(select, block) {
return false;
}
if block.sources.iter().any(|inner| {
matches!(
inner.rows,
SourceRows::Recursive(_) | SourceRows::RecursiveSelf { .. }
)
}) {
return false; }
if reads_the_rowid(select, source.id) {
return false;
}
!block
.columns
.iter()
.any(|column| holds_a_correlated_subquery(&column.expr))
}
fn limits_allow(
select: &BoundSelect,
block: &BoundSelect,
position: usize,
in_compound: bool,
aggregate: bool,
) -> bool {
let sub_limit = block.limit.is_some();
let outer_limit = !in_compound && select.limit.is_some();
let outer_order = !in_compound && !select.order_by.is_empty();
if block.offset.is_some()
|| (sub_limit && outer_limit)
|| (sub_limit && in_compound)
|| (sub_limit && (select.sources.len() > 1 || aggregate))
|| (sub_limit && select.filter.is_some())
|| (sub_limit && select.distinct)
{
return false;
}
let sub_order = !block.order_by.is_empty() && !drops_its_order_by(select, block, in_compound);
if sub_order && (outer_order || aggregate) {
return false;
}
let computes = select
.columns
.iter()
.any(|column| !matches!(column.expr, BoundExpr::Column { .. }));
let alone = select.sources.len() == 1
|| select
.sources
.get(1)
.is_some_and(|next| matches!(next.join, JoinKind::Left | JoinKind::Cross));
!(sub_order && position == 0 && computes && alone)
}
fn drops_its_order_by(select: &BoundSelect, block: &BoundSelect, in_compound: bool) -> bool {
let outer_order = !in_compound && !select.order_by.is_empty();
let order_matters = select.aggregates.iter().any(|aggregate| {
!matches!(
aggregate.func,
crate::function::AggregateFunc::Count
| crate::function::AggregateFunc::Min
| crate::function::AggregateFunc::Max
)
});
(outer_order || select.sources.len() > 1) && block.limit.is_none() && !order_matters
}
fn outer_join_allows(select: &BoundSelect, block: &BoundSelect) -> bool {
let [only] = block.sources.as_slice() else {
return false;
};
!select.distinct
&& block.columns.iter().all(
|column| matches!(column.expr, BoundExpr::Column { source, .. } if source == only.id),
)
}
fn reads_the_rowid(select: &BoundSelect, id: usize) -> bool {
let mut found = false;
let mut probe = select.clone();
crate::rewrite::rewrite_select(&mut probe, &mut |expr: &mut BoundExpr| {
if matches!(expr, BoundExpr::Rowid { source } if *source == id) {
found = true;
}
});
found
}
fn holds_a_correlated_subquery(expr: &BoundExpr) -> bool {
if let BoundExpr::Subquery { block, .. } = expr {
if !block.correlations.is_empty() {
return true;
}
}
expr.children().into_iter().any(holds_a_correlated_subquery)
}
fn inline(select: &mut BoundSelect, position: usize, in_compound: bool) {
let drop_order = match select.sources.get(position).map(|source| &source.rows) {
Some(SourceRows::Subquery(block)) => drops_its_order_by(select, block, in_compound),
_ => return,
};
let source = select.sources.remove(position);
let SourceRows::Subquery(block) = source.rows else {
return;
};
let mut block = *block;
let id = source.id;
let replacements: Vec<BoundExpr> = block
.columns
.iter()
.map(|column| column.expr.clone())
.collect();
let mut substitute = |expr: &mut BoundExpr| {
if let BoundExpr::Column { source, column, .. } = expr {
if *source == id {
if let Some(replacement) = replacements.get(usize::from(*column)) {
*expr = replacement.clone();
}
}
}
};
crate::rewrite::rewrite_select(select, &mut substitute);
let mut on = source.constraint;
if let Some(on) = on.as_mut() {
crate::rewrite::rewrite_expr(on, &mut substitute);
}
let inner_ids: Vec<usize> = block.sources.iter().map(|inner| inner.id).collect();
replace_correlation(select, id, &inner_ids);
let inner_filter = block.filter.take();
let mut inner_sources = core::mem::take(&mut block.sources);
if let Some(first) = inner_sources.first_mut() {
first.join = source.join;
if source.join == JoinKind::Left {
first.constraint = conjoined([on, inner_filter, first.constraint.take()]);
} else {
select.filter = conjoined([select.filter.take(), on, inner_filter]);
}
}
for (offset, inner) in inner_sources.into_iter().enumerate() {
select.sources.insert(position + offset, inner);
}
if !in_compound {
if !drop_order && select.order_by.is_empty() {
select.order_by = block.order_by;
}
if block.limit.is_some() {
select.limit = block.limit;
}
}
}
fn conjoined<const N: usize>(parts: [Option<BoundExpr>; N]) -> Option<BoundExpr> {
parts
.into_iter()
.flatten()
.reduce(|left, right| BoundExpr::And(Box::new(left), Box::new(right)))
}
fn replace_correlation(select: &mut BoundSelect, old: usize, new: &[usize]) {
replace_in(&mut select.correlations, old, new);
for source in &mut select.sources {
match &mut source.rows {
SourceRows::Subquery(block) => replace_correlation(block, old, new),
SourceRows::Recursive(body) => {
for (_, arm) in body.seeds.iter_mut().chain(body.steps.iter_mut()) {
replace_correlation(arm, old, new);
}
}
_ => {}
}
}
for (_, arm) in &mut select.compounds {
replace_correlation(arm, old, new);
}
crate::rewrite::rewrite_select(select, &mut |expr: &mut BoundExpr| {
if let Some(block) = expr.block_mut() {
replace_in(&mut block.correlations, old, new);
}
});
}
fn replace_in(list: &mut Vec<usize>, old: usize, new: &[usize]) {
if !list.contains(&old) {
return;
}
list.retain(|held| *held != old);
for id in new {
if !list.contains(id) {
list.push(*id);
}
}
}
pub(super) fn unshare_coroutines(select: &mut BoundSelect) {
let mut unshare = Vec::new();
for (position, source) in select.sources.iter().enumerate() {
let SourceRows::Subquery(block) = &source.rows else {
continue;
};
if block
.shared
.is_none_or(|key| key < crate::bind::FIRST_ANONYMOUS_SHARED)
{
continue;
}
if super::tree::runs_as_coroutine(select, position)
&& !reads_through_an_automatic_index(select, source)
{
unshare.push(position);
}
}
for position in unshare {
if let Some(SourceRows::Subquery(block)) = select
.sources
.get_mut(position)
.map(|source| &mut source.rows)
{
block.shared = None;
}
}
}
fn reads_through_an_automatic_index(select: &BoundSelect, source: &BoundSource) -> bool {
let mut terms: Vec<BoundExpr> = Vec::new();
if let Some(filter) = &select.filter {
terms.extend(conjunction(filter));
}
if let Some(on) = &source.constraint {
terms.extend(conjunction(on));
}
terms.iter().any(|term| {
let BoundExpr::Compare {
op: BinaryOp::Equal,
left,
right,
..
} = term
else {
return false;
};
let keyed = |side: &BoundExpr, other: &BoundExpr| {
let mut used = Vec::new();
other.sources_used(&mut used);
let outside = matches!(other, BoundExpr::Parameter(_))
|| (!used.is_empty() && !used.contains(&source.id));
matches!(side, BoundExpr::Column { source: id, .. } if *id == source.id) && outside
};
keyed(left, right) || keyed(right, left)
})
}