use crate::ParquetFileMetrics;
use crate::metadata::DFParquetMetadata;
use bytes::Bytes;
use datafusion_common::HashMap;
use datafusion_datasource::PartitionedFile;
use datafusion_execution::cache::cache_manager::FileMetadata;
use datafusion_execution::cache::cache_manager::FileMetadataCache;
use datafusion_physical_plan::metrics::ExecutionPlanMetricsSet;
use futures::FutureExt;
use futures::TryFutureExt;
use futures::future::BoxFuture;
use object_store::{ObjectStore, ObjectStoreExt};
use parquet::arrow::arrow_reader::ArrowReaderOptions;
use parquet::arrow::async_reader::AsyncFileReader;
use parquet::errors::ParquetError;
use parquet::file::metadata::ParquetMetaData;
use std::any::Any;
use std::fmt::Debug;
use std::ops::Range;
use std::sync::Arc;
pub trait ParquetFileReaderFactory: Debug + Send + Sync + 'static {
fn create_reader(
&self,
partition_index: usize,
partitioned_file: PartitionedFile,
metadata_size_hint: Option<usize>,
metrics: &ExecutionPlanMetricsSet,
) -> datafusion_common::Result<Box<dyn AsyncFileReader + Send>>;
}
#[derive(Debug)]
pub struct DefaultParquetFileReaderFactory {
store: Arc<dyn ObjectStore>,
}
impl DefaultParquetFileReaderFactory {
pub fn new(store: Arc<dyn ObjectStore>) -> Self {
Self { store }
}
}
impl ParquetFileReaderFactory for DefaultParquetFileReaderFactory {
fn create_reader(
&self,
partition_index: usize,
partitioned_file: PartitionedFile,
metadata_size_hint: Option<usize>,
metrics: &ExecutionPlanMetricsSet,
) -> datafusion_common::Result<Box<dyn AsyncFileReader + Send>> {
let file_metrics = ParquetFileMetrics::new(
partition_index,
partitioned_file.object_meta.location.as_ref(),
metrics,
);
let reader = ParquetFileReader::new(
file_metrics,
Arc::clone(&self.store),
partitioned_file,
)
.with_metadata_hint(metadata_size_hint);
Ok(Box::new(reader))
}
}
#[derive(Debug)]
pub struct CachedParquetFileReaderFactory {
store: Arc<dyn ObjectStore>,
metadata_cache: Arc<FileMetadataCache>,
}
impl CachedParquetFileReaderFactory {
pub fn new(
store: Arc<dyn ObjectStore>,
metadata_cache: Arc<FileMetadataCache>,
) -> Self {
Self {
store,
metadata_cache,
}
}
}
impl ParquetFileReaderFactory for CachedParquetFileReaderFactory {
fn create_reader(
&self,
partition_index: usize,
partitioned_file: PartitionedFile,
metadata_size_hint: Option<usize>,
metrics: &ExecutionPlanMetricsSet,
) -> datafusion_common::Result<Box<dyn AsyncFileReader + Send>> {
let file_metrics = ParquetFileMetrics::new(
partition_index,
partitioned_file.object_meta.location.as_ref(),
metrics,
);
let reader = ParquetFileReader::new(
file_metrics,
Arc::clone(&self.store),
partitioned_file,
)
.with_metadata_hint(metadata_size_hint)
.with_metadata_cache(Some(Arc::clone(&self.metadata_cache)));
Ok(Box::new(reader))
}
}
pub struct ParquetFileReader {
file_metrics: ParquetFileMetrics,
store: Arc<dyn ObjectStore>,
partitioned_file: PartitionedFile,
metadata_cache: Option<Arc<FileMetadataCache>>,
metadata_size_hint: Option<usize>,
}
impl ParquetFileReader {
pub(crate) fn new(
file_metrics: ParquetFileMetrics,
store: Arc<dyn ObjectStore>,
partitioned_file: PartitionedFile,
) -> Self {
Self {
file_metrics,
store,
partitioned_file,
metadata_cache: None,
metadata_size_hint: None,
}
}
pub fn file_metrics(&self) -> &ParquetFileMetrics {
&self.file_metrics
}
pub fn partitioned_file(&self) -> &PartitionedFile {
&self.partitioned_file
}
pub fn with_metadata_cache(
mut self,
metadata_cache: Option<Arc<FileMetadataCache>>,
) -> Self {
self.metadata_cache = metadata_cache;
self
}
pub fn with_metadata_hint(mut self, metadata_size_hint: Option<usize>) -> Self {
self.metadata_size_hint = metadata_size_hint;
self
}
}
impl AsyncFileReader for ParquetFileReader {
fn get_bytes(
&mut self,
range: Range<u64>,
) -> BoxFuture<'_, parquet::errors::Result<Bytes>> {
let bytes_scanned = range.end - range.start;
self.file_metrics.bytes_scanned.add(bytes_scanned as usize);
self.store
.get_range(&self.partitioned_file.object_meta.location, range)
.map_err(|e| ParquetError::External(Box::new(e)))
.boxed()
}
fn get_byte_ranges(
&mut self,
ranges: Vec<Range<u64>>,
) -> BoxFuture<'_, parquet::errors::Result<Vec<Bytes>>>
where
Self: Send,
{
let total: u64 = ranges.iter().map(|r| r.end - r.start).sum();
self.file_metrics.bytes_scanned.add(total as usize);
async move {
self.store
.get_ranges(&self.partitioned_file.object_meta.location, &ranges)
.await
.map_err(|e| ParquetError::External(Box::new(e)))
}
.boxed()
}
fn get_metadata<'a>(
&'a mut self,
options: Option<&'a ArrowReaderOptions>,
) -> BoxFuture<'a, parquet::errors::Result<Arc<ParquetMetaData>>> {
let object_meta = self.partitioned_file.object_meta.clone();
let metadata_cache = self.metadata_cache.clone();
async move {
#[cfg(feature = "parquet_encryption")]
let file_decryption_properties = options
.and_then(|o| o.file_decryption_properties())
.map(Arc::clone);
#[cfg(not(feature = "parquet_encryption"))]
let file_decryption_properties = None;
let page_index_policy = options.map(|o| o.column_index_policy());
DFParquetMetadata::new(&self.store, &object_meta)
.with_decryption_properties(file_decryption_properties)
.with_file_metadata_cache(metadata_cache)
.with_metadata_size_hint(self.metadata_size_hint)
.with_page_index_policy(page_index_policy)
.fetch_metadata()
.await
.map_err(|e| {
ParquetError::General(format!(
"Failed to fetch metadata for file {}: {e}",
object_meta.location,
))
})
}
.boxed()
}
}
impl Drop for ParquetFileReader {
fn drop(&mut self) {
self.file_metrics
.scan_efficiency_ratio
.add_part(self.file_metrics.bytes_scanned.value());
self.file_metrics
.scan_efficiency_ratio
.set_total(self.partitioned_file.object_meta.size as usize);
}
}
pub struct CachedParquetMetaData(Arc<ParquetMetaData>);
impl CachedParquetMetaData {
pub fn new(metadata: Arc<ParquetMetaData>) -> Self {
Self(metadata)
}
pub fn parquet_metadata(&self) -> &Arc<ParquetMetaData> {
&self.0
}
}
impl FileMetadata for CachedParquetMetaData {
fn as_any(&self) -> &dyn Any {
self
}
fn memory_size(&self) -> usize {
self.0.memory_size()
}
fn extra_info(&self) -> HashMap<String, String> {
let page_index =
self.0.column_index().is_some() && self.0.offset_index().is_some();
HashMap::from([("page_index".to_owned(), page_index.to_string())])
}
}