Skip to content

fix: correct list field inner type in array functions - #24345

Merged
timsaucer merged 2 commits into
apache:mainfrom
timsaucer:fix/inner-list-field
Aug 14, 2026
Merged

fix: correct list field inner type in array functions#24345
timsaucer merged 2 commits into
apache:mainfrom
timsaucer:fix/inner-list-field

Conversation

@timsaucer

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

There is a regression from 54.1.0 to 55.0.0rc2 where the spark function slice can fail as demonstrated in the updated unit test, and as described in the connected issue.

What changes are included in this PR?

In the nested functions, get the inner field directly from the input for array slicing operations. Also, since it is possible the inner list could be non-nullable emit an empty slice instead of a null child element for the outer lists's nulls.

Are these changes tested?

Added 6 new tests in the SLT suite.

Are there any user-facing changes?

None

@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) functions Changes to functions implementation labels Aug 13, 2026
@timsaucer

Copy link
Copy Markdown
Member Author

If this is approved I'll create a PR targeting branch-55.

@codecov-commenter

codecov-commenter commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.17%. Comparing base (f171587) to head (7bd8af6).
⚠️ Report is 18 commits behind head on main.

Files with missing lines Patch % Lines
datafusion/functions-nested/src/extract.rs 66.66% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #24345    +/-   ##
========================================
  Coverage   81.17%   81.17%            
========================================
  Files        1109     1109            
  Lines      388033   388167   +134     
  Branches   388033   388167   +134     
========================================
+ Hits       314985   315107   +122     
- Misses      54509    54513     +4     
- Partials    18539    18547     +8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@adriangb adriangb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks for the fix!

One finding out of scope for this PR: array_append, array_prepend and array_replace{,_n,_all} have the same defect and regressed in the same commit. Filed as #24347. They need a slightly different fix, since the appended element can be null and so the promised type is wrong at the source, not just the payload. array_concat, array_remove, array_distinct, array_union, array_intersect, array_sort and array_resize are all fine.

+1 on the branch-55 backport

Comment thread datafusion/functions-nested/src/extract.rs Outdated
Comment thread datafusion/functions-nested/src/extract.rs Outdated
Comment thread datafusion/sqllogictest/test_files/array/array_slice.slt
Comment thread datafusion/sqllogictest/test_files/array/array_pop.slt
Comment thread datafusion/functions-nested/src/extract.rs Outdated
Comment thread datafusion/functions-nested/src/extract.rs Outdated
- Pass `use_nulls: false` to `MutableArrayData` in both `general_array_slice`
  and `general_list_view_array_slice`. Neither calls `try_extend_nulls` any
  more, and arrow ORs the flag with the child array's null count internally,
  so this only avoids an unnecessary validity buffer allocation.
- Name the actual internal function in the `internal_err!` messages. These
  paths are also reachable from `array_pop_front` / `array_pop_back`, so
  reporting "array_slice" was misleading.
- Extend the LargeList slice and pop tests to cover empty and NULL rows so
  the null branch is exercised for i64 offsets, not just i32.
@timsaucer
timsaucer enabled auto-merge August 14, 2026 11:06
@timsaucer
timsaucer added this pull request to the merge queue Aug 14, 2026
Merged via the queue into apache:main with commit 9f377c4 Aug 14, 2026
37 checks passed
@timsaucer
timsaucer deleted the fix/inner-list-field branch August 14, 2026 11:40
timsaucer added a commit that referenced this pull request Aug 14, 2026
Theodus pushed a commit to Theodus/datafusion that referenced this pull request Aug 14, 2026
…place (apache#24365)

## Which issue does this PR close?

- Closes apache#24347.

## Rationale for this change

`array_append`, `array_prepend`, `array_replace`, `array_replace_n` and
`array_replace_all` promise the input list type verbatim — inner field
name, nullability and metadata included — but their kernels rebuilt the
output's inner field from scratch with `Field::new_list_field(...,
true)`. On debug builds this trips the return-type assertion from
apache#17515; on release builds it silently yields a batch whose inner field
disagrees with the schema the planner recorded.

This is the other half of apache#24341, whose `array_slice` part was fixed in
apache#24345. The field-name symptom is a 55.0.0 regression from the same
commit (5b22857, apache#20945); the non-nullable symptom is not a regression.

Unlike `array_slice`, threading the input's field through is not
sufficient here: the appended, prepended or replacement element can
itself be null, so a promise cloned from a `List(non-null T)` input is
wrong at the source and arrow rejects the array with `Non-nullable field
of ListArray cannot contain nulls`.

## What changes are included in this PR?

Each of the five functions now implements `return_field_from_args`,
carrying the input field's name and metadata through while widening
`nullable` when the new element's argument is nullable. The kernels
build their output from `args.return_field` instead of deriving a field
of their own, so promise and payload come from a single source.
Nullability is therefore only widened when the result can genuinely
contain a null:

```sql
array_append(List(non-null Int64), 3)     -> List(non-null Int64)
array_append(List(non-null Int64), NULL)  -> List(Int64)
```

Two paths beyond those listed in the issue turned out to have the same
defect and are fixed too: `array_append` / `array_prepend` with a
**nested** value type (which delegates to `concat_internal`), and the
`LargeList` variants of all five.

Since these five now implement `return_field_from_args`, their
`return_type` becomes unreachable and returns
`internal_err!("return_field_from_args should be used instead")`,
matching the guidance on `ScalarUDFImpl::return_type` and the existing
convention in `remove.rs` and `map_values.rs`.

Two small cleanups while in here:

- The `List`/`LargeList` inner-field extraction added to
`general_array_slice` by apache#24345 is now shared as
`utils::list_inner_field`, used by all three files. Its error text is
unchanged. The `ListView` variant in `general_list_view_array_slice` is
deliberately left alone — folding all four variants into one helper
would let `general_array_slice` silently accept a `ListView` that its
match currently rejects.
- `array_concat` is **not** affected and its behaviour is unchanged: it
derives a fresh return type via `type_union_resolution` rather than
cloning an input's, so it keeps passing `None` to `concat_internal` and
deriving the field from the aligned inputs.

Behaviour for `Null`-typed array arguments is unchanged in all five
functions (`array_replace*` return `Null`, `array_append` /
`array_prepend` return `List(element)`).

## Are these changes tested?

Yes — 14 new SLT tests across `array_append.slt`, `array_prepend.slt`,
`array_replace.slt`, plus two guard cases in `array_concat.slt` pinning
down that it is unaffected.

`arrow_cast` can express a named inner field (`'List(Int64, field:
''element'')'`), so these reproduce the field-name half of the bug
without needing the Spark dialect. Coverage: inner field name preserved,
non-nullable inner field preserved, nullability widened only when the
new element is nullable (both literal `NULL` and a nullable column),
`LargeList`, nested value types, the `max <= 0` short circuit, and a
`NULL` `max`.

Every one of these queries fails on `main` with the return-type
assertion.

Also run: `cargo clippy --all-targets --all-features -- -D warnings`,
the full sqllogictest suite, and the extended workspace test suite (68
test binaries, 0 failures). The `array_replace` and `array_concat`
benchmarks show no regression against `main`.

## Are there any user-facing changes?

The five functions now return the inner field they promise instead of a
rebuilt one, which is the bug fix. As a consequence, appending or
replacing with a nullable element widens the declared inner nullability
of the result (`List(non-null Int64)` -> `List(Int64)`), which is
required for the result to be representable at all.

No breaking changes to public APIs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

functions Changes to functions implementation sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

array_slice discards the input list's inner field (name, nullability, metadata), contradicting its promised return type

4 participants