andygrove commented on code in PR #5051:
URL: https://github.com/apache/datafusion-comet/pull/5051#discussion_r3709229295


##########
spark/src/test/scala/org/apache/comet/exec/CometInMemoryCacheSuite.scala:
##########
@@ -412,6 +413,114 @@ class CometInMemoryCacheSuite extends CometTestBase {
     }
   }
 
+  test("Comet in-memory cache honors 
inMemoryColumnarStorage.partitionPruning=false") {
+    // CometInMemoryTableScanExec applies the serializer's stats filter before 
decoding, the same
+    // way Spark's InMemoryTableScanExec.filteredCachedBatches does. Spark 
gates that on
+    // spark.sql.inMemoryColumnarStorage.partitionPruning, so Comet must too.
+    //
+    // Pruning is transparent in the results, so it is observed through the 
scan's numOutputRows:
+    // that counts the rows in the batches actually decoded, so pruning fewer 
batches means fewer
+    // rows. With pruning off, every cached row must be decoded.
+    def scanRowsFor(pruning: Boolean): (Long, Long) = {
+      var result: (Long, Long) = (0L, 0L)
+      withSQLConf(
+        SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "false",
+        CometConf.COMET_SHUFFLE_MODE.key -> "jvm",
+        SQLConf.CACHE_VECTORIZED_READER_ENABLED.key -> "true",
+        CometConf.COMET_EXEC_IN_MEMORY_CACHE_ENABLED.key -> "true",
+        "spark.comet.sparkToColumnar.enabled" -> "true",
+        "spark.sql.inMemoryColumnarStorage.batchSize" -> "100",
+        SQLConf.IN_MEMORY_PARTITION_PRUNING.key -> pruning.toString) {
+
+        spark.catalog.clearCache()
+        spark
+          .range(0, 1000, 1, 10)
+          .selectExpr("id as key", "id % 7 as value")
+          .createOrReplaceTempView("prune_conf_cache")
+        spark.catalog.cacheTable("prune_conf_cache")
+        val totalRows = spark.table("prune_conf_cache").count()
+
+        val df =
+          spark.sql("SELECT key, value FROM prune_conf_cache WHERE key >= 900 
AND key < 905")
+        checkSparkAnswer(df)
+
+        val scans = df.queryExecution.executedPlan.collect {
+          case s: org.apache.spark.sql.comet.CometInMemoryTableScanExec => s
+        }
+        assert(scans.length == 1, s"expected one CometInMemoryTableScan, got 
${scans.length}")
+        // scalastyle:off println
+        println(
+          "DIAG rows=" + df.collect().length + " metrics=" + scans.head.metrics
+            .map { case (k, v) => k + "=" + v.value }
+            .mkString(","))
+        println("DIAG plan=" + df.queryExecution.executedPlan.getClass.getName)
+        // scalastyle:on println

Review Comment:
   Removed in ebefc1cf4. One thing worth keeping visible: the `df.collect()` 
buried in that `println` was load-bearing, not diagnostic. `checkSparkAnswer` 
takes its argument by name and executes its own copies of the query, so without 
an explicit force this `df`'s plan instance has never run and every metric on 
it reads zero, which makes the pruned-vs-unpruned comparison pass vacuously as 
0 == 0. So the `collect()` is now an explicit call with that reason in a 
comment above it, rather than a side effect of a debug line.
   
   Same commit fixes an unrelated failure I hit while re-running the suite: 
"cache a non-Arrow-backed Spark columnar plan with complex types" built its 
binary column with `cast(id as binary)`, which ANSI mode rejects 
(`DATATYPE_MISMATCH.CAST_WITH_CONF_SUGGESTION`). It passed when I wrote it on 
spark-3.5 and would have failed the Spark 4.x jobs. Now cast via string. All 21 
tests pass on the default 4.1 profile.



##########
spark/src/main/scala/org/apache/spark/sql/comet/execution/arrow/CometArrowConverters.scala:
##########
@@ -64,15 +66,23 @@ object CometArrowConverters extends Logging {
 
       override def next(): ColumnarBatch = {
         val root = VectorSchemaRoot.create(arrowSchema, allocator)
-        val writer = ArrowWriter.create(root)
-        var rowCount = 0L
-        while (rowIter.hasNext &&
-          (maxRecordsPerBatch <= 0 || rowCount < maxRecordsPerBatch)) {
-          writer.write(rowIter.next())
-          rowCount += 1
+        // Same ownership rule as columnarBatchToArrowBatch: the caller only 
owns the batch that
+        // rootAsBatch returns, so a throw from writing a row has to release 
the root here.
+        try {
+          val writer = ArrowWriter.create(root)
+          var rowCount = 0L
+          while (rowIter.hasNext &&
+            (maxRecordsPerBatch <= 0 || rowCount < maxRecordsPerBatch)) {
+            writer.write(rowIter.next())
+            rowCount += 1
+          }
+          writer.finish()
+          NativeUtil.rootAsBatch(root)
+        } catch {
+          case NonFatal(e) =>
+            root.close()
+            throw e

Review Comment:
   Useful reference. `tryWithSafeFinally` itself does not fit here, since 
ownership of the root transfers to the returned batch on success and it must 
deliberately stay open, so the release is failure-only rather than a `finally`. 
But the part of it I was missing does apply: my guard closed the root with a 
bare `root.close()`, and an Arrow root can throw from `close` 
(`IllegalStateException` for outstanding child allocations). That exception 
would have replaced the failure that actually caused the abort, which is the 
less useful of the two.
   
   4b9a5167a adopts the suppressed-exception semantics: the close error is 
attached to the original with `addSuppressed` and the original is rethrown. The 
guard is now one helper shared by `rowToArrowBatchIter` and 
`columnarBatchToArrowBatch` instead of two copies.



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