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 fa96419f5c Add `ArrayData::sliced` to avoid allocations when slicing 
owned ArrayData (#11433)
fa96419f5c is described below

commit fa96419f5cff79d98b7ae77c9fabb62dfd173346
Author: memosmorgan <[email protected]>
AuthorDate: Fri Oct 9 20:31:26 2026 +0000

    Add `ArrayData::sliced` to avoid allocations when slicing owned ArrayData 
(#11433)
    
    # Which issue does this PR close?
    
    - Closes #9140
    
    Replaces #11432.
    
    # Rationale for this change
    
    Owned `ArrayData` can be sliced without allocating new buffer or child
    vectors.
    
    AI assistance: OpenAI Codex generated the implementation and tests, ran
    the local checks, and reviewed the diff.
    
    # What changes are included in this PR?
    
    Add `ArrayData::sliced`, which consumes the array and reuses its
    allocations, including nested struct children. Use it when converting
    owned data to `StructArray`.
    
    # Are these changes tested?
    
    Regression tests cover equivalence with borrowed slicing, offsets,
    nested structs, lists, nulls, empty/end slices, and bounds. They also
    check that vector allocations are retained.
    
    A local release-mode allocation counter measured the following for
    1,024-element inputs sliced at offset 1 for 512 elements, excluding
    input construction:
    
    | Data | `slice` allocations | `sliced` allocations |
    | --- | ---: | ---: |
    | Int32 | 1 | 0 |
    | Nested Struct | 3 | 0 |
    | List<Int32> | 3 | 0 |
    
    # Are there any significant user-facing changes?
    
    No breaking changes. Borrowed `slice` retains its existing behavior.
    
    Co-authored-by: Andrew Lamb <[email protected]>
---
 arrow-array/src/array/struct_array.rs |   2 +-
 arrow-data/src/data.rs                | 200 ++++++++++++++++++++++++++++++----
 2 files changed, 181 insertions(+), 21 deletions(-)

diff --git a/arrow-array/src/array/struct_array.rs 
b/arrow-array/src/array/struct_array.rs
index 592126318a..266d1abdea 100644
--- a/arrow-array/src/array/struct_array.rs
+++ b/arrow-array/src/array/struct_array.rs
@@ -435,7 +435,7 @@ impl From<ArrayData> for StructArray {
             .into_iter()
             .map(|cd| {
                 if parent_offset != 0 || parent_len != cd.len() {
-                    make_array(cd.slice(parent_offset, parent_len))
+                    make_array(cd.sliced(parent_offset, parent_len))
                 } else {
                     make_array(cd)
                 }
diff --git a/arrow-data/src/data.rs b/arrow-data/src/data.rs
index a8a6970125..82dfefcac0 100644
--- a/arrow-data/src/data.rs
+++ b/arrow-data/src/data.rs
@@ -688,6 +688,22 @@ impl ArrayData {
     ///
     /// Panics if `offset + length` overflows or is greater than `self.len()`.
     pub fn slice(&self, offset: usize, length: usize) -> ArrayData {
+        self.clone().sliced(offset, length)
+    }
+
+    /// Creates a zero-copy slice of this array, reusing its buffer and child 
allocations.
+    ///
+    /// This is equivalent to [`Self::slice`], but consumes `self`.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `offset + length` overflows or is greater than `self.len()`.
+    pub fn sliced(mut self, offset: usize, length: usize) -> Self {
+        self.slice_in_place(offset, length);
+        self
+    }
+
+    fn slice_in_place(&mut self, offset: usize, length: usize) {
         let end = offset
             .checked_add(length)
             .expect("offset + length overflow");
@@ -699,29 +715,17 @@ impl ArrayData {
             // the slice to both would count it twice, so the cumulative offset
             // goes to the children and this array's offset is reset to 0.
             let child_offset = self.offset + offset;
-            ArrayData {
-                data_type: self.data_type().clone(),
-                len: length,
-                offset: 0,
-                buffers: self.buffers.clone(),
-                child_data: self
-                    .child_data()
-                    .iter()
-                    .map(|data| data.slice(child_offset, length))
-                    .collect(),
-                // `nulls` belongs to this array rather than to the children, 
so
-                // it is sliced by `offset` alone.
-                nulls: self.nulls.as_ref().map(|x| x.slice(offset, length)),
+            for child in &mut self.child_data {
+                child.slice_in_place(child_offset, length);
             }
+            self.offset = 0;
         } else {
-            let mut new_data = self.clone();
-
-            new_data.len = length;
-            new_data.offset = offset + self.offset;
-            new_data.nulls = self.nulls.as_ref().map(|x| x.slice(offset, 
length));
-
-            new_data
+            self.offset += offset;
         }
+        self.len = length;
+        // `nulls` belongs to this array rather than to the children, so
+        // it is sliced by `offset` alone.
+        self.nulls = self.nulls.as_ref().map(|x| x.slice(offset, length));
     }
 
     /// Returns the `buffer` as a slice of type `T` starting at self.offset
@@ -2803,6 +2807,162 @@ mod tests {
         sliced.slice(1, usize::MAX);
     }
 
+    #[test]
+    fn test_sliced() {
+        for nulls in [
+            None,
+            Some(NullBuffer::from(vec![true, false, true, false, true])),
+        ] {
+            let data = ArrayData::builder(DataType::Int32)
+                .len(5)
+                .offset(1)
+                .add_buffer(make_i32_buffer(6))
+                .nulls(nulls)
+                .build()
+                .unwrap();
+
+            for offset in 0..=data.len() {
+                for length in 0..=data.len() - offset {
+                    let owned = data.clone();
+                    let buffers = owned.buffers().as_ptr();
+                    let sliced = owned.sliced(offset, length);
+                    assert_eq!(sliced, data.slice(offset, length));
+                    assert_eq!(sliced.len(), length);
+                    assert_eq!(sliced.offset(), 1 + offset);
+                    assert_eq!(sliced.buffers().as_ptr(), buffers);
+                    assert_eq!(sliced.buffers()[0].as_ptr(), 
data.buffers()[0].as_ptr());
+                    if let Some(nulls) = data.nulls() {
+                        assert_eq!(
+                            sliced.nulls().unwrap().iter().collect::<Vec<_>>(),
+                            
nulls.iter().skip(offset).take(length).collect::<Vec<_>>()
+                        );
+                    }
+                    sliced.validate_full().unwrap();
+                }
+            }
+        }
+
+        let empty = ArrayData::new_empty(&DataType::Int32);
+        assert_eq!(empty.clone().sliced(0, 0), empty);
+    }
+
+    #[test]
+    fn test_sliced_nested_struct() {
+        let child = ArrayData::builder(DataType::Int32)
+            .len(8)
+            .offset(1)
+            .add_buffer(make_i32_buffer(9))
+            .nulls(Some(NullBuffer::from(vec![
+                true, false, true, false, true, true, false, true,
+            ])))
+            .build()
+            .unwrap();
+        let inner = 
ArrayData::builder(DataType::Struct(Fields::from(vec![Field::new(
+            "x",
+            DataType::Int32,
+            true,
+        )])))
+        .len(6)
+        .offset(1)
+        .nulls(Some(NullBuffer::from(vec![
+            true, false, true, true, false, true,
+        ])))
+        .add_child_data(child)
+        .build()
+        .unwrap();
+        let data = 
ArrayData::builder(DataType::Struct(Fields::from(vec![Field::new(
+            "inner",
+            inner.data_type().clone(),
+            true,
+        )])))
+        .len(4)
+        .offset(1)
+        .nulls(Some(NullBuffer::from(vec![false, true, false, true])))
+        .add_child_data(inner)
+        .build()
+        .unwrap();
+
+        for (offset, length) in [(0, 4), (1, 2), (4, 0), (0, 0)] {
+            let owned = data.clone();
+            let children = owned.child_data().as_ptr();
+            let grandchildren = owned.child_data()[0].child_data().as_ptr();
+            let buffers = 
owned.child_data()[0].child_data()[0].buffers().as_ptr();
+            let sliced = owned.sliced(offset, length);
+            let inner = &sliced.child_data()[0];
+            let child = &inner.child_data()[0];
+            assert_eq!(sliced, data.slice(offset, length));
+            assert_eq!(sliced.offset(), 0);
+            assert_eq!(inner.offset(), 0);
+            assert_eq!(child.offset(), 3 + offset);
+            assert_eq!(sliced.child_data().as_ptr(), children);
+            assert_eq!(inner.child_data().as_ptr(), grandchildren);
+            assert_eq!(child.buffers().as_ptr(), buffers);
+            sliced.validate_full().unwrap();
+        }
+
+        let sliced = data.sliced(1, 2).sliced(1, 1);
+        assert_eq!(sliced.null_count(), 1);
+        assert_eq!(sliced.child_data()[0].null_count(), 0);
+        assert_eq!(sliced.child_data()[0].child_data()[0].offset(), 5);
+        assert_eq!(sliced.child_data()[0].child_data()[0].null_count(), 0);
+    }
+
+    #[test]
+    fn test_sliced_list_preserves_children() {
+        let child = ArrayData::builder(DataType::Int32)
+            .len(6)
+            .offset(1)
+            .add_buffer(make_i32_buffer(7))
+            .nulls(Some(NullBuffer::from(vec![
+                true, false, true, false, true, true,
+            ])))
+            .build()
+            .unwrap();
+        let data = ArrayData::builder(DataType::List(Arc::new(Field::new(
+            "item",
+            DataType::Int32,
+            true,
+        ))))
+        .len(4)
+        .offset(1)
+        .add_buffer(Buffer::from_slice_ref([0i32, 1, 2, 3, 4, 5]))
+        .nulls(Some(NullBuffer::from(vec![false, true, false, true])))
+        .add_child_data(child)
+        .build()
+        .unwrap();
+        let buffers = data.buffers().as_ptr();
+        let children = data.child_data().as_ptr();
+        let child_buffers = data.child_data()[0].buffers().as_ptr();
+        let expected = data.slice(1, 2);
+        let sliced = data.sliced(1, 2);
+        assert_eq!(sliced, expected);
+        assert_eq!(sliced.offset(), 2);
+        assert_eq!(sliced.child_data()[0].len(), 6);
+        assert_eq!(sliced.child_data()[0].offset(), 1);
+        assert_eq!(sliced.buffers().as_ptr(), buffers);
+        assert_eq!(sliced.child_data().as_ptr(), children);
+        assert_eq!(sliced.child_data()[0].buffers().as_ptr(), child_buffers);
+        sliced.validate_full().unwrap();
+    }
+
+    #[test]
+    #[should_panic(expected = "offset + length overflow")]
+    fn test_sliced_panics_on_offset_length_overflow() {
+        ArrayData::new_null(&DataType::Int32, 4).sliced(1, usize::MAX);
+    }
+
+    #[test]
+    #[should_panic(expected = "end <= self.len()")]
+    fn test_sliced_panics_on_offset_out_of_bounds() {
+        ArrayData::new_null(&DataType::Int32, 4).sliced(5, 0);
+    }
+
+    #[test]
+    #[should_panic(expected = "end <= self.len()")]
+    fn test_sliced_panics_on_length_out_of_bounds() {
+        ArrayData::new_null(&DataType::Int32, 4).sliced(3, 2);
+    }
+
     #[test]
     fn test_typed_offsets_length_overflow() {
         let data = ArrayData {

Reply via email to