peter-toth commented on code in PR #59818:
URL: https://github.com/apache/spark/pull/59818#discussion_r4387493180


##########
sql/core/src/test/scala/org/apache/spark/sql/SubquerySuite.scala:
##########
@@ -2510,6 +2510,56 @@ class SubquerySuite extends SharedSparkSession
     }
   }
 
+  test("SPARK-60577: FileSourceScanExec supports push down IN data filter with 
scalar subquery") {
+    def checkFileSourceScan(query: String, numScalarSubqueries: Int): Unit = {
+      val df = sql(query)
+      // `PushedFilters` is a part of the output of EXPLAIN, and of the plan 
description that is
+      // built when the query starts.
+      Seq(SimpleMode, 
FormattedMode).foreach(df.queryExecution.explainString(_))
+      checkAnswer(df, Seq.empty)
+      val fileSourceScanExec = collect(df.queryExecution.executedPlan) {
+        case f: FileSourceScanExec => f
+      }
+      sparkContext.listenerBus.waitUntilEmpty()
+      assert(fileSourceScanExec.size === 1)
+      val scalarSubquery = 
fileSourceScanExec.head.dataFilters.flatMap(_.collect {
+        case s: ScalarSubquery => s
+      })
+      assert(scalarSubquery.length === numScalarSubqueries)
+      // The `In` is not displayed in `PushedFilters`, but it is pushed down 
to skip the row group.
+      
assert(!fileSourceScanExec.head.metadata("PushedFilters").contains("In("))
+      assert(fileSourceScanExec.head.metrics("numOutputRows").value === 0)
+    }
+
+    withTable("t1", "t2") {
+      // Parquet does not skip row groups by INT96 timestamps, which are the 
default.
+      withSQLConf(
+          SQLConf.LEAF_NODE_DEFAULT_PARALLELISM.key -> "1",
+          SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key -> "TIMESTAMP_MICROS") {
+        Seq("t1" -> Seq(1, 2, 3), "t2" -> Seq(4, 5, 6)).foreach { case (table, 
ids) =>
+          ids.toDF("id").selectExpr(
+            "DATE_FROM_UNIX_DATE(id) AS d",
+            "TIMESTAMP_SECONDS(id) AS ts",
+            "CAST(id AS DECIMAL(10, 2)) AS 
dec").write.format("parquet").saveAsTable(table)
+        }
+
+        Seq(
+          "d" -> "DATE_FROM_UNIX_DATE(5)",
+          "ts" -> "TIMESTAMP_SECONDS(5)",
+          "dec" -> "CAST(5 AS DECIMAL(10, 2))").foreach { case (column, 
literal) =>
+          checkFileSourceScan(
+            s"SELECT * FROM t1 WHERE $column IN ($literal, (SELECT 
max($column) FROM t2))", 1)
+          // Otherwise, the two scalar subqueries are merged into one.
+          withSQLConf(SQLConf.SUBQUERY_REUSE_ENABLED.key -> "false") {
+            checkFileSourceScan(
+              s"SELECT * FROM t1 WHERE $column IN " +
+                s"((SELECT min($column) FROM t2), (SELECT max($column) FROM 
t2))", 2)
+          }
+        }

Review Comment:
   **Finding 1.** The description says `NOT IN` and an `IN` under `OR` fail the 
same way. Their `In` sits below a `Not` or an `Or`, and `_.exists` at 
`DataSourceScanExec.scala:555` is what finds it there. No test reaches that 
case, though. I replaced `filterNot(_.exists { ... })` with a top-level 
`filterNot { case in: In => ... }`. The new test still passed, while both 
shapes threw `ClassCastException` again. These lines fail with that change and 
pass at `e433df0`:
   
   ```suggestion
           }
   
           // `NOT IN` and an `IN` under `OR` hold the `In` below the top of 
the filter.
           Seq(
             "SELECT * FROM t1 WHERE d NOT IN (DATE_FROM_UNIX_DATE(5), (SELECT 
max(d) FROM t2))",
             "SELECT * FROM t1 WHERE d IN (DATE_FROM_UNIX_DATE(5), (SELECT 
max(d) FROM t2)) " +
               "OR dec IS NULL").foreach { query =>
             val df = sql(query)
             Seq(SimpleMode, 
FormattedMode).foreach(df.queryExecution.explainString(_))
             df.collect()
           }
   ```
   



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