mbutrovich opened a new issue, #7441:
URL: https://github.com/apache/datafusion-comet/issues/7441

   ### Describe the bug
   
   With AQE on, a query fails with `java.lang.AssertionError: assertion failed` 
in `AdaptiveSparkPlanExec.setLogicalLinkForNewQueryStage` when the build side 
of a dynamic partition pruning (DPP) broadcast contains a storage-partitioned 
join over native Iceberg scans. A storage-partitioned join is a sort-merge join 
that Spark plans without a shuffle because both scans report 
`KeyGroupedPartitioning`.
   
   ```
   org.apache.spark.SparkException: Exception thrown in awaitResult:
     at org.apache.spark.util.ThreadUtils$.awaitResult(ThreadUtils.scala:326)
     at 
org.apache.spark.sql.comet.CometSubqueryBroadcastExec.executeCollect(CometSubqueryBroadcastExec.scala:149)
     at 
org.apache.spark.sql.execution.InSubqueryExec.updateResult(subquery.scala:129)
     ...
   Caused by: java.lang.AssertionError: assertion failed
     at scala.Predef$.assert(Predef.scala:208)
     at 
org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec.setLogicalLinkForNewQueryStage(AdaptiveSparkPlanExec.scala:648)
     at 
org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec.newQueryStage(AdaptiveSparkPlanExec.scala:622)
     at 
org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec.createQueryStages(AdaptiveSparkPlanExec.scala:540)
     ...
   ```
   
   The trace is from Spark 3.5. The same assertion fails on Spark 3.4, 4.0, and 
4.1. On Spark 3.4 the DPP subquery is Spark's `SubqueryBroadcastExec` instead 
of `CometSubqueryBroadcastExec`. The `spark-4.2` profile has no Iceberg 
runtime, so the test below does not run there.
   
   It is a regression from #7231. The query passes at 7f50037fd, the parent of 
#7231, where the DPP subquery reuses the join's broadcast through a 
`ReusedExchange`. It fails at ce3e665e8.
   
   ### Steps to reproduce
   
   Add this test to `CometIcebergNativeSuite`:
   
   ```scala
   test("AQE DPP - storage-partitioned join on the DPP build side") {
     assume(icebergAvailable, "Iceberg not available")
     withTempIcebergDir { warehouseDir =>
       withSQLConf(
         "spark.sql.catalog.aqe_cat" -> "org.apache.iceberg.spark.SparkCatalog",
         "spark.sql.catalog.aqe_cat.type" -> "hadoop",
         "spark.sql.catalog.aqe_cat.warehouse" -> warehouseDir.getAbsolutePath,
         SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true",
         SQLConf.DYNAMIC_PARTITION_PRUNING_REUSE_BROADCAST_ONLY.key -> "true",
         SQLConf.V2_BUCKETING_ENABLED.key -> "true",
         SQLConf.V2_BUCKETING_PUSH_PART_VALUES_ENABLED.key -> "true",
         "spark.sql.iceberg.planning.preserve-data-grouping" -> "true",
         CometConf.COMET_ENABLED.key -> "true",
         CometConf.COMET_EXEC_ENABLED.key -> "true",
         CometConf.COMET_ICEBERG_NATIVE_ENABLED.key -> "true") {
   
         spark.sql("""
           CREATE TABLE aqe_cat.db.probe_fact (amount INT, store_id INT)
           USING iceberg PARTITIONED BY (store_id)
         """)
         spark.sql("""
           CREATE TABLE aqe_cat.db.probe_dim_a (id INT, country STRING)
           USING iceberg PARTITIONED BY (id)
         """)
         spark.sql("""
           CREATE TABLE aqe_cat.db.probe_dim_b (id INT, name STRING)
           USING iceberg PARTITIONED BY (id)
         """)
         spark.sql(
           "INSERT INTO aqe_cat.db.probe_fact SELECT CAST(id AS INT), CAST(id % 
10 AS INT) " +
             "FROM range(0, 100)")
         spark.sql(
           "INSERT INTO aqe_cat.db.probe_dim_a SELECT CAST(id AS INT), " +
             "IF(id < 3, 'X', 'Y') FROM range(0, 10)")
         spark.sql(
           "INSERT INTO aqe_cat.db.probe_dim_b SELECT CAST(id AS INT), 
concat('n', id) " +
             "FROM range(0, 10)")
   
         val query =
           """SELECT /*+ BROADCAST(d) */ f.store_id, f.amount, d.name
             |FROM aqe_cat.db.probe_fact f JOIN (
             |  SELECT /*+ MERGE(a, b) */ a.id, b.name
             |  FROM aqe_cat.db.probe_dim_a a JOIN aqe_cat.db.probe_dim_b b ON 
a.id = b.id
             |  WHERE a.country = 'X'
             |) d ON f.store_id = d.id""".stripMargin
         checkSparkAnswer(query)
   
         spark.sql("DROP TABLE aqe_cat.db.probe_fact")
         spark.sql("DROP TABLE aqe_cat.db.probe_dim_a")
         spark.sql("DROP TABLE aqe_cat.db.probe_dim_b")
       }
     }
   }
   ```
   
   Then run it:
   
   ```sh
   make core
   ./mvnw test -Pspark-3.5 -Dtest=none 
-Dsuites="org.apache.comet.CometIcebergNativeSuite AQE DPP - 
storage-partitioned join on the DPP build side"
   ```
   
   ### Expected behavior
   
   The query returns the same rows as Spark, and the DPP subquery reuses the 
join's broadcast, as it does at 7f50037fd.
   
   ### Additional context
   
   To build the DPP subquery's plan, Comet wraps the build side it already 
converted in a new `AdaptiveSparkPlanExec` 
([`CometPlanAdaptiveDynamicPruningFilters.scala#L262-L302`](https://github.com/apache/datafusion-comet/blob/79b5ddd50ba2e3f605399d641e4c0b3be84c20ef/spark/src/main/scala/org/apache/comet/rules/CometPlanAdaptiveDynamicPruningFilters.scala#L262-L302)).
 On Spark 3.4, Spark's own rule does the same 
([`PlanAdaptiveDynamicPruningFilters.scala#L48-L61`](https://github.com/apache/spark/blob/1eb558c3a6fbdd59e5a305bc3ab12ce748f6511f/sql/core/src/main/scala/org/apache/spark/sql/execution/adaptive/PlanAdaptiveDynamicPruningFilters.scala#L48-L61)).
 The `AdaptiveSparkPlanExec` constructor runs the query stage preparation rules 
over that plan again, including `EnsureRequirements` 
([`AdaptiveSparkPlanExec.scala#L113-L134`](https://github.com/apache/spark/blob/7c14a3c28b141cc97a330c4d0f5d2a6da7267f85/sql/core/src/main/scala/org/apache/spark/sql/execution/adaptive/AdaptiveSparkPlanExec
 .scala#L113-L134), 
[`#L204-L208`](https://github.com/apache/spark/blob/7c14a3c28b141cc97a330c4d0f5d2a6da7267f85/sql/core/src/main/scala/org/apache/spark/sql/execution/adaptive/AdaptiveSparkPlanExec.scala#L204-L208)).
   
   `BatchScanExec` reports `KeyGroupedPartitioning` for these scans 
([`DataSourceV2ScanExecBase.scala#L93-L103`](https://github.com/apache/spark/blob/7c14a3c28b141cc97a330c4d0f5d2a6da7267f85/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2ScanExecBase.scala#L93-L103)),
 but `CometIcebergNativeScanExec` reports `UnknownPartitioning(0)` 
([`CometIcebergNativeScanExec.scala#L138`](https://github.com/apache/datafusion-comet/blob/79b5ddd50ba2e3f605399d641e4c0b3be84c20ef/spark/src/main/scala/org/apache/spark/sql/comet/CometIcebergNativeScanExec.scala#L138)).
 Since #7231, `CometSortMergeJoinExec` declares Spark's required child 
distribution 
([`operators.scala#L746-L749`](https://github.com/apache/datafusion-comet/blob/79b5ddd50ba2e3f605399d641e4c0b3be84c20ef/spark/src/main/scala/org/apache/spark/sql/comet/operators.scala#L746-L749),
 
[`#L3740-L3753`](https://github.com/apache/datafusion-comet/blob/79b5ddd50ba2e3f605399d641e4c0b3be84c20ef/spark/src/main/scala/o
 rg/apache/spark/sql/comet/operators.scala#L3740-L3753)). So 
`EnsureRequirements` inserts an exchange under each side of the join in the 
subquery's plan, although the main plan has none:
   
   ```
   CometSubqueryBroadcast dynamicpruning#93, [0], [id#61], [id=#835]
   +- AdaptiveSparkPlan isFinalPlan=false
      +- CometBroadcastExchange [id#61, name#64]
         +- CometProject [id#61, name#64], [id#61, name#64]
            +- CometSortMergeJoin [id#61], [id#63], Inner
               :- CometExchange hashpartitioning(id#61, 10), 
ENSURE_REQUIREMENTS, CometNativeShuffle, [plan_id=830]
               :  +- CometSort [id#61], [id#61 ASC NULLS FIRST]
               :     +- CometProject [id#61], [id#61]
               :        +- CometFilter [id#61, country#62], 
(isnotnull(country#62) AND (country#62 = X))
               :           +- CometIcebergNativeScan [id#61, country#62], ... 
[filters=country IS NOT NULL, country = 'X', id IS NOT NULL, groupedBy=id], 3
               +- CometExchange hashpartitioning(id#63, 10), 
ENSURE_REQUIREMENTS, CometNativeShuffle, [plan_id=820]
                  +- CometSort [id#63, name#64], [id#63 ASC NULLS FIRST]
                     +- CometIcebergNativeScan [id#63, name#64], ... 
[filters=id IS NOT NULL, groupedBy=id], 
runtimeFilters=[dynamicpruningexpression(id#63 IN dynamicpruning#92)], 10
   ```
   
   AQE finds no logical link on the inserted exchange or below it, and the 
assertion fails 
([`AdaptiveSparkPlanExec.scala#L641-L650`](https://github.com/apache/spark/blob/7c14a3c28b141cc97a330c4d0f5d2a6da7267f85/sql/core/src/main/scala/org/apache/spark/sql/execution/adaptive/AdaptiveSparkPlanExec.scala#L641-L650)).
   
   #7422 is the `SinglePartition` case of the same mismatch on Spark 3.4, and 
#7424 fixes it by having the native scan report what `BatchScanExec` reports. 
Could the native scan report `originalPlan.outputPartitioning` for 
`KeyGroupedPartitioning` too? #6268 moved it to `UnknownPartitioning(0)` 
because counting the scan's tasks went through `BatchScanExec.inputRDD`, which 
translates the DPP filter before its subquery has a result. As I read Spark 
3.5.9, `BatchScanExec.outputPartitioning` reads `groupedPartitions`, which 
groups `inputPartitions` from `planInputPartitions()`, not the runtime-filtered 
partitions 
([`BatchScanExec.scala#L59`](https://github.com/apache/spark/blob/7c14a3c28b141cc97a330c4d0f5d2a6da7267f85/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/BatchScanExec.scala#L59),
 
[`#L116-L128`](https://github.com/apache/spark/blob/7c14a3c28b141cc97a330c4d0f5d2a6da7267f85/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/BatchScanExec.s
 cala#L116-L128), 
[`DataSourceV2ScanExecBase.scala#L106-L112`](https://github.com/apache/spark/blob/7c14a3c28b141cc97a330c4d0f5d2a6da7267f85/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2ScanExecBase.scala#L106-L112)).
   
   ### Willingness to contribute
   
   I cannot contribute a fix for this bug at this time


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