use super::filter::Candidates;
use super::{
graph_execution_error, DocId, DriverResult, OperatorOutput, OperatorTree, PostingList,
Predicate, SQLError,
};
use crate::parallel::ParallelExecutor;
mod support;
pub(super) fn execute(
parts: &[OperatorTree],
parallel: &ParallelExecutor,
execute: impl Fn(&OperatorTree) -> DriverResult<OperatorOutput> + Sync,
filter: impl Fn(&str, &Predicate, Option<Candidates<'_>>) -> DriverResult<PostingList> + Sync,
) -> DriverResult<OperatorOutput> {
let execute = &execute;
let filter = &filter;
let membership_only = parts.iter().all(OperatorTree::is_membership_only);
if !parts
.iter()
.any(|part| matches!(part, OperatorTree::Filter { source: None, .. }))
{
let workers: Vec<_> = parts.iter().map(|part| move || execute(part)).collect();
let outputs: DriverResult<Vec<_>> =
parallel.execute_branches(&workers).into_iter().collect();
return merge(outputs?, membership_only);
}
let workers: Vec<_> = parts
.iter()
.map(|part| {
move || match part {
OperatorTree::Filter { source: None, .. } => None,
_ => Some(execute(part)),
}
})
.collect();
let outputs = parallel.execute_branches(&workers);
let candidates = support(&outputs);
let require_documents = parts.iter().any(|part| {
!matches!(part, OperatorTree::Filter { source: None, .. })
&& support::promises_documents(part)
});
let candidates = candidates.as_deref().map(|ids| {
if require_documents {
Candidates::Documents(ids)
} else {
Candidates::Intersection(ids)
}
});
let workers: Vec<_> = parts
.iter()
.map(|part| {
move || match part {
OperatorTree::Filter {
field,
predicate,
source: None,
} => Some(filter(field, predicate, candidates).map(OperatorOutput::Posting)),
_ => None,
}
})
.collect();
let filters = parallel.execute_branches(&workers);
let outputs: DriverResult<Vec<_>> = outputs
.into_iter()
.zip(filters)
.map(|(output, filter)| {
output
.or(filter)
.expect("each intersection branch is executed once")
})
.collect();
merge(outputs?, membership_only)
}
fn support(outputs: &[Option<DriverResult<OperatorOutput>>]) -> Option<Vec<DocId>> {
let mut postings = Vec::new();
for output in outputs.iter().flatten() {
match output {
Ok(OperatorOutput::Posting(posting)) => postings.push(posting),
_ => return None,
}
}
let smallest = postings.iter().min_by_key(|posting| posting.len())?;
Some(
smallest
.entries()
.iter()
.filter_map(|entry| {
postings
.iter()
.all(|posting| {
posting
.entries()
.binary_search_by_key(&entry.doc_id, |entry| entry.doc_id)
.is_ok()
})
.then_some(entry.doc_id)
})
.collect(),
)
}
fn merge(outputs: Vec<OperatorOutput>, membership_only: bool) -> DriverResult<OperatorOutput> {
let mut iter = outputs.into_iter();
let Some(first) = iter.next() else {
return Ok(PostingList::new().into());
};
iter.try_fold(first, |acc, next| match (acc, next) {
(OperatorOutput::Posting(left), OperatorOutput::Posting(right)) => {
Ok(OperatorOutput::Posting(if membership_only {
left.merge_support_intersection_owned(&right)
} else {
left.merge_intersection_owned(&right)
}))
}
(OperatorOutput::Graph(left), OperatorOutput::Graph(right)) => left
.merge_intersection(&right)
.map(OperatorOutput::Graph)
.map_err(|error| graph_execution_error("GraphIntersect", error)),
(OperatorOutput::Generalized(left), OperatorOutput::Generalized(right)) => {
Ok(OperatorOutput::Generalized(left.merge_intersection(&right)))
}
_ => Err(SQLError::TypeMismatch(
"Intersect operands must use the same posting-list carrier".into(),
)),
})
}
#[cfg(test)]
mod tests;