hudi-agent commented on code in PR #19836:
URL: https://github.com/apache/hudi/pull/19836#discussion_r3947966138


##########
hudi-spark-datasource/hudi-spark/src/main/scala/org/apache/spark/sql/hudi/command/procedures/HoodieProcedureFilterUtils.scala:
##########
@@ -521,11 +521,15 @@ object HoodieProcedureFilterUtils {
                                                                                
             right: org.apache.spark.sql.catalyst.expressions.Expression,
                                                                                
             constructor: 
(org.apache.spark.sql.catalyst.expressions.Expression, 
org.apache.spark.sql.catalyst.expressions.Expression) => T,
                                                                                
             original: T): T = {
-    (left, right) match {
-      case (boundRef: 
org.apache.spark.sql.catalyst.expressions.BoundReference, literal: 
org.apache.spark.sql.catalyst.expressions.Literal)
-        if boundRef.dataType == org.apache.spark.sql.types.LongType && 
literal.dataType == org.apache.spark.sql.types.IntegerType =>
-        val castExpr = 
org.apache.spark.sql.catalyst.expressions.Cast(boundRef, 
org.apache.spark.sql.types.IntegerType)
-        constructor(castExpr, literal)
+    (left.dataType, right.dataType) match {
+      case (_: NumericType, _: NumericType) =>

Review Comment:
   🤖 Since this now handles all numeric pairs, is it worth wiring the same 
coercion into `EqualNullSafe` too? `ts <=> 1000` on a Long column still hits 
the un-coerced path, and the ordering comparison of a boxed Long against a 
boxed Int throws, which the per-row `Try` swallows into a dropped row.
   
   <sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag 
quality.</i></sub>



##########
hudi-spark-datasource/hudi-spark/src/main/scala/org/apache/spark/sql/hudi/command/procedures/HoodieProcedureFilterUtils.scala:
##########
@@ -496,11 +496,15 @@ object HoodieProcedureFilterUtils {
                                                                                
             right: org.apache.spark.sql.catalyst.expressions.Expression,
                                                                                
             constructor: 
(org.apache.spark.sql.catalyst.expressions.Expression, 
org.apache.spark.sql.catalyst.expressions.Expression) => T,
                                                                                
             original: T): T = {
-    (left, right) match {
-      case (boundRef: 
org.apache.spark.sql.catalyst.expressions.BoundReference, literal: 
org.apache.spark.sql.catalyst.expressions.Literal)
-        if boundRef.dataType == org.apache.spark.sql.types.LongType && 
literal.dataType == org.apache.spark.sql.types.IntegerType =>
-        val castExpr = 
org.apache.spark.sql.catalyst.expressions.Cast(boundRef, 
org.apache.spark.sql.types.IntegerType)
-        constructor(castExpr, literal)
+    (left.dataType, right.dataType) match {

Review Comment:
   🤖 Confirmed the mechanism from the source: `concat` isn't in the 
function-resolution whitelist here (only upper/lower/length/trim/abs/... are), 
so it stays an `UnresolvedFunction`, and `Or.eval` returns early on a true left 
child — which is why only OR regresses (AND lands on `false` either way, before 
and after). `resolved` is the right guard since it's recursive, so it also 
covers nesting like `ts > abs(concat(name, 'x'))` where the top-level operand 
isn't itself an `Unresolved*` node; alternatively wrapping the coercion body in 
`Try(...).getOrElse(original)` would cover any node whose `dataType` throws.



##########
hudi-spark-datasource/hudi-spark/src/main/scala/org/apache/spark/sql/hudi/command/procedures/HoodieProcedureFilterUtils.scala:
##########
@@ -496,11 +496,15 @@ object HoodieProcedureFilterUtils {
                                                                                
             right: org.apache.spark.sql.catalyst.expressions.Expression,
                                                                                
             constructor: 
(org.apache.spark.sql.catalyst.expressions.Expression, 
org.apache.spark.sql.catalyst.expressions.Expression) => T,
                                                                                
             original: T): T = {
-    (left, right) match {
-      case (boundRef: 
org.apache.spark.sql.catalyst.expressions.BoundReference, literal: 
org.apache.spark.sql.catalyst.expressions.Literal)
-        if boundRef.dataType == org.apache.spark.sql.types.LongType && 
literal.dataType == org.apache.spark.sql.types.IntegerType =>
-        val castExpr = 
org.apache.spark.sql.catalyst.expressions.Cast(boundRef, 
org.apache.spark.sql.types.IntegerType)
-        constructor(castExpr, literal)
+    (left.dataType, right.dataType) match {
+      case (_: NumericType, _: NumericType) =>
+        TypeCoercion.findWiderTypeForTwo(left.dataType, right.dataType)

Review Comment:
   🤖 Worth weighing which way we'd rather this fail: `AnsiTypeCoercion` gives 
parity with what the engine's analyzer would do, but as you point out it picks 
FLOAT for BIGINT/FLOAT, which is strictly less accurate than the DOUBLE 
`TypeCoercion` yields — and this util evaluates the predicate itself rather 
than feeding the analyzer, so precision may matter more than parity here. 
Either way, one thing to keep in mind is that `Cast`'s default `evalMode` is 
derived from `SQLConf.get`, so under ANSI an overflowing coercion throws and 
the per-row `Try` silently drops the row instead of producing null.



##########
hudi-spark-datasource/hudi-spark/src/main/scala/org/apache/spark/sql/hudi/command/procedures/HoodieProcedureFilterUtils.scala:
##########
@@ -496,11 +496,15 @@ object HoodieProcedureFilterUtils {
                                                                                
             right: org.apache.spark.sql.catalyst.expressions.Expression,
                                                                                
             constructor: 
(org.apache.spark.sql.catalyst.expressions.Expression, 
org.apache.spark.sql.catalyst.expressions.Expression) => T,
                                                                                
             original: T): T = {
-    (left, right) match {
-      case (boundRef: 
org.apache.spark.sql.catalyst.expressions.BoundReference, literal: 
org.apache.spark.sql.catalyst.expressions.Literal)
-        if boundRef.dataType == org.apache.spark.sql.types.LongType && 
literal.dataType == org.apache.spark.sql.types.IntegerType =>
-        val castExpr = 
org.apache.spark.sql.catalyst.expressions.Cast(boundRef, 
org.apache.spark.sql.types.IntegerType)
-        constructor(castExpr, literal)
+    (left.dataType, right.dataType) match {
+      case (_: NumericType, _: NumericType) =>
+        TypeCoercion.findWiderTypeForTwo(left.dataType, right.dataType)
+          .map { widerType =>
+            val coercedLeft = if (left.dataType == widerType) left else 
Cast(left, widerType)

Review Comment:
   🤖 The `original` fallback does look safe: `BinaryComparison` takes its 
ordering from `left.dataType`, and `Decimal.compare` falls back to 
`toJavaBigDecimal.compareTo` whenever the two scales differ, so two 
DecimalTypes compare by value with no cast needed. Also worth noting 
`convertValueToInternal` already normalizes the column value to the declared 
(precision, scale), so the cast isn't buying anything on that side beyond the 
overflow path you describe.



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

Reply via email to