mbutrovich commented on code in PR #4941:
URL: https://github.com/apache/datafusion-comet/pull/4941#discussion_r3591287265


##########
native/spark-expr/src/conversion_funcs/numeric.rs:
##########
@@ -301,52 +301,37 @@ macro_rules! cast_float_to_int16_down {
         $rust_dest_type:ty,
         $src_type_str:expr,
         $dest_type_str:expr,
+        $dest_arrow_type:ty,
         $format_str:expr
     ) => {{
         let cast_array = $array
             .as_any()
             .downcast_ref::<$src_array_type>()
             .expect(concat!("Expected a ", stringify!($src_array_type)));
 
-        let output_array = match $eval_mode {
-            EvalMode::Ansi => cast_array
-                .iter()
-                .map(|value| match value {
-                    Some(value) => {
-                        let is_overflow = value.is_nan() || value.abs() as i32 
== i32::MAX;
-                        if is_overflow {
-                            return Err(cast_overflow(
-                                &format!($format_str, value).replace("e", "E"),
-                                $src_type_str,
-                                $dest_type_str,
-                            ));
-                        }
-                        let i32_value = value as i32;
-                        <$rust_dest_type>::try_from(i32_value)
-                            .map_err(|_| {
-                                cast_overflow(
-                                    &format!($format_str, value).replace("e", 
"E"),
-                                    $src_type_str,
-                                    $dest_type_str,
-                                )
-                            })
-                            .map(Some)
-                    }
-                    None => Ok(None),
-                })
-                .collect::<Result<$dest_array_type, _>>()?,
-            _ => cast_array
-                .iter()
-                .map(|value| match value {
-                    Some(value) => {
-                        let i32_value = value as i32;
-                        Ok::<Option<$rust_dest_type>, SparkError>(Some(
-                            i32_value as $rust_dest_type,
-                        ))
-                    }
-                    None => Ok(None),
+        // Spark casts float -> Byte/Short by going through Int first (with 
its own overflow),
+        // then narrowing. `unary`/`try_unary` map the values buffer in one 
pass and carry the
+        // null buffer over, replacing the per-element iterator-collect.
+        let output_array: $dest_array_type = match $eval_mode {
+            EvalMode::Ansi => cast_array.try_unary::<_, $dest_arrow_type, 
SparkError>(|value| {
+                let is_overflow = value.is_nan() || value.abs() as i32 == 
i32::MAX;
+                if is_overflow {
+                    return Err(cast_overflow(
+                        &format!($format_str, value).replace("e", "E"),
+                        $src_type_str,
+                        $dest_type_str,
+                    ));
+                }
+                let i32_value = value as i32;
+                <$rust_dest_type>::try_from(i32_value).map_err(|_| {
+                    cast_overflow(
+                        &format!($format_str, value).replace("e", "E"),
+                        $src_type_str,
+                        $dest_type_str,
+                    )
                 })
-                .collect::<Result<$dest_array_type, _>>()?,
+            })?,
+            _ => cast_array.unary::<_, $dest_arrow_type>(|value| (value as 
i32) as $rust_dest_type),

Review Comment:
   `native/spark-expr/src/conversion_funcs/numeric.rs:334` (float legacy arm 
`cast_array.unary::<_, $dest_arrow_type>(|value| (value as i32) as 
$rust_dest_type)`), `:373` (float32_up legacy), `:428` and `:473` (decimal 
legacy arms with `value / divisor`).
   
   `PrimitiveArray::unary` applies the op to every slot including nulls 
(`arrow-array/src/array/primitive_array.rs:880-903`: "Applies the function for 
all values, including those on null slots ... requires that the operation must 
be infallible (not error/panic) for any value"). The old iterator-collect only 
ran the body for `Some` values, so this PR widens the set of inputs the closure 
sees to include whatever garbage sits in null slots.
   
   I verified this is safe here. Float `value as i32` and `as $rust_dest_type` 
are saturating `as` casts that never panic on any bit pattern including NaN. 
Decimal `value / divisor` cannot panic because `divisor = 10^scale` is always 
positive (the only `i128` division panic is `i128::MIN / -1`). So there is no 
regression. The risk is a future edit adding a fallible op (a `checked_*`, an 
index, an `unwrap`) into one of these closures without realizing it now runs on 
null slots. The existing comments explain the Spark cast-through-Int semantics 
but not this invariant. Suggested change: add one line to the legacy-arm 
comment in each macro, e.g. `// unary runs op on null slots too; the as-cast / 
positive-divisor division here is infallible for any bit pattern.`
   



##########
native/spark-expr/src/conversion_funcs/numeric.rs:
##########
@@ -1160,6 +1104,111 @@ mod tests {
         assert_eq!(decimal_array.value(1), -10000); // -100 * 10^2
         assert!(decimal_array.is_null(2));
     }
+

Review Comment:
   (`test_cast_float64_to_int8_legacy_wraps`) covers `300.7 -> 300 -> 44`, 
which exercises the Byte narrowing wrap but keeps the intermediate inside i32 
range. The distinctive Spark behavior these macros exist to replicate is the 
double narrowing: float overflows i32 first, then that already-wrapped Int 
narrows to Byte/Short. The legacy float path is `(value as i32) as 
$rust_dest_type`, and `value as i32` saturates on overflow in Rust (`3e9_f64 as 
i32 == i32::MAX`), so a value like `3e9` produces `i32::MAX` then narrows to 
`-1` for i8. That saturate-then-narrow is exactly the fragile path. Suggested 
change: add a case with `Some(3e9)` (or `Some(f64::INFINITY)`) to 
`test_cast_float64_to_int8_legacy_wraps` and assert the wrapped byte, so the 
legacy overflow-then-narrow behavior is pinned and cannot silently drift if the 
`as i32` step is ever refactored.



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