use std::any::Any;
use std::ops::BitAnd;
use std::ops::Range;
use std::sync::Arc;
use futures::FutureExt;
use futures::TryFutureExt;
use futures::future::BoxFuture;
use futures::future::Shared;
use futures::future::try_join_all;
use itertools::Itertools;
use roaring::RoaringBitmap;
use tracing::trace;
use vortex_array::MaskFuture;
use vortex_array::VortexSessionExecute;
use vortex_array::dtype::DType;
use vortex_array::dtype::FieldMask;
use vortex_array::expr::BoundExpression;
use vortex_buffer::BitBufferMut;
use vortex_error::SharedVortexResult;
use vortex_error::VortexError;
use vortex_error::VortexResult;
use vortex_mask::Mask;
use vortex_session::VortexSession;
use vortex_utils::aliases::dash_map::DashMap;
use vortex_utils::aliases::dash_map::Entry;
use crate::ArrayFuture;
use crate::LayoutReader;
use crate::LayoutReaderContext;
use crate::LayoutReaderRef;
use crate::LazyReaderChildren;
use crate::RowSplits;
use crate::SplitRange;
use crate::layouts::indexed::IndexSpec;
use crate::layouts::indexed::IndexedLayout;
use crate::layouts::indexed::index::IndexExactness;
use crate::layouts::indexed::index::IndexId;
use crate::layouts::indexed::index::IndexVTable;
use crate::layouts::indexed::index::RowLocator;
use crate::scan::split_by::SplitBy;
use crate::segments::SegmentSource;
type SharedProbe = Shared<BoxFuture<'static, SharedVortexResult<Arc<RowLocator>>>>;
pub struct IndexedReader {
layout: IndexedLayout,
name: Arc<str>,
lazy_children: Arc<LazyReaderChildren>,
indexes: Vec<Arc<dyn OpenIndex>>,
claims: DashMap<BoundExpression, Option<Claims>>,
}
#[derive(Clone)]
struct Claims {
exact: Option<Arc<Claim>>,
pruning: Vec<Arc<Claim>>,
}
impl IndexedReader {
pub(crate) fn try_new(
layout: IndexedLayout,
name: Arc<str>,
segment_source: Arc<dyn SegmentSource>,
session: VortexSession,
ctx: LayoutReaderContext,
) -> VortexResult<Self> {
let mut dtypes = Vec::with_capacity(1 + layout.indexes().len());
let mut names = Vec::with_capacity(1 + layout.indexes().len());
dtypes.push(layout.dtype().clone());
names.push(Arc::clone(&name));
for spec in layout.indexes().iter() {
dtypes.push(spec.index_dtype().clone());
names.push(format!("{}.index:{}", name, spec.id()).into());
}
let lazy_children = Arc::new(LazyReaderChildren::new(
Arc::clone(layout.children()),
dtypes,
names,
segment_source,
session.clone(),
ctx,
));
let indexes = open_indexes(&layout, &lazy_children, &session)?;
Ok(Self {
layout,
name,
lazy_children,
indexes,
claims: DashMap::default(),
})
}
fn data_child(&self) -> VortexResult<&LayoutReaderRef> {
self.lazy_children.get(0)
}
fn claims(&self, expr: &BoundExpression) -> VortexResult<Option<Claims>> {
if let Some(cached) = self.claims.get(expr) {
return Ok(cached.value().clone());
}
match self.claims.entry(expr.clone()) {
Entry::Occupied(entry) => Ok(entry.get().clone()),
Entry::Vacant(entry) => {
let claims = self.plan_claims(expr)?;
entry.insert(claims.clone());
Ok(claims)
}
}
}
fn plan_claims(&self, expr: &BoundExpression) -> VortexResult<Option<Claims>> {
let mut exact = None;
let mut pruning = Vec::new();
for index in &self.indexes {
let Some(claim) = Arc::clone(index).claim(expr)? else {
continue;
};
let claim = Arc::new(claim);
if claim.exactness == IndexExactness::Exact {
if claim.partitions.declined.is_empty() {
return Ok(Some(Claims {
exact: Some(Arc::clone(&claim)),
pruning: vec![claim],
}));
}
exact.get_or_insert_with(|| Arc::clone(&claim));
}
pruning.push(claim);
}
if pruning.is_empty() {
return Ok(None);
}
Ok(Some(Claims { exact, pruning }))
}
}
fn open_indexes(
layout: &IndexedLayout,
children: &Arc<LazyReaderChildren>,
session: &VortexSession,
) -> VortexResult<Vec<Arc<dyn OpenIndex>>> {
let mut indexes = Vec::with_capacity(layout.indexes().len());
for (idx, spec) in layout.indexes().iter().enumerate() {
let Some(vtable) = spec.vtable() else {
trace!(index = %spec.id(), "index kind not registered, skipping");
continue;
};
let slot = idx + 1;
let partitions = Partitions::new(
spec,
layout.row_count(),
layout.children().child_row_count(slot),
);
let opened = vtable.open_reader(OpenArgs {
spec,
slot,
dtype: layout.dtype(),
children: Arc::clone(children),
partitions: Arc::new(partitions),
session,
})?;
match opened {
Some(index) => indexes.push(index),
None => trace!(
index = %spec.id(),
stored = %spec.index_dtype(),
"index dtype does not match what its kind declares, skipping"
),
}
}
Ok(indexes)
}
pub(crate) struct OpenArgs<'a> {
spec: &'a IndexSpec,
slot: usize,
dtype: &'a DType,
children: Arc<LazyReaderChildren>,
partitions: Arc<Partitions>,
session: &'a VortexSession,
}
pub(crate) trait OpenIndex: Send + Sync {
fn claim(self: Arc<Self>, expr: &BoundExpression) -> VortexResult<Option<Claim>>;
}
pub(crate) fn open_index<V: IndexVTable>(
vtable: Arc<V>,
args: OpenArgs<'_>,
) -> VortexResult<Option<Arc<dyn OpenIndex>>> {
let options = vtable.deserialize_options(args.spec.options())?;
let index_dtype = args.spec.index_dtype();
if vtable.index_dtype(args.dtype, &options).as_ref() != Some(index_dtype) {
return Ok(None);
}
Ok(Some(Arc::new(TypedOpenIndex {
id: args.spec.id(),
vtable,
options: Arc::new(options),
dtype: args.dtype.clone(),
index_dtype: index_dtype.clone(),
children: args.children,
slot: args.slot,
partitions: args.partitions,
chunks: DashMap::default(),
session: args.session.clone(),
})))
}
type SharedChunk<C> = Shared<BoxFuture<'static, SharedVortexResult<Arc<C>>>>;
struct TypedOpenIndex<V: IndexVTable> {
id: IndexId,
vtable: Arc<V>,
options: Arc<V::Options>,
dtype: DType,
index_dtype: DType,
children: Arc<LazyReaderChildren>,
slot: usize,
partitions: Arc<Partitions>,
chunks: DashMap<(u64, u64), SharedChunk<V::Chunk>>,
session: VortexSession,
}
impl<V: IndexVTable> TypedOpenIndex<V> {
fn index_reader(&self) -> VortexResult<&LayoutReaderRef> {
self.children.get(self.slot)
}
fn chunk(&self, rows: Range<u64>) -> VortexResult<SharedChunk<V::Chunk>> {
match self.chunks.entry((rows.start, rows.end)) {
Entry::Occupied(entry) => Ok(entry.get().clone()),
Entry::Vacant(entry) => {
let len = usize::try_from(rows.end - rows.start)?;
let array = self.index_reader()?.projection_evaluation(
&rows,
&BoundExpression::new_root(self.index_dtype.clone()),
MaskFuture::new_true(len),
)?;
let vtable = Arc::clone(&self.vtable);
let options = Arc::clone(&self.options);
let session = self.session.clone();
let chunk = async move {
let array = array.await?;
let mut ctx = session.create_execution_ctx();
Ok(Arc::new(vtable.decode(array, &options, &mut ctx)?))
}
.map_err(Arc::new)
.boxed()
.shared();
entry.insert(chunk.clone());
Ok(chunk)
}
}
}
}
impl<V: IndexVTable> OpenIndex for TypedOpenIndex<V> {
fn claim(self: Arc<Self>, expr: &BoundExpression) -> VortexResult<Option<Claim>> {
let Some(plan) = self
.vtable
.plan(expr, &self.dtype, &self.index_dtype, &self.options)?
else {
return Ok(None);
};
trace!(index = %self.id, %expr, filter = %plan.filter, "index claimed expression");
Ok(Some(Claim {
exactness: plan.exactness,
partitions: Arc::clone(&self.partitions),
prober: Arc::new(TypedProber {
index: self,
filter: plan.filter,
query: Arc::new(plan.query),
}),
probes: DashMap::default(),
}))
}
}
trait Prober: Send + Sync {
fn probe(&self, partition: usize) -> VortexResult<SharedProbe>;
}
struct TypedProber<V: IndexVTable> {
index: Arc<TypedOpenIndex<V>>,
filter: BoundExpression,
query: Arc<V::Query>,
}
impl<V: IndexVTable> Prober for TypedProber<V> {
fn probe(&self, partition: usize) -> VortexResult<SharedProbe> {
let index_rows = self.index.partitions.index_rows(partition);
let data_rows = self.index.partitions.data_rows(partition);
let mut chunks = Vec::new();
if !index_rows.is_empty() {
let reader = self.index.index_reader()?;
let bounds = SplitBy::Layout.splits(reader.as_ref(), &index_rows, &[FieldMask::All])?;
for (start, end) in bounds.into_iter().tuple_windows() {
let len = usize::try_from(end - start)?;
let pruned =
reader.pruning_evaluation(&(start..end), &self.filter, Mask::new_true(len))?;
chunks.push((start..end, pruned));
}
}
let index = Arc::clone(&self.index);
let query = Arc::clone(&self.query);
Ok(async move {
let mut surviving = Vec::with_capacity(chunks.len());
for (rows, pruned) in chunks {
if !pruned.await?.all_false() {
surviving.push(index.chunk(rows)?);
}
}
let decoded = try_join_all(surviving).await?;
let mut ctx = index.session.create_execution_ctx();
let locator = index.vtable.resolve(
&query,
&decoded,
data_rows.end - data_rows.start,
&index.options,
&mut ctx,
)?;
Ok(Arc::new(locator))
}
.map_err(|err: VortexError| Arc::new(err))
.boxed()
.shared())
}
}
pub(crate) struct Partitions {
len: u64,
data_row_count: u64,
index_ends: Arc<[u64]>,
declined: RoaringBitmap,
}
impl Partitions {
fn new(spec: &IndexSpec, data_row_count: u64, index_row_count: u64) -> Self {
match spec.partitioning() {
Some(partitioning) => Self {
len: partitioning.partition_len(),
data_row_count,
index_ends: partitioning.index_ends().into(),
declined: partitioning.declined().clone(),
},
None => Self {
len: data_row_count.max(1),
data_row_count,
index_ends: Arc::new([index_row_count]),
declined: RoaringBitmap::new(),
},
}
}
fn overlapping(&self, row_range: &Range<u64>) -> Range<usize> {
let count = self.index_ends.len();
let clamp = |partition: u64| usize::try_from(partition).map_or(count, |p| p.min(count));
let end = clamp(row_range.end.div_ceil(self.len));
clamp(row_range.start / self.len).min(end)..end
}
fn data_rows(&self, partition: usize) -> Range<u64> {
let start = partition as u64 * self.len;
start..(start + self.len).min(self.data_row_count)
}
fn index_rows(&self, partition: usize) -> Range<u64> {
let start = partition
.checked_sub(1)
.map_or(0, |prev| self.index_ends[prev]);
start..self.index_ends[partition]
}
fn is_declined(&self, partition: usize) -> bool {
u32::try_from(partition).is_ok_and(|partition| self.declined.contains(partition))
}
fn covers(&self, row_range: &Range<u64>) -> bool {
!self
.overlapping(row_range)
.any(|partition| self.is_declined(partition))
}
}
pub(crate) struct Claim {
exactness: IndexExactness,
partitions: Arc<Partitions>,
prober: Arc<dyn Prober>,
probes: DashMap<usize, SharedProbe>,
}
enum Part {
Fill(bool),
Probe(SharedProbe),
Deferred(usize),
}
impl Claim {
fn mask(
self: &Arc<Self>,
row_range: &Range<u64>,
selection: MaskFuture,
) -> VortexResult<BoxFuture<'static, VortexResult<Mask>>> {
let resolved = selection.clone().now_or_never().transpose()?;
let mut parts = Vec::new();
for partition in self.partitions.overlapping(row_range) {
let rows = self.partitions.data_rows(partition);
let overlap = row_range.start.max(rows.start)..row_range.end.min(rows.end);
let local = overlap.start - rows.start..overlap.end - rows.start;
let selected = usize::try_from(overlap.start - row_range.start)?
..usize::try_from(overlap.end - row_range.start)?;
let part = if self.partitions.is_declined(partition) {
Part::Fill(true)
} else if let Some(probe) = self.probes.get(&partition) {
Part::Probe(probe.clone())
} else {
match &resolved {
Some(mask) if mask.slice(selected.clone()).all_false() => Part::Fill(false),
Some(_) => Part::Probe(self.probe(partition)?),
None => Part::Deferred(partition),
}
};
parts.push((local, selected, part));
}
let len = usize::try_from(row_range.end - row_range.start)?;
let claim = Arc::clone(self);
Ok(async move {
let mut resolved = resolved;
let mut bits = BitBufferMut::with_capacity(len);
for (local, selected, part) in parts {
let probe = match part {
Part::Fill(value) => {
bits.append_n(value, usize::try_from(local.end - local.start)?);
continue;
}
Part::Probe(probe) => probe,
Part::Deferred(partition) => {
let mask = match resolved.take() {
Some(mask) => mask,
None => selection.clone().await?,
};
let skip = mask.slice(selected).all_false();
resolved = Some(mask);
if skip {
bits.append_n(false, usize::try_from(local.end - local.start)?);
continue;
}
claim.probe(partition)?
}
};
probe.await?.append_to(&local, &mut bits)?;
}
Ok(Mask::from(bits.freeze()))
}
.boxed())
}
fn probe(&self, partition: usize) -> VortexResult<SharedProbe> {
match self.probes.entry(partition) {
Entry::Occupied(entry) => Ok(entry.get().clone()),
Entry::Vacant(entry) => {
let probe = self.prober.probe(partition)?;
entry.insert(probe.clone());
Ok(probe)
}
}
}
}
impl LayoutReader for IndexedReader {
fn name(&self) -> &Arc<str> {
&self.name
}
fn as_any(&self) -> &dyn Any {
self
}
fn dtype(&self) -> &DType {
self.layout.dtype()
}
fn row_count(&self) -> u64 {
self.layout.row_count()
}
fn register_splits(
&self,
field_mask: &[FieldMask],
split_range: &SplitRange,
splits: &mut RowSplits,
) -> VortexResult<()> {
self.data_child()?
.register_splits(field_mask, split_range, splits)
}
fn pruning_evaluation(
&self,
row_range: &Range<u64>,
expr: &BoundExpression,
mask: Mask,
) -> VortexResult<MaskFuture> {
let data_eval = self
.data_child()?
.pruning_evaluation(row_range, expr, mask.clone())?;
let Some(claims) = self.claims(expr)? else {
return Ok(data_eval);
};
let masks = claims
.pruning
.iter()
.map(|claim| claim.mask(row_range, MaskFuture::ready(mask.clone())))
.collect::<VortexResult<Vec<_>>>()?;
let name = Arc::clone(&self.name);
let expr = expr.clone();
Ok(MaskFuture::new(mask.len(), async move {
let mut result = mask;
for claim_mask in masks {
if result.all_false() {
break;
}
result = result.bitand(&claim_mask.await?);
}
if !result.all_false() {
result = result.bitand(&data_eval.await?);
}
trace!(%name, %expr, density = result.density(), "index pruning evaluation");
Ok(result)
}))
}
fn filter_evaluation(
&self,
row_range: &Range<u64>,
expr: &BoundExpression,
mask: MaskFuture,
) -> VortexResult<MaskFuture> {
if let Some(exact) = self.claims(expr)?.and_then(|claims| claims.exact)
&& exact.partitions.covers(row_range)
{
let index_mask = exact.mask(row_range, mask.clone())?;
let len = mask.len();
return Ok(MaskFuture::new(len, async move {
let index_mask = index_mask.await?;
Ok(mask.await?.bitand(&index_mask))
}));
}
self.data_child()?.filter_evaluation(row_range, expr, mask)
}
fn projection_evaluation(
&self,
row_range: &Range<u64>,
expr: &BoundExpression,
mask: MaskFuture,
) -> VortexResult<ArrayFuture> {
self.data_child()?
.projection_evaluation(row_range, expr, mask)
}
}