Skip to content

Commit 7239ab9

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 8bbcaef commit 7239ab9

File tree

1 file changed

+141
-24
lines changed

1 file changed

+141
-24
lines changed

arrow-array/src/array/fixed_size_list_array.rs

Lines changed: 141 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,88 @@ 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+
if values.len() % s != 0 {
177+
return Err(ArrowError::InvalidArgumentError(format!(
178+
"Incorrect length of values buffer for FixedSizeListArray, \
179+
expected a multiple of {s} got {}",
180+
values.len(),
181+
)));
182+
}
183+
184+
let len = values.len() / s;
185+
186+
// Check that the null buffer length is correct (if it exists).
187+
if let Some(null_buffer) = &nulls {
188+
if s * null_buffer.len() != values.len() {
189+
return Err(ArrowError::InvalidArgumentError(format!(
190+
"Incorrect length of values buffer for FixedSizeListArray, expected {} got {}",
191+
s * null_buffer.len(),
192+
values.len(),
193+
)));
167194
}
168-
len
169195
}
196+
197+
len
170198
};
171199

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

676-
let list = FixedSizeListArray::new(field.clone(), 4, values.clone(), None);
677-
assert_eq!(list.len(), 1);
757+
let list = FixedSizeListArray::new(field.clone(), 3, values.clone(), None);
758+
assert_eq!(list.len(), 2);
759+
760+
let err = FixedSizeListArray::try_new(field.clone(), 4, values.clone(), None).unwrap_err();
761+
assert_eq!(
762+
err.to_string(),
763+
"Invalid argument error: Incorrect length of values buffer for FixedSizeListArray, \
764+
expected a multiple of 4 got 6",
765+
);
766+
767+
let err =
768+
FixedSizeListArray::try_new_with_length(field.clone(), 4, values.clone(), None, 1)
769+
.unwrap_err();
770+
assert_eq!(
771+
err.to_string(),
772+
"Invalid argument error: Incorrect length of values buffer for FixedSizeListArray, expected 4 got 6"
773+
);
678774

679775
let err = FixedSizeListArray::try_new(field.clone(), -1, values.clone(), None).unwrap_err();
680776
assert_eq!(
681777
err.to_string(),
682778
"Invalid argument error: Size cannot be negative, got -1"
683779
);
684780

685-
let list = FixedSizeListArray::new(field.clone(), 0, values.clone(), None);
686-
assert_eq!(list.len(), 0);
687-
688781
let nulls = NullBuffer::new_null(2);
689782
let err = FixedSizeListArray::try_new(field, 2, values.clone(), Some(nulls)).unwrap_err();
690783
assert_eq!(
691784
err.to_string(),
692-
"Invalid argument error: Incorrect length of null buffer for FixedSizeListArray, expected 3 got 2"
785+
"Invalid argument error: Incorrect length of values buffer for FixedSizeListArray, expected 4 got 6"
693786
);
694787

695788
let field = Arc::new(Field::new_list_field(DataType::Int32, false));
@@ -712,11 +805,35 @@ mod tests {
712805
}
713806

714807
#[test]
715-
fn empty_fixed_size_list() {
808+
fn degenerate_fixed_size_list() {
716809
let field = Arc::new(Field::new_list_field(DataType::Int32, true));
717810
let nulls = NullBuffer::new_null(2);
718811
let values = new_empty_array(&DataType::Int32);
719-
let list = FixedSizeListArray::new(field.clone(), 0, values, Some(nulls));
812+
let list = FixedSizeListArray::new(field.clone(), 0, values.clone(), Some(nulls.clone()));
720813
assert_eq!(list.len(), 2);
814+
815+
// Test invalid null buffer length.
816+
let err = FixedSizeListArray::try_new_with_length(
817+
field.clone(),
818+
0,
819+
values.clone(),
820+
Some(nulls),
821+
5,
822+
)
823+
.unwrap_err();
824+
assert_eq!(
825+
err.to_string(),
826+
"Invalid argument error: Invalid null buffer for FixedSizeListArray, expected 5 found 2"
827+
);
828+
829+
// Test non-empty values for degenerate list.
830+
let non_empty_values = Arc::new(Int32Array::from(vec![1, 2, 3]));
831+
let err =
832+
FixedSizeListArray::try_new_with_length(field.clone(), 0, non_empty_values, None, 3)
833+
.unwrap_err();
834+
assert_eq!(
835+
err.to_string(),
836+
"Invalid argument error: An degenerate FixedSizeListArray should have no underlying values, found 3 values"
837+
);
721838
}
722839
}

0 commit comments

Comments
 (0)