use std::sync::Arc;
use object_store::path::Path;
use parquet::{
arrow::arrow_reader::RowSelection,
file::metadata::{PageIndexPolicy, ParquetMetaData},
};
use crate::{
data_file::usize_as_u64,
error::{Error, Result},
};
#[derive(Debug, Clone, Copy)]
pub(crate) enum ScopedRows<'a> {
All,
At(&'a RowPositions),
From(u64),
}
impl ScopedRows<'_> {
pub(super) fn is_empty(self) -> bool {
matches!(self, Self::At(wanted) if wanted.is_empty())
}
pub(super) fn page_index_policy(self) -> PageIndexPolicy {
match self {
Self::All => PageIndexPolicy::Skip,
Self::At(_) | Self::From(_) => PageIndexPolicy::Optional,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct RowPositions(Arc<[u64]>);
impl RowPositions {
pub(crate) fn from_unsorted(mut positions: Vec<u64>) -> Self {
positions.sort_unstable();
positions.dedup();
Self(positions.into())
}
pub(crate) fn as_slice(&self) -> &[u64] {
&self.0
}
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub(crate) fn contains(&self, position: u64) -> bool {
self.0.binary_search(&position).is_ok()
}
}
impl FromIterator<u64> for RowPositions {
fn from_iter<T: IntoIterator<Item = u64>>(iter: T) -> Self {
Self::from_unsorted(iter.into_iter().collect())
}
}
#[derive(Debug, Clone, Copy)]
pub(super) enum Ordinals<'a> {
Dense,
Offset(u64),
Selected(&'a [u64]),
}
impl Ordinals<'_> {
pub(super) fn at(self, nth: usize) -> Result<u64> {
match self {
Self::Dense => Ok(usize_as_u64(nth)),
Self::Offset(start) => Ok(start.saturating_add(usize_as_u64(nth))),
Self::Selected(positions) => positions.get(nth).copied().ok_or_else(|| {
Error::Corruption(
"scoped read: the reader emitted more rows than the selection named".to_owned(),
)
}),
}
}
}
pub(super) enum OwnedOrdinals {
Dense,
Offset(u64),
Selected(RowPositions),
}
impl OwnedOrdinals {
pub(super) fn borrowed(&self) -> Ordinals<'_> {
match self {
Self::Dense => Ordinals::Dense,
Self::Offset(start) => Ordinals::Offset(*start),
Self::Selected(positions) => Ordinals::Selected(positions.as_slice()),
}
}
}
pub(super) fn scoped_selection(
rows: ScopedRows<'_>,
total: usize,
) -> Result<(Option<RowSelection>, OwnedOrdinals)> {
match rows {
ScopedRows::All => Ok((None, OwnedOrdinals::Dense)),
ScopedRows::At(positions) => Ok((
Some(row_selection(positions.as_slice(), total)?),
OwnedOrdinals::Selected(positions.clone()),
)),
ScopedRows::From(start_ordinal) => {
let start = usize::try_from(start_ordinal)
.ok()
.filter(|start| *start <= total)
.ok_or_else(|| {
Error::Corruption(format!(
"scoped read: resume row {start_ordinal} is past the file's {total} rows"
))
})?;
Ok((
Some(RowSelection::from_consecutive_ranges(
std::iter::once(start..total),
total,
)),
OwnedOrdinals::Offset(start_ordinal),
))
}
}
}
fn row_selection(positions: &[u64], total_rows: usize) -> Result<RowSelection> {
let ranges = positions
.iter()
.map(|&position| {
usize::try_from(position)
.ok()
.filter(|start| *start < total_rows)
.map(|start| start..start.saturating_add(1))
.ok_or_else(|| {
Error::Corruption(format!(
"scoped read: selected row {position} is past the file's {total_rows} rows"
))
})
})
.collect::<Result<Vec<_>>>()?;
Ok(RowSelection::from_consecutive_ranges(
ranges.into_iter(),
total_rows,
))
}
pub(super) fn total_rows(metadata: &ParquetMetaData, path: &Path) -> Result<usize> {
usize::try_from(metadata.file_metadata().num_rows())
.map_err(|_| Error::Corruption(format!("scoped read: {path} reports a negative row count")))
}