dwsmith1983 commented on code in PR #5867:
URL: https://github.com/apache/datafusion-comet/pull/5867#discussion_r4320844036


##########
spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala:
##########
@@ -1347,10 +1352,50 @@ object QueryPlanSerde extends Logging with 
CometExprShim with CometTypeShim {
     val later = expr.children.tail
     // Subexpression elimination never shares a leaf, such as a column or a 
literal
     later.forall(_.isInstanceOf[LeafExpression]) || 
sharedSubexpressions.value.exists { s =>
-      !later.exists(_.exists(e => s.common.exists(_.semanticEquals(e))))
+      !holdsSharedSubexpression(later, s)
     }
   }
 
+  /**
+   * Whether some node of `expr` with more than one child holds, in a child it 
can skip, a
+   * subexpression that the operator's expressions share. A node can skip any 
child after its
+   * first, a `DivModLike` or `Pmod` its dividend too, and a `StringLocate`, 
which evaluates its
+   * start first, its other two children, while an `ArrayAppend` skips none. 
Spark evaluates that
+   * subexpression for every row, while `expr` evaluated alone, as the JVM 
codegen dispatcher
+   * does, can skip it on the rows where a child evaluated before it is NULL, 
so an ANSI error
+   * Spark raises goes unraised. With 
`spark.sql.subexpressionElimination.enabled` off only an
+   * aggregation shares subexpressions (see `sharedSubexpressions`). A 
subexpression that `expr`
+   * itself repeats does not count while the setting is on: the dispatcher 
then eliminates it too,
+   * and evaluates it for every row. False outside `withSharedSubexpressions`, 
where what the
+   * operator shares is unknown.
+   */
+  def skipsSharedSubexpression(expr: Expression): Boolean =
+    sharedSubexpressions.value.exists { s =>
+      // The dispatcher eliminates subexpressions only while the setting is on
+      lazy val repeated =
+        if (SQLConf.get.subexpressionEliminationEnabled) 
commonSubexpressions(Seq(expr))
+        else Nil
+      expr.exists { node =>
+        val skippable = node match {
+          // `DivModLike` and `Pmod` skip the dividend when the divisor, 
evaluated first, is NULL
+          case _: DivModLike | _: Pmod => node.children
+          // `StringLocate` evaluates `start`, its last child, first and skips 
the others on NULL
+          case _: StringLocate => node.children.init
+          // Spark 3.x's `ArrayAppend` evaluates both children before its NULL 
check
+          case _: ArrayAppend => Nil
+          case _ => node.children.drop(1)

Review Comment:
   Fixed in 9e95eca27. When the replacement can be NULL, `array_join`'s array 
and delimiter now count as skippable, since the generated code evaluates the 
replacement first and skips both when it is NULL. A replacement that can't be 
NULL keeps the default rule, because the array is then evaluated for every row.
   
   `map_lookup_shared_subexpression.sql` now has your query, plus a shared 
subexpression in the delimiter, one only in the replacement (still dispatched), 
a literal and a NULL literal replacement, and the two-argument form. The joins 
read their delimiters from nullable columns. Before Spark 4.2, generated code 
for a join whose array and delimiter can't be NULL returns NULL on every row 
when the replacement can (SPARK-57200), which would hide the branch the `if` 
takes.
   



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