Repository navigation
Conversation
…low it A filter that reaches a pure extraction projection after the pass that created the projection never reached TableScan::filters, because PushDownFilter does not move a filter below such a projection. Offer the predicates that read only pass-through columns to the scan directly below the projection, without moving the filter. Carried from apache#26281 (open).
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #26281 +/- ##
========================================
Coverage 82.83% 82.83%
========================================
Files 1151 1151
Lines 455332 455452 +120
Branches 455332 455452 +120
========================================
+ Hits 377165 377270 +105
- Misses 55262 55267 +5
- Partials 22905 22915 +10 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…low it A filter that reaches a pure extraction projection after the pass that created the projection never reached TableScan::filters, because PushDownFilter does not move a filter below such a projection. Offer the predicates that read only pass-through columns to the scan directly below the projection, without moving the filter. Carried from apache#26281 (open).
7705e95 to
ade7bae
Compare
…low it A filter that reaches a pure extraction projection after the pass that created the projection never reached TableScan::filters, because PushDownFilter does not move a filter below such a projection. Offer the predicates that read only pass-through columns to the scan directly below the projection, without moving the filter. Carried from apache#26281 (open).
ade7bae to
7b6df75
Compare
…low it A filter that reaches a pure extraction projection after the pass that created the projection never reached TableScan::filters, because PushDownFilter does not move a filter below such a projection. Offer the predicates that read only pass-through columns to the scan directly below the projection, without moving the filter.
…ransformed in extraction-projection pushdown Co-Authored-By: Claude Opus 5.5 <[email protected]>
7b6df75 to
f8c53ed
Compare
kosiew
left a comment
There was a problem hiding this comment.
Thanks for addressing the late-filter regression. Offering pass-through predicates directly to the scan restores source-level pruning without moving filters below pure extraction projections. The approach is appropriate, and the new tests cover the main exact, inexact, and fixed-point behaviors.
The architectural boundary is important: extraction projections must remain adjacent to scans for struct-leaf pruning, while eligible predicates must still reach the source. Reusing the scan's acceptance policy preserves that separation, provided pushdown also respects scan row-selection semantics.
I'm requesting changes because the new path can push predicates into scans with a positive offset but no fetch, changing which rows survive. The fetch guard needs to cover offsets as well. I've also noted two optional regression-coverage improvements.
| }; | ||
| // The fetch of a scan applies after its filters, so a filter above a scan | ||
| // with a fetch cannot move into it without changing the result. | ||
| if scan.fetch.is_some() { |
There was a problem hiding this comment.
Please guard positive scan.skip values as well as scan.fetch. For b=[0,10,20], Filter(b > 5) -> Projection -> Scan(skip=1, fetch=None) should return [10,20], but pushing even an inexact predicate before the skip can change the result to [20]. Add an offset-only regression using a source supporting exact filter and skip pushdown, and check the direct-scan path too. The direct-scan gap predates this PR, but the extraction-projection path newly exposes it.
| }; | ||
| // Keep the predicates in their original order, which `reorder_predicates` | ||
| // chose by cost. | ||
| let keep_predicates = predicates |
There was a problem hiding this comment.
Consider adding a mixed-acceptance regression with Exact and Inexact or Unsupported pass-through conjuncts plus an extracted-alias conjunct. Verify that only Exact predicates leave the filter, accepted predicates reach the scan, residual order is preserved, and a second rewrite is unchanged. This is optional coverage for selective predicate reconciliation.
| /// and the module documentation. | ||
| #[test] | ||
| fn filter_not_pushed_through_pure_extraction_projection() -> Result<()> { | ||
| fn filter_above_pure_extraction_projection_reaches_the_scan() -> Result<()> { |
There was a problem hiding this comment.
Consider an end-to-end regression for optimizing a plan, appending a partition filter, and optimizing again. With three partition files, verify the returned rows, preserved extraction projection, and physical scan file groups containing only the matching partition. This would cover real get_field extraction and dictionary partition literals beyond the existing unit tests. Optional coverage improvement.
…low it Backport of apache#26281 (open), squashed from its head f8c53ed. Claude-Session: https://claude.ai/code/session_01USkuh48jV15LVKgyqXmCod
Which issue does this PR close?
Rationale for this change
Since #25455, a filter on a partition column can stop reaching the scan. Partition pruning is then lost, and the scan reads every file.
This occurs when a filter reaches a pure extraction projection (one with only
__datafusion_extracted_Naliases and pass-through columns) after the optimizer pass that created the projection.PushDownFilterno longer moves a filter below such a projection, so the predicate never gets intoTableScan::filters. A common way to get this shape is to put a filter on an already optimized plan and optimize it again. Query rewrites that reuse an optimized subtree do this.Reproduction
Data:
Program:
Before #25455, the predicate is in
full_filtersand the scan reads one file:On
main, the predicate stays in the filter above the projection, and the scan reads all three files:With this PR, the logical plan and the file list are the same as before #25455.
What changes are included in this PR?
PushDownFilterfinds a pure extraction projection directly above aTableScan, it keeps the filter above the projection, as fix: decide the precedence between PushDownFilter and PushDownLeafProjections #25455 requires. But it offers the scan each predicate that reads only pass-through columns. The scan records the predicates that its source accepts. A predicate that the source applies exactly leaves the filter. ThusPushDownLeafProjectionshas nothing to undo, and a second pass changes nothing.TableScanarm ofPushDownFiltermoves into the functionpush_down_into_table_scan, so both paths use the same code. Its behaviour does not change.push_down_filterandextract_leaf_expressions, and the "Rule Precedence" section of the query optimizer guide, no longer say that the earlier pass always setsTableScan::filters. They describe the new step.No new public API and no new configuration option.
What is the testing strategy for this PR?
Unit tests in
push_down_filter.rs:filter_above_pure_extraction_projection_reaches_the_scanreplacesfilter_not_pushed_through_pure_extraction_projection. It shows that a predicate on a pass-through column reachesfull_filtersand leaves no filter node, with the projection still next to the scan.inexact_predicate_above_pure_extraction_projection_reaches_the_scanshows that an inexact source records the predicate inpartial_filters, and that the filter keeps it. A predicate on an extracted alias stays in the filter only.pushdown_into_scan_below_extraction_projection_is_a_fixed_pointapplies the rule two times and shows that the second application changes nothing.unsupported_predicate_stays_above_pure_extraction_projectionshows that the plan does not change when the source does not support the predicate.The existing
filter_and_extraction_projection_reach_fixed_pointtest still passes.Commands run:
cargo test -p datafusion-optimizer: all pass.cargo test -p datafusion-sqllogictest --test sqllogictests: all files pass.cargo clippy -p datafusion-optimizer --all-targets -- -D warnings: clean.I could not reproduce the shape with one SQL statement, because in that case
PushDownFilterruns before the extraction projection exists. Thus the tests are unit tests, notsqllogictestcases.Are there any user-facing changes?
Yes. A filter that is put on an optimized plan reaches the scan again, so partition pruning and the provider's filter pushdown work again for such plans. No API changes.