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


##########
spark/src/test/scala/org/apache/spark/sql/CometTestBase.scala:
##########
@@ -342,6 +342,84 @@ abstract class CometTestBase
     (sparkPlan, cometPlan)
   }
 
+  /**
+   * Check for the correct results, that Comet replaced all possible 
operators, and that the named
+   * expressions ran through the mechanism the caller expects.
+   *
+   * Comet evaluates an expression one of three ways: natively (a DataFusion 
expression), through
+   * the JVM codegen dispatcher (Spark's own `doGenCode` compiled into an 
Arrow batch kernel), or
+   * not at all (the operator falls back to Spark). Only the third is visible 
to
+   * [[checkSparkAnswerAndOperator]]; the first two produce Spark-matching 
results by
+   * construction, so a serde that quietly widens from native to dispatch 
(losing the native
+   * kernel) or narrows from dispatch to native (losing Spark-exact semantics) 
passes every other
+   * assertion here. Use this to pin which one actually ran.
+   *
+   * Names are the expression's `prettyName` lowercased, as 
[[ExtendedExplainInfo]] reports them
+   * (`bit_length`, `octet_length`, `rlike`), not necessarily the SQL alias 
used to invoke it: a
+   * function registered with `setAlias` reports the invoked alias, everything 
else reports its
+   * own `prettyName`.
+   *
+   * For fallback assertions use [[checkSparkAnswerAndFallbackReason]] instead.
+   */
+  protected def checkSparkAnswerAndImpl(
+      df: => DataFrame,
+      native: Seq[String] = Seq.empty,
+      dispatched: Seq[String] = Seq.empty): (SparkPlan, SparkPlan) = {
+    val (sparkPlan, cometPlan) = checkSparkAnswerAndOperator(df)
+    assertExpressionImpl(cometPlan, native, dispatched)
+    (sparkPlan, cometPlan)
+  }
+
+  /** Check for the correct results and the expected per-expression 
implementation. */
+  protected def checkSparkAnswerAndImpl(
+      query: String,
+      native: Seq[String],
+      dispatched: Seq[String]): (SparkPlan, SparkPlan) = {
+    checkSparkAnswerAndImpl(sql(query), native, dispatched)
+  }
+
+  /**
+   * Assert how Comet evaluated the named expressions in an already-executed 
Comet plan. Split out
+   * from [[checkSparkAnswerAndImpl]] so callers holding a plan can reuse it, 
and so the assertion
+   * itself is testable.
+   *
+   * Each name must appear in its expected set and must be absent from the 
other, so naming an
+   * expression is a claim about which mechanism ran it rather than a claim 
that it ran somehow.
+   */
+  protected def assertExpressionImpl(
+      cometPlan: SparkPlan,
+      native: Seq[String],
+      dispatched: Seq[String]): Unit = {
+    val explainInfo = new ExtendedExplainInfo()
+    val actualNative = explainInfo.getNativeExpressions(cometPlan)
+    val actualDispatched = explainInfo.getCodegenDispatchExpressions(cometPlan)
+    def detail: String =
+      s"native=[${actualNative.mkString(", ")}] " +
+        s"codegen-dispatched=[${actualDispatched.mkString(", ")}]"
+    native.foreach { name =>
+      if (actualDispatched.contains(name)) {

Review Comment:
   Fixed in b3fcf823e. You were right, and it was source-derivable — I 
confirmed it by writing the case you described and watching it pass when it 
should not have.
   
   The cause is at the tagging site rather than in the exclusion check. 
`emitJvmCodegenDispatch` binds and closure-serializes the *whole* subtree into 
one kernel — its only inputs are the `AttributeReference`s the tree reads — but 
it named only the root. So for `hypot(abs(b), c)` the dispatched set held 
`hypot` alone, and the inner `abs` was in neither set.
   
   Rather than teach the exclusion check about descendants, I made the 
classification true at the source: the dispatch site now also names every 
expression in the subtree. Attribute references and literals are left out, 
since they are the kernel's inputs rather than work it performed (and would 
otherwise show up in the coverage stats as "expressions").
   
   That makes the helper reject both halves of your example, which is the point 
— `abs` is genuinely on both sides of the fence in that query, so neither claim 
about it alone is true:
   
   ```scala
   val query = "SELECT abs(a), hypot(abs(b), c) FROM t"
   intercept[TestFailedException] { checkSparkAnswerAndImpl(sql(query), native 
= Seq("abs")) }
   intercept[TestFailedException] { checkSparkAnswerAndImpl(sql(query), 
dispatched = Seq("abs")) }
   checkSparkAnswerAndImpl(sql(query), dispatched = Seq("hypot"))   // still 
unambiguous
   ```
   
   That is *an expression nested inside a dispatched subtree is classified as 
dispatched* in `CometCodegenSuite`. Against the previous head the first 
`intercept` fails with "Expected exception ... but no exception was thrown", so 
it does reproduce your case rather than just documenting it.
   
   **Two consequences worth flagging, since this tag is not only used by the 
test helper.**
   
   The extended-explain line and the coverage stats read the same tag, so both 
got more accurate: a query like `sequence(a, comet_seq_stopper())` now reports 
`JVM codegen dispatcher: comet_seq_stopper, sequence` and counts 2 rather than 
1. Previously the nested UDF was counted in neither bucket.
   
   That broke two existing assertions in `CometCodegenSuite` that did 
`explain.contains("JVM codegen dispatcher: sequence")` — a substring match 
pinned to the name being first in a sorted list. I replaced that pattern 
everywhere it appeared with a `dispatchedNames(explain)` helper that parses the 
segment and checks membership, so it does not break again the next time a query 
dispatches a second expression. Full `CometCodegenSuite` (95) and 
`CometSqlFileTestSuite` (472) pass.



##########
spark/src/test/scala/org/apache/comet/CometSqlFileTestSuite.scala:
##########
@@ -155,6 +155,10 @@ class CometSqlFileTestSuite extends CometTestBase with 
AdaptiveSparkPlanHelper {
                     checkSparkAnswerAndOperatorWithTolerance(sql, tol)
                   case ExpectFallback(reason) =>
                     checkSparkAnswerAndFallbackReason(sql, reason)
+                  case ExpectDispatch(names) =>

Review Comment:
   Done in b3fcf823e — agreed, and the argument that they are *stronger* 
controls is the right one: both branches call `checkSparkAnswerAndImpl`, which 
runs the same answer and operator checks a plain `query` does before asserting 
anything about the mechanism. Refusing them meant the preflight pushed fixtures 
toward a weaker sentinel.
   
   ```scala
   val hasSentinel = file.records.exists {
     case SqlQuery(_, CheckCoverageAndAnswer | _: ExpectDispatch | _: 
ExpectNative, _) => true
     case _ => false
   }
   ```
   
   I also updated the failure message, which still told authors to add a plain 
`query` — the advice would have been wrong as soon as this changed:
   
   > Add at least one `query`, `expect_dispatch` or `expect_native` over valid 
input so the operator check fails if the expression did not execute natively.



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