ShayanGho opened a new pull request, #25589:
URL: https://github.com/apache/datafusion/pull/25589

   ## Which issue does this PR close?
   
   - Closes #25435.
   
   Part of epic #25421.
   
   ## Rationale for this change
   
   Two inputs that are both range-partitioned on `(a, b)` with identical split 
points are co-partitioned, so a partitioned join can run within each partition 
with no shuffle. Whether the planner produced that plan depended on the order 
the equality conjuncts were written in:
   
   ```sql
   SELECT ... FROM t1 JOIN t2 ON t1.b = t2.b AND t1.a = t2.a
   ```
   
   gives `HashJoinExec` `on = [(b, b), (a, a)]`, so the join required 
`KeyPartitioned([b, a])`. `Partitioning::satisfaction` compares key lists 
positionally, reported `NotSatisfied` against `Range([a, b])`, and 
`EnsureRequirements` hash-repartitioned both already co-partitioned inputs.
   
   Before, from `range_join_key_alignment_reversed_keys` (both inputs ranged on 
`(a, b)`, the right one aliased to `(a1, b1)`, joined `b = b1 AND a = a1`):
   
   ```
   HashJoinExec: mode=Partitioned, join_type=Inner, on=[(b@1, b1@1), (a@0, 
a1@0)]
     RepartitionExec: partitioning=Hash([b@1, a@0], 4), input_partitions=4
       DataSourceExec: file_groups={4 groups: [[p0], [p1], [p2], [p3]]}, 
projection=[a, b, c, d, e], output_partitioning=Range([a@0 ASC, b@1 ASC], [(10, 
0), (20, 0), (30, 0)], 4), file_type=parquet
     RepartitionExec: partitioning=Hash([b1@1, a1@0], 4), input_partitions=4
       ProjectionExec: expr=[a@0 as a1, b@1 as b1]
         DataSourceExec: file_groups={4 groups: [[p0], [p1], [p2], [p3]]}, 
projection=[a, b, c, d, e], output_partitioning=Range([a@0 ASC, b@1 ASC], [(10, 
0), (20, 0), (30, 0)], 4), file_type=parquet
   ```
   
   After:
   
   ```
   HashJoinExec: mode=Partitioned, join_type=Inner, on=[(a@0, a1@0), (b@1, 
b1@1)]
     DataSourceExec: file_groups={4 groups: [[p0], [p1], [p2], [p3]]}, 
projection=[a, b, c, d, e], output_partitioning=Range([a@0 ASC, b@1 ASC], [(10, 
0), (20, 0), (30, 0)], 4), file_type=parquet
     ProjectionExec: expr=[a@0 as a1, b@1 as b1]
       DataSourceExec: file_groups={4 groups: [[p0], [p1], [p2], [p3]]}, 
projection=[a, b, c, d, e], output_partitioning=Range([a@0 ASC, b@1 ASC], [(10, 
0), (20, 0), (30, 0)], 4), file_type=parquet
   ```
   
   ## What changes are included in this PR?
   
   - In `ensure_distribution_with_stats`, before a node's distribution 
requirements are read, a new `reorder_join_keys_to_range_inputs` reorders a 
`Partitioned` `HashJoinExec`'s or a `SortMergeJoinExec`'s `on` pairs (and, for 
sort-merge joins, the matching `sort_options`) to follow the left, else the 
right, input's Range key order, when the join keys are a complete permutation 
of that input's range keys. Whole pairs move together, so the join condition is 
unchanged. It runs in the bottom-up distribution pass so each join sees its 
inputs' partitioning after their own requirements have been enforced, and it 
applies regardless of `datafusion.optimizer.top_down_join_key_reordering`.
   - Matching is per input (`range_join_key_positions`): an input's range key 
expressions are compared only with that same input's join keys, using that 
input's equivalence classes. The existing `try_reorder` also compares against 
the other side's keys, which for an unaliased crossed join (left `Range([a, 
b])`, right `Range([b, a])`, `ON l.b = r.a AND l.a = r.b`) reports "already 
aligned" and misses the permutation.
   - Distribution satisfaction is deliberately left positional; reordering the 
pairs is what makes the requirement satisfiable. Making satisfaction 
order-insensitive instead would be unsound: a row with `a = 10, b = 20` is in 
partition 1 under `Range([a, b])` but in partition 2 under `Range([b, a])`, so 
inputs with the same split values in a different key order are still 
repartitioned (covered by 
`range_join_key_alignment_swapped_range_keys_still_repartition`).
   - The existing hash-key reordering in the join-key reordering phase is 
unchanged. Doc comments updated to say where each kind of alignment happens.
   - Behavior note: when one side's layout is the swapped one, the reference 
side changes. Previously the left input was repartitioned to match the right; 
now the join is aligned to the left and the right input is repartitioned. Both 
plans are correct.
   
   ## What is the testing strategy for this PR?
   
   - `datafusion/core/tests/physical_optimizer/enforce_distribution.rs`: 
`range_join_key_alignment_reversed_keys`, 
`range_join_key_alignment_crossed_keys`, 
`range_join_key_alignment_sort_merge_join` (asserts `sort_options` are permuted 
with `on` and the projection survives the rebuild), and 
`range_join_key_alignment_swapped_range_keys_still_repartition`. Each runs 
under both `top_down_join_key_reordering` settings, passes `SanityCheckPlan`, 
and is unchanged by a second `EnsureRequirements` pass.
   - `datafusion/sqllogictest/test_files/range_partitioning.slt` TEST 49, over 
a new composite-key fixture `range_partitioned_composite` registered in 
`test_context/range_partitioning.rs`: `EXPLAIN` of the query from the issue 
plus result verification, with rows on the split boundaries and duplicate 
matching keys.
   
   ## Are there any user-facing changes?
   
   No API changes. Plans for multi-key joins over compatible range-partitioned 
inputs no longer depend on the order of the `ON` conjuncts.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to