Skip to content

Commit f5f1b7f

Browse files
committed
update
1 parent 36afade commit f5f1b7f

1 file changed

Lines changed: 160 additions & 9 deletions

File tree

  • datafusion/datasource-parquet/src/opener

‎datafusion/datasource-parquet/src/opener/mod.rs‎

Lines changed: 160 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,11 +1337,17 @@ impl RowGroupsPrunedParquetOpen {
13371337
replace_columns_with_literals(expr, state.null_replacements())
13381338
})?,
13391339
};
1340+
let pushdown_predicate = prepared
1341+
.pushdown_filters
1342+
.then_some(prepared.predicate.as_ref())
1343+
.flatten();
13401344
let read_plan = build_projection_read_plan(
1341-
projection.expr_iter(),
1345+
projection.expr_iter().chain(pushdown_predicate.cloned()),
13421346
&prepared.physical_file_schema,
13431347
parquet_metadata.file_metadata().schema_descr(),
13441348
);
1349+
// Partial indexes are supported: only a column read from a row group
1350+
// with a partial selection needs an offset index to benefit.
13451351
if selected_row_groups.any(|idx| {
13461352
parquet_metadata
13471353
.row_group(idx)
@@ -1395,7 +1401,14 @@ impl RowGroupsPrunedParquetOpen {
13951401
.loaded
13961402
.options
13971403
.clone()
1398-
.with_page_index_policy(PageIndexPolicy::Optional),
1404+
.with_offset_index_policy(PageIndexPolicy::Optional)
1405+
.with_column_index_policy(
1406+
if self.prepared.page_pruning_predicate.is_some() {
1407+
PageIndexPolicy::Optional
1408+
} else {
1409+
PageIndexPolicy::Skip
1410+
},
1411+
),
13991412
)
14001413
.await?;
14011414

@@ -2058,12 +2071,14 @@ async fn load_page_index<T: AsyncFileReader>(
20582071
options: ArrowReaderOptions,
20592072
) -> Result<ArrowReaderMetadata> {
20602073
let parquet_metadata = reader_metadata.metadata();
2061-
let missing_column_index = !parquet_metadata
2062-
.page_index()
2063-
.is_some_and(|page_index| page_index.has_column_indexes());
2064-
let missing_offset_index = !parquet_metadata
2065-
.page_index()
2066-
.is_some_and(|page_index| page_index.has_offset_indexes());
2074+
let missing_column_index = options.column_index_policy() != PageIndexPolicy::Skip
2075+
&& !parquet_metadata
2076+
.page_index()
2077+
.is_some_and(|page_index| page_index.has_column_indexes());
2078+
let missing_offset_index = options.offset_index_policy() != PageIndexPolicy::Skip
2079+
&& !parquet_metadata
2080+
.page_index()
2081+
.is_some_and(|page_index| page_index.has_offset_indexes());
20672082
// You may ask yourself: why are we even checking if the page index is already loaded here?
20682083
// Didn't we explicitly *not* load it above?
20692084
// Well it's possible that a custom implementation of `AsyncFileReader` gives you
@@ -2073,7 +2088,8 @@ async fn load_page_index<T: AsyncFileReader>(
20732088
let m = Arc::try_unwrap(Arc::clone(parquet_metadata))
20742089
.unwrap_or_else(|e| e.as_ref().clone());
20752090
let mut reader = ParquetMetaDataReader::new_with_metadata(m)
2076-
.with_page_index_policy(PageIndexPolicy::Optional);
2091+
.with_column_index_policy(options.column_index_policy())
2092+
.with_offset_index_policy(options.offset_index_policy());
20772093
reader.load_page_index(input).await?;
20782094
let new_parquet_metadata = reader.finish()?;
20792095
let new_arrow_reader =
@@ -4650,6 +4666,43 @@ mod test {
46504666
expected,
46514667
);
46524668
}
4669+
for projection in [vec![0], vec![1], vec![0, 1], vec![]] {
4670+
assert_eq!(
4671+
should_load_page_index_with_projection(
4672+
page_index_metadata(&[("a", true), ("b", true)], 1),
4673+
None,
4674+
plan.clone(),
4675+
Some(&projection),
4676+
),
4677+
!projection.is_empty(),
4678+
);
4679+
}
4680+
// A missing index in a skipped row group does not prevent loading.
4681+
let mixed_metadata = page_index_metadata(&[("a", true)], 1)
4682+
.into_builder()
4683+
.set_row_groups(vec![
4684+
page_index_metadata(&[("a", true)], 1).row_group(0).clone(),
4685+
page_index_metadata(&[("a", false)], 1).row_group(0).clone(),
4686+
])
4687+
.build();
4688+
let mixed_plan =
4689+
ParquetAccessPlan::new(vec![plan.inner()[0].clone(), RowGroupAccess::Skip]);
4690+
assert!(should_load_page_index(
4691+
mixed_metadata.clone(),
4692+
None,
4693+
mixed_plan
4694+
));
4695+
// An index in a skipped or fully scanned row group does not help the
4696+
// partially selected row group that lacks an index.
4697+
for access in [RowGroupAccess::Skip, RowGroupAccess::Scan] {
4698+
let mixed_plan =
4699+
ParquetAccessPlan::new(vec![access, plan.inner()[0].clone()]);
4700+
assert!(!should_load_page_index(
4701+
mixed_metadata.clone(),
4702+
None,
4703+
mixed_plan
4704+
));
4705+
}
46534706
// Projection restriction applies only to the external-selection path.
46544707
assert!(should_load_page_index_with_projection(
46554708
metadata,
@@ -4659,6 +4712,77 @@ mod test {
46594712
));
46604713
}
46614714

4715+
#[tokio::test]
4716+
async fn test_load_page_index_policies_and_cached_offset_index() {
4717+
let store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4718+
let batch = record_batch!(("a", Int32, [1, 2, 3])).unwrap();
4719+
let schema = batch.schema();
4720+
let props = WriterProperties::builder()
4721+
.set_statistics_enabled(EnabledStatistics::Page)
4722+
.build();
4723+
let data_len = write_parquet_batches(
4724+
Arc::clone(&store),
4725+
"test.parquet",
4726+
vec![batch],
4727+
Some(props),
4728+
)
4729+
.await;
4730+
let metrics = ExecutionPlanMetricsSet::new();
4731+
let opener = ParquetMorselizerBuilder::new()
4732+
.with_store(store)
4733+
.with_schema(schema)
4734+
.with_metrics(metrics.clone())
4735+
.build();
4736+
let file = PartitionedFile::new("test.parquet".to_string(), data_len as u64);
4737+
let mut loaded = opener
4738+
.prepare_open_file(file)
4739+
.unwrap()
4740+
.load()
4741+
.await
4742+
.unwrap();
4743+
let options = loaded
4744+
.options
4745+
.clone()
4746+
.with_column_index_policy(PageIndexPolicy::Skip)
4747+
.with_offset_index_policy(PageIndexPolicy::Optional);
4748+
let metadata = load_page_index(
4749+
loaded.reader_metadata,
4750+
&mut loaded.prepared.async_file_reader,
4751+
options.clone(),
4752+
)
4753+
.await
4754+
.unwrap();
4755+
let page_index = metadata.metadata().page_index().unwrap();
4756+
assert!(page_index.has_offset_indexes());
4757+
assert!(!page_index.has_column_indexes());
4758+
let bytes_scanned = counter_metric_value(&metrics, "bytes_scanned");
4759+
let cached = load_page_index(
4760+
metadata.clone(),
4761+
&mut loaded.prepared.async_file_reader,
4762+
options.clone(),
4763+
)
4764+
.await
4765+
.unwrap();
4766+
assert!(Arc::ptr_eq(metadata.metadata(), cached.metadata()));
4767+
assert_eq!(
4768+
counter_metric_value(&metrics, "bytes_scanned"),
4769+
bytes_scanned
4770+
);
4771+
4772+
// A later predicate scan must still load the missing column index.
4773+
let metadata = load_page_index(
4774+
cached,
4775+
&mut loaded.prepared.async_file_reader,
4776+
options.with_column_index_policy(PageIndexPolicy::Optional),
4777+
)
4778+
.await
4779+
.unwrap();
4780+
let page_index = metadata.metadata().page_index().unwrap();
4781+
assert!(page_index.has_offset_indexes());
4782+
assert!(page_index.has_column_indexes());
4783+
assert!(counter_metric_value(&metrics, "bytes_scanned") > bytes_scanned);
4784+
}
4785+
46624786
#[tokio::test]
46634787
async fn test_page_index_with_external_row_selection() {
46644788
use parquet::arrow::arrow_reader::{RowSelection, RowSelector};
@@ -4723,6 +4847,33 @@ mod test {
47234847
"offset indexes should reduce I/O: {bytes_scanned:?}"
47244848
);
47254849
}
4850+
// Filter columns still read pages when the output projection is empty.
4851+
let mut bytes_scanned = Vec::new();
4852+
for enabled in [false, true] {
4853+
let metrics = ExecutionPlanMetricsSet::new();
4854+
let opener = ParquetMorselizerBuilder::new()
4855+
.with_store(Arc::clone(&store))
4856+
.with_schema(Arc::clone(&schema))
4857+
.with_projection_indices(&[])
4858+
.with_pushdown_filters(true)
4859+
.with_enable_page_index(enabled)
4860+
.with_predicate(logical2physical(&col("a").gt_eq(lit(9950i32)), &schema))
4861+
.with_metrics(metrics.clone())
4862+
.build();
4863+
let batches = open_file(&opener, file.clone())
4864+
.await
4865+
.unwrap()
4866+
.try_collect::<Vec<_>>()
4867+
.await
4868+
.unwrap();
4869+
assert_eq!(
4870+
batches.iter().map(|batch| batch.num_rows()).sum::<usize>(),
4871+
50
4872+
);
4873+
assert!(batches.iter().all(|batch| batch.num_columns() == 0));
4874+
bytes_scanned.push(counter_metric_value(&metrics, "bytes_scanned"));
4875+
}
4876+
assert!(bytes_scanned[1] < bytes_scanned[0], "{bytes_scanned:?}");
47264877
}
47274878

47284879
#[test]

0 commit comments

Comments
 (0)