use super::*;
pub(super) struct TermPath {
pub path: AccessPath,
pub on_enforced: bool,
pub remaining_on: Option<Option<BoundExpr>>,
}
pub(super) fn choose_term_path(
level: usize,
ids: &[usize],
source: &BoundSource,
select: &BoundSelect,
terms: &[BoundExpr],
consumed: &mut [bool],
levers: Levers,
) -> TermPath {
let mut on_enforced = false;
let mut remaining_on: Option<Option<BoundExpr>> = None;
let path = if is_outer(source.join) && matches!(source.rows, SourceRows::Table) {
let on_terms = if source.join == JoinKind::Left {
outer_terms(source)
} else {
Vec::new()
};
let mut on_consumed = vec![false; on_terms.len()];
let chosen = choose_path(
level,
ids,
source,
select,
&on_terms,
&mut on_consumed,
levers,
);
on_enforced = !on_terms.is_empty() && on_consumed.iter().all(|held| *held);
if source.table.module.is_some() {
remaining_on = Some(
on_terms
.iter()
.zip(&on_consumed)
.filter(|(_, held)| !**held)
.map(|(term, _)| term.clone())
.reduce(|left, right| BoundExpr::And(Box::new(left), Box::new(right))),
);
}
chosen
} else if precedes_a_right_join(select, source.id) {
choose_path_without_where(level, ids, source, select, terms, consumed, levers)
} else {
choose_path(level, ids, source, select, terms, consumed, levers)
};
TermPath {
path,
on_enforced,
remaining_on,
}
}
fn precedes_a_right_join(select: &BoundSelect, id: usize) -> bool {
let Some(position) = select.sources.iter().position(|source| source.id == id) else {
return false;
};
select
.sources
.iter()
.skip(position.saturating_add(1))
.any(|source| matches!(source.join, JoinKind::Right | JoinKind::Full))
}
fn choose_path_without_where(
level: usize,
ids: &[usize],
source: &BoundSource,
select: &BoundSelect,
terms: &[BoundExpr],
consumed: &mut [bool],
levers: Levers,
) -> AccessPath {
let mut filter_terms = Vec::new();
if let Some(filter) = &select.filter {
split_conjunction(filter, &mut filter_terms);
}
let withheld: Vec<bool> = filter_terms
.iter()
.map(|term| !super::flatten::is_table_argument(select, term))
.collect();
let mut offered = consumed.to_vec();
for (slot, held_back) in offered.iter_mut().zip(&withheld) {
if *held_back {
*slot = true;
}
}
let path = choose_path(level, ids, source, select, terms, &mut offered, levers);
for (index, (slot, held)) in consumed.iter_mut().zip(&offered).enumerate() {
if !withheld.get(index).copied().unwrap_or(false) {
*slot = *held;
}
}
path
}
pub(super) fn hold_terms_before_right_joins(
select: &BoundSelect,
terms: &[BoundExpr],
held_before: &[Option<usize>],
consumed: &mut [bool],
sources: &mut [PlannedSource],
) {
for (index, term) in terms.iter().enumerate() {
let Some(Some(position)) = held_before.get(index) else {
continue;
};
if consumed.get(index).copied().unwrap_or(false) {
continue;
}
let Some(id) = select.sources.get(*position).map(|source| source.id) else {
continue;
};
let Some(planned) = sources.iter_mut().find(|planned| planned.id == id) else {
continue;
};
planned.before = Some(match planned.before.take() {
Some(existing) => BoundExpr::And(Box::new(existing), Box::new(term.clone())),
None => term.clone(),
});
if let Some(mark) = consumed.get_mut(index) {
*mark = true;
}
}
}
pub(super) fn derived_affinities(block: &BoundSelect) -> Vec<inillucent_value::Affinity> {
block
.columns
.iter()
.enumerate()
.map(|(at, column)| match &column.expr {
BoundExpr::Column { affinity, .. }
if *affinity != inillucent_value::Affinity::Real
|| block.column_affinity(at).is_numeric() =>
{
*affinity
}
_ => inillucent_value::Affinity::Blob,
})
.collect()
}