use std::collections::BTreeSet;
use parquet::{
arrow::{ProjectionMask, arrow_reader::RowSelection},
schema::types::SchemaDescriptor,
};
use roaring::RoaringBitmap;
use crate::{AisleResult, Expr, selection::row_selection_to_roaring};
#[derive(Clone, Debug)]
pub struct PruneResult {
row_groups: Vec<usize>,
row_selection: Option<RowSelection>,
roaring: Option<RoaringBitmap>,
compile: AisleResult,
output_projection: Option<Vec<String>>,
predicate_columns: Vec<String>,
required_columns: Vec<String>,
fallback_required_projection_all: bool,
}
impl PruneResult {
pub fn row_groups(&self) -> &[usize] {
&self.row_groups
}
pub fn row_selection(&self) -> Option<&RowSelection> {
self.row_selection.as_ref()
}
pub fn roaring(&self) -> Option<&RoaringBitmap> {
self.roaring.as_ref()
}
pub fn compile_result(&self) -> &AisleResult {
&self.compile
}
pub fn output_projection(&self) -> Option<&[String]> {
self.output_projection.as_deref()
}
pub fn predicate_columns(&self) -> &[String] {
&self.predicate_columns
}
pub fn required_columns(&self) -> &[String] {
&self.required_columns
}
pub fn output_projection_mask(
&self,
parquet_schema: &SchemaDescriptor,
) -> Option<ProjectionMask> {
self.output_projection.as_ref().map(|columns| {
ProjectionMask::columns(parquet_schema, columns.iter().map(String::as_str))
})
}
pub fn required_projection_mask(&self, parquet_schema: &SchemaDescriptor) -> ProjectionMask {
if self.fallback_required_projection_all || self.required_columns.is_empty() {
ProjectionMask::all()
} else {
ProjectionMask::columns(
parquet_schema,
self.required_columns.iter().map(String::as_str),
)
}
}
pub fn into_roaring(self, total_rows: u64) -> Option<RoaringBitmap> {
if let Some(roaring) = self.roaring {
return Some(roaring);
}
self.row_selection
.and_then(|sel| row_selection_to_roaring(&sel, total_rows))
}
pub fn into_parts(
self,
) -> (
Vec<usize>,
Option<RowSelection>,
Option<RoaringBitmap>,
AisleResult,
) {
(
self.row_groups,
self.row_selection,
self.roaring,
self.compile,
)
}
pub fn into_parts_with_projection(
self,
) -> (
Vec<usize>,
Option<RowSelection>,
Option<RoaringBitmap>,
AisleResult,
Option<Vec<String>>,
Vec<String>,
Vec<String>,
) {
(
self.row_groups,
self.row_selection,
self.roaring,
self.compile,
self.output_projection,
self.predicate_columns,
self.required_columns,
)
}
}
impl PruneResult {
pub(super) fn new(
row_groups: Vec<usize>,
row_selection: Option<RowSelection>,
roaring: Option<RoaringBitmap>,
compile: AisleResult,
output_projection: Option<Vec<String>>,
predicate_columns: Option<Vec<String>>,
) -> Self {
let fallback_required_projection_all = compile.has_errors() && predicate_columns.is_none();
let predicate_columns = predicate_columns
.map(normalize_columns)
.unwrap_or_else(|| collect_columns_from_predicates(compile.ir_exprs()));
let required_columns = merge_columns(&predicate_columns, output_projection.as_deref());
Self {
row_groups,
row_selection,
roaring,
compile,
output_projection,
predicate_columns,
required_columns,
fallback_required_projection_all,
}
}
}
fn collect_columns_from_predicates(predicates: &[Expr]) -> Vec<String> {
let mut columns = BTreeSet::new();
for predicate in predicates {
collect_columns_from_expr(predicate, &mut columns);
}
columns.into_iter().collect()
}
fn collect_columns_from_expr(expr: &Expr, columns: &mut BTreeSet<String>) {
match expr {
Expr::Cmp { column, .. }
| Expr::Between { column, .. }
| Expr::InList { column, .. }
| Expr::BloomFilterEq { column, .. }
| Expr::BloomFilterInList { column, .. }
| Expr::DictionaryHintEq { column, .. }
| Expr::DictionaryHintInList { column, .. }
| Expr::StartsWith { column, .. }
| Expr::IsNull { column, .. } => {
columns.insert(column.clone());
}
Expr::And(parts) | Expr::Or(parts) => {
for part in parts {
collect_columns_from_expr(part, columns);
}
}
Expr::Not(inner) => collect_columns_from_expr(inner, columns),
Expr::True | Expr::False => {}
}
}
fn merge_columns(
predicate_columns: &[String],
output_projection: Option<&[String]>,
) -> Vec<String> {
let mut columns: BTreeSet<String> = predicate_columns.iter().cloned().collect();
if let Some(output_columns) = output_projection {
for column in output_columns {
columns.insert(column.clone());
}
}
columns.into_iter().collect()
}
fn normalize_columns(columns: Vec<String>) -> Vec<String> {
let mut unique = BTreeSet::new();
for column in columns {
if !column.is_empty() {
unique.insert(column);
}
}
unique.into_iter().collect()
}