Repository navigation
fix: preserve TopK dynamic filters across recursive CTE iterations - #26670
Conversation
| .clone(); | ||
| // Our dynamic filter and execution metrics are the state we need to reset. | ||
| new_sort.filter = Some(new_sort.create_filter()); | ||
| let expr = new_sort.filter.as_ref().map(|filter| filter.read().expr()); |
There was a problem hiding this comment.
nit: after the reset this is the same as the existing rebuild_filter_for_current_partitioning() helper, so it could reuse it:
- let expr = new_sort.filter.as_ref().map(|filter| filter.read().expr());
- if let Some(expr) = expr {
- expr.reset()?;
- new_sort.filter = Some(new_sort.create_filter_with_expr(expr));
+ if let Some(filter) = &new_sort.filter {
+ filter.read().expr().reset()?;
}
-
+ new_sort.rebuild_filter_for_current_partitioning();
new_sort.metrics_set = ExecutionPlanMetricsSet::new();There was a problem hiding this comment.
My bad, missed the existing helper. reset_state() uses it now.
| // Our dynamic filter and execution metrics are the state we need to reset. | ||
| new_sort.filter = Some(new_sort.create_filter()); | ||
| if let Some(filter) = &new_sort.filter { | ||
| filter.read().expr().reset()?; |
There was a problem hiding this comment.
nit: #26669 makes DynamicFilterPhysicalExpr::reset() infallible, so this ? won't compile once both PRs are in. The two PRs touch different files, so git won't flag a conflict. Could you drop the ? in whichever of the two merges second (or rebase this one on #26669)?
- filter.read().expr().reset()?;
+ filter.read().expr().reset();There was a problem hiding this comment.
let me address this as well
There was a problem hiding this comment.
sorry didnt see it until now, Thanks
|
Thanks @Kristofferjoha |
|
This PR appears to have introduced a 56 regression: |
|
We are going to revert this change for 56, and then we can work on the longer term fix for 57. |
…(revert apache#26670) (apache#27142) ## Which issue does this PR close? - Closes apache#27130. ## Rationale for this change As discussed in apache#27130, this reverts apache#26670 so copies made by `SortExec::reset_state()` no longer share one TopK dynamic filter. When a cached `LIMIT 1` plan was reset twice and both copies ran, the first one to finish tightened the shared filter and the second returned no rows. A plain revert brings back a different wrong result, though: a plan that has run once and is then reset still has its old bound in the scan's predicate (the scan keeps the filter it got at planning time). For `SELECT a FROM t ORDER BY a LIMIT 1` over a parquet file with pushdown enabled, the rerun returned no rows with the plain revert. So this keeps the in-place `reset()` from apache#26056 on the old filter and then gives the new plan its own filter, as @Kristofferjoha suggested keeping. | | main | plain revert | this PR | |---|---|---|---| | reset twice, run both copies (apache#27130) | 2nd: no rows | ok | ok | | run, reset, run again (parquet, pushdown) | ok | 2nd: no rows | ok | ## What changes are included in this PR? - `SortExec::reset_state` resets the old dynamic filter in place, then creates a new one for the reset plan. The `sort_concurrency` reset from apache#26242 is kept. (The plain revert did not apply cleanly because apache#26242 changed the same lines.) - Removes the recursive CTE pruning test apache#26670 added to `cte.slt`, since the scan now keeps the old filter (reset to `true`) and the pruning no longer happens, as in 55. - Adds two unit tests in `sorts/sort.rs`: the reproducer from apache#27130, and a test that the filter a scan would hold is back to `true` after `reset_state`. The second one fails with the plain revert. ## Are these changes tested? Yes. Both new tests fail on main or on the plain revert respectively, and pass here. All `datafusion-physical-plan` unit tests and sqllogictests pass. ## Are there any user-facing changes? The TopK dynamic filter inside a recursive CTE no longer prunes after the first iteration, as in 55 (the apache#26670 test's scan reads 300 rows instead of 30). Query results are unchanged. The proper fix (a fresh filter per copy that the scan also picks up, which would also cover `AggregateExec`) can follow after 56. This reopens apache#26642.
…amic filter (backport #27142) (#27174) ## Which issue does this PR close? - part of #24461 - Backport of #27142 to `branch-56` - Fixes the 56 regression reported in #27130. ## What changes are included in this PR? Clean cherry-pick of #27142: copies made by `SortExec::reset_state()` each get their own TopK dynamic filter again (reverting #26670), and the old filter is reset so a plan that is run, reset and run again doesn't keep the previous bound in the scan. ## Are these changes tested? Same tests as #27142. On `branch-56`: `datafusion-physical-plan` sort tests 164/164, sqllogictest 544/544, clippy and fmt clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Which issue does this PR close?
Closes #26642.
Rationale for this change
TopK dynamic filters inside recursive CTEs stop updating the scan because
SortExec::reset_state()creates a new filter on each iteration, while the scan still references the original one. This prevents pruning during recursive queries.What changes are included in this PR?
DynamicFilterPhysicalExpr.cte.slt.Depends on #26056 for
DynamicFilterPhysicalExpr::reset(). It hasn't been merged yet, but it has been approved, so I based this change on its implementation.What is the testing strategy for this PR?
The new
cte.sltcase checks that the recursive query returns 1–4 and that the Parquet scan outputs 30 rows instead of 300, pruning 27 row groups across three iterations.Are there any user-facing changes?
No.