use super::*;
const MAX_SEEK_UNION_BRANCHES: usize = 512;
pub(super) fn rowid_in_list_path(
id: usize,
position: usize,
ids: &[usize],
table: &TableInfo,
terms: &[BoundExpr],
consumed: &mut [bool],
) -> Option<AccessPath> {
if position != 0 {
return None;
}
for (index, term) in terms.iter().enumerate() {
if consumed.get(index).copied().unwrap_or(false) {
continue;
}
let BoundExpr::InList {
negated: false,
operand,
list,
..
} = term
else {
continue;
};
if !matches!(operand.as_ref(), BoundExpr::Rowid { source } if *source == id) {
continue;
}
if list.is_empty() || list.len() > MAX_SEEK_UNION_BRANCHES {
continue;
}
if !list.iter().all(|value| is_available(position, ids, value)) {
continue;
}
let mut keys: Vec<BoundExpr> = Vec::with_capacity(list.len());
for value in list {
if !keys.contains(value) {
keys.push(value.clone());
}
}
if let Some(slot) = consumed.get_mut(index) {
*slot = true;
}
return Some(AccessPath::RowidSeekUnion {
root: table.root,
keys,
});
}
None
}
pub(super) fn in_list_union_path(
context: &super::CandidateContext<'_>,
index: &IndexInfo,
usable: bool,
) -> Option<(AccessPath, Vec<usize>)> {
let super::CandidateContext {
id,
position,
ids,
table,
terms,
consumed,
levers,
..
} = *context;
if position != 0 {
return None;
}
let mut prefix: Vec<BoundExpr> = Vec::new();
let mut prefix_unconverted: Vec<usize> = Vec::new();
let mut prefix_terms: Vec<usize> = Vec::new();
let mut collations: Vec<Collation> = Vec::new();
let mut descending: Vec<bool> = Vec::new();
let mut columns: Vec<Option<u16>> = Vec::new();
let mut at = 0usize;
while let Some(key_column) = index.columns.get(at) {
let Some(column) = key_column.plain_column() else {
break;
};
let collation = collation_of(&key_column.collation);
let found = terms.iter().enumerate().find(|(term_index, term)| {
!consumed.get(*term_index).copied().unwrap_or(false)
&& !prefix_terms.contains(term_index)
&& comparison_collation(term) == collation
&& indexable_comparison(id, column, term).is_some_and(|(op, value)| {
op == BinaryOp::Equal && is_available(position, ids, &value)
})
});
let Some((term_index, term)) = found else {
break;
};
let Some((_, value)) = indexable_comparison(id, column, term) else {
break;
};
if compares_unconverted(term) {
prefix_unconverted.push(prefix.len());
}
prefix.push(value);
prefix_terms.push(term_index);
collations.push(collation);
descending.push(key_column.descending);
columns.push(Some(column));
at = at.saturating_add(1);
}
let key_column = index.columns.get(at)?;
let column = key_column.plain_column()?;
let collation = collation_of(&key_column.collation);
collations.push(collation);
descending.push(key_column.descending);
columns.push(Some(column));
for (term_index, term) in terms.iter().enumerate() {
if consumed.get(term_index).copied().unwrap_or(false) || prefix_terms.contains(&term_index)
{
continue;
}
let BoundExpr::InList {
negated: false,
operand,
list,
collation: in_collation,
..
} = term
else {
continue;
};
let BoundExpr::Column {
source: term_source,
column: candidate,
..
} = operand.as_ref()
else {
continue;
};
if *term_source != id || *candidate != column || *in_collation != collation {
continue;
}
if list.is_empty() || list.len() > MAX_SEEK_UNION_BRANCHES {
continue;
}
if !list.iter().all(|value| is_available(position, ids, value)) {
continue;
}
let branches = in_list_branches(list, &prefix, &prefix_unconverted);
let covering = levers
.has(Levers::COVERING_INDEX)
.then(|| covering_slots(table, index, context.needed, usable))
.flatten();
return Some((
AccessPath::IndexSeekUnion {
table_root: table.root,
index_root: index.root,
index_name: index.name.clone(),
branches,
collations: collations.clone(),
descending: descending.clone(),
columns: columns.clone(),
without_rowid: table.without_rowid,
key_entry_slots: if table.without_rowid && index.root != table.root {
let leading = index.columns.len();
(0..table.primary_key().len())
.map(|offset| leading.saturating_add(offset))
.collect()
} else {
Vec::new()
},
covering,
dedup: true,
},
{
let mut used = prefix_terms.clone();
used.push(term_index);
used
},
));
}
None
}
pub(super) fn keyset_range_union_path(
context: &super::CandidateContext<'_>,
index: &IndexInfo,
usable: bool,
) -> Option<(AccessPath, Vec<usize>)> {
let super::CandidateContext {
id,
position,
ids,
table,
terms,
consumed,
needed,
levers,
..
} = *context;
if position != 0 {
return None;
}
for (term_index, term) in terms.iter().enumerate() {
if consumed.get(term_index).copied().unwrap_or(false) {
continue;
}
if !matches!(term, BoundExpr::Or(_, _)) {
continue;
}
let mut flat = Vec::new();
flatten_or(term, &mut flat);
if flat.len() < 2 {
continue;
}
let Some(branches) = keyset_branches(id, position, ids, index, &flat) else {
continue;
};
let depth = branches.len();
let mut collations = Vec::with_capacity(depth);
let mut descending = Vec::with_capacity(depth);
let mut columns = Vec::with_capacity(depth);
for key_column in index.columns.iter().take(depth) {
collations.push(collation_of(&key_column.collation));
descending.push(key_column.descending);
columns.push(key_column.plain_column());
}
let covering = levers
.has(Levers::COVERING_INDEX)
.then(|| covering_slots(table, index, needed, usable))
.flatten();
return Some((
AccessPath::IndexSeekUnion {
table_root: table.root,
index_root: index.root,
index_name: index.name.clone(),
branches,
collations,
descending,
columns,
without_rowid: table.without_rowid,
key_entry_slots: if table.without_rowid && index.root != table.root {
let leading = index.columns.len();
(0..table.primary_key().len())
.map(|offset| leading.saturating_add(offset))
.collect()
} else {
Vec::new()
},
covering,
dedup: false,
},
vec![term_index],
));
}
None
}
fn flatten_or(expr: &BoundExpr, into: &mut Vec<BoundExpr>) {
match expr {
BoundExpr::Or(left, right) => {
flatten_or(left, into);
flatten_or(right, into);
}
other => into.push(other.clone()),
}
}
fn in_list_branches(
list: &[BoundExpr],
prefix: &[BoundExpr],
prefix_unconverted: &[usize],
) -> Vec<IndexSeekBranch> {
let mut branches = Vec::with_capacity(list.len());
let mut seen: Vec<&BoundExpr> = Vec::with_capacity(list.len());
for value in list {
if matches!(value, BoundExpr::Null) {
continue;
}
if seen.contains(&value) {
continue;
}
seen.push(value);
let mut equalities = prefix.to_vec();
equalities.push(value.clone());
branches.push(IndexSeekBranch {
equalities,
unconverted: prefix_unconverted.to_vec(),
low: None,
high: None,
});
}
branches
}
fn keyset_branches(
id: usize,
position: usize,
ids: &[usize],
index: &IndexInfo,
flat: &[BoundExpr],
) -> Option<Vec<IndexSeekBranch>> {
let max_depth = index.columns.len().min(flat.len());
if max_depth == 0 || flat.len() != max_depth {
return None;
}
let mut by_depth: Vec<Option<IndexSeekBranch>> = vec![None; max_depth];
for arm in flat {
let mut conjuncts = Vec::new();
split_conjunction(arm, &mut conjuncts);
let (range_term, equality_terms) = conjuncts.split_last()?;
let depth = equality_terms.len().saturating_add(1);
if depth > max_depth || by_depth.get(depth - 1)?.is_some() {
return None;
}
let mut equalities = Vec::with_capacity(equality_terms.len());
let mut unconverted = Vec::new();
for (at, eq_term) in equality_terms.iter().enumerate() {
let key_column = index.columns.get(at)?;
let column = key_column.plain_column()?;
let collation = collation_of(&key_column.collation);
let (op, value) = indexable_comparison(id, column, eq_term)?;
if op != BinaryOp::Equal
|| comparison_collation(eq_term) != collation
|| !is_available(position, ids, &value)
{
return None;
}
if compares_unconverted(eq_term) {
unconverted.push(equalities.len());
}
equalities.push(value);
}
let key_column = index.columns.get(equality_terms.len())?;
let column = key_column.plain_column()?;
if key_column.descending {
return None;
}
let collation = collation_of(&key_column.collation);
let (op, value) = indexable_comparison(id, column, range_term)?;
if op != BinaryOp::Greater
|| comparison_collation(range_term) != collation
|| !is_available(position, ids, &value)
{
return None;
}
let slot = by_depth.get_mut(depth.saturating_sub(1))?;
*slot = Some(IndexSeekBranch {
equalities,
unconverted,
low: Some(RangeBound {
kind: BoundKind::Greater,
value,
unconverted: compares_unconverted(range_term),
}),
high: None,
});
}
if by_depth.iter().any(Option::is_none) {
return None;
}
let mut ordered: Vec<IndexSeekBranch> = by_depth.into_iter().flatten().collect();
ordered.reverse();
Some(ordered)
}