Repository navigation
Full join output partitioning - #25554
gstamatakis95 wants to merge 2 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #25554 +/- ##
========================================
Coverage 82.83% 82.84%
========================================
Files 1149 1149
Lines 452798 453103 +305
Branches 452798 453103 +305
========================================
+ Hits 375082 375372 +290
- Misses 54893 54899 +6
- Partials 22823 22832 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
kosiew
left a comment
There was a problem hiding this comment.
Thanks for working on this. Preserving partitioning across full outer joins is a useful optimization, and the added regression coverage is substantial.
I found one correctness issue with computed join keys that needs to be addressed before merging. I've also left an optional suggestion for sort-merge join projection coverage.
| let mut coalesced = Vec::with_capacity(on.len()); | ||
| for (l, r) in on { | ||
| let r = add_offset_to_expr(Arc::clone(r), left_columns_len as _)?; | ||
| let left_first = coalesce_keys(Arc::clone(l), Arc::clone(&r))?; |
There was a problem hiding this comment.
Computed join keys can invalidate the reported partitioning and coalesce-order equivalence. For example, with a left key CASE WHEN a IS NOT NULL THEN a ELSE 0 END and right key c, right-only rows produce 0 for the left-first coalesce but c for the right-first one, potentially causing incorrect downstream aggregation. Please restrict this optimization to column keys or validate that computed keys preserve their values and become NULL on null-extended rows, falling back to UnknownPartitioning otherwise. Please also add a regression test covering unmatched rows and a downstream aggregate.
There was a problem hiding this comment.
Good catch, thanks! It now only kicks in when both join keys are plain columns, otherwise it falls back to UnknownPartitioning like before. I added a test as well
| } | ||
|
|
||
| #[test] | ||
| fn full_sort_merge_join_reports_coalesced_partitioning() { |
There was a problem hiding this comment.
Could we also test sort-merge join projections? SortMergeJoinExec handles property projection separately, so it would be useful to verify that retaining or reordering both keys preserves the remapped coalesced partitioning, while dropping one key invalidates it. This is optional coverage and can be deferred.
There was a problem hiding this comment.
Added three tests for this through with_projection
ed37a26 to
aa4e3cb
Compare
Which issue does this PR close?
Rationale for this change
A partitioned Full Outer Join reports
UnknownPartitioningeven when both inputs are already partitioned the same way on the join keys. Every output row stays in the partition its key was routed to, so the report throws away a real guarantee. A later aggregate or join on the join key then gets aRepartitionExecit does not need.The left key alone cannot describe the output, because right only rows carry NULL in the left key in every partition. The key that agrees with the partition number is
coalesce(left_key, right_key).What changes are included in this PR?
symmetric_join_output_partitioningtakes the join keys and the join's equivalence properties. For a Full join whose inputs pass the existing co-partitioning check, it reports the left partitioning restated onCASE WHEN l IS NOT NULL THEN l ELSE r ENDfor each key pair, throughPartitioning::adapt. Hash keeps its count, Range keeps its split points and sort options.COALESCE(r.k, l.k)matches as well.coalesce(a, 0)is not NULL on an unmatched row, so the coalesced key would be wrong. SQL planning already projects computed keys into columns below the join, so this check covers plans built directly through the physical plan API.c = coalesce(y, 0). The join output inherits that equality, but it fails on unmatched rows, and a parent could then skip a shuffle it needs. This is the check that protects SQL plans.UnknownPartitioning.HashJoinExecin partitioned mode,SortMergeJoinExec, andSymmetricHashJoinExecpass their keys and equivalence properties into the helper.The CASE form is what the planner produces for a two argument
COALESCE, so a parent's expression compares equal structurally.What is the testing strategy for this PR?
joins/utils.rscover the Hash, Range, and multi key outputs, the fallbacks toUnknownPartitioningincluding a computed key and a key held equal to a computed expression by a filter, projections on the hash and sort merge joins that keep, reorder, or drop a key, the symmetric hash join path, and the equivalence of both coalesce orders.enforce_distribution.rstests show no shuffle for a parent join on the coalesced key over Hash and Range inputs, in either key order and over a swapped join, and a shuffle for a parent join on the plain key.range_partitioning.slttests 50 to 54 show plans and results for an aggregate onCOALESCE, a chained Full join, the hash repartitioned variant, the negativeGROUP BY l.range_keycase with its single NULL group, and the reversed coalesce order.repartition.sltruns small nullable tables over four partitions: the filter equality case, which keeps its shuffle and returns one group, a computed join key with an unmatched row feeding an aggregate, and NULL join keys under=andIS NOT DISTINCT FROM, which need no shuffle and return a single NULL group.UnknownPartitioningfor Full joins now asserts the coalesced Hash partitioning.Are there any user-facing changes?
Plans with a partitioned Full join on column keys followed by an operator keyed on
COALESCEof the join keys lose aRepartitionExec. Results do not change. In a chain of Full joins on the coalesced key, the outer join reportsUnknownPartitioningwhen its input still carries the inner join's key columns, because the inner join's coalesce equivalence then shares a class with the outer key. Results stay correct, and a parent of the outer join keeps one shuffle.symmetric_join_output_partitioningis crate private, so there is no public API change.