viirya commented on code in PR #59581:
URL: https://github.com/apache/spark/pull/59581#discussion_r4381035297


##########
sql/core/src/test/scala/org/apache/spark/sql/execution/python/ArrowColumnarPythonUDFSuite.scala:
##########
@@ -114,6 +115,103 @@ class ArrowColumnarPythonUDFSuite extends 
SharedSparkSession {
     }
   }
 
+  test("ColumnarToRow releases each batch once its rows are consumed") {
+    Seq(true, false).foreach { codegen =>
+      withSQLConf(SQLConf.WHOLESTAGE_CODEGEN_ENABLED.key -> codegen.toString) {
+        val numRows = 2000
+        val numBatches = 20
+        val df = readArrowSource(numRows = numRows, batchSize = numRows / 
numBatches)
+        val plan = df.queryExecution.executedPlan
+        assert(collectNodes[ColumnarToRowExec](plan).nonEmpty)
+        assert(collectNodes[WholeStageCodegenExec](plan).nonEmpty == codegen)
+
+        ArrowBackedDataSourceV2.resetBatchCounters()
+        val rows = df.collect()
+        assert(rows.map(_.getInt(0)).toSeq == (0 until numRows))
+        assert(rows.forall(row => row.getString(1) == s"row_${row.getInt(0)}"))
+        // A batch is released before the next one is read.
+        assert(ArrowBackedDataSourceV2.returnedBatches.get == numBatches)
+        assert(ArrowBackedDataSourceV2.releasedBatches.get == numBatches)
+        assert(ArrowBackedDataSourceV2.maxUnreleasedBatches.get == 1)
+      }
+    }
+  }
+
+  test("Arrow Python UDF over many batches releases each input batch") {
+    assume(shouldTestPandasUDFs)
+    val pandasUDF = TestScalarPandasUDF(
+      name = "arrow_release_udf", returnType = Some(StringType))
+    registerTestUDF(pandasUDF, spark)
+
+    val numRows = 20000
+    val numBatches = 20
+    // On a string column, the cast of the test UDF is removed, so the batches 
take the Arrow
+    // columnar path, whose output batches carry the input columns to the 
ColumnarToRowExec above
+    // ArrowEvalPythonExec, which releases them. On an expression, they take 
the row fallback
+    // path, which releases each batch once it has been written to Python.
+    for {
+      codegen <- Seq(true, false)
+      (input, suffix) <- Seq("name" -> "", "concat(name, '_')" -> "_")
+    } withSQLConf(
+        SQLConf.ARROW_PYSPARK_EXECUTION_ENABLED.key -> "true",
+        SQLConf.WHOLESTAGE_CODEGEN_ENABLED.key -> codegen.toString) {
+        val result = readArrowSource(numRows = numRows, batchSize = numRows / 
numBatches)
+          .selectExpr("id", "name", "value", "data", 
s"arrow_release_udf($input) as udf_out")
+        val plan = result.queryExecution.executedPlan
+        val arrowExec = collectNodes[ArrowEvalPythonExec](plan)
+        assert(arrowExec.nonEmpty && arrowExec.head.supportsColumnar)
+        
assert(arrowExec.head.udfs.forall(_.children.forall(_.isInstanceOf[AttributeReference]))
+          == suffix.isEmpty, s"Unexpected UDF inputs:\n$plan")
+        assert(collectNodes[ColumnarToRowExec](plan).exists(
+          _.find(_.isInstanceOf[ArrowEvalPythonExec]).isDefined), s"No 
ColumnarToRowExec:\n$plan")
+
+        ArrowBackedDataSourceV2.resetBatchCounters()
+        val rows = result.collect()
+        assert(rows.map(_.getInt(0)).toSeq == (0 until numRows))
+        rows.foreach { row =>
+          val id = row.getInt(0)
+          assert(row.getString(1) == s"row_$id", s"name mismatch for id $id")
+          assert(row.getDouble(2) == id * 0.1, s"value mismatch for id $id")
+          assert(row.getString(3).length == 100, s"data mismatch for id $id")
+          assert(row.getString(4) == s"row_$id$suffix", s"UDF result mismatch 
for id $id")
+        }
+        // Every batch is released before the task completes. The row fallback 
path releases a
+        // batch before it reads the next one, while the Arrow columnar path 
holds the batches
+        // that it has written to Python until their results are consumed.
+        assert(ArrowBackedDataSourceV2.returnedBatches.get == numBatches)
+        assert(ArrowBackedDataSourceV2.releasedBatches.get == numBatches)
+        if (suffix.nonEmpty) {
+          assert(ArrowBackedDataSourceV2.maxUnreleasedBatches.get == 1)
+        }
+
+        // A limit stops consuming the output batches early, and the generated 
code of
+        // ColumnarToRowExec closes the current one.
+        assert(result.limit(10).selectExpr("count(*)").head().getLong(0) == 10)
+      }
+  }
+
+  test("A plan built with columnar input runs as built after the configuration 
changes") {

Review Comment:
   Done in dd1c0fefaab for the three tests in this suite and the one in 
`ArrowOutputProcessorSuite`.
   



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