Skip to content

Commit f5d6cbd

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 6be6cba commit f5d6cbd

File tree

1 file changed

+143
-25
lines changed

1 file changed

+143
-25
lines changed

arrow-array/src/array/fixed_size_list_array.rs

Lines changed: 143 additions & 25 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,22 +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+
if s == 0 {
172+
// Note that for degenerate (`size == 0`) and non-nullable `FixedSizeList`s, we will set
173+
// the length to 0 (`_or_default`).
174+
let len = nulls.as_ref().map(|x| x.len()).unwrap_or_default();
175+
176+
Self::try_new_with_length(field, size, values, nulls, len)
177+
} else {
178+
if values.len() % s != 0 {
179+
return Err(ArrowError::InvalidArgumentError(format!(
180+
"Incorrect length of values buffer for FixedSizeListArray, \
181+
expected a multiple of {s} got {}",
182+
values.len(),
183+
)));
184+
}
185+
186+
let len = values.len() / s;
187+
188+
// Check that the null buffer length is correct (if it exists).
189+
if let Some(null_buffer) = &nulls {
190+
if s * null_buffer.len() != values.len() {
191+
return Err(ArrowError::InvalidArgumentError(format!(
192+
"Incorrect length of values buffer for FixedSizeListArray, \
193+
expected {} got {}",
194+
s * null_buffer.len(),
195+
values.len(),
196+
)));
167197
}
168-
len
169198
}
170-
};
199+
200+
Self::try_new_with_length(field, size, values, nulls, len)
201+
}
202+
}
203+
204+
/// Create a new [`FixedSizeListArray`] from the provided parts, returning an error on failure.
205+
///
206+
/// This method exists to allow the construction of arbitrary length degenerate (`size == 0`)
207+
/// and non-nullable `FixedSizeListArray`s. If you want a nullable `FixedSizeListArray`, then
208+
/// you can use [`try_new()`] instead.
209+
///
210+
/// [`try_new()`]: Self::try_new
211+
///
212+
/// # Errors
213+
///
214+
/// * `size < 0`
215+
/// * `nulls.len() != len` if `nulls` is `Some`
216+
/// * `values.len() != len * size`
217+
/// * `values.data_type() != field.data_type()`
218+
/// * `!field.is_nullable() && !nulls.expand(size).contains(values.logical_nulls())`
219+
pub fn try_new_with_length(
220+
field: FieldRef,
221+
size: i32,
222+
values: ArrayRef,
223+
nulls: Option<NullBuffer>,
224+
len: usize,
225+
) -> Result<Self, ArrowError> {
226+
let s = size.to_usize().ok_or_else(|| {
227+
ArrowError::InvalidArgumentError(format!("Size cannot be negative, got {size}"))
228+
})?;
229+
230+
if let Some(null_buffer) = &nulls {
231+
if null_buffer.len() != len {
232+
return Err(ArrowError::InvalidArgumentError(format!(
233+
"Invalid null buffer for FixedSizeListArray, expected {len} found {}",
234+
null_buffer.len()
235+
)));
236+
}
237+
}
238+
239+
if s == 0 && !values.is_empty() {
240+
return Err(ArrowError::InvalidArgumentError(format!(
241+
"An degenerate FixedSizeListArray should have no underlying values, found {} values",
242+
values.len()
243+
)));
244+
}
245+
246+
if values.len() != len * s {
247+
return Err(ArrowError::InvalidArgumentError(format!(
248+
"Incorrect length of values buffer for FixedSizeListArray, expected {} got {}",
249+
len * s,
250+
values.len(),
251+
)));
252+
}
171253

172254
if field.data_type() != values.data_type() {
173255
return Err(ArrowError::InvalidArgumentError(format!(
@@ -673,23 +755,35 @@ mod tests {
673755
let list = FixedSizeListArray::new(field.clone(), 2, values.clone(), Some(nulls));
674756
assert_eq!(list.len(), 3);
675757

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

679776
let err = FixedSizeListArray::try_new(field.clone(), -1, values.clone(), None).unwrap_err();
680777
assert_eq!(
681778
err.to_string(),
682779
"Invalid argument error: Size cannot be negative, got -1"
683780
);
684781

685-
let list = FixedSizeListArray::new(field.clone(), 0, values.clone(), None);
686-
assert_eq!(list.len(), 0);
687-
688782
let nulls = NullBuffer::new_null(2);
689783
let err = FixedSizeListArray::try_new(field, 2, values.clone(), Some(nulls)).unwrap_err();
690784
assert_eq!(
691785
err.to_string(),
692-
"Invalid argument error: Incorrect length of null buffer for FixedSizeListArray, expected 3 got 2"
786+
"Invalid argument error: Incorrect length of values buffer for FixedSizeListArray, expected 4 got 6"
693787
);
694788

695789
let field = Arc::new(Field::new_list_field(DataType::Int32, false));
@@ -712,11 +806,35 @@ mod tests {
712806
}
713807

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

0 commit comments

Comments
 (0)