metanil commented on code in PR #59760:
URL: https://github.com/apache/spark/pull/59760#discussion_r4432840456


##########
sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala:
##########
@@ -1792,6 +1792,77 @@ class KeyGroupedPartitioningSuite
     }
   }
 
+  test("SPARK-60522: a transform Spark cannot evaluate is not reduced onto or 
shuffled onto") {
+    // With `UnboundBucketFunction`, the first join reduces `ident` onto 
`bucket(4, id)`, and the
+    // second shuffles `plain` onto it, both by evaluating `bucket`. A 
connector may bind `bucket`
+    // to a function that is not a `ScalarFunction`, or to one Spark finds 
nothing to call on, and
+    // Spark cannot evaluate either. Then both joins shuffle both sides.
+    Seq(
+      UnboundBucketFunction -> (0, 1),
+      UnboundNonScalarBucketFunction -> (2, 2),
+      UnboundInheritedBucketFunction -> (2, 2)
+    ).foreach {
+      case (fn, (reduceShuffles, oneSideShuffles)) =>
+        withFunction(fn) {
+          createBucketedIdTable("bucket4", 4, numIds = 8)
+          createIdentityIdTable()
+          createPlainTable()
+
+          withSQLConf(
+              SQLConf.V2_BUCKETING_PUSH_PART_VALUES_ENABLED.key -> "true",
+              SQLConf.V2_BUCKETING_ALLOW_COMPATIBLE_TRANSFORMS.key -> "true") {
+            val df = sql(
+              "SELECT k.id FROM testcat.ns.bucket4 k JOIN testcat.ns.ident i 
ON k.id = i.id")
+            checkAnswer(df, (0 until 8).map(i => Row(i.toLong)))
+            assert(collectAllShuffles(df.queryExecution.executedPlan).size == 
reduceShuffles,
+              fn.name())
+          }
+
+          withSQLConf(SQLConf.V2_BUCKETING_SHUFFLE_ENABLED.key -> "true") {
+            val df = sql(
+              "SELECT k.id FROM testcat.ns.bucket4 k JOIN testcat.ns.plain p 
ON k.id = p.b")
+            checkAnswer(df, (0 until 8).map(i => Row(i.toLong)))
+            assert(collectAllShuffles(df.queryExecution.executedPlan).size == 
oneSideShuffles,
+              fn.name())
+          }
+          catalog.clearTables()
+        }
+    }
+  }
+
+  test("SPARK-60522: a child Spark cannot evaluate leaves the other child as 
the shuffle target") {
+    // `bucket8` lives in a second catalog, whose `bucket` Spark can evaluate. 
Picking the target,
+    // `EnsureRequirements` asks every child whether it can serve as one, and 
`bucket4`'s must
+    // answer no, not throw, so that `bucket4` is shuffled onto `bucket8`.
+    val catalogName = "spark60522cat"
+    spark.conf.set(s"spark.sql.catalog.$catalogName", 
classOf[InMemoryCatalog].getName)
+    val catalog2 = spark.sessionState.catalogManager.catalog(catalogName)
+      .asInstanceOf[InMemoryCatalog]
+    try {
+      withFunction(UnboundInheritedBucketFunction) {
+        catalog2.createFunction(Identifier.of(Array.empty, "bucket"), 
UnboundBucketFunction)
+        createBucketedIdTable("bucket4", 4, numIds = 8)
+        createTable("bucket8", Array(Column.create("id", LongType)), 
Array(bucket(8, "id")),
+          catalog = catalog2)
+        sql(s"INSERT INTO $catalogName.ns.bucket8 VALUES " +
+          (0 until 8).map(i => s"($i)").mkString(", "))
+
+        withSQLConf(SQLConf.V2_BUCKETING_SHUFFLE_ENABLED.key -> "true") {
+          Seq(s"testcat.ns.bucket4 a JOIN $catalogName.ns.bucket8 b",

Review Comment:
   Good catch, thanks. The test now also runs with the counts swapped: the 
inherited-`produceResult` `bucket(8) `against a scalar `bucket(4)` in the 
second catalog. That's the case that fails on the base, so the test now 
actually exercises the gate.



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