ForVic commented on code in PR #59486:
URL: https://github.com/apache/spark/pull/59486#discussion_r4356098200


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetStorageFilter.scala:
##########
@@ -142,7 +143,15 @@ object ParquetStorageFilter {
       maxSplicedRowGroupBytes: Long): ParquetStorageFilter = {
     require(boundExpressions.nonEmpty,
       "storage filters must be non-empty; callers with nothing to push must 
not call create")
-    val expr = boundExpressions.reduce(And)
+    // The conjuncts over the blooms' key columns go under an `If` on the 
blooms, so they run only
+    // on the rows the blooms keep. In one `And`, subexpression elimination 
would compute what
+    // they share on every row.
+    val (blooms, onKeys) = 
boundExpressions.partition(_.isInstanceOf[BloomFilterMightContain])
+    val expr = if (blooms.isEmpty || onKeys.isEmpty) {

Review Comment:
   Fixed. The messages now list `boundExpressions.map(_.prettyName)`.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetStorageFilterSuite.scala:
##########
@@ -558,9 +558,11 @@ class ParquetStorageFilterSuite extends QueryTest with 
SharedSparkSession
 
   // A runtime bloom's `Literal` holding one long key, built the way the 
planner builds one. It is

Review Comment:
   Fixed, thanks.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetStorageFilter.scala:
##########
@@ -216,6 +225,17 @@ object ParquetStorageFilter {
     case _ => false
   }
 
+  /**
+   * Whether the reader can evaluate `expr`, a conjunct over the key columns 
of the accepted
+   * storage filters, together with them: no subquery, and nothing unevaluable 
but column
+   * references.
+   */
+  def isSupportedFilterOnKeys(expr: Expression): Boolean =
+    !expr.containsPattern(PLAN_EXPRESSION) && !expr.exists {

Review Comment:
   Makes sense, thanks. I'd like to keep this PR focused, so I'll do it as a 
follow-up and file a JIRA for it.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetStorageFilterSuite.scala:
##########
@@ -558,9 +558,11 @@ class ParquetStorageFilterSuite extends QueryTest with 
SharedSparkSession
 
   // A runtime bloom's `Literal` holding one long key, built the way the 
planner builds one. It is
   // serialized as `BloomFilterAggregate` serializes it, and the key is hashed 
with the same seed.
-  private def bloomLiteralOf(key: Long): Literal = {
+  private def bloomLiteralOf(keys: Long*): Literal = {

Review Comment:
   Fixed, thanks.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/FileFormat.scala:
##########
@@ -229,6 +229,14 @@ trait FileFormat {
    */
   def supportsStorageFilter(expr: Expression): Boolean = false
 
+  /**
+   * Whether this format's reader can evaluate `expr` along with the storage 
filters
+   * [[supportsStorageFilter]] accepted. The planner asks only about a 
conjunct that references
+   * nothing but their key columns, and offers it after them. A reader should 
evaluate it only on

Review Comment:
   Updated the `buildReaderWithStorageFilters` doc. It now says the filters 
`supportsStorageFilter` accepted come first and the ones 
`supportsStorageFilterOnKeys` accepted come second, with none in both. Since 
the planner doesn't mark where the first group ends, a format that takes the 
second group must accept a first-group shape it can tell apart, the way Parquet 
accepts only a bare `BloomFilterMightContain` there.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetStorageFilterSuite.scala:
##########
@@ -2249,6 +2282,80 @@ class ParquetStorageFilterSuite extends QueryTest with 
SharedSparkSession
     }
   }
 
+  test("FileSourceStrategy offers a conjunct over the bloom's key columns 
along with the bloom") {
+    // It adds no column to the read, so it goes to the reader after the 
bloom. A conjunct over
+    // another column does not, nor an `IsNotNull`, which the Filter needs 
bare, nor a conjunct over
+    // the key with no bloom to go with.
+    withTempDir { dir =>
+      val path = writeKeyParquetFile(dir, 0L until 50L, rowGroupSize = 64 * 
1024L)
+      withSQLConf(SQLConf.PARQUET_STORAGE_FILTER_PUSHDOWN_ENABLED.key -> 
"true") {
+        val relation = spark.read.parquet(path).queryExecution.optimizedPlan
+        val k = columnOf(relation, "k")
+        val bloom = bloomOn(k)
+        val onKey = EqualTo(Remainder(k, Literal(4L)), Literal(0L))
+        val onValue = EqualTo(columnOf(relation, "v"), Literal("42"))
+        def offered(condition: Expression): Seq[Expression] =
+          scanOf(planned(LogicalFilter(condition, relation))).storageFilters
+        val withBloom = offered(And(And(And(IsNotNull(k), onKey), bloom), 
onValue))

Review Comment:
   Added `k + CAST(v AS BIGINT) > 0` to the planner test. It asserts that 
conjunct isn't offered, while `k % 4 = 0` still follows the bloom.



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