@@ -582,6 +582,8 @@ case class KeyLayout(
582582 *
583583 * - `keysSatisfy()`: do the keys as they stand co-locate every cluster key, with nothing left for
584584 * a node to project away? This is the strict question, and `satisfies()` is it plus `isGrouped`.
585+ * Strict for a `ClusteredDistribution`; its `OrderedDistribution` arm is a gate on what may claim
586+ * a global ordering, and the order itself is still the caller's to check.
585587 * - `keysCanSatisfy()`: `keysSatisfy()`, or the keys co-locate them after a node has projected
586588 * away the expressions that carry none. Only
587589 * `spark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys` admits the second half.
@@ -818,22 +820,18 @@ case class KeyedPartitioning(
818820 /**
819821 * The counts `numPartitionsProjectedOn` has already worked out, by position set.
820822 *
821- * Memoized here rather than at a caller because there is no one caller. `satisfies` asks this
822- * question now, and `EnsureRequirements`, `ValidateRequirements` and every `AQEShuffleReadRule`
823- * application all ask `satisfies` of the same partitioning, so a memo scoped to one rule pass
824- * would miss most of the repeats. The partitioning is immutable and the answer depends only on
825- * the keys and the position set, so the cache cannot go stale.
823+ * Kept on the partitioning rather than at a caller because there is no one caller: `satisfies`
824+ * asks this question, and the planner, `ValidateRequirements` and every `AQEShuffleReadRule`
825+ * application all ask `satisfies` of the same partitioning. The partitioning is immutable and the
826+ * answer depends only on the keys and the position set, so the cache cannot go stale.
826827 *
827828 * Not a constructor field, so it stays out of the product and leaves equality, `hashCode` and
828- * canonicalization alone. The map is built on first use, and only for a partitioning something
829- * actually asks a narrowing question of. On the default configuration nothing does, since
830- * `mayProjectToClusterKeys` requires
829+ * canonicalization alone. The map is built on first use, and on the default configuration nothing
830+ * asks a narrowing question at all, since `mayProjectToClusterKeys` requires
831831 * `spark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys`.
832832 *
833- * [[TransientBestEffortLazyVal ]] rather than a `lazy val`, for the reason that class exists: a
834- * Scala 2 `lazy val` locks the instance to initialize it, and this is read from whichever thread
835- * is planning or validating. Two threads racing here each build an empty map and one is dropped,
836- * which is all the best-effort part costs.
833+ * [[TransientBestEffortLazyVal ]] because a Scala 2 `lazy val` locks the instance to initialize
834+ * it, and this is read from whichever thread is planning or validating.
837835 */
838836 private val projectedPartitionCounts =
839837 new TransientBestEffortLazyVal [ConcurrentHashMap [BitSet , java.lang.Integer ]](
@@ -935,8 +933,8 @@ case class KeyedPartitioning(
935933 expressions.forall(_.references.size == 1 )
936934
937935 /**
938- * The strict question of the family the class doc lists. `true` only when the
939- * keys as they stand co-locate every cluster key, with nothing left for a
936+ * The strict question of the family the class doc lists, for a [[ ClusteredDistribution ]] . `true`
937+ * only when the keys as they stand co-locate every cluster key, with nothing left for a
940938 * `GroupPartitionsExec` to do about it.
941939 *
942940 * Two ways to be true. Every partition expression is a function of cluster keys alone, so
@@ -945,6 +943,12 @@ case class KeyedPartitioning(
945943 * second is the only branch that reads a partition key, and only
946944 * `spark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys` admits it, so the ordinary
947945 * configuration answers structurally.
946+ *
947+ * The strictness claim stops at [[ClusteredDistribution ]]. The [[OrderedDistribution ]] arm is a
948+ * local gate on what may claim a global ordering at all, and it does not read the key order, so a
949+ * side whose keys run the wrong way still answers `true` here. `EnsureRequirements.resolveChild`
950+ * is what compares the keys against the required ordering and builds the sorting node, and it has
951+ * to stay: nothing below tells it the keys are already in order.
948952 */
949953 private def keysSatisfy (required : Distribution ): Boolean = {
950954 required match {
@@ -1383,24 +1387,18 @@ case class PartitioningCollection(partitionings: Seq[Partitioning])
13831387 override def createShuffleSpec (distribution : ClusteredDistribution ): ShuffleSpec = {
13841388 // `maySatisfyAfterProjection`, not `satisfies`. A spec says what its partitioning could
13851389 // co-partition on, and a grouped `KeyedPartitioning` whose keys are coarser than the
1386- // operation's still can, through the projection a `GroupPartitionsExec` performs. The strict
1387- // question would drop it, which is narrower than what this filter admitted before `satisfies`
1388- // became strict.
1389- //
1390- // It is not wider either. `ValidateRequirements` builds a spec from a finished plan through
1391- // here, and is the gate that makes AQE drop a rewrite that breaks co-partitioning, so this
1392- // admits exactly what it admitted before and no more. `checkKeyedPartitioningInvariant` forces
1393- // one `KeyLayout` on all keyed members, so `isGrouped` is uniform across them and the filter
1394- // either keeps every keyed member or none: the latter only for a partitioning that does not
1395- // serve the distribution through a keyed member at all, which neither caller passes.
1390+ // operation's still can, through the projection a `GroupPartitionsExec` performs. That is the
1391+ // admission set this filter had before `satisfies` became strict, up to one shape it now also
1392+ // keeps, a partition expression that *is* a cluster key, which `areKeysCompatible` turns away
1393+ // anyway. The set matters because `ValidateRequirements` builds a spec from a finished plan
1394+ // through here.
13961395 //
1397- // Every admitted member has to stay, because `isCompatibleWith` answers for any of them and
1398- // the collection cannot know which one the other side matched. That has a cost worth knowing:
1399- // `KeyedShuffleSpec.canCreatePartitioning` is false without `v2BucketingShuffleEnabled` and
1400- // `ShuffleSpecCollection.canCreatePartitioning` is a `forall`, so a groupable keyed member
1401- // beside a usable non-keyed one would cost the collection its role as a shuffle template. No
1402- // operator is known to report that mixture, since `EnsureRequirements` groups a keyed child
1403- // before it can reach a join's output.
1396+ // Every admitted member stays, because `isCompatibleWith` answers for any of them and the
1397+ // collection cannot know which one the other side matched. The cost is that
1398+ // `ShuffleSpecCollection.canCreatePartitioning` is a `forall` over members whose keyed half is
1399+ // false without `v2BucketingShuffleEnabled`, so a groupable keyed member beside a usable
1400+ // non-keyed one would cost the collection its role as a shuffle template. No operator is known
1401+ // to report that mixture.
14041402 val filtered =
14051403 partitionings.filter(PartitioningCollection .maySatisfyAfterProjection(_, distribution))
14061404 ShuffleSpecCollection (filtered.map(_.createShuffleSpec(distribution)))
@@ -1446,18 +1444,15 @@ object PartitioningCollection {
14461444 * finished plan is checked against. `EnsureRequirements.createKeyedShuffleSpecs` is where the
14471445 * planner asks the wider question, for a child it is about to group itself.
14481446 *
1449- * A required partition count still has to match, which is what `satisfies` applies to every other
1450- * partitioning. A node changes the count, so the pre-grouping one is not a bound on what it will
1451- * be. This clause is here for consistency with the strict question, not as a prediction.
1447+ * The partition count clause is here for consistency with the strict question. A node changes the
1448+ * count, so the pre-grouping one is no prediction of it.
14521449 */
14531450 private [sql] def maySatisfyAfterProjection (
14541451 p : Partitioning ,
1455- required : ClusteredDistribution ): Boolean = p match {
1452+ required : ClusteredDistribution ): Boolean = flatten(p).exists {
14561453 case k : KeyedPartitioning =>
14571454 k.isGrouped && required.requiredNumPartitions.forall(_ == k.numPartitions) &&
14581455 k.keysMaySatisfy(required)
1459- case pc : PartitioningCollection =>
1460- pc.partitionings.exists(maySatisfyAfterProjection(_, required))
14611456 case other => other.satisfies(required)
14621457 }
14631458
0 commit comments