use object_store::path::Path;
use parquet::arrow::ProjectionMask;
use crate::{
data_file::{IndexProjection, ROW_ID_FIELD_ID, RowIdSource},
error::{Error, Result},
};
pub(super) fn embedded_row_id_position(
schema: &parquet::schema::types::SchemaDescriptor,
) -> Option<usize> {
schema.root_schema().get_fields().iter().position(|field| {
let info = field.get_basic_info();
info.has_id() && u64::try_from(info.id()).ok() == Some(ROW_ID_FIELD_ID)
})
}
pub(super) fn resolve_row_id_source(
schema: &parquet::schema::types::SchemaDescriptor,
source: RowIdSource,
path: &Path,
) -> Result<(Option<usize>, u64)> {
match (source, embedded_row_id_position(schema)) {
(RowIdSource::Ordinal, Some(_)) => Err(Error::Constraint(format!(
"scoped read: {path} carries an embedded row-id column and cannot be read by ordinal"
))),
(RowIdSource::Ordinal, None) => Ok((None, 0)),
(RowIdSource::Resolve { .. }, Some(position)) => Ok((Some(position), 0)),
(
RowIdSource::Resolve {
row_id_start: Some(start),
},
None,
) => Ok((None, start)),
(RowIdSource::Resolve { row_id_start: None }, None) => Err(Error::Corruption(format!(
"scoped read: {path} is recorded as carrying per-row ids but has no embedded \
row-id column"
))),
}
}
pub(super) fn projection(
schema: &parquet::schema::types::SchemaDescriptor,
indexed_positions: &[usize],
row_id_position: Option<usize>,
) -> Result<(ProjectionMask, Vec<usize>, Option<usize>)> {
let mut projected: Vec<usize> = indexed_positions.to_vec();
if let Some(position) = row_id_position {
projected.push(position);
}
projected.sort_unstable();
projected.dedup();
if projected
.iter()
.any(|&position| position >= schema.root_schema().get_fields().len())
{
return Err(Error::Corruption(
"scoped read: projected column is out of bounds".to_owned(),
));
}
let mask = ProjectionMask::roots(schema, projected.iter().copied());
let output_index = |position: usize| {
projected
.iter()
.position(|&candidate| candidate == position)
.ok_or_else(|| Error::Corruption("scoped read: projected column vanished".to_owned()))
};
let indexed_output = indexed_positions
.iter()
.map(|&position| output_index(position))
.collect::<Result<Vec<_>>>()?;
let row_id_output = row_id_position.map(output_index).transpose()?;
Ok((mask, indexed_output, row_id_output))
}
pub(super) fn index_positions(projections: &[IndexProjection]) -> Vec<usize> {
let mut positions: Vec<_> = projections
.iter()
.flat_map(|projection| projection.positions.iter().copied())
.collect();
positions.sort_unstable();
positions.dedup();
positions
}
pub(super) fn remap_index_projections(
projections: Vec<IndexProjection>,
source_positions: &[usize],
output_positions: &[usize],
) -> Result<Vec<IndexProjection>> {
projections
.into_iter()
.map(|mut projection| {
let columns = projection.positions.len();
if projection.directions.len() != columns || projection.nulls.len() != columns {
return Err(Error::Corruption(format!(
"scoped read: index {} names {columns} columns but {} directions and {} \
null orders",
projection.index_id,
projection.directions.len(),
projection.nulls.len()
)));
}
for position in &mut projection.positions {
let source = source_positions.binary_search(position).map_err(|_| {
Error::Corruption(
"scoped read: index column vanished from projection".to_owned(),
)
})?;
*position = output_positions.get(source).copied().ok_or_else(|| {
Error::Corruption(
"scoped read: projected index column has no output".to_owned(),
)
})?;
}
Ok(projection)
})
.collect()
}