sunchao commented on code in PR #24767:
URL: https://github.com/apache/datafusion/pull/24767#discussion_r4027951448


##########
datafusion/spark/src/function/aggregate/collect.rs:
##########
@@ -180,32 +234,67 @@ impl<T: Accumulator> NullToEmptyListAccumulator<T> {
     pub fn new(inner: T, list_type: DataType) -> Self {
         Self { inner, list_type }
     }
+
+    fn normalize_input(&self, value: &ArrayRef) -> Result<ArrayRef> {
+        let DataType::List(field) = &self.list_type else {
+            return internal_err!(
+                "collect_list/collect_set expected List return type, got {:?}",
+                self.list_type
+            );
+        };
+        if value.data_type() == field.data_type() {
+            Ok(Arc::clone(value))
+        } else {
+            // Materialize only retained rows before narrowing nested fields.
+            // A slice can still reference null payload outside its logical 
rows.
+            let indices = match value.logical_nulls() {
+                Some(nulls) => UInt64Array::from_iter_values(
+                    nulls.valid_indices().map(|index| index as u64),
+                ),
+                None => UInt64Array::from_iter_values(0..value.len() as u64),
+            };
+            let value = take(value.as_ref(), &indices, None)?;
+            Ok(cast(value.as_ref(), field.data_type())?)

Review Comment:
   ### [P2] Remove unreachable nested payload before narrowing fields
   
   The top-level `take` fixes the original ignored-row case, but it does not 
remove payload beneath null inner lists. A single retained outer list with 
logical value `[NULL, [1]]` still fails when the aggregate's declared element 
type is `List<nullable List<non-null Int32>>` and the runtime type is 
`List<nullable List<nullable Int32>>`.
   
   Concrete Arrow layout:
   
   - inner lists: offsets `[0, 1, 2]`, Int32 child `[NULL, 1]`, validity 
`[false, true]`;
   - outer list: offsets `[0, 2]`, the inner lists as its child, and no null 
bitmap.
   
   The runtime input array passes `ArrayData::validate_full()`; the only 
logically visible Int32 is `1`. Taking the retained outer row copies the null 
inner list's unused payload, so the subsequent cast rejects it with 
`Non-nullable field of ListArray "item" cannot contain nulls`.
   
   I executed identical accumulator probes on `4f792b3` and merge base 
`c4910e0`: both `collect_list` and `collect_set` return an error from 
`update_batch` on the head, while both accept and return the expected logical 
value on the base. These are accumulator-API reproductions of the 
runtime/declared schema difference; I have not established a native SQL 
producer of that difference.
   
   Related base-pass/head-fail cases are sliced ListView/LargeListView inputs 
containing only `[1]` while retaining an unused NULL backing value (both 
aggregates), and a dictionary of lists retaining an unused `[NULL]` dictionary 
value (`collect_list`).
   
   Please account for logical reachability recursively before narrowing nested 
fields, including shared backing values, and add regressions for both collect 
functions.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to