[SPARK-59659][SQL] Consolidate the repeated cluster-key matching in Partitioning.satisfies0 - #58928
peter-toth wants to merge 1 commit into
Conversation
…artitioning.satisfies0 Five `satisfies0` implementations spelled out the same two-branch test on `ClusteredDistribution.requireAllClusterKeys`, so the flag was read in five places and the membership test written out five times. `ClusteredDistribution.matchesClusterKeys` is that pair, beside the `areAllClusterKeysMatched` it wraps and the `isClusterKey` it is built from, and the five callers ask it: `HashPartitioningLike`, `NullAwareHashPartitioning`, `CoalescedNullAwareHashPartitioning`, `RangePartitioning` and `ShufflePartitionIdPassThrough`. Three hand-written zip-and-compare folds are one `Seq.corresponds` each: `ClusteredDistribution.areAllClusterKeysMatched`, `OrderedDistribution.areAllClusterKeysMatched`, and the `StatefulOpClusteredDistribution` arm that spelled the first one out at a call site. `AQEUtils.getRequiredDistribution`'s mirror fold, which asks whether a project list covers the clustering, is `allClusterKeysAmong`. Behaviour neutral. `corresponds` is same-length-and-element-wise, which is what each fold computed, and the argument order of every `semanticEquals` is preserved. Replacing the `case c @ ClusteredDistribution(...)` extractors with `case c: ClusteredDistribution` matches the same set, since the physical `ClusteredDistribution` is a leaf case class; the `ClusteredDistributionImpl` in `connector.distributions` extends the connector interface of that name, not this one. `matchesClusterKeys` and the reverted form of the rest were written for apache#58659 and taken out of it, because they accounted for most of that PR's `partitioning.scala` diff and are unrelated to what it fixes.
|
@ulysses-you this is the |
dongjoon-hyun
left a comment
There was a problem hiding this comment.
+1, LGTM.
I checked the equivalence of each rewrite and they all hold:
correspondsis same-length-and-elementwise, so it computes exactly what the three zip-folds did, and thesemanticEqualsargument order is preserved in each.- Replacing
case c @ ClusteredDistribution(_, _, _, _)withcase c: ClusteredDistributionmatches the same set, since the extractor bound all four parameters of a leaf case class. TheClusteredDistributioninconnector.distributionsis a different type and is not imported here. allowNullKeySpreadingis the fourth constructor parameter, so the guard is unchanged in the two null-aware partitionings.allClusterKeysAmongis literally the fold it replaces inAQEUtils, argument order included.
I also think the scope is right: KeyGroupedPartitioning.keysSatisfy reads the same flag but its else branch is isFunctionOfClusterKeys || (mayProjectToClusterKeys && ...), not the membership test, and the two canCreatePartitioning sites are on the co-partition flag. Folding either into matchesClusterKeys would have changed behaviour; leaving them alone is correct.
Two non-blocking doc nits, feel free to take them in a follow-up:
-
The new scaladoc says "This is the whole of what
requireAllClusterKeysgoverns, so asatisfies0reads it here rather than branching on the flag itself."KeyGroupedPartitioning.satisfies0still branches on the flag viakeysSatisfy, andmayProjectToClusterKeysreads it too. A reader who takes the sentence at face value might "finish the job" there and drop the projection logic. Narrowing it to these fivesatisfies0implementations, or naming the exception, would prevent that. -
The existing comment on
areAllClusterKeysMatchedstill says aPartitioningshould call it whenrequireAllClusterKeysis set. After this PR the partitionings callmatchesClusterKeysinstead, so it could point there.
What changes were proposed in this pull request?
Five
Partitioning.satisfies0implementations spelled out the same two-branch test onClusteredDistribution.requireAllClusterKeys:So the flag was read in five places and the membership test written out five times.
ClusteredDistribution.matchesClusterKeysis that pair, beside theareAllClusterKeysMatchedit wraps and theisClusterKeyit is built from, and the five callers ask it:HashPartitioningLike,NullAwareHashPartitioning,CoalescedNullAwareHashPartitioning,RangePartitioningandShufflePartitionIdPassThrough.Three hand-written zip-and-compare folds become one
Seq.correspondseach:ClusteredDistribution.areAllClusterKeysMatched,OrderedDistribution.areAllClusterKeysMatched, and theStatefulOpClusteredDistributionarm ofHashPartitioningLike.satisfies0that spelled the first one out at a call site rather than calling it.AQEUtils.getRequiredDistribution's mirror fold, which asks whether a project list covers the clustering rather than the other way round, isallClusterKeysAmong.Net 28 lines removed, no new API beyond
matchesClusterKeyson aprivate[sql]-reachable case class insql.catalyst.Why are the changes needed?
requireAllClusterKeysis one rule about what a partitioning has to be partitioned on, and it was five copies of that rule. A change to it had to be made in five places, and a reader comparing twosatisfies0implementations had to diff the copies to see that they agree.isClusterKeyandallClusterKeysAmongalready exist onClusteredDistributionfor exactly this reason, added by SPARK-59289; this finishes the set and uses them.Does this PR introduce any user-facing change?
No. This is behaviour neutral, and three things are worth stating because each is a place it could have failed to be:
Seq.correspondsis same-length-and-element-wise, which is what each of the three folds computed.semanticEqualsis preserved, so nothing relies on it being symmetric.case c @ ClusteredDistribution(...)extractor patterns withcase c: ClusteredDistributionmatches the same set: the physicalClusteredDistributionis a leaf case class. TheClusteredDistributionImplinconnector.distributionsextends the connector interface of that name, not this one.How was this patch tested?
No new tests: there is no new behaviour to pin, and the existing suites already cover each rewritten site.
Green:
DistributionSuite,ShuffleSpecSuite,PlannerSuite,EnsureRequirementsSuite,ValidateRequirementsSuite,KeyGroupedPartitioningSuite,GroupPartitionsExecSuite,AdaptiveQueryExecSuiteandExchangeSuite, 593 tests.The touched arms that are easy to miss got their own runs.
StatefulOpClusteredDistributionand the two null-aware partitionings:StreamingAggregationDistributionSuite,StreamingDeduplicationDistributionSuite,StreamingSessionWindowDistributionSuite,FlatMapGroupsWithStateDistributionSuiteandStreamingQueryHashPartitionVerifySuite, 12 tests. And sincesatisfies0runs on every query, all five plan-stability suites, 172 tests, so no golden TPCDS or TPCH plan changed.dev/lint-scalais clean.Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 5)