@@ -329,3 +329,90 @@ set datafusion.execution.target_partitions = 4;
329329
330330statement ok
331331RESET datafusion.execution.parquet.pushdown_filters;
332+
333+ # Page-index pruning that skips no rows must not disable runtime row-group
334+ # pruning. `w = 1` cannot be proven by row-group statistics (each row group
335+ # holds both `w = 0` and `w = 1`), so page-index pruning runs, but it prunes
336+ # no page: it selects all rows of each row group. After the first row group,
337+ # the TopK dynamic filter `v < 1` proves that row groups 1..=4 cannot hold the
338+ # top-1 row, so the runtime pruner must skip them with the page index enabled
339+ # and with it disabled. The rows of each row group are in descending order, so
340+ # the file has no output ordering and the TopK cannot stop early. Filter
341+ # pushdown removes the `FilterExec`, which would otherwise buffer all rows
342+ # before the TopK sees them.
343+ # RG 0: v=2,1,0 RG 1: v=5,4,3 RG 2: v=8,7,6 RG 3: v=11,10,9 RG 4: v=14,13,12
344+ # w = v % 2
345+ statement ok
346+ set datafusion.execution.target_partitions = 1;
347+
348+ statement ok
349+ set datafusion.execution.parquet.pushdown_filters = true;
350+
351+ statement ok
352+ set datafusion.explain.analyze_level = summary;
353+
354+ statement ok
355+ COPY (
356+ SELECT v, v % 2 AS w FROM (
357+ SELECT CAST(3 * (i / 3) + 2 - i % 3 AS BIGINT) AS v FROM generate_series(0, 14) AS t(i)
358+ )
359+ )
360+ TO 'test_files/scratch/dynamic_row_group_pruning/select_all.parquet'
361+ STORED AS PARQUET
362+ OPTIONS ('format.max_row_group_size' '3');
363+
364+ statement ok
365+ CREATE EXTERNAL TABLE select_all (v BIGINT NOT NULL, w BIGINT NOT NULL)
366+ STORED AS PARQUET
367+ LOCATION 'test_files/scratch/dynamic_row_group_pruning/select_all.parquet';
368+
369+ query I
370+ SELECT v FROM select_all WHERE w = 1 ORDER BY v LIMIT 1;
371+ ----
372+ 1
373+
374+ # Page index enabled (the default): `page_index_pages_pruned` shows that no
375+ # page is pruned, and `row_groups_pruned_dynamic_filter=4` shows that runtime
376+ # row-group pruning still happens.
377+ query TT
378+ explain analyze SELECT v FROM select_all WHERE w = 1 ORDER BY v LIMIT 1;
379+ ----
380+ Plan with Metrics
381+ 01)SortExec: TopK(fetch=1), expr=[v@0 ASC NULLS LAST], preserve_partitioning=[false], filter=[v@0 < 1], metrics=[output_rows=1, elapsed_compute=<slt:ignore>, output_bytes=<slt:ignore>]
382+ 02)--DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/dynamic_row_group_pruning/select_all.parquet]]}, projection=[v], file_type=parquet, predicate=w@1 = 1 AND DynamicFilter [ v@0 < 1 ], sort_order_for_reorder=[v@0 ASC NULLS LAST], dynamic_rg_pruning=eligible, pruning_predicate=w_null_count@2 != row_count@3 AND w_min@0 <= 1 AND 1 <= w_max@1 AND v_null_count@5 != row_count@3 AND v_min@4 < 1, required_guarantees=[w in (1)], metrics=[output_rows=1, elapsed_compute=<slt:ignore>, output_bytes=<slt:ignore>, files_ranges_pruned_statistics=1 total → 1 matched, row_groups_pruned_statistics=5 total → 5 matched, row_groups_pruned_bloom_filter=5 total → 5 matched, page_index_pages_pruned=5 total → 5 matched, limit_pruned_row_groups=0 total → 0 matched, bytes_processed=<slt:ignore>, bytes_scanned=<slt:ignore>, row_groups_pruned_dynamic_filter=4, metadata_load_time=<slt:ignore>, scan_efficiency_ratio=<slt:ignore>]
383+
384+ statement ok
385+ drop table select_all;
386+
387+ # Page index disabled: the same runtime row-group pruning. Create the table
388+ # again after the option change, because the table keeps the parquet options
389+ # of its creation time.
390+ statement ok
391+ set datafusion.execution.parquet.enable_page_index = false;
392+
393+ statement ok
394+ CREATE EXTERNAL TABLE select_all (v BIGINT NOT NULL, w BIGINT NOT NULL)
395+ STORED AS PARQUET
396+ LOCATION 'test_files/scratch/dynamic_row_group_pruning/select_all.parquet';
397+
398+ query TT
399+ explain analyze SELECT v FROM select_all WHERE w = 1 ORDER BY v LIMIT 1;
400+ ----
401+ Plan with Metrics
402+ 01)SortExec: TopK(fetch=1), expr=[v@0 ASC NULLS LAST], preserve_partitioning=[false], filter=[v@0 < 1], metrics=[output_rows=1, elapsed_compute=<slt:ignore>, output_bytes=<slt:ignore>]
403+ 02)--DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/dynamic_row_group_pruning/select_all.parquet]]}, projection=[v], file_type=parquet, predicate=w@1 = 1 AND DynamicFilter [ v@0 < 1 ], sort_order_for_reorder=[v@0 ASC NULLS LAST], dynamic_rg_pruning=eligible, pruning_predicate=w_null_count@2 != row_count@3 AND w_min@0 <= 1 AND 1 <= w_max@1 AND v_null_count@5 != row_count@3 AND v_min@4 < 1, required_guarantees=[w in (1)], metrics=[output_rows=1, elapsed_compute=<slt:ignore>, output_bytes=<slt:ignore>, files_ranges_pruned_statistics=1 total → 1 matched, row_groups_pruned_statistics=5 total → 5 matched, row_groups_pruned_bloom_filter=5 total → 5 matched, page_index_pages_pruned=0 total → 0 matched, limit_pruned_row_groups=0 total → 0 matched, bytes_processed=<slt:ignore>, bytes_scanned=<slt:ignore>, row_groups_pruned_dynamic_filter=4, metadata_load_time=<slt:ignore>, scan_efficiency_ratio=<slt:ignore>]
404+
405+ statement ok
406+ drop table select_all;
407+
408+ statement ok
409+ RESET datafusion.execution.parquet.enable_page_index;
410+
411+ statement ok
412+ set datafusion.execution.target_partitions = 4;
413+
414+ statement ok
415+ RESET datafusion.explain.analyze_level;
416+
417+ statement ok
418+ RESET datafusion.execution.parquet.pushdown_filters;
0 commit comments