viirya commented on code in PR #6852:
URL: https://github.com/apache/datafusion-comet/pull/6852#discussion_r4238845482


##########
spark/src/main/scala/org/apache/comet/rules/RevertNativeForTransitionHeavyStages.scala:
##########
@@ -65,19 +65,27 @@ case class RevertNativeForTransitionHeavyStages(session: 
SparkSession, wholePlan
       case exchange: ShuffleExchangeLike =>
         revertShuffleStageIfNeeded(exchange).getOrElse(plan)
       case _ =>
-        // Result stage: its output is collected as rows.
-        revertStageIfNeeded(plan, outputColumnar = false).getOrElse(plan)
+        revertResultStageIfNeeded(plan).getOrElse(plan)
     }
   }
 
   private def applyForNonAQE(plan: SparkPlan): SparkPlan = {
     val withRevertedStages = plan.transformUp { case exchange: 
ShuffleExchangeLike =>
       revertShuffleStageIfNeeded(exchange).getOrElse(exchange)
     }
-    revertStageIfNeeded(withRevertedStages, outputColumnar = false)
-      .getOrElse(withRevertedStages)
+    revertResultStageIfNeeded(withRevertedStages).getOrElse(withRevertedStages)
   }
 
+  /**
+   * Reverts the result stage if needed, keeping the output format its 
consumer reads. That is
+   * usually rows, but on Spark 4.0+, when the cache serializer accepts 
columnar input, as Comet's
+   * does, Spark marks a cached `AdaptiveSparkPlanExec` columnar and AQE plans 
its final stage to
+   * output columnar batches. Only then does Spark's transition insertion 
leave a columnar-only
+   * root; when rows are wanted it puts a columnar-to-row transition on top.
+   */
+  private def revertResultStageIfNeeded(plan: SparkPlan): Option[SparkPlan] =
+    revertStageIfNeeded(plan, outputColumnar = plan.supportsColumnar && 
!plan.supportsRowBased)

Review Comment:
   What should happen when the root supports both formats? With 
`supportsColumnar && supportsRowBased` this infers rows. But when columnar 
output was requested, Spark's `ensureOutputsColumnar` keeps such a root as is, 
so the root alone doesn't say which format the consumer wants.
   
   Up to 4.1 I couldn't find a way to reach this. The only dual-format 
operators there are `UnionExec` and `InMemoryTableScanExec`, and neither can 
have a C2R below it in the same stage. On Spark 4.2, though, 
`ArrowEvalPythonExec` reports `supportsRowBased = true` and `supportsColumnar = 
child.supportsColumnar && conf.arrowPySparkUDFColumnarInputEnabled`, and that 
conf defaults to true. Take a cached query whose result stage is rooted at a 
pandas UDF over a columnar child with C2Rs below it, for example with 
`spark.comet.sparkToColumnar.enabled`. The reverted `ArrowEvalPythonExec` 
becomes row-only, and the cache builder's `executeColumnar()` would hit the 
same mismatch this PR fixes.
   
   Would it make sense to skip the revert when the original root supports both 
formats and the reverted root no longer supports columnar? The doc comment 
above could also note that a dual-format root leaves the requested format 
unknown.



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