This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-23775-dd1ebde2d312a81ab42cbc8f2f3e3ecc53e5c97c
in repository https://gitbox.apache.org/repos/asf/datafusion.git

commit e084034f7c45c5e8e38d7a2a619369a43e6cf347
Author: Ben Chambers <[email protected]>
AuthorDate: Thu Jul 23 13:28:27 2026 -0700

    fix: array_any_value returns NULL for empty list elements (#23775)
    
    ## Which issue does this PR close?
    
    - Closes #23773.
    
    ## Rationale for this change
    
    `array_any_value` reads the wrong value (or panics) when its input list
    column
    contains a non-null **empty** (length-0) element.
    
    `general_array_any_value` guards null and all-null elements, but a
    non-null
    empty element falls through to the no-nulls branch, which
    unconditionally reads
    `values[start]`:
    
    - **interior empty list** → reads `values[start]`, i.e. the *next*
    element's
      value (silently wrong data)
    - **trailing empty list** (`start == values.len()`) → out-of-bounds
    slice →
      panic `range end index N out of range for slice of length N-1`
    
    The panic is easy to trigger in practice when the `array_any_value`
    output flows
    into a hash `RepartitionExec` (e.g. the value is used as an equi-join
    key):
    repartitioning slices batches so an empty element can land at the end of
    a
    values buffer, tripping the out-of-bounds read on a spawned task.
    
    ## What changes are included in this PR?
    
    Guard the empty case explicitly in `general_array_any_value` — an empty
    list has
    no value to take, so the result is `NULL`.
    
    Sibling functions in `extract.rs` were audited and are already safe:
    `array_element` bounds-checks the index against `len`; `array_slice` /
    `array_pop_front` / `array_pop_back` guard `len == 0`.
    
    ## Are these changes tested?
    
    Yes:
    
    - Kernel-level regression tests for `general_array_any_value`: an
    interior empty
    element (previously wrong value) and a trailing empty element
    (previously
      panic).
    - `array_any_value.slt` cases covering `List` and `LargeList` with
    interior and
      trailing empty elements.
    
    ## Are there any user-facing changes?
    
    `array_any_value` now returns `NULL` for an empty list element instead
    of
    returning the next element's value or panicking the query. No API
    changes.
    
    ---------
    
    Signed-off-by: Ben Chambers <[email protected]>
---
 datafusion/functions-nested/src/extract.rs         | 12 +++++++--
 .../test_files/array/array_any_value.slt           | 29 ++++++++++++++++++++++
 docs/source/user-guide/sql/scalar_functions.md     |  2 +-
 3 files changed, 40 insertions(+), 3 deletions(-)

diff --git a/datafusion/functions-nested/src/extract.rs 
b/datafusion/functions-nested/src/extract.rs
index 900b408bff..b1c22822df 100644
--- a/datafusion/functions-nested/src/extract.rs
+++ b/datafusion/functions-nested/src/extract.rs
@@ -970,7 +970,7 @@ where
 
 #[user_doc(
     doc_section(label = "Array Functions"),
-    description = "Returns the first non-null element in the array.",
+    description = "Returns the first non-null element in the array. Returns 
NULL if the array is empty or NULL.",
     syntax_example = "array_any_value(array)",
     sql_example = r#"```sql
 > select array_any_value([NULL, 1, 2, 3]);
@@ -1062,13 +1062,21 @@ where
 
     for (row_index, offset_window) in array.offsets().windows(2).enumerate() {
         let start = offset_window[0];
+        let end = offset_window[1];
 
-        // array is null
+        // the list element is null
         if array.is_null(row_index) {
             mutable.try_extend_nulls(1)?;
             continue;
         }
 
+        // the list element is empty; there is no value to take, so the result
+        // is NULL.
+        if start == end {
+            mutable.try_extend_nulls(1)?;
+            continue;
+        }
+
         let row_value = array.value(row_index);
         match row_value.nulls() {
             Some(row_nulls_buffer) => {
diff --git a/datafusion/sqllogictest/test_files/array/array_any_value.slt 
b/datafusion/sqllogictest/test_files/array/array_any_value.slt
index 6579e88ac7..c8976e8493 100644
--- a/datafusion/sqllogictest/test_files/array/array_any_value.slt
+++ b/datafusion/sqllogictest/test_files/array/array_any_value.slt
@@ -145,6 +145,35 @@ select array_any_value(make_array(NULL, 1, 2, 3, 4, 5)), 
array_any_value(column1
 1 41
 1 51
 
+# array_any_value with empty (length-0) list elements
+# A non-null but empty list must yield NULL, including a trailing empty element
+# whose start offset equals the values length
+statement ok
+create table any_value_empty (id int, tags bigint[]) as values
+  (1, make_array(10)),
+  (2, cast(make_array() as bigint[])),
+  (3, make_array(20, 30)),
+  (4, cast(make_array() as bigint[]));
+
+query II
+select id, array_any_value(tags) from any_value_empty order by id;
+----
+1 10
+2 NULL
+3 20
+4 NULL
+
+query II
+select id, array_any_value(arrow_cast(tags, 'LargeList(Int64)')) from 
any_value_empty order by id;
+----
+1 10
+2 NULL
+3 20
+4 NULL
+
+statement ok
+drop table any_value_empty;
+
 # make_array with nulls
 query ???????
 select make_array(make_array('a','b'), null),
diff --git a/docs/source/user-guide/sql/scalar_functions.md 
b/docs/source/user-guide/sql/scalar_functions.md
index cb748f57d9..a285b9e5f5 100644
--- a/docs/source/user-guide/sql/scalar_functions.md
+++ b/docs/source/user-guide/sql/scalar_functions.md
@@ -3430,7 +3430,7 @@ any_match(array, predicate)
 
 ### `array_any_value`
 
-Returns the first non-null element in the array.
+Returns the first non-null element in the array. Returns NULL if the array is 
empty or NULL.
 
 ```sql
 array_any_value(array)


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

Reply via email to