-
Notifications
You must be signed in to change notification settings - Fork 2.4k
fix: align Spark collect aggregate element nullability #24767
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e3f28e6
ddbadd3
4f792b3
735a4b3
d7d26bd
eb0a76d
3cf0870
cdba3e3
985de98
ae8c9c0
bfa35cc
209b774
d65cf5f
f83f0f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,8 +30,8 @@ use crate::error::{ | |
| }; | ||
| use crate::{Result, ScalarValue}; | ||
| use arrow::array::{ | ||
| Array, ArrayRef, FixedSizeListArray, LargeListArray, ListArray, OffsetSizeTrait, | ||
| cast::AsArray, | ||
| Array, ArrayData, ArrayRef, FixedSizeListArray, LargeListArray, ListArray, | ||
| OffsetSizeTrait, cast::AsArray, | ||
| }; | ||
| use arrow::array::{ | ||
| ArrowPrimitiveType, BooleanArray, Datum, GenericListArray, Int32Array, Int64Array, | ||
|
|
@@ -591,9 +591,7 @@ impl SingleRowListArrayBuilder { | |
|
|
||
| /// Build a single element [`ListArray`] | ||
| pub fn build_list_array(self) -> ListArray { | ||
| let (field, arr) = self.into_field_and_arr(); | ||
| let offsets = OffsetBuffer::from_lengths([arr.len()]); | ||
| ListArray::new(field, offsets, arr, None) | ||
| self.build_generic_list_array() | ||
| } | ||
|
|
||
| /// Build a single element [`ListArray`] and wrap as [`ScalarValue::List`] | ||
|
|
@@ -603,9 +601,31 @@ impl SingleRowListArrayBuilder { | |
|
|
||
| /// Build a single element [`LargeListArray`] | ||
| pub fn build_large_list_array(self) -> LargeListArray { | ||
| self.build_generic_list_array() | ||
| } | ||
|
|
||
| fn build_generic_list_array<OffsetSize: OffsetSizeTrait>( | ||
| self, | ||
| ) -> GenericListArray<OffsetSize> { | ||
| let (field, arr) = self.into_field_and_arr(); | ||
| let offsets = OffsetBuffer::from_lengths([arr.len()]); | ||
| LargeListArray::new(field, offsets, arr, None) | ||
|
|
||
| // `is_nullable` is conservative for encoded arrays and can be true | ||
| // even when the array contains no logical nulls. In that case the | ||
| // generic constructor rejects a valid non-nullable list child. | ||
| if !field.is_nullable() && arr.is_nullable() && arr.logical_null_count() == 0 { | ||
| let data = ArrayData::builder( | ||
| GenericListArray::<OffsetSize>::DATA_TYPE_CONSTRUCTOR(field), | ||
| ) | ||
| .len(1) | ||
| .add_buffer(offsets.into_inner().into_inner()) | ||
| .add_child_data(arr.to_data()) | ||
| .build() | ||
| .expect("single-row list array should contain valid data"); | ||
| return GenericListArray::from(data); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Handle encoded children during scalar compaction tooThe constructor workaround preserves a dictionary child's unused null entry, so the resulting non-nullable list still fails in an ordinary consumer. With the input from This also affects composition: ordered Please preserve support for these valid encoded children through compaction as well, and extend the dictionary regression to compact the result or feed it into an ordered aggregate. This finding is source-traced against Arrow 59.2.0; I have not executed the proposed reproducer locally.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed by executing dictionary scalar compaction; sparse-union compaction fails as well. Arrow's list constructors still reject these logically non-null encoded children. This remains unresolved, and the follow-up does not widen the result type or add another constructor bypass.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rechecked at I also reproduced the existing grouped/window issue using Dictionary<Int8, Utf8> keys SELECT g, collect_list(x) FROM t GROUP BY g ORDER BY g;
SELECT id, collect_list(x) OVER (
ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
) FROM t ORDER BY id;Both queries pass on the base and fail on the head with Dictionary |
||
| } | ||
|
|
||
| GenericListArray::new(field, offsets, arr, None) | ||
| } | ||
|
|
||
| /// Build a single element [`LargeListArray`] and wrap as [`ScalarValue::LargeList`] | ||
|
|
@@ -1528,9 +1548,9 @@ mod tests { | |
| use super::*; | ||
| use crate::ScalarValue::Null; | ||
| use arrow::{ | ||
| array::{Float64Array, Int32Array}, | ||
| array::{DictionaryArray, Float64Array, Int8Array, Int32Array, StringArray}, | ||
| buffer::NullBuffer, | ||
| datatypes::Int32Type, | ||
| datatypes::{Int8Type, Int32Type}, | ||
| }; | ||
| #[cfg(feature = "sql")] | ||
| use sqlparser::ast::Ident; | ||
|
|
@@ -1572,6 +1592,34 @@ mod tests { | |
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn single_row_list_builder_handles_conservatively_nullable_values() { | ||
| let keys = Int8Array::from(vec![0, 0]); | ||
| let dictionary_values = Arc::new(StringArray::from(vec![Some("a"), None])); | ||
| let values = Arc::new( | ||
| DictionaryArray::<Int8Type>::try_new(keys, dictionary_values).unwrap(), | ||
| ) as ArrayRef; | ||
|
|
||
| assert_eq!(values.logical_null_count(), 0); | ||
| assert!(values.is_nullable()); | ||
|
|
||
| let list = SingleRowListArrayBuilder::new(Arc::clone(&values)) | ||
| .with_nullable(false) | ||
| .build_list_array(); | ||
| list.to_data().validate_full().unwrap(); | ||
|
|
||
| let value = ScalarValue::try_from_array(&list, 0).unwrap(); | ||
| assert_eq!(value.data_type(), list.data_type().clone()); | ||
|
|
||
| let list = SingleRowListArrayBuilder::new(values) | ||
| .with_nullable(false) | ||
| .build_large_list_array(); | ||
| list.to_data().validate_full().unwrap(); | ||
|
|
||
| let value = ScalarValue::try_from_array(&list, 0).unwrap(); | ||
| assert_eq!(value.data_type(), list.data_type().clone()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_bisect_linear_left_and_right() -> Result<()> { | ||
| let arrays: Vec<ArrayRef> = vec![ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Handle encoded nullability in downstream list operations
This preserves a dictionary child containing unused null entries. The resulting list passes
ArrayData::validate_full(), but Arrow'sconcatandtakeconstructors still reject its non-nullable element field.I reproduced this with
Dictionary<Int8, Utf8>keys[0, 0, 0, 0], dictionary values[Some("a"), None], group keys[0, 0, 1, 1], and row IDs[0, 1, 2, 3]. Both queries fail on this head:The error is
Non-nullable field of ListArray "item" cannot contain nulls. Grouped/window output concatenates list scalars throughScalarValue::iter_to_array, reaching the conservative constructor again. Both queries pass with merge-base production code; the correspondingcollect_setcontrols pass on both versions.Please reconcile encoded-child nullability beyond this constructor and add grouped/window regression coverage. The existing global-aggregation test produces only one scalar and misses this path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed in grouped and sliding-window queries. The constructor workaround does not address downstream Arrow ListArray validation. These still fail on the current branch, so this remains unresolved.