use super::describe;
use super::*;
use crate::bind::{BoundOrderTerm, SubqueryKind};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PlanLine {
pub depth: u16,
pub detail: String,
}
fn push(out: &mut Vec<PlanLine>, depth: u16, detail: impl Into<String>) {
out.push(PlanLine {
depth,
detail: detail.into(),
});
}
pub(super) fn tree_of(plan: &PhysicalPlan, depth: u16, out: &mut Vec<PlanLine>) {
if !plan.compounds.is_empty() {
compound_tree(plan, depth, out);
return;
}
if !plan.select.windows.is_empty() {
window_tree(plan, depth, out);
return;
}
select_tree(plan, depth, out);
}
fn select_tree(plan: &PhysicalPlan, depth: u16, out: &mut Vec<PlanLine>) {
if let Some(inner) = flattened_compound(plan) {
tree_of(inner, depth, out);
return;
}
derived_nodes(plan, depth, out);
if plan.sources.is_empty() {
let rows = plan.select.values.len();
if rows > 1 {
push(out, depth, format!("SCAN {rows}-ROW VALUES CLAUSE"));
} else {
push(out, depth, "SCAN CONSTANT ROW");
}
}
let extreme = min_or_max_search(plan);
for (position, line) in describe::loop_lines(plan).into_iter().enumerate() {
match (&extreme, position) {
(Some(search), 0) => push(out, depth, search.clone()),
_ => push(out, depth, line),
}
}
expression_subqueries(plan, depth, out);
for line in describe::temp_lines(plan) {
push(out, depth, line);
}
}
fn flattened_compound(plan: &PhysicalPlan) -> Option<&PhysicalPlan> {
let select = &plan.select;
let [only] = select.sources.as_slice() else {
return None;
};
let SourceRows::Subquery(block) = &only.rows else {
return None;
};
let simple = |arm: &BoundSelect| {
arm.aggregates.is_empty()
&& arm.group_by.is_empty()
&& !arm.distinct
&& arm.windows.is_empty()
&& !arm.sources.is_empty()
};
if block.compounds.is_empty()
|| !block
.compounds
.iter()
.all(|(op, arm)| *op == CompoundOp::UnionAll && simple(arm))
|| !simple(block)
|| !block.order_by.is_empty()
|| block.limit.is_some()
|| only.derived.materialized == Some(true)
|| !select.aggregates.is_empty()
|| !select.group_by.is_empty()
|| select.distinct
|| !select.windows.is_empty()
|| !select.order_by.is_empty()
|| select.limit.is_some()
{
return None;
}
match &plan.sources.first()?.path {
AccessPath::Subquery { plan: inner, .. } => Some(inner),
_ => None,
}
}
fn min_or_max_search(plan: &PhysicalPlan) -> Option<String> {
let select = &plan.select;
let [aggregate] = select.aggregates.as_slice() else {
return None;
};
let extreme = matches!(
aggregate.func,
crate::function::AggregateFunc::Min | crate::function::AggregateFunc::Max
);
let [only] = plan.sources.as_slice() else {
return None;
};
let unbounded = match &only.path {
AccessPath::TableScan { .. } => true,
AccessPath::IndexSeek {
equalities,
low,
high,
..
} => equalities.is_empty() && low.is_none() && high.is_none(),
_ => false,
};
if !extreme || !unbounded || !select.group_by.is_empty() || aggregate.distinct {
return None;
}
let [argument] = aggregate.arguments.as_slice() else {
return None;
};
let bound = select.sources.first()?;
let name = loop_name(bound);
let column = match argument {
BoundExpr::Column { source, column, .. } if *source == only.id => *column,
_ => return Some(format!("SEARCH {name}")),
};
if only.table.rowid_alias == Some(column) {
return Some(format!("SEARCH {name}"));
}
let Some(index) = only.table.indexes.iter().find(|index| {
index
.columns
.first()
.is_some_and(|key| key.column == Some(column))
&& index.partial_sql.is_none()
&& index.metric.is_none()
}) else {
return Some(format!("SEARCH {name}"));
};
let reads = select.columns_read(only.id);
let covering = !reads.opaque
&& reads.columns.iter().all(|read| {
Some(*read) == only.table.rowid_alias
|| index.columns.iter().any(|key| key.column == Some(*read))
});
let kind = if covering { "COVERING INDEX" } else { "INDEX" };
Some(format!(
"SEARCH {name} USING {kind} {}",
String::from_utf8_lossy(&index.name)
))
}
fn node_name(source: &BoundSource) -> String {
if (source.derived.cte || source.derived.view) && !source.derived.name.is_empty() {
return String::from_utf8_lossy(&source.derived.name).into_owned();
}
loop_name(source)
}
pub(super) fn loop_name(source: &BoundSource) -> String {
if let Some(rows) = values_rows(source) {
return format!("{rows}-ROW VALUES CLAUSE");
}
if source.derived.anonymous {
if let SourceRows::Subquery(block) = &source.rows {
return format!("(subquery-{})", last_serial(block));
}
}
String::from_utf8_lossy(&source.alias).into_owned()
}
fn values_rows(source: &BoundSource) -> Option<usize> {
match &source.rows {
SourceRows::Subquery(block) if block.values.len() > 1 => Some(block.values.len()),
_ => None,
}
}
pub(super) fn last_serial(block: &BoundSelect) -> u32 {
block
.compounds
.last()
.map_or(block.serial, |(_, arm)| arm.serial)
}
pub(super) fn runs_as_coroutine(select: &BoundSelect, position: usize) -> bool {
let Some(source) = select.sources.get(position) else {
return false;
};
let note = &source.derived;
if note.cte
&& (note.materialized == Some(true) || (note.uses >= 2 && note.materialized != Some(false)))
{
return false;
}
if select
.sources
.first()
.is_some_and(|first| matches!(first.join, JoinKind::Right | JoinKind::Full))
{
return false;
}
if position == 0 {
return true;
}
let mut at = position;
loop {
let Some(held) = select.sources.get(at) else {
return false;
};
if matches!(
held.join,
JoinKind::Left | JoinKind::Full | JoinKind::Right | JoinKind::Cross
) {
return false;
}
if at == 0 {
return true;
}
at -= 1;
if select
.sources
.get(at)
.is_some_and(|before| !matches!(before.rows, SourceRows::Table))
{
return false;
}
}
}
fn derived_nodes(plan: &PhysicalPlan, depth: u16, out: &mut Vec<PlanLine>) {
let mut filled: Vec<String> = Vec::new();
for (position, bound) in plan.select.sources.iter().enumerate() {
let Some(planned) = plan.sources.iter().find(|held| held.id == bound.id) else {
continue;
};
if values_rows(bound).is_some() {
continue;
}
let coroutine = runs_as_coroutine(&plan.select, position);
let name = node_name(bound);
if bound.derived.cte && !coroutine {
if filled.contains(&name) {
continue;
}
filled.push(name.clone());
}
let kind = if coroutine {
"CO-ROUTINE"
} else {
"MATERIALIZE"
};
match &planned.path {
AccessPath::Subquery { plan: inner, .. } => {
push(out, depth, format!("{kind} {name}"));
tree_of(inner, depth + 1, out);
}
AccessPath::Recursive { seeds, steps, .. } => {
push(out, depth, format!("{kind} {name}"));
push(out, depth + 1, "SETUP");
for (_, seed) in seeds {
tree_of(seed, depth + 2, out);
}
push(out, depth + 1, "RECURSIVE STEP");
for (_, step) in steps {
tree_of(step, depth + 2, out);
}
}
_ => {}
}
}
}
fn expression_subqueries(plan: &PhysicalPlan, depth: u16, out: &mut Vec<PlanLine>) {
let select = &plan.select;
let mut roots: Vec<&BoundExpr> = Vec::new();
roots.extend(select.columns.iter().map(|column| &column.expr));
roots.extend(select.group_by.iter());
roots.extend(select.having.iter());
roots.extend(select.order_by.iter().map(|term| &term.expr));
subquery_nodes_into(select.filter.as_ref(), &roots, plan.levers, depth, out);
}
pub fn subquery_nodes(
filter: Option<&BoundExpr>,
others: &[&BoundExpr],
levers: Levers,
) -> Vec<PlanLine> {
let mut out = Vec::new();
subquery_nodes_into(filter, others, levers, 0, &mut out);
out
}
fn subquery_nodes_into(
filter: Option<&BoundExpr>,
others: &[&BoundExpr],
levers: Levers,
depth: u16,
out: &mut Vec<PlanLine>,
) {
let mut filtering: Vec<&BoundExpr> = Vec::new();
if let Some(filter) = filter {
subqueries_in(filter, &mut filtering);
}
let in_filter: Vec<usize> = filtering
.iter()
.filter_map(|expr| match expr {
BoundExpr::Subquery { id, .. } => Some(*id),
_ => None,
})
.collect();
let mut found = filtering;
for root in others {
subqueries_in(root, &mut found);
}
let mut seen: Vec<usize> = Vec::new();
for expr in found {
let BoundExpr::Subquery {
id, kind, block, ..
} = expr
else {
continue;
};
if seen.contains(id) {
continue;
}
seen.push(*id);
let correlated = if block.correlations.is_empty() {
""
} else {
"CORRELATED "
};
let noun = match kind {
SubqueryKind::In => "LIST",
_ => "SCALAR",
};
push(
out,
depth,
format!("{correlated}{noun} SUBQUERY {}", last_serial(block)),
);
let inner = plan_select_with((**block).clone(), levers);
tree_of(&inner, depth + 1, out);
if *kind == SubqueryKind::In && in_filter.contains(id) {
push(out, depth + 1, "CREATE BLOOM FILTER");
}
}
}
fn subqueries_in<'e>(expr: &'e BoundExpr, found: &mut Vec<&'e BoundExpr>) {
if matches!(expr, BoundExpr::Subquery { .. }) {
found.push(expr);
if let BoundExpr::Subquery {
operand: Some(operand),
..
} = expr
{
subqueries_in(operand, found);
}
return;
}
for child in expr.children() {
subqueries_in(child, found);
}
}
fn operator_name(op: CompoundOp) -> &'static str {
match op {
CompoundOp::Union => "UNION",
CompoundOp::UnionAll => "UNION ALL",
CompoundOp::Intersect => "INTERSECT",
CompoundOp::Except => "EXCEPT",
}
}
fn first_arm(plan: &PhysicalPlan) -> PhysicalPlan {
let mut arm = plan.clone();
arm.compounds.clear();
arm.needs_sort = false;
arm.reverse = false;
arm.select.order_by.clear();
arm.select.limit = None;
arm.select.offset = None;
arm
}
fn compound_tree(plan: &PhysicalPlan, depth: u16, out: &mut Vec<PlanLine>) {
let ordered = !plan.select.order_by.is_empty();
if !ordered
&& plan
.compounds
.iter()
.all(|(op, _)| *op == CompoundOp::UnionAll)
{
push(out, depth, "COMPOUND QUERY");
push(out, depth + 1, "LEFT-MOST SUBQUERY");
tree_of(&first_arm(plan), depth + 2, out);
for (op, arm) in &plan.compounds {
push(out, depth + 1, operator_name(*op));
tree_of(arm, depth + 2, out);
}
return;
}
let mut arms: Vec<(Option<CompoundOp>, BoundSelect)> = vec![(None, first_arm(plan).select)];
for (op, arm) in &plan.compounds {
arms.push((Some(*op), arm.select.clone()));
}
merge_tree(&arms, &plan.select.order_by, plan.levers, depth, out);
}
fn merge_tree(
arms: &[(Option<CompoundOp>, BoundSelect)],
order: &[BoundOrderTerm],
levers: Levers,
depth: u16,
out: &mut Vec<PlanLine>,
) {
let Some(((op, last), before)) = arms.split_last() else {
return;
};
let op = op.unwrap_or(CompoundOp::UnionAll);
push(out, depth, format!("MERGE ({})", operator_name(op)));
push(out, depth + 1, "LEFT");
match before {
[(_, only)] => sorted_arm(only, order, levers, depth + 2, out),
_ => merge_tree(before, order, levers, depth + 2, out),
}
push(out, depth + 1, "RIGHT");
sorted_arm(last, order, levers, depth + 2, out);
}
fn sorted_arm(
arm: &BoundSelect,
order: &[BoundOrderTerm],
levers: Levers,
depth: u16,
out: &mut Vec<PlanLine>,
) {
let mut sorted = arm.clone();
let mut terms: Vec<BoundOrderTerm> = Vec::new();
let mut named: Vec<usize> = Vec::new();
for term in order {
let BoundExpr::SorterColumn { column } = term.expr else {
continue;
};
let Some(result) = arm.columns.get(usize::from(column)) else {
continue;
};
named.push(usize::from(column));
terms.push(BoundOrderTerm {
expr: result.expr.clone(),
..term.clone()
});
}
for (position, result) in arm.columns.iter().enumerate() {
if named.contains(&position) {
continue;
}
terms.push(BoundOrderTerm {
expr: result.expr.clone(),
order: crate::ast::SortOrder::Ascending,
nulls: crate::ast::NullOrder::First,
collation: Collation::Binary,
});
}
sorted.order_by = terms;
let planned = plan_select_with(sorted, levers);
select_tree(&planned, depth, out);
}
fn window_tree(plan: &PhysicalPlan, depth: u16, out: &mut Vec<PlanLine>) {
let number = highest_serial(&plan.select).saturating_add(1);
let name = format!("(subquery-{number})");
push(out, depth, format!("CO-ROUTINE {name}"));
derived_nodes(plan, depth + 1, out);
for line in describe::loop_lines(plan) {
push(out, depth + 1, line);
}
let sorts = plan
.select
.windows
.first()
.is_some_and(|window| !window.partition_by.is_empty() || !window.order_by.is_empty());
if sorts {
push(out, depth + 1, "USE TEMP B-TREE FOR ORDER BY");
}
push(out, depth, format!("SCAN {name}"));
let same_order = plan.select.windows.first().is_some_and(|window| {
window.partition_by.is_empty()
&& window.order_by.len() == plan.select.order_by.len()
&& window
.order_by
.iter()
.zip(&plan.select.order_by)
.all(|(left, right)| left.expr == right.expr && left.order == right.order)
});
if !plan.select.order_by.is_empty() && !same_order {
push(out, depth, "USE TEMP B-TREE FOR ORDER BY");
}
}
fn highest_serial(select: &BoundSelect) -> u32 {
let mut highest = select.serial;
for source in &select.sources {
if let SourceRows::Subquery(block) = &source.rows {
highest = highest.max(highest_serial(block));
}
}
for (_, arm) in &select.compounds {
highest = highest.max(highest_serial(arm));
}
let mut probe = select.clone();
crate::rewrite::rewrite_select(&mut probe, &mut |expr: &mut BoundExpr| {
if let Some(block) = expr.block_mut() {
highest = highest.max(block.serial);
}
});
highest
}