sunchao commented on code in PR #5409:
URL: https://github.com/apache/datafusion-comet/pull/5409#discussion_r3836632726


##########
spark/src/main/scala/org/apache/comet/serde/strings.scala:
##########
@@ -178,29 +182,84 @@ object CometStringReplace
     extends CometScalarFunction[StringReplace]("replace")
     with NativeOptInAvailable {
 
+  /**
+   * The DataFusion `replace` kernel matches Spark only for a non-empty search 
string. Kernel
+   * compatibility is not enough: `CometLiteral` serializes strings via 
`UTF8String.toString`
+   * (malformed UTF-8 becomes U+FFFD), DataFusion evaluates every child before 
`replace` (so a
+   * NULL `src` does not skip a throwing replacement), and scalar literals are 
broadcast into
+   * Arrow `Utf8` arrays that overflow 32-bit offsets on a large batch.
+   *
+   * The default native path is therefore limited to a plan-time subset that 
avoids those
+   * boundaries. Non-default collations stay on the dispatcher.
+   * https://github.com/apache/datafusion-comet/issues/4496
+   */
+  private def nativeSafeSubset(expr: StringReplace): Boolean = {
+    val children = expr.children
+    if (children.length != 3) {
+      return false
+    }
+    val searchIsSafe = children(1) match {
+      case Literal(v: UTF8String, _) => isNativeSafeStringLiteral(v, 
allowEmpty = false)
+      case _ => false
+    }
+    val replacementIsSafe = children(2) match {
+      case Literal(null, _) => true
+      case Literal(v: UTF8String, _) => isNativeSafeStringLiteral(v, 
allowEmpty = true)
+      case _: Attribute | _: BoundReference => true
+      case _ => false
+    }
+    val utf8BinaryCollation =
+      !children.exists(c => QueryPlanSerde.isStringCollationType(c.dataType))
+    utf8BinaryCollation && searchIsSafe && replacementIsSafe

Review Comment:
   [P2] Check the source subtree before declaring the call native-safe
   
   Could this eligibility check cover `children(0)` as well? Under ANSI, with 
Parquet rows `(s=NULL,n=0)` and `(s='a',n=1)`, `replace(substring(s, 1, CAST(1 
/ n AS INT)), 'a', 'x')` still passes this guard and throws `DIVIDE_BY_ZERO`. 
The base dispatcher returns NULL and `'x'`, because Spark's `Substring` skips 
the length expression on the NULL row. Native conversion recursively evaluates 
that unchecked source subtree instead.
   
   Source literals bypass the new byte/size checks too. With a replacement 
column `r='x'`, `replace(CAST(X'FF' AS STRING), 'a', r)` changes raw `FF` into 
`EF BF BD`. A 256 KiB folded source with an empty replacement column still 
overflows when broadcast to 8,192 rows. These all reproduced in the base/head 
A/B with `allowIncompatible=false`. Checking only a top-level source literal 
would miss malformed literal descendants such as `concat(CAST(X'FF' AS STRING), 
r)`, so please retain dispatcher routing unless the source subtree is safe too.



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