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

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 0028aaa593 fix(arrow-select): fix `take` output capacity for sliced 
lists (#11360)
0028aaa593 is described below

commit 0028aaa593bed532ce7893c5a0aca17b0a7139e9
Author: Neil Conway <[email protected]>
AuthorDate: Sun Oct 4 02:41:18 2026 +0000

    fix(arrow-select): fix `take` output capacity for sliced lists (#11360)
    
    # Which issue does this PR close?
    
    - N/A
    
    # Rationale for this change
    
    `take` on a list or map uses the average input row length as part of its
    estimate for sizing its output buffer. However, it calculated the
    average row length starting from raw size of the input values buffer,
    not the visible span. This could result in computing a very large
    "average row length" for sliced lists and maps, and therefore
    over-allocating output capacity.
    
    # What changes are included in this PR?
    
    * Use the visible span to compute average row length
    * Refactor capacity calculations to avoid duplication
    * Add unit test
    
    # Are these changes tested?
    
    Yes; existing tests pass, new test added.
    
    # Are there any significant user-facing changes?
    
    No.
    
    # AI usage
    
    Developed and revised with Claude Code (Opus 5.5), reviewed with Codex.
---
 arrow-select/src/take.rs | 60 +++++++++++++++++++++++++++++++++++-------------
 1 file changed, 44 insertions(+), 16 deletions(-)

diff --git a/arrow-select/src/take.rs b/arrow-select/src/take.rs
index ea505762e8..38d7945700 100644
--- a/arrow-select/src/take.rs
+++ b/arrow-select/src/take.rs
@@ -1027,6 +1027,12 @@ where
 {
     let src_offsets = values.value_offsets();
     let child_data = values.values().to_data();
+    // The number of child values that the rows use, excluding values outside 
a slice
+    let visible_child_len = (values.offsets().last() - 
values.offsets().first()).as_usize();
+    // An estimate of the output's child length, from the average length of 
the rows
+    let estimated_child_len = visible_child_len
+        .checked_div(values.len())
+        .map_or(0, |avg| avg.saturating_mul(indices.len()));
     let nulls = take_nulls::<_, VALIDATE_INDICES>(values.nulls(), indices);
 
     let mut dst_offsets = Vec::with_capacity(indices.len() + 1);
@@ -1040,15 +1046,7 @@ where
         let values_buf = &child_data.buffers()[0];
         let child_buf_offset = child_data.offset() * bytes_per_value;
 
-        let avg_row_len = child_data
-            .len()
-            .checked_div(values.len().max(1))
-            .unwrap_or(0);
-        let mut dst_buf = MutableBuffer::new(
-            avg_row_len
-                .saturating_mul(indices.len())
-                .saturating_mul(bytes_per_value),
-        );
+        let mut dst_buf = 
MutableBuffer::new(estimated_child_len.saturating_mul(bytes_per_value));
 
         let mut child_len = OffsetType::Native::zero();
 
@@ -1121,13 +1119,11 @@ where
         return GenericListArray::<OffsetType::Native>::try_new(field, offsets, 
child, nulls);
     }
 
-    let capacity = child_data
-        .len()
-        .checked_div(values.len())
-        .map(|avg| avg * indices.len())
-        .unwrap_or_default();
-    let mut mutable =
-        MutableArrayData::new(vec![&child_data], child_data.null_count() > 0, 
capacity);
+    let mut mutable = MutableArrayData::new(
+        vec![&child_data],
+        child_data.null_count() > 0,
+        estimated_child_len,
+    );
 
     match nulls.as_ref().filter(|n| n.null_count() > 0) {
         None => {
@@ -3258,6 +3254,38 @@ mod tests {
         test_take_sliced_list_with_value_nulls_generic::<i64>();
     }
 
+    #[test]
+    fn test_take_sliced_list_memory_size() {
+        // Takes the same rows from a two-row slice of a large array and from 
an unsliced
+        // copy of those two rows. The results should be equal and use the 
same memory.
+        fn check(parent: ArrayRef, unsliced: ArrayRef) {
+            let sliced = parent.slice(500, 2);
+            let indices = UInt32Array::from_iter_values((0..100).map(|i| i % 
2));
+            let actual = take(&sliced, &indices, None).unwrap();
+            let expected = take(&unsliced, &indices, None).unwrap();
+            assert_eq!(&actual, &expected);
+            assert_eq!(
+                actual.get_array_memory_size(),
+                expected.get_array_memory_size()
+            );
+        }
+
+        // Lists of 10 values per row
+        let list = |rows: usize, values: ArrayRef| -> ArrayRef {
+            let field = 
Arc::new(Field::new_list_field(values.data_type().clone(), false));
+            let offsets = OffsetBuffer::from_repeated_length(10, rows);
+            Arc::new(ListArray::new(field, offsets, values, None))
+        };
+        check(
+            list(1000, Arc::new(Int64Array::from(vec![1; 10_000]))),
+            list(2, Arc::new(Int64Array::from(vec![1; 20]))),
+        );
+        check(
+            list(1000, Arc::new(StringArray::from(vec!["a"; 10_000]))),
+            list(2, Arc::new(StringArray::from(vec!["a"; 20]))),
+        );
+    }
+
     #[test]
     fn test_take_runs() {
         let logical_array: Vec<i32> = vec![1_i32, 1, 2, 2, 1, 1, 1, 2, 2, 1, 
1, 2, 2];

Reply via email to