This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new d2519a1b72 fix(arrow-row): allow to convert non empty fixed size 
binary/list array with size length 0 and no nulls (#10271)
d2519a1b72 is described below

commit d2519a1b721a968301b8f7ebb02af46cfa64e644
Author: Raz Luvaton <[email protected]>
AuthorDate: Fri Jul 3 00:59:24 2026 +0300

    fix(arrow-row): allow to convert non empty fixed size binary/list array 
with size length 0 and no nulls (#10271)
    
    # Which issue does this PR close?
    
    - Closes #10270.
    
    # Rationale for this change
    
    for bug fix - because it should allow that case
    for the `FixedSizeBinaryArray::try_new_with_length` - so we can create
    fixed size binary array with size 0 and no nulls with non 0 length. this
    is similar to `FixedSizeListArray::try_new_with_length` already exists
    
    # What changes are included in this PR?
    fixed the bug by using `FixedSizeBinaryArray::try_new_with_length` and
    `FixedSizeListArray::try_new_with_length` with the expected row count
    
    created `FixedSizeBinaryArray::try_new_with_length` that allow getting
    length
    
    # Are these changes tested?
    yes
    
    # Are there any user-facing changes?
    
    yes, new function `FixedSizeBinaryArray::try_new_with_length`
    
    ---------
    
    Co-authored-by: Andrew Lamb <[email protected]>
---
 arrow-array/src/array/fixed_size_binary_array.rs | 146 +++++++++++++++++++----
 arrow-row/src/fixed.rs                           |   6 +-
 arrow-row/src/lib.rs                             |  81 +++++++++++++
 arrow-row/src/list.rs                            |   6 +-
 4 files changed, 213 insertions(+), 26 deletions(-)

diff --git a/arrow-array/src/array/fixed_size_binary_array.rs 
b/arrow-array/src/array/fixed_size_binary_array.rs
index 9bf3b6ef38..85e5d266c9 100644
--- a/arrow-array/src/array/fixed_size_binary_array.rs
+++ b/arrow-array/src/array/fixed_size_binary_array.rs
@@ -130,6 +130,7 @@ impl FixedSizeBinaryArray {
     ///
     /// Creating an array with `value_length == 0` will try to get the length 
from the null
     /// buffer. If no null buffer is provided, the resulting array will have 
length zero.
+    /// You can use [`Self::try_new_with_len`] to provide the length
     ///
     /// # Errors
     ///
@@ -141,7 +142,6 @@ impl FixedSizeBinaryArray {
         values: Buffer,
         nulls: Option<NullBuffer>,
     ) -> Result<Self, ArrowError> {
-        let data_type = DataType::FixedSizeBinary(value_length);
         let value_size = value_length.to_usize().ok_or_else(|| {
             ArrowError::InvalidArgumentError(format!(
                 "Value length cannot be negative, got {value_length}"
@@ -149,30 +149,60 @@ impl FixedSizeBinaryArray {
         })?;
 
         let len = match values.len().checked_div(value_size) {
-            Some(len) => {
-                if let Some(n) = nulls.as_ref() {
-                    if n.len() != len {
-                        return Err(ArrowError::InvalidArgumentError(format!(
-                            "Incorrect length of null buffer for 
FixedSizeBinaryArray, expected {} got {}",
-                            len,
-                            n.len(),
-                        )));
-                    }
-                }
+            Some(len) => len,
+            None => nulls.as_ref().map(|n| n.len()).unwrap_or(0),
+        };
 
-                len
-            }
-            None => {
-                if !values.is_empty() {
-                    return Err(ArrowError::InvalidArgumentError(
-                        "Buffer cannot have non-zero length if the value 
length is zero".to_owned(),
-                    ));
-                }
+        Self::try_new_with_len(value_length, values, nulls, len)
+    }
+
+    /// Create a new [`FixedSizeBinaryArray`] from the provided parts and 
number of elements, returning an error on failure
+    ///
+    /// This is useful when the length cannot be determinated from the 
provided values (in case of `value_length == 0`) or nulls (`nulls.is_none()`).
+    ///
+    /// # Errors
+    ///
+    /// * `value_length < 0`
+    /// * `values.len() / value_length != len`
+    /// * `value_length == 0 && values.len() != 0`
+    /// * `nulls.len() != len`
+    /// * `value_length != 0 && values.len() / value_length != len`
+    pub fn try_new_with_len(
+        value_length: i32,
+        values: Buffer,
+        nulls: Option<NullBuffer>,
+        len: usize,
+    ) -> Result<Self, ArrowError> {
+        let data_type = DataType::FixedSizeBinary(value_length);
+        let value_size = value_length.to_usize().ok_or_else(|| {
+            ArrowError::InvalidArgumentError(format!(
+                "Value length cannot be negative, got {value_length}"
+            ))
+        })?;
 
-                // If the value length is zero, try to determine the length 
from the null buffer
-                nulls.as_ref().map(|n| n.len()).unwrap_or(0)
+        if let Some(nulls) = &nulls {
+            if nulls.len() != len {
+                return Err(ArrowError::InvalidArgumentError(format!(
+                    "Incorrect length of null buffer for FixedSizeBinaryArray, 
expected {} got {}",
+                    len,
+                    nulls.len(),
+                )));
             }
-        };
+        }
+
+        if value_size != 0 && values.len() / value_size != len {
+            return Err(ArrowError::InvalidArgumentError(format!(
+                "Incorrect length of values buffer for FixedSizeBinaryArray, 
expected {} got {}",
+                len,
+                values.len() / value_size,
+            )));
+        }
+
+        if value_size == 0 && !values.is_empty() {
+            return Err(ArrowError::InvalidArgumentError(
+                "Buffer cannot have non-zero length if the value length is 
zero".to_owned(),
+            ));
+        }
 
         Ok(Self {
             data_type,
@@ -1172,4 +1202,76 @@ mod tests {
             "Invalid argument error: Buffer cannot have non-zero length if the 
value length is zero"
         );
     }
+
+    #[test]
+    fn test_try_new_with_len() {
+        let buffer = Buffer::from_vec(vec![0_u8; 10]);
+
+        let a = FixedSizeBinaryArray::try_new_with_len(2, buffer.clone(), 
None, 5).unwrap();
+        assert_eq!(a.len(), 5);
+
+        let nulls = NullBuffer::new_null(5);
+        let a = FixedSizeBinaryArray::try_new_with_len(2, buffer, Some(nulls), 
5).unwrap();
+        assert_eq!(a.len(), 5);
+        assert_eq!(a.null_count(), 5);
+
+        let a = FixedSizeBinaryArray::try_new_with_len(2, Buffer::default(), 
None, 0).unwrap();
+        assert_eq!(a.len(), 0);
+    }
+
+    #[test]
+    fn test_try_new_with_len_zero_width() {
+        // Zero-width with no nulls: the case where the length cannot be 
inferred from the parts
+        let a = FixedSizeBinaryArray::try_new_with_len(0, Buffer::default(), 
None, 5).unwrap();
+        assert_eq!(a.len(), 5);
+        assert_eq!(a.null_count(), 0);
+        assert_eq!(a.values().len(), 0);
+
+        let nulls = NullBuffer::new_null(3);
+        let a =
+            FixedSizeBinaryArray::try_new_with_len(0, Buffer::default(), 
Some(nulls), 3).unwrap();
+        assert_eq!(a.len(), 3);
+        assert_eq!(a.null_count(), 3);
+    }
+
+    #[test]
+    fn test_try_new_with_len_negative_value_length() {
+        let buffer = Buffer::from_vec(vec![0_u8; 10]);
+        let err = FixedSizeBinaryArray::try_new_with_len(-1, buffer, None, 
5).unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Invalid argument error: Value length cannot be negative, got -1"
+        );
+    }
+
+    #[test]
+    fn test_try_new_with_len_incorrect_null_buffer_length() {
+        let buffer = Buffer::from_vec(vec![0_u8; 10]);
+        let nulls = NullBuffer::new_null(3);
+        let err = FixedSizeBinaryArray::try_new_with_len(2, buffer, 
Some(nulls), 5).unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Invalid argument error: Incorrect length of null buffer for 
FixedSizeBinaryArray, expected 5 got 3"
+        );
+    }
+
+    #[test]
+    fn test_try_new_with_len_incorrect_values_buffer_length() {
+        let buffer = Buffer::from_vec(vec![0_u8; 10]);
+        let err = FixedSizeBinaryArray::try_new_with_len(2, buffer, None, 
3).unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Invalid argument error: Incorrect length of values buffer for 
FixedSizeBinaryArray, expected 3 got 5"
+        );
+    }
+
+    #[test]
+    fn test_try_new_with_len_zero_width_non_empty_buffer() {
+        let buffer = Buffer::from_vec(vec![0_u8; 10]);
+        let err = FixedSizeBinaryArray::try_new_with_len(0, buffer, None, 
5).unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Invalid argument error: Buffer cannot have non-zero length if the 
value length is zero"
+        );
+    }
 }
diff --git a/arrow-row/src/fixed.rs b/arrow-row/src/fixed.rs
index 4268c42ace..d4cd3f8dde 100644
--- a/arrow-row/src/fixed.rs
+++ b/arrow-row/src/fixed.rs
@@ -450,7 +450,8 @@ pub fn decode_fixed_size_binary(
     if size < 0 {
         panic!("cannot decode FixedSizeBinary({size})");
     }
-    let mut values = MutableBuffer::new(size as usize * rows.len());
+    let num_rows = rows.len();
+    let mut values = MutableBuffer::new(size as usize * num_rows);
     let nulls = decode_nulls(rows);
 
     let encoded_len = size as usize + 1;
@@ -466,5 +467,6 @@ pub fn decode_fixed_size_binary(
         }
     }
 
-    FixedSizeBinaryArray::new(size, values.into(), nulls)
+    // Need to set the length since when size is 0 and no nulls the length 
could not be determined by FixedSizeBinaryArray
+    FixedSizeBinaryArray::try_new_with_len(size, values.into(), nulls, 
num_rows).unwrap()
 }
diff --git a/arrow-row/src/lib.rs b/arrow-row/src/lib.rs
index 17ede5a0c4..d02d29e4e9 100644
--- a/arrow-row/src/lib.rs
+++ b/arrow-row/src/lib.rs
@@ -2325,6 +2325,27 @@ mod tests {
 
     use super::*;
 
+    fn all_sort_options() -> [SortOptions; 4] {
+        [
+            SortOptions {
+                descending: false,
+                nulls_first: false,
+            },
+            SortOptions {
+                descending: false,
+                nulls_first: true,
+            },
+            SortOptions {
+                descending: true,
+                nulls_first: false,
+            },
+            SortOptions {
+                descending: true,
+                nulls_first: true,
+            },
+        ]
+    }
+
     #[test]
     fn test_fixed_width() {
         let cols = [
@@ -2388,6 +2409,66 @@ mod tests {
         }
     }
 
+    fn test_roundtrip(sort_option: SortOptions, col: ArrayRef) {
+        let converter = RowConverter::new(vec![SortField::new_with_options(
+            col.data_type().clone(),
+            sort_option,
+        )])
+        .unwrap();
+        let rows = converter.convert_columns(&[Arc::clone(&col)]).unwrap();
+        let back = converter.convert_rows(&rows).unwrap();
+        assert_eq!(back.len(), 1);
+        assert_eq!(&back[0], &col);
+        back[0].to_data().validate_full().unwrap();
+    }
+
+    #[test]
+    fn test_zero_width_fixed_size_binary_roundtrip() {
+        for sort_option in all_sort_options() {
+            // With a zero byte width and no nulls, the decoded length cannot 
be
+            // inferred from the value or null buffers and must come from the 
row count
+            for with_null in [true, false] {
+                let nulls = if with_null {
+                    Some(NullBuffer::from(vec![true, false, true, false, 
true]))
+                } else {
+                    None
+                };
+                let col: ArrayRef = Arc::new(
+                    FixedSizeBinaryArray::try_new_with_len(0, 
Buffer::default(), nulls, 5).unwrap(),
+                );
+
+                test_roundtrip(sort_option, col);
+            }
+        }
+    }
+
+    #[test]
+    fn test_zero_width_fixed_size_list_roundtrip() {
+        for sort_option in all_sort_options() {
+            // With a zero byte width and no nulls, the decoded length cannot 
be
+            // inferred from the value or null buffers and must come from the 
row count
+            for with_null in [true, false] {
+                let nulls = if with_null {
+                    Some(NullBuffer::from(vec![true, false, true, false, 
true]))
+                } else {
+                    None
+                };
+                let col: ArrayRef = Arc::new(
+                    FixedSizeListArray::try_new_with_length(
+                        Arc::new(Field::new("item", DataType::Boolean, false)),
+                        0,
+                        new_empty_array(&DataType::Boolean),
+                        nulls,
+                        5,
+                    )
+                    .unwrap(),
+                );
+
+                test_roundtrip(sort_option, col);
+            }
+        }
+    }
+
     #[test]
     fn test_decimal32() {
         let converter = 
RowConverter::new(vec![SortField::new(DataType::Decimal32(
diff --git a/arrow-row/src/list.rs b/arrow-row/src/list.rs
index 7f76e88cec..f2d810d2c1 100644
--- a/arrow-row/src/list.rs
+++ b/arrow-row/src/list.rs
@@ -264,6 +264,7 @@ pub unsafe fn decode_fixed_size_list(
         )));
     };
 
+    let num_rows = rows.len();
     let nulls = fixed::decode_nulls(rows);
 
     let null_element_encoded =
@@ -295,12 +296,13 @@ pub unsafe fn decode_fixed_size_list(
     let mut children = unsafe { converter.convert_raw(&mut child_rows, 
validate_utf8) }?;
     assert_eq!(children.len(), 1);
 
-    Ok(FixedSizeListArray::new(
+    FixedSizeListArray::try_new_with_length(
         Arc::clone(element_field),
         *size,
         children.pop().unwrap(),
         nulls,
-    ))
+        num_rows,
+    )
 }
 
 /// Computes the encoded length for a single list element given its child rows.

Reply via email to