ulysses-you commented on code in PR #58339:
URL: https://github.com/apache/spark/pull/58339#discussion_r3939155993


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/GroupPartitionsExec.scala:
##########
@@ -78,6 +78,18 @@ case class GroupPartitionsExec(
         // data types match the reduced partition keys for the 
identity-vs-transform and
         // single-side-transform reducers; for the both-sides-reduce shape no 
single transform
         // describes the keys (see `KeyedShuffleSpec.reducersBothWays`).
+        //
+        // A marked claim pins undeclared rows to hash(key) % numPartitions 
(see
+        // `KeyedPartitioning.mayContainUnknownPartitionKeys`). Only an 
identity grouping keeps
+        // that relationship: any other grouping -- a reorder, a coalesce, a 
resize, or the
+        // collapse a reduction applies -- moves those rows. Clearing only the 
marker would
+        // misreport the undeclared rows that remain, so give up the keyed 
partitioning at the
+        // physical output count (one per group, padding included) that a 
parent's
+        // `PartitioningCollection` requires for uniformity. 
`identityGrouping` is a lazy val, so
+        // repeated `outputPartitioning` calls scan it at most once.
+        if (PartitioningCollection.keyedMarkerOf(p).contains(true) && 
!identityGrouping) {

Review Comment:
   Thank you for the second look. The pairing-commit gap is real: a marked side 
whose regrouping is not the identity gives up its keyed claim, and the 
regrouping test exercises that on this head (the committed pair then carries a 
child `ValidateRequirements` rejects, at the cost of AQE rules on that stage; 
results stay correct because the give-up forces downstream re-shuffles).
   
   Closing it at the pairing site is not a per-side `satisfies` though. The 
soundness of the join scenario rests on `alignToExpectedKeys`: the pairing 
aligns both rebuilt children onto the same expected key sequence - same per-key 
partition spread, padding and partial clustering included - so they co-locate 
as a pair even when neither satisfies its `ClusteredDistribution` individually 
(partial clustering deliberately leaves them ungrouped; measured, a per-side 
`satisfies` gate regresses the partial-clustering suites). What the commit 
should be re-checked against is that joint alignment: whether both rebuilt 
children still declare the same aligned key sequence and neither gave up its 
keyed layout. A pairwise check capturing that, reconciled with 
`ValidateRequirements`' per-side semantics, is the scope of SPARK-59272.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/joins/ShuffledJoin.scala:
##########
@@ -69,8 +69,15 @@ trait ShuffledJoin extends JoinCodegenSupport {
 
   override def outputPartitioning: Partitioning = joinType match {
     case _: InnerLike =>
+      // Every `KeyedPartitioning` in the joined collection speaks the same 
declared key set (the
+      // `PartitioningCollection` invariant), and `keysSatisfy` admits a 
marked side into it only
+      // for full-key join keys, where the join equality ties every column of 
its claim: a row of
+      // an undeclared key matches nothing on the accurate side and is 
filtered, so a marked
+      // member among an unmarked one is spurious. Clear it at the one site 
that can mix them, so

Review Comment:
   Fixed in 3c6b0143308: `a marked member alongside an unmarked one is 
spurious`.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/GroupPartitionsExec.scala:
##########
@@ -216,9 +239,31 @@ case class GroupPartitionsExec(
         group.tail.exists(childKeys(_) != first)
       }
     }
-    PartitionGrouping(partitions, isGrouped, isCollapsed)
+    PartitionGrouping(partitions, isGrouped, isCollapsed, keysRewritten)
   }
 
+  /**
+   * Whether this node's grouping leaves the declared keys and every partition 
where they were:
+   * no projection or reduction rewrote the keys, output partition i holds 
exactly input
+   * partition i, and there is one output per input. That is the only grouping 
that keeps a
+   * marked layout's undeclared rows at hash(key) % numPartitions. A 
projection or reduction
+   * re-labels the groups into a different key space, so even a grouping whose 
indices line up
+   * would pin the claim to keys it no longer declares; `keysRewritten` 
rejects it up front --
+   * it covers a narrowing projection, a reordering one, and any reducer slot. 
A reducer slot
+   * is treated as key-changing: a conforming self-reducer cannot rewrite a 
reachable key
+   * value, so the give-up there loses at most an optimization. A grouping 
that drops trailing
+   * declared keys still reads identity for every group it keeps, but the 
partition count
+   * shrinks and the hash modulus with it. The `forall` stops at the first 
moved partition, so
+   * a reorder or coalesce is rejected without a full scan.
+   */
+  @transient private lazy val identityGrouping: Boolean =
+    !grouping.keysRewritten &&
+      grouping.partitions.size == child.outputPartitioning.numPartitions &&
+      grouping.partitions.zipWithIndex.forall {

Review Comment:
   Fixed in 3c6b0143308: the identity check now streams 
`grouping.partitions.iterator.zipWithIndex` instead of materializing one tuple 
per physical partition.



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