cloud-fan commented on code in PR #58942:
URL: https://github.com/apache/spark/pull/58942#discussion_r4085063362


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/exchange/ValidateRequirements.scala:
##########
@@ -45,29 +47,81 @@ object ValidateRequirements extends Logging {
     assert(requiredChildDistributions.length == children.length)
     assert(requiredChildOrderings.length == children.length)
 
+    // A `ClusteredDistribution` is the one distribution an operator can owe 
its children together
+    // rather than one by one, so an operator whose children all owe one is 
judged on their mutual
+    // layout below, and every other child, an operator with a single 
clustered child included,
+    // answers for itself. That is every such operator, not only a join: one 
that zips corresponding
+    // partitions, a cogroup for instance, reads a layout both children have 
to hold together.
+    val clusteredMultiChild = children.length > 1 &&
+      requiredChildDistributions.forall(_.isInstanceOf[ClusteredDistribution])
+
+    // The one member a finished plan may report without satisfying the 
distribution is the shape
+    // partially clustered distribution spreads ungrouped, and one producer 
builds it:
+    // `EnsureRequirements.checkKeyGroupCompatible`. Both halves of that 
admission are asked here,
+    // and the answer is passed down to the pairing below so the waiver cannot 
be read one way here
+    // and the other way there. Neither half is a second copy: the operator 
kinds come from the
+    // producer itself. What is left to the member, its count and the 
permission for the collapse it
+    // went through, is asked there.
+    val mayBeUngrouped = clusteredMultiChild &&
+      SQLConf.get.v2BucketingPartiallyClusteredDistributionEnabled &&
+      ShuffledJoin.partiallyClusteredJoinType(plan).isDefined

Review Comment:
   **Non-blocking (P2):** `partiallyClusteredJoinType(plan).isDefined` proves 
only that this is an SMJ/SHJ, not that this join type can produce the 
spread/repeat layout the waiver requires. For `FullOuter`, both duplication 
capabilities are false, so `EnsureRequirements` skips partial clustering, yet 
matching repeated children can pass here and execute split groups 
partition-by-partition, missing cross-split matches or emitting matched rows as 
unmatched. Please make the shared predicate exclude join types with no 
duplicable side and add a focused FullOuter refusal test.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/exchange/ValidateRequirements.scala:
##########
@@ -45,29 +47,81 @@ object ValidateRequirements extends Logging {
     assert(requiredChildDistributions.length == children.length)
     assert(requiredChildOrderings.length == children.length)
 
+    // A `ClusteredDistribution` is the one distribution an operator can owe 
its children together
+    // rather than one by one, so an operator whose children all owe one is 
judged on their mutual
+    // layout below, and every other child, an operator with a single 
clustered child included,
+    // answers for itself. That is every such operator, not only a join: one 
that zips corresponding
+    // partitions, a cogroup for instance, reads a layout both children have 
to hold together.
+    val clusteredMultiChild = children.length > 1 &&
+      requiredChildDistributions.forall(_.isInstanceOf[ClusteredDistribution])
+
+    // The one member a finished plan may report without satisfying the 
distribution is the shape
+    // partially clustered distribution spreads ungrouped, and one producer 
builds it:
+    // `EnsureRequirements.checkKeyGroupCompatible`. Both halves of that 
admission are asked here,
+    // and the answer is passed down to the pairing below so the waiver cannot 
be read one way here
+    // and the other way there. Neither half is a second copy: the operator 
kinds come from the
+    // producer itself. What is left to the member, its count and the 
permission for the collapse it
+    // went through, is asked there.
+    val mayBeUngrouped = clusteredMultiChild &&
+      SQLConf.get.v2BucketingPartiallyClusteredDistributionEnabled &&
+      ShuffledJoin.partiallyClusteredJoinType(plan).isDefined
+
     val satisfied = 
children.zip(requiredChildDistributions.zip(requiredChildOrderings)).forall {
       case (child, (distribution, ordering))
-          if !child.outputPartitioning.satisfies(distribution)
+          if (!child.outputPartitioning.satisfies(distribution) &&
+              !(mayBeUngrouped &&
+                
PartitioningCollection.representativeOf(child.outputPartitioning).isDefined))
             || !SortOrder.orderingSatisfies(child.outputOrdering, ordering) =>
         logDebug(s"ValidateRequirements failed: $distribution, 
$ordering\n$plan")
         false
       case _ => true
     }
 
-    if (satisfied && children.length > 1 &&
-      
requiredChildDistributions.forall(_.isInstanceOf[ClusteredDistribution])) {
-      // Check the co-partitioning requirement.
-      val specs = 
children.map(_.outputPartitioning).zip(requiredChildDistributions).map {
-        case (p, d) => 
p.createShuffleSpec(d.asInstanceOf[ClusteredDistribution])
-      }
-      if (specs.tail.forall(_.isCompatibleWith(specs.head))) {
-        true
-      } else {
+    // What a multi-child clustered operator reads is the pairing: a pair 
aligned without grouping,
+    // which partially clustered distribution builds on purpose, is one the 
sides agree on while
+    // neither is grouped. The pairing cannot tell how the two sides hold a 
key's rows, since a
+    // spread side and one that repeats the whole group report the same keys 
as two sides that
+    // split the key, so that rests on the producer, which is why the 
ungrouped shape alone is
+    // waived above.
+    if (!satisfied) {
+      false
+    } else if (clusteredMultiChild) {
+      val paired = satisfiesForPairing(children, requiredChildDistributions, 
mayBeUngrouped)
+      if (!paired) {
         logDebug(s"ValidateRequirements failed: children not co-partitioned 
in\n$plan")
-        false
       }
+      paired
     } else {
-      satisfied
+      true
+    }
+  }
+
+  /**
+   * Whether the sides of a multi-child clustered operator line up: every side 
offers the layouts it
+   * reports ([[PartitioningCollection.specsForPairing]]), and one member of 
the first side pairs
+   * with every other side. A plan holds what its members report, so no key is 
deduped and none is
+   * re-sorted to make a pair: a side is judged on the partitions it has, 
under the key the
+   * operation clusters on.
+   *
+   * This is the question `EnsureRequirements` asks of a pair it takes as it 
stands, the

Review Comment:
   **Nit (P3):** This is not quite the same question as `compatibleAsIs`. Here, 
`reportedSpecOf` may relabel a grouped `[a, b]` member for an `[a]` 
distribution when dropping `b` merges no partition; the planner represents that 
candidate as projected, and `compatibleAsIs` requires both specs to be 
unprojected. Please describe the relationship without claiming predicate 
equivalence across this projection case.



-- 
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