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 all_on = outer_terms(source);
let on_terms: Vec<BoundExpr> = if source.join == JoinKind::Left {
all_on.clone()
} else if source.table.module.is_some() {
all_on
.iter()
.filter(|term| is_constant_table_argument(select, term))
.cloned()
.collect()
} 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 = source.join == JoinKind::Left
&& !on_terms.is_empty()
&& on_consumed.iter().all(|held| *held);
if source.table.module.is_some() {
let taken: Vec<&BoundExpr> = on_terms
.iter()
.zip(&on_consumed)
.filter(|(_, held)| **held)
.map(|(term, _)| term)
.collect();
remaining_on = Some(
all_on
.iter()
.filter(|term| !taken.contains(term))
.cloned()
.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 is_constant_table_argument(select: &BoundSelect, term: &BoundExpr) -> bool {
let BoundExpr::Compare { right, .. } = term else {
return false;
};
let mut used = Vec::new();
right.sources_used(&mut used);
used.is_empty() && super::flatten::is_table_argument(select, term)
}
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 late = ons_written_after_the_right_join(select, source.id);
for (slot, held_back) in offered.iter_mut().zip(&late) {
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)
&& !late.get(index).copied().unwrap_or(false)
{
*slot = *held;
}
}
path
}
fn ons_written_after_the_right_join(select: &BoundSelect, id: usize) -> Vec<bool> {
let owners = hint::statement_terms_with_owners(select).1;
let at = select.sources.iter().position(|source| source.id == id);
let right = at.and_then(|at| {
(at.saturating_add(1)..select.sources.len()).find(|later| {
select
.sources
.get(*later)
.is_some_and(|source| matches!(source.join, JoinKind::Right | JoinKind::Full))
})
});
owners
.iter()
.map(|owner| matches!((owner, right), (Some(owner), Some(right)) if *owner > right))
.collect()
}
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()
}