use std::ops::BitAnd;
use std::ops::Range;
use std::sync::Arc;
use bit_vec::BitVec;
use futures::FutureExt;
use futures::future::BoxFuture;
use vortex_array::ArrayRef;
use vortex_array::MaskFuture;
use vortex_array::expr::BoundExpression;
use vortex_error::VortexResult;
use vortex_mask::Mask;
use vortex_scan::row_mask::RowMask;
use crate::LayoutReaderRef;
use crate::scan::filter::FilterExpr;
pub(crate) type SplitFuture = BoxFuture<'static, VortexResult<Option<ArrayRef>>>;
pub(crate) fn split_exec(ctx: &TaskContext, read_mask: RowMask) -> VortexResult<SplitFuture> {
let row_range = read_mask.row_range();
let row_mask = read_mask.mask().clone();
let Some(filter) = ctx.filter.as_ref() else {
return split_projection(ctx, &row_range, row_mask);
};
let filter_mask = build_filter_mask(&ctx.reader, filter, &row_range, row_mask);
let projection =
ctx.reader
.projection_evaluation(&row_range, &ctx.projection, filter_mask.clone())?;
Ok(async move {
if filter_mask.await?.all_false() {
return Ok(None);
}
projection.await.map(Some)
}
.boxed())
}
pub(crate) fn split_projection(
ctx: &TaskContext,
row_range: &Range<u64>,
mask: Mask,
) -> VortexResult<SplitFuture> {
if mask.all_false() {
return Ok(futures::future::ready(Ok(None)).boxed());
}
let projection =
ctx.reader
.projection_evaluation(row_range, &ctx.projection, MaskFuture::ready(mask))?;
Ok(projection.map(|array| array.map(Some)).boxed())
}
fn build_filter_mask(
reader: &LayoutReaderRef,
filter: &Arc<FilterExpr>,
row_range: &Range<u64>,
row_mask: Mask,
) -> MaskFuture {
let reader = Arc::clone(reader);
let filter = Arc::clone(filter);
let filter_row_range = row_range.clone();
MaskFuture::new(row_mask.len(), async move {
let mut mask = row_mask;
let mut dynamic_versions = vec![None; filter.conjuncts().len()];
for (idx, conjunct) in filter.conjuncts().iter().enumerate() {
if mask.all_false() {
return Ok(mask);
}
dynamic_versions[idx] = filter.dynamic_updates(idx).map(|du| du.version());
let conjunct_mask = reader
.pruning_evaluation(&filter_row_range, conjunct, mask.clone())?
.await?;
mask = mask.bitand(&conjunct_mask);
}
let mut remaining = BitVec::from_elem(filter.conjuncts().len(), true);
while let Some(idx) = filter.next_conjunct(&remaining) {
remaining.set(idx, false);
if mask.all_false() {
return Ok(mask);
}
let conjunct = &filter.conjuncts()[idx];
let current_version = filter.dynamic_updates(idx).map(|du| du.version());
if let Some(dv) = current_version
&& dynamic_versions[idx].is_none_or(|v| v < dv)
{
dynamic_versions[idx] = Some(dv);
let conjunct_mask = reader
.pruning_evaluation(&filter_row_range, conjunct, mask.clone())?
.await?;
mask = mask.bitand(&conjunct_mask);
}
if mask.all_false() {
return Ok(mask);
}
let input_true_count = mask.true_count();
let conjunct_mask = reader
.filter_evaluation(&filter_row_range, conjunct, MaskFuture::ready(mask))?
.await?;
filter.report_selectivity(
idx,
conditional_selectivity(input_true_count, conjunct_mask.true_count()),
);
mask = conjunct_mask;
}
Ok(mask)
})
}
fn conditional_selectivity(input_true_count: usize, output_true_count: usize) -> f64 {
debug_assert!(input_true_count > 0);
debug_assert!(output_true_count <= input_true_count);
output_true_count as f64 / input_true_count as f64
}
pub(crate) struct TaskContext {
pub(crate) filter: Option<Arc<FilterExpr>>,
pub(crate) reader: LayoutReaderRef,
pub(crate) projection: BoundExpression,
}
#[cfg(test)]
mod tests {
use super::conditional_selectivity;
#[test]
fn selectivity_is_relative_to_the_input_mask() {
assert_eq!(conditional_selectivity(20, 5), 0.25);
}
}