use super::*;
pub(super) fn statement_terms(select: &BoundSelect) -> Vec<BoundExpr> {
statement_terms_with_owners(select).0
}
pub(super) fn statement_terms_with_owners(
select: &BoundSelect,
) -> (Vec<BoundExpr>, Vec<Option<usize>>) {
let track = select
.sources
.iter()
.any(|source| matches!(source.join, JoinKind::Right | JoinKind::Full));
let mut terms = Vec::new();
let mut owners = Vec::new();
if let Some(filter) = &select.filter {
split_conjunction(filter, &mut terms);
}
for (position, source) in select.sources.iter().enumerate() {
if is_outer(source.join) {
continue;
}
if let Some(constraint) = &source.constraint {
if track {
owners.resize(terms.len(), None);
}
split_conjunction(constraint, &mut terms);
if track {
owners.resize(terms.len(), Some(position));
}
}
}
(terms, owners)
}
pub(super) fn terms_held_before_a_right_join(
select: &BoundSelect,
owners: &[Option<usize>],
) -> Vec<Option<usize>> {
if owners.is_empty() {
return Vec::new();
}
owners
.iter()
.map(|owner| {
let from = (*owner)?;
(from.saturating_add(1)..select.sources.len()).find(|later| {
select
.sources
.get(*later)
.is_some_and(|source| matches!(source.join, JoinKind::Right | JoinKind::Full))
})
})
.collect()
}
pub(super) fn outer_terms(source: &BoundSource) -> Vec<BoundExpr> {
let mut terms = Vec::new();
if let Some(constraint) = &source.constraint {
split_conjunction(constraint, &mut terms);
}
terms
}
pub fn unanswerable_index_hint(select: &BoundSelect) -> Option<Vec<u8>> {
if is_a_plain_count(select) {
return None;
}
let mut shared: Option<Vec<BoundExpr>> = None;
for (position, source) in select.sources.iter().enumerate() {
let crate::bind::IndexChoice::Only(wanted) = &source.index_hint else {
continue;
};
if !matches!(source.rows, SourceRows::Table) {
continue;
}
let table = &source.table;
let Some((at, index)) = table
.indexes
.iter()
.enumerate()
.find(|(_, index)| &index.folded == wanted)
else {
continue;
};
let answerable = if table.module.is_some() {
false
} else if index.origin == crate::catalog_view::IndexOrigin::Module {
let id = source.id;
matches!(
vector_path(id, position, source, select),
Some(AccessPath::VectorProbe { index: ref chosen, .. }) if chosen == &index.name
)
} else if is_outer(source.join) {
index_usable(source, at, index, &outer_terms(source))
} else {
let terms = shared.get_or_insert_with(|| statement_terms(select));
index_usable(source, at, index, terms)
};
if !answerable {
return Some(index.name.clone());
}
}
None
}
fn is_a_plain_count(select: &BoundSelect) -> bool {
select.sources.len() == 1
&& select.filter.is_none()
&& select.group_by.is_empty()
&& select.having.is_none()
&& select.compounds.is_empty()
&& select.windows.is_empty()
&& !select.distinct
&& select.columns.len() == 1
&& select.aggregates.len() == 1
&& select
.aggregates
.iter()
.all(|aggregate| aggregate.star && aggregate.filter.is_none() && !aggregate.distinct)
}
pub(super) fn index_usable(
source: &BoundSource,
at: usize,
index: &IndexInfo,
terms: &[BoundExpr],
) -> bool {
let computed = source.index_exprs.iter().find(|held| held.position == at);
index.partial_sql.is_none() || implies(computed, terms)
}
pub(super) fn forced_path(
id: usize,
position: usize,
ids: &[usize],
source: &BoundSource,
select: &BoundSelect,
terms: &[BoundExpr],
consumed: &mut [bool],
levers: Levers,
) -> AccessPath {
let needed = select.columns_read(id);
index_path(id, position, ids, source, terms, consumed, &needed, levers).unwrap_or(
AccessPath::TableScan {
root: source.table.root,
},
)
}