Repository navigation
fix: preserve computed projections in unions_to_filter - #25074
kumarUjjawal merged 6 commits into
Conversation
kumarUjjawal
left a comment
There was a problem hiding this comment.
Thank you @pushnanashi2 for the fix. I have left few comment for you. Please take a look. Some slt test would also be nice to have.
| LogicalPlan::Projection(Projection { input, .. }) => { | ||
| Arc::unwrap_or_clone(input) | ||
| } | ||
| LogicalPlan::Projection(_) => return None, |
There was a problem hiding this comment.
Removing SubqueryAlias has the same defect you just fixed for projections? SELECT x.a FROM t AS x WHERE x.a = 1 UNION SELECT x.a FROM t AS x WHERE x.a = 2 plans as a filter over the alias over TableScan: t, so the merged predicate still names x.a while the source only exposes t.a.
We should trip nothing and take the filter input as the source. GroupKey equality then keeps different projections apart and still merges identical ones, so views and derived tables keep the rewrite instead of losing it. That source needs the volatility test that the wrappers already get.
There was a problem hiding this comment.
Thanks, keeping the filter input intact makes sense. I’ll update the implementation and add tests over the next day or two.
I’ll also check whether different underlying sources with otherwise identical scan metadata can end up in the same GroupKey, since TableScan::eq does not compare source. I haven’t reproduced this yet; I’ll investigate and report back.
| wrappers, | ||
| })), | ||
| other => { | ||
| let Some(source) = strip_passthrough_nodes(other) else { |
There was a problem hiding this comment.
peel_wrappers already consumed every Projection and SubqueryAlias before this match, so the plan that reaches this arm can never be either one.
strip_passthrough_nodes returns Some on the first iteration here, and the new debug message cannot fire. I would use other directly as the source.
There was a problem hiding this comment.
Thanks for pointing this out. You're right: after peel_wrappers, other cannot be a Projection or SubqueryAlias, so strip_passthrough_nodes always returns Some(other) here. I’ll use other directly as the source and remove the redundant check and unreachable debug message
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #25074 +/- ##
========================================
Coverage 81.88% 81.89%
========================================
Files 1133 1133
Lines 424522 424855 +333
Branches 424522 424855 +333
========================================
+ Hits 347622 347936 +314
+ Misses 56288 56285 -3
- Partials 20612 20634 +22 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@kumarUjjawal san
Regarding the source-identity concern I mentioned earlier, GroupKey now also checks the corresponding TableSource instances using Arc::ptr_eq, since TableScan equality ignores source. This conservatively prevents distinct source instances with identical scan metadata from being merged. I added regression tests for both direct scans and scans nested below projections and aliases. I also used unwrap() instead of ? in the newly added tests to address a CI coverage issue: when using ?, coverage was silently missed without an explicit error. Please take another look when you have a chance. |
|
Thank you @aoto-tech I will take a look on the weekends. |
| query I rowsort | ||
| SELECT x.id FROM t1 AS x WHERE x.id = 1 | ||
| UNION | ||
| SELECT x.id FROM t1 AS x WHERE x.id = 2 |
There was a problem hiding this comment.
It would be nice to add another explain plan to show the plan
There was a problem hiding this comment.
Great suggestion! I’ll make that change.
jayzhan211
left a comment
There was a problem hiding this comment.
Thanks @aoto-tech , non-blocking suggestions
| fn rewrite_union_distinct_matching_computed_projection_below_filter() -> Result<()> { | ||
| let scan = test_table_scan_with_name("prices").unwrap(); | ||
| let left_source = LogicalPlanBuilder::from(scan.clone()) | ||
| .project(vec![col("a").add(lit(100)).alias("amount")]) |
There was a problem hiding this comment.
col("a") + lit(100) without std::ops::Add
There was a problem hiding this comment.
The current test only checks that it returns 1 and 2, so it doesn’t prove that the rewrite still happens with the alias. I’ll add an EXPLAIN case for that.
|
@jayzhan211 san |
|
Thank you @aoto-tech and @jayzhan211 for the review. |
Which issue does this PR close?
Rationale for this change
With
enable_unions_to_filterenabled,UNION DISTINCTbranches containing different computed projections below their filters can be incorrectly treated as equivalent. This produces wrong result values and row counts without an error.What changes are included in this PR?
unions_to_filternow skips the rewrite when a projection appears below a branch filter. Alias nodes remain safe to strip. A regression test covers branches computinga + 100anda + 200below their filters.What is the testing strategy for this PR?
cargo +1.97.0-x86_64-pc-windows-gnullvm test --locked -p datafusion-optimizer unions_to_filter --lib(9 passed)[1100, 1200]with the optimizer setting both disabled and enabled after the fix.git diff --checkAre there any user-facing changes?
This prevents incorrect query results when the opt-in
unions_to_filteroptimizer rule is enabled.