mbutrovich commented on code in PR #26958:
URL: https://github.com/apache/datafusion/pull/26958#discussion_r4485137830


##########
datafusion/functions/src/unicode/lpad.rs:
##########
@@ -345,4 +347,49 @@ mod tests {
 
         Ok(())
     }
+
+    fn array_args(lengths: Vec<i64>) -> Vec<ColumnarValue> {
+        vec![
+            ColumnarValue::Array(Arc::new(StringArray::from(vec!["hi"; 
lengths.len()]))),
+            ColumnarValue::Array(Arc::new(Int64Array::from(lengths))),
+        ]
+    }
+
+    #[test]
+    fn test_array_length_out_of_range() -> Result<()> {
+        test_function!(
+            LPadFunc::new(),
+            array_args(vec![-1]),
+            Ok(Some("")),
+            &str,
+            Utf8,
+            StringArray
+        );
+        test_function!(
+            LPadFunc::new(),
+            array_args(vec![1 << 62]),
+            exec_err!(
+                "lpad requested length {} too large, maximum allowed length is 
{}",
+                1i64 << 62,
+                i32::MAX
+            ),
+            &str,
+            Utf8,
+            StringArray
+        );
+        test_function!(
+            LPadFunc::new(),
+            array_args(vec![i64::MAX, i64::MAX]),
+            exec_err!(
+                "lpad requested length {} too large, maximum allowed length is 
{}",
+                i64::MAX,
+                i32::MAX
+            ),
+            &str,
+            Utf8,
+            StringArray
+        );
+
+        Ok(())
+    }

Review Comment:
   Should we replace this test and the matching one in `rpad.rs` with a unit 
test of `pad_data_capacity`?
   
   The new sqllogictest cases already cover the user-visible fix. Each of the 
four fails on `main` with `capacity overflow`, for both `lpad` and `rpad`. 
These unit tests check the same results again. The `1 << 62`, `i64::MAX`, and 
`[i64::MAX, i64::MAX]` cases also all take the same branch of the filter.
   
   What no test in the PR checks is either half of the fix. If I remove 
`.min(i32::MAX as usize)`, every test in the PR still passes. The same happens 
if I remove the `.filter(...)`, and then a single `-1` row reserves about 2 GiB 
up front. A direct test checks the filter's bounds and the cap without 
allocating anything:
   
   ```rust
   #[cfg(test)]
   mod tests {
       use arrow::array::Int64Array;
   
       use super::pad_data_capacity;
   
       #[test]
       fn test_pad_data_capacity() {
           let capacity =
               |lengths: Vec<Option<i64>>| 
pad_data_capacity(&Int64Array::from(lengths));
           assert_eq!(capacity(vec![]), 0);
           // Negative lengths produce "" and lengths above i32::MAX are an 
error
           assert_eq!(capacity(vec![Some(3), None, Some(-1), Some(i64::MIN)]), 
3);
           assert_eq!(
               capacity(vec![Some(3), Some(i32::MAX as i64 + 1), 
Some(i64::MAX)]),
               3
           );
           assert_eq!(
               capacity(vec![Some(i32::MAX as i64), Some(1)]),
               i32::MAX as usize
           );
       }
   }
   ```
   
   With that in `common.rs`, the import changes in `lpad.rs` and `rpad.rs` can 
go back to what they were.



##########
datafusion/functions/src/unicode/common.rs:
##########
@@ -44,7 +44,10 @@ pub(crate) fn pad_data_capacity(length_array: &Int64Array) 
-> usize {
     length_array
         .iter()
         .flatten()
-        .fold(0, |acc, len| acc.saturating_add(len as usize))
+        // Out-of-range lengths produce "", an error or null, so add no bytes
+        .filter(|len| (0..=i32::MAX as i64).contains(len))
+        .fold(0usize, |acc, len| acc.saturating_add(len as usize))
+        .min(i32::MAX as usize)

Review Comment:
   Could this comment say what each out-of-range length becomes? As I read 
[`pad_impl`](https://github.com/apache/datafusion/blob/668468808f6dbb2d206e854251956912fc7e5302/datafusion/functions/src/unicode/pad.rs#L397-L410),
 a negative length appends `""` and a length above `i32::MAX` returns the 
error. No out-of-range length produces a null. A null comes only from a null 
string, length, or fill.
   
   Could the `.min` also get a short comment? After the filter every length is 
in range, but many of them can still add up to more than one buffer should 
reserve up front, and that isn't obvious from the code.
   
   ```suggestion
           // Negative lengths produce "" and lengths above i32::MAX are an 
error,
           // so neither adds bytes
           .filter(|len| (0..=i32::MAX as i64).contains(len))
           .fold(0usize, |acc, len| acc.saturating_add(len as usize))
           // Many in-range lengths can still add up to more than one buffer
           // should reserve up front
           .min(i32::MAX as usize)
   ```



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