use super::{AccessPath, AggregationMode, Levers, PhysicalPlan};
use crate::ast::{BinaryOp, JoinKind};
use crate::bind::BoundExpr;
use crate::catalog_view::{TableInfo, TableKind};
pub(super) fn index_seek_detail(
index_name: &[u8],
info: Option<&TableInfo>,
equalities: usize,
ends: (bool, bool),
) -> String {
let keyed = info.and_then(|held| {
held.indexes
.iter()
.find(|candidate| candidate.name == index_name)
});
let named = |position: usize| -> String {
let Some(key) = keyed.and_then(|index| index.columns.get(position)) else {
return "?".to_string();
};
match key.column {
None => "<expr>".to_string(),
Some(at) => info
.and_then(|held| held.column(at))
.map(|column| String::from_utf8_lossy(&column.name).into_owned())
.unwrap_or_else(|| "?".to_string()),
}
};
let mut detail = String::new();
for index in 0..equalities {
if index > 0 {
detail.push_str(" AND ");
}
detail.push_str(&format!("{}=?", named(index)));
}
if ends.0 || ends.1 {
if !detail.is_empty() {
detail.push_str(" AND ");
}
detail.push_str(&range_detail(&named(equalities), ends.0, ends.1));
}
detail
}
pub(super) fn range_detail(column: &str, low: bool, high: bool) -> String {
match (low, high) {
(true, true) => format!("{column}>? AND {column}<?"),
(false, true) => format!("{column}<?"),
_ => format!("{column}>?"),
}
}
pub(super) fn automatic_index_lines(
plan: &PhysicalPlan,
position: usize,
shown: &str,
) -> Option<Vec<String>> {
let source = plan.sources.get(position)?;
let derived = matches!(
source.path,
AccessPath::Subquery {
correlated: false,
..
}
);
let table = matches!(source.path, AccessPath::TableScan { .. })
&& source.table.kind == TableKind::Table;
let eligible = (position > 0 || derived)
&& plan.levers.has(Levers::AUTOMATIC_INDEX)
&& (table || derived)
&& matches!(
source.join,
JoinKind::Inner | JoinKind::Cross | JoinKind::Comma | JoinKind::Left
);
if !eligible {
return None;
}
let condition = source
.on
.as_ref()
.or_else(|| plan.residuals.get(position).and_then(Option::as_ref))?;
let local: Vec<usize> = plan.sources.iter().map(|held| held.id).collect();
let earlier: Vec<usize> = plan
.sources
.iter()
.take(position)
.map(|held| held.id)
.collect();
let mut written = Vec::new();
let mut flipped = Vec::new();
for term in super::conjunction(condition) {
let BoundExpr::Compare {
op: BinaryOp::Equal,
left,
right,
..
} = &term
else {
continue;
};
match (key_column(left, source.id), key_column(right, source.id)) {
(Some(column), None) if reads_only(right, &earlier, &local) => written.push(column),
(None, Some(column)) if reads_only(left, &earlier, &local) => flipped.push(column),
_ => {}
}
}
if written.is_empty() && flipped.is_empty() {
return None;
}
let keys: Vec<String> = written
.into_iter()
.chain(flipped.into_iter().rev())
.map(|column| {
let name = source
.table
.column(column)
.map(|held| String::from_utf8_lossy(&held.name).into_owned())
.unwrap_or_else(|| "?".to_string());
format!("{name}=?")
})
.collect();
let detail = keys.join(" AND ");
let suffix = if source.join == JoinKind::Left {
" LEFT-JOIN"
} else {
""
};
Some(vec![
format!("BLOOM FILTER ON {shown} ({detail})"),
format!("SEARCH {shown} USING AUTOMATIC COVERING INDEX ({detail}){suffix}"),
])
}
fn key_column(expr: &BoundExpr, id: usize) -> Option<u16> {
match expr {
BoundExpr::Column { source, column, .. } if *source == id => Some(*column),
_ => None,
}
}
fn reads_only(expr: &BoundExpr, earlier: &[usize], local: &[usize]) -> bool {
let mut used = Vec::new();
expr.sources_used(&mut used);
!used.is_empty()
&& used
.iter()
.all(|source| earlier.contains(source) || !local.contains(source))
}
pub(super) fn loop_lines(plan: &PhysicalPlan) -> Vec<String> {
let mut lines = Vec::new();
for (position, source) in plan.sources.iter().enumerate() {
let bound = plan.select.sources.iter().find(|held| held.id == source.id);
let shown = match (&source.written_schema, bound) {
(Some(schema), _) => format!(
"{}.{}",
String::from_utf8_lossy(schema),
String::from_utf8_lossy(&source.alias)
),
(None, Some(bound)) => super::tree::loop_name(bound),
(None, None) => String::from_utf8_lossy(&source.alias).into_owned(),
};
if let Some(automatic) = automatic_index_lines(plan, position, &shown) {
lines.extend(automatic);
continue;
}
let mut line = source.path.describe_over(&shown, Some(&source.table));
if source.join == JoinKind::Left && position > 0 {
line.push_str(" LEFT-JOIN");
}
lines.push(line);
}
lines
}
pub(super) fn temp_lines(plan: &PhysicalPlan) -> Vec<String> {
let mut lines = Vec::new();
if plan.aggregation == AggregationMode::Grouped && !plan.grouped_walk {
lines.push("USE TEMP B-TREE FOR GROUP BY".to_string());
}
if plan.needs_sort {
lines.push("USE TEMP B-TREE FOR ORDER BY".to_string());
}
if plan.select.distinct && !plan.distinct_walk {
lines.push("USE TEMP B-TREE FOR DISTINCT".to_string());
}
lines
}
pub(super) fn write_lines(plan: &PhysicalPlan) -> Vec<String> {
loop_lines(plan)
.into_iter()
.chain(temp_lines(plan))
.map(|line| line.replace(" USING COVERING INDEX ", " USING INDEX "))
.collect()
}