blob: ee86a8cc8cd58b2ca74846fa3b839153c1ece432 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! File-level pruning based on partition values and file-level statistics
use std::sync::Arc;
use arrow::datatypes::{FieldRef, Schema, SchemaRef};
use datafusion_common::{
pruning::{
CompositePruningStatistics, PartitionPruningStatistics, PrunableStatistics,
PruningStatistics,
},
Result,
};
use datafusion_datasource::PartitionedFile;
use datafusion_physical_expr_common::physical_expr::{snapshot_generation, PhysicalExpr};
use datafusion_physical_plan::metrics::Count;
use itertools::Itertools;
use log::debug;
use crate::build_pruning_predicate;
/// Prune based on partition values and file-level statistics.
pub struct FilePruner {
predicate_generation: Option<u64>,
predicate: Arc<dyn PhysicalExpr>,
/// Schema used for pruning, which combines the file schema and partition fields.
/// Partition fields are always at the end, as they are during scans.
pruning_schema: Arc<Schema>,
partitioned_file: PartitionedFile,
partition_fields: Vec<FieldRef>,
predicate_creation_errors: Count,
}
impl FilePruner {
pub fn new(
predicate: Arc<dyn PhysicalExpr>,
logical_file_schema: &SchemaRef,
partition_fields: Vec<FieldRef>,
partitioned_file: PartitionedFile,
predicate_creation_errors: Count,
) -> Result<Self> {
// Build a pruning schema that combines the file fields and partition fields.
// Partition fields are always at the end.
let pruning_schema = Arc::new(
Schema::new(
logical_file_schema
.fields()
.iter()
.cloned()
.chain(partition_fields.iter().cloned())
.collect_vec(),
)
.with_metadata(logical_file_schema.metadata().clone()),
);
Ok(Self {
// Initialize the predicate generation to None so that the first time we call `should_prune` we actually check the predicate
// Subsequent calls will only do work if the predicate itself has changed.
// See `snapshot_generation` for more info.
predicate_generation: None,
predicate,
pruning_schema,
partitioned_file,
partition_fields,
predicate_creation_errors,
})
}
pub fn should_prune(&mut self) -> Result<bool> {
let new_generation = snapshot_generation(&self.predicate);
if let Some(current_generation) = self.predicate_generation.as_mut() {
if *current_generation == new_generation {
return Ok(false);
}
*current_generation = new_generation;
} else {
self.predicate_generation = Some(new_generation);
}
let pruning_predicate = build_pruning_predicate(
Arc::clone(&self.predicate),
&self.pruning_schema,
&self.predicate_creation_errors,
);
if let Some(pruning_predicate) = pruning_predicate {
// The partition column schema is the schema of the table - the schema of the file
let mut pruning = Box::new(PartitionPruningStatistics::try_new(
vec![self.partitioned_file.partition_values.clone()],
self.partition_fields.clone(),
)?) as Box<dyn PruningStatistics>;
if let Some(stats) = &self.partitioned_file.statistics {
let stats_pruning = Box::new(PrunableStatistics::new(
vec![Arc::clone(stats)],
Arc::clone(&self.pruning_schema),
));
pruning = Box::new(CompositePruningStatistics::new(vec![
pruning,
stats_pruning,
]));
}
match pruning_predicate.prune(pruning.as_ref()) {
Ok(values) => {
assert!(values.len() == 1);
// We expect a single container -> if all containers are false skip this file
if values.into_iter().all(|v| !v) {
return Ok(true);
}
}
// Stats filter array could not be built, so we can't prune
Err(e) => {
debug!("Ignoring error building pruning predicate for file: {e}");
self.predicate_creation_errors.add(1);
}
}
}
Ok(false)
}
}