Skip to content

Commit bc2a8bd

Browse files
committed
add try_new_with_length constructor to FixedSizeList
Also fixes existing tests and adds some more test cases for degenerate `FixedSizeList`s. Signed-off-by: Connor Tsui <[email protected]>
1 parent 751b082 commit bc2a8bd

File tree

1 file changed

+125
-24
lines changed

1 file changed

+125
-24
lines changed

arrow-array/src/array/fixed_size_list_array.rs

Lines changed: 125 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,15 @@ pub struct FixedSizeListArray {
125125
}
126126

127127
impl FixedSizeListArray {
128-
/// Create a new [`FixedSizeListArray`] with `size` element size, panicking on failure
128+
/// Create a new [`FixedSizeListArray`] with `size` element size, panicking on failure.
129+
///
130+
/// Note that if `size == 0` and `nulls` is `None` (a degenerate, non-nullable
131+
/// `FixedSizeListArray`), this function will set the length of the array to 0.
132+
///
133+
/// If you would like to have a degenerate, non-nullable `FixedSizeListArray` with arbitrary
134+
/// length, use the [`try_new_with_length()`] constructor.
135+
///
136+
/// [`try_new_with_length()`]: Self::try_new_with_length
129137
///
130138
/// # Panics
131139
///
@@ -134,12 +142,20 @@ impl FixedSizeListArray {
134142
Self::try_new(field, size, values, nulls).unwrap()
135143
}
136144

137-
/// Create a new [`FixedSizeListArray`] from the provided parts, returning an error on failure
145+
/// Create a new [`FixedSizeListArray`] from the provided parts, returning an error on failure.
146+
///
147+
/// Note that if `size == 0` and `nulls` is `None` (a degenerate, non-nullable
148+
/// `FixedSizeListArray`), this function will set the length of the array to 0.
149+
///
150+
/// If you would like to have a degenerate, non-nullable `FixedSizeListArray` with arbitrary
151+
/// length, use the [`try_new_with_length()`] constructor.
152+
///
153+
/// [`try_new_with_length()`]: Self::try_new_with_length
138154
///
139155
/// # Errors
140156
///
141157
/// * `size < 0`
142-
/// * `values.len() / size != nulls.len()`
158+
/// * `values.len() != nulls.len() * size` if `nulls` is `Some`
143159
/// * `values.data_type() != field.data_type()`
144160
/// * `!field.is_nullable() && !nulls.expand(size).contains(values.logical_nulls())`
145161
pub fn try_new(
@@ -152,23 +168,79 @@ impl FixedSizeListArray {
152168
ArrowError::InvalidArgumentError(format!("Size cannot be negative, got {size}"))
153169
})?;
154170

155-
let len = match s {
156-
0 => nulls.as_ref().map(|x| x.len()).unwrap_or_default(),
157-
_ => {
158-
let len = values.len() / s.max(1);
159-
if let Some(n) = nulls.as_ref() {
160-
if n.len() != len {
161-
return Err(ArrowError::InvalidArgumentError(format!(
162-
"Incorrect length of null buffer for FixedSizeListArray, expected {} got {}",
163-
len,
164-
n.len(),
165-
)));
166-
}
171+
let len = if s == 0 {
172+
// Note that for degenerate and non-nullable `FixedSizeList`s, we will set the length to
173+
// 0 (`_or_default`).
174+
nulls.as_ref().map(|x| x.len()).unwrap_or_default()
175+
} else {
176+
// Check that the null buffer length is correct (if it exists).
177+
let len = values.len() / s;
178+
179+
if let Some(null_buffer) = &nulls {
180+
if null_buffer.len() != len {
181+
return Err(ArrowError::InvalidArgumentError(format!(
182+
"Incorrect length of null buffer for FixedSizeListArray, expected {len} got {}",
183+
null_buffer.len(),
184+
)));
167185
}
168-
len
169186
}
187+
188+
len
170189
};
171190

191+
Self::try_new_with_length(field, size, values, nulls, len)
192+
}
193+
194+
/// Create a new [`FixedSizeListArray`] from the provided parts, returning an error on failure.
195+
///
196+
/// This method exists to allow the construction of arbitrary length degenerate (`size == 0`)
197+
/// and non-nullable `FixedSizeListArray`s. If you want a nullable `FixedSizeListArray`, then
198+
/// you can use [`try_new()`] instead.
199+
///
200+
/// [`try_new()`]: Self::try_new
201+
///
202+
/// # Errors
203+
///
204+
/// * `size < 0`
205+
/// * `nulls.len() != len` if `nulls` is `Some`
206+
/// * `values.len() != len * size`
207+
/// * `values.data_type() != field.data_type()`
208+
/// * `!field.is_nullable() && !nulls.expand(size).contains(values.logical_nulls())`
209+
pub fn try_new_with_length(
210+
field: FieldRef,
211+
size: i32,
212+
values: ArrayRef,
213+
nulls: Option<NullBuffer>,
214+
len: usize,
215+
) -> Result<Self, ArrowError> {
216+
let s = size.to_usize().ok_or_else(|| {
217+
ArrowError::InvalidArgumentError(format!("Size cannot be negative, got {size}"))
218+
})?;
219+
220+
if let Some(null_buffer) = &nulls
221+
&& null_buffer.len() != len
222+
{
223+
return Err(ArrowError::InvalidArgumentError(format!(
224+
"Invalid null buffer for FixedSizeListArray, expected {len} found {}",
225+
null_buffer.len()
226+
)));
227+
}
228+
229+
if s == 0 && !values.is_empty() {
230+
return Err(ArrowError::InvalidArgumentError(format!(
231+
"An degenerate FixedSizeListArray should have no underlying values, found {} values",
232+
values.len()
233+
)));
234+
}
235+
236+
if values.len() != len * s {
237+
return Err(ArrowError::InvalidArgumentError(format!(
238+
"Incorrect length of values buffer for FixedSizeListArray, expected {} got {}",
239+
len * s,
240+
values.len(),
241+
)));
242+
}
243+
172244
if field.data_type() != values.data_type() {
173245
return Err(ArrowError::InvalidArgumentError(format!(
174246
"FixedSizeListArray expected data type {} got {} for {:?}",
@@ -673,18 +745,23 @@ mod tests {
673745
let list = FixedSizeListArray::new(field.clone(), 2, values.clone(), Some(nulls));
674746
assert_eq!(list.len(), 3);
675747

676-
let list = FixedSizeListArray::new(field.clone(), 4, values.clone(), None);
677-
assert_eq!(list.len(), 1);
748+
let list = FixedSizeListArray::new(field.clone(), 3, values.clone(), None);
749+
assert_eq!(list.len(), 2);
750+
751+
let err =
752+
FixedSizeListArray::try_new_with_length(field.clone(), 1, values.clone(), None, 4)
753+
.unwrap_err();
754+
assert_eq!(
755+
err.to_string(),
756+
"Invalid argument error: Incorrect length of values buffer for FixedSizeListArray, expected 4 got 6"
757+
);
678758

679759
let err = FixedSizeListArray::try_new(field.clone(), -1, values.clone(), None).unwrap_err();
680760
assert_eq!(
681761
err.to_string(),
682762
"Invalid argument error: Size cannot be negative, got -1"
683763
);
684764

685-
let list = FixedSizeListArray::new(field.clone(), 0, values.clone(), None);
686-
assert_eq!(list.len(), 0);
687-
688765
let nulls = NullBuffer::new_null(2);
689766
let err = FixedSizeListArray::try_new(field, 2, values.clone(), Some(nulls)).unwrap_err();
690767
assert_eq!(
@@ -704,19 +781,43 @@ mod tests {
704781
FixedSizeListArray::new(field, 2, values.clone(), Some(nulls));
705782

706783
let field = Arc::new(Field::new_list_field(DataType::Int64, true));
707-
let err = FixedSizeListArray::try_new(field, 2, values, None).unwrap_err();
784+
let err = FixedSizeListArray::try_new(field.clone(), 2, values, None).unwrap_err();
708785
assert_eq!(
709786
err.to_string(),
710787
"Invalid argument error: FixedSizeListArray expected data type Int64 got Int32 for \"item\""
711788
);
712789
}
713790

714791
#[test]
715-
fn empty_fixed_size_list() {
792+
fn degenerate_fixed_size_list() {
716793
let field = Arc::new(Field::new_list_field(DataType::Int32, true));
717794
let nulls = NullBuffer::new_null(2);
718795
let values = new_empty_array(&DataType::Int32);
719-
let list = FixedSizeListArray::new(field.clone(), 0, values, Some(nulls));
796+
let list = FixedSizeListArray::new(field.clone(), 0, values.clone(), Some(nulls.clone()));
720797
assert_eq!(list.len(), 2);
798+
799+
// Test invalid null buffer length.
800+
let err = FixedSizeListArray::try_new_with_length(
801+
field.clone(),
802+
0,
803+
values.clone(),
804+
Some(nulls),
805+
5,
806+
)
807+
.unwrap_err();
808+
assert_eq!(
809+
err.to_string(),
810+
"Invalid argument error: Invalid null buffer for FixedSizeListArray, expected 5 found 2"
811+
);
812+
813+
// Test non-empty values for degenerate list.
814+
let non_empty_values = Arc::new(Int32Array::from(vec![1, 2, 3]));
815+
let err =
816+
FixedSizeListArray::try_new_with_length(field.clone(), 0, non_empty_values, None, 3)
817+
.unwrap_err();
818+
assert_eq!(
819+
err.to_string(),
820+
"Invalid argument error: An degenerate FixedSizeListArray should have no underlying values, found 3 values"
821+
);
721822
}
722823
}

0 commit comments

Comments
 (0)