dwsmith1983 commented on code in PR #6270:
URL: https://github.com/apache/datafusion-comet/pull/6270#discussion_r4266623601


##########
spark/src/test/scala/org/apache/comet/exec/CometExecSuite.scala:
##########
@@ -1769,6 +1769,117 @@ class CometExecSuite extends CometTestBase {
     }
   }
 
+  // Tables for the #6264 tests. The fact table has 10 store partitions. 
Dimension group 'a' keeps
+  // store 1 and group 'b' keeps stores 2 to 4, so the two DPP scans prune to 
1 and 3 partitions.
+  private def withDppBranchTables(f: => Unit): Unit = {
+    withTempDir { dir =>
+      val path = dir.getAbsolutePath
+      spark
+        .range(1000)
+        .selectExpr(
+          "cast(id % 10 as int) as store_id",
+          "cast(id % 13 as int) as item",
+          "cast(id as int) as amount")
+        .write
+        .partitionBy("store_id")
+        .parquet(s"$path/fact")
+      spark
+        .range(10)
+        .selectExpr(
+          "cast(id as int) as store_id",
+          "case when id = 1 then 'a' when id in (2, 3, 4) then 'b' else 'c' 
end as grp")
+        .write
+        .parquet(s"$path/dim")
+      spark
+        .range(13)
+        .selectExpr("cast(id as int) as item", "cast(id * 10 as int) as w")
+        .write
+        .parquet(s"$path/other")
+      withTempView("dpp_branch_fact", "dpp_branch_dim", "dpp_branch_other") {
+        
spark.read.parquet(s"$path/fact").createOrReplaceTempView("dpp_branch_fact")
+        
spark.read.parquet(s"$path/dim").createOrReplaceTempView("dpp_branch_dim")
+        
spark.read.parquet(s"$path/other").createOrReplaceTempView("dpp_branch_other")
+        f
+      }
+    }
+  }
+
+  // Runs a UNION ALL of one CTE joined to dimension group 'a' and to group 
'b'. Coalescing is off:
+  // a coalesced shuffle read carries per-partition sizes that would keep the 
two branches' parent
+  // exchanges apart and hide a wrong reuse.
+  private def checkDppBranchesKeepTheirRows(query: String): Unit = {
+    withSQLConf(
+      SQLConf.USE_V1_SOURCE_LIST.key -> "parquet",
+      SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true",
+      SQLConf.DYNAMIC_PARTITION_PRUNING_REUSE_BROADCAST_ONLY.key -> "true",
+      SQLConf.COALESCE_PARTITIONS_ENABLED.key -> "false") {
+      if (isSpark35Plus) {
+        val (_, cometPlan) = checkSparkAnswerAndOperator(sql(query))
+        def dppScans(plan: SparkPlan): Seq[CometNativeScanExec] = 
collect(plan) {
+          case s: CometNativeScanExec
+              if 
s.partitionFilters.exists(_.isInstanceOf[DynamicPruningExpression]) =>
+            s
+        }
+        assert(
+          dppScans(cometPlan).map(_.metrics("numPartitions").value).sorted == 
Seq(1L, 3L),
+          "Expected one DPP scan per branch, pruned to 1 and 3 partitions:\n" +
+            cometPlan.treeString)
+        val reusedDpp = collect(cometPlan) {
+          case r: ReusedExchangeExec if dppScans(r.child).nonEmpty => r
+        }
+        assert(
+          reusedDpp.isEmpty,
+          s"An exchange over a DPP scan was reused by the other 
branch:\n${cometPlan.treeString}")
+      } else {
+        // On 3.4, AQE DPP falls back to Spark.
+        checkSparkAnswer(sql(query))
+      }
+    }
+  }
+
+  // TPC-DS q64 shape: a broadcast of (fact SMJ other), with the dimension 
join above it. The two
+  // broadcasts differ only in the DPP filter of the fact scan inside a child 
shuffle stage.
+  test("AQE DPP: broadcast over a join of DPP scans is not reused across 
branches (#6264)") {
+    withDppBranchTables {
+      checkDppBranchesKeepTheirRows("""WITH x AS (
+          |  SELECT /*+ MERGE(f, o) */ f.store_id, f.item, f.amount, o.w
+          |  FROM dpp_branch_fact f JOIN dpp_branch_other o ON f.item = o.item)
+          |SELECT /*+ BROADCAST(x) */ o2.item, x.store_id, x.amount, x.w
+          |FROM dpp_branch_other o2 JOIN x ON o2.item = x.item
+          |JOIN dpp_branch_dim d ON x.store_id = d.store_id WHERE d.grp = 'a'
+          |UNION ALL
+          |SELECT /*+ BROADCAST(x) */ o2.item, x.store_id, x.amount, x.w
+          |FROM dpp_branch_other o2 JOIN x ON o2.item = x.item
+          |JOIN dpp_branch_dim d ON x.store_id = d.store_id WHERE d.grp = 
'b'""".stripMargin)
+    }
+  }
+
+  test("AQE DPP: shuffle over a DPP scan stage is not reused across branches 
(#6264)") {
+    withDppBranchTables {
+      checkDppBranchesKeepTheirRows("""WITH x AS (
+          |  SELECT store_id, item, sum(amount) AS s FROM dpp_branch_fact 
GROUP BY store_id, item),
+          |y AS (SELECT store_id, s % 7 AS b, count(*) AS c FROM x GROUP BY 
store_id, s % 7)
+          |SELECT y.store_id, y.b, y.c FROM y JOIN dpp_branch_dim d ON 
y.store_id = d.store_id
+          |WHERE d.grp = 'a'
+          |UNION ALL
+          |SELECT y.store_id, y.b, y.c FROM y JOIN dpp_branch_dim d ON 
y.store_id = d.store_id
+          |WHERE d.grp = 'b'""".stripMargin)
+    }
+  }
+
+  // Guards the scan-stage path, which passes without keeping the DPP 
placeholder in the canonical
+  // form: the dimension join sits in the scan's own stage, and AQE keys each 
scan stage after its
+  // DPP filter is converted.
+  test("AQE DPP: DPP join in the scan's own stage keeps both branches 
(#6264)") {

Review Comment:
   Replaced it with that case: the shuffle query with `grp = 'a'` in both 
branches. The test compares the Comet plan's `ReusedExchangeExec` count with 
the Spark plan from `checkSparkAnswer`, and on 3.5 and later also checks that a 
reused exchange sits over the DPP scan. Spark does reuse the shuffle there, and 
Comet matches it.
   
   It passes on main as well, because identical branches shared the stage 
before this change too. What it catches is a fix that keeps too much in the 
canonical form. Keeping `originalPlan`'s DPP filters, skipping the 
normalization of the scan's partition filters, or keeping the converted DPP 
subquery's name each leave Comet with one reused exchange fewer than Spark, and 
the test fails. The reuse comes from the second `stageCache` lookup, after the 
placeholder is converted, so it does not depend on the placeholder itself 
canonicalizing alike.
   



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