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

Jefffrey 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 c44f8d4c2f fix(arrow-data): don't double-count offset when slicing 
struct ArrayData (#10835)
c44f8d4c2f is described below

commit c44f8d4c2fa6da9e990b273da60fa8a4add14419
Author: Jaideep Pyne <[email protected]>
AuthorDate: Tue Sep 1 13:10:41 2026 +0530

    fix(arrow-data): don't double-count offset when slicing struct ArrayData 
(#10835)
    
    # Which issue does this PR close?
    
    - Closes #7750.
    - Closes #7595.
    
    # Rationale for this change
    
    Slicing a `Struct` `ArrayData` pushed the new offset into the child data
    *and* also added it to the parent's own `offset`. Rebuilding an array
    from the sliced data (`make_array` / `From<ArrayData> for StructArray`)
    then windowed the already-windowed children a second time and panicked
    with `(offset + length) <= self.len()`. This is a regression from 54.3.0
    (it panics on 55.0.0 through current main).
    
    This finishes the approach from #7596, which @alamb approved pending
    additional testing before it went stale.
    
    # What changes are included in this PR?
    
    In the `Struct` arm of `ArrayData::slice`, keep `self.offset` unchanged
    and let the cumulative child offsets carry the slice (a struct's
    `ArrayData` has no buffers of its own, now guarded by an assert).
    
    # Are these changes tested?
    
    Yes. Added `test_struct_array_data_slice` (the C data interface offset
    representation) and `test_make_array_sliced_struct_data` (the exact
    #7750 reproducer), both failing before the change and passing after.
    Verified no regressions across arrow-data, arrow-array, arrow-select,
    arrow-ord, arrow-cast, and arrow-ipc.
    
    # Are there any user-facing changes?
    
    Slicing a struct `ArrayData` no longer panics on rebuild. The sliced
    `ArrayData` now represents the offset on its children with the parent
    offset unchanged — consistent with how `From<ArrayData> for StructArray`
    already interprets it.
---
 arrow-array/src/array/struct_array.rs | 133 +++++++++++++++++++++++++++++++++-
 arrow-data/src/data.rs                |  14 ++--
 2 files changed, 141 insertions(+), 6 deletions(-)

diff --git a/arrow-array/src/array/struct_array.rs 
b/arrow-array/src/array/struct_array.rs
index fa2718c7fd..0086cae8ed 100644
--- a/arrow-array/src/array/struct_array.rs
+++ b/arrow-array/src/array/struct_array.rs
@@ -630,7 +630,10 @@ impl Index<&str> for StructArray {
 mod tests {
     use super::*;
 
-    use crate::{BooleanArray, Float32Array, Float64Array, Int32Array, 
Int64Array, StringArray};
+    use crate::{
+        BooleanArray, Float32Array, Float64Array, Int32Array, Int64Array, 
StringArray,
+        cast::AsArray, types::Int32Type,
+    };
     use arrow_buffer::ToByteSlice;
 
     #[test]
@@ -732,6 +735,134 @@ mod tests {
         }
     }
 
+    #[test]
+    fn test_struct_array_data_slice() {
+        // Slicing a struct's `ArrayData` and rebuilding an array from it has 
to
+        // apply the offset to the children exactly once (#7595, #7750).
+        let x = Int32Array::from(vec![Some(0), Some(1), Some(2), Some(3), 
None, Some(5)]);
+        let struct_array = StructArray::new(
+            Fields::from(vec![Field::new("x", DataType::Int32, true)]),
+            vec![Arc::new(x.clone())],
+            Some(NullBuffer::from(vec![true, true, true, false, true, true])),
+        )
+        .into_data();
+        let sliced = struct_array.slice(1, 4);
+
+        let arr = make_array(sliced);
+        assert_eq!(
+            arr.as_struct().column(0).as_primitive::<Int32Type>(),
+            &x.slice(1, 4)
+        );
+
+        // A struct whose top-level `ArrayData` carries a non-zero offset over
+        // full-length children is how the C++ implementation of Arrow (and the
+        // C data interface) represents a sliced struct: the offset/length live
+        // on the struct, not on the children. arrow-rs must decode it to the
+        // same logical array its own `StructArray::slice` produces.
+        let x = Int32Array::from(vec![Some(0), Some(1), Some(2), Some(3), 
None, Some(5)]);
+        let y = Int32Array::from(vec![Some(5), Some(6), None, Some(8), 
Some(9), Some(10)]);
+        let struct_array = StructArray::new(
+            Fields::from(vec![
+                Field::new("x", DataType::Int32, true),
+                Field::new("y", DataType::Int32, true),
+            ]),
+            vec![Arc::new(x), Arc::new(y)],
+            Some(NullBuffer::from(vec![true, true, true, false, true, true])),
+        );
+        let struct_array = StructArray::new(
+            Fields::from(vec![Field::new(
+                "inner",
+                struct_array.data_type().clone(),
+                true,
+            )]),
+            vec![Arc::new(struct_array)],
+            Some(NullBuffer::from(vec![true, false, true, true, true, true])),
+        );
+
+        let cpp_sliced_array = make_array(
+            struct_array
+                .to_data()
+                .into_builder()
+                .offset(1)
+                .len(4)
+                .nulls(Some(NullBuffer::from(vec![false, true, true, true])))
+                .build()
+                .unwrap(),
+        );
+
+        assert_eq!(cpp_sliced_array.as_struct(), &struct_array.slice(1, 4));
+    }
+
+    #[test]
+    fn test_make_array_sliced_struct_data() {
+        // Exact reproducer from #7750: `make_array` on a sliced struct's
+        // `ArrayData`.
+        let strings: ArrayRef = Arc::new(StringArray::from(vec![
+            Some("joe"),
+            None,
+            None,
+            Some("mark"),
+            Some("doe"),
+        ]));
+        let ints: ArrayRef = Arc::new(Int32Array::from(vec![
+            Some(1),
+            Some(2),
+            Some(3),
+            Some(4),
+            Some(5),
+        ]));
+
+        let array = StructArray::try_from(vec![("f1", strings.clone()), ("f2", 
ints.clone())])
+            .unwrap()
+            .into_data()
+            .slice(1, 3);
+
+        let arr = make_array(array);
+        let expected = StructArray::try_from(vec![("f1", strings), ("f2", 
ints)])
+            .unwrap()
+            .slice(1, 3);
+        assert_eq!(arr.as_struct(), &expected);
+    }
+
+    #[test]
+    fn test_slice_struct_data_with_existing_offset() {
+        // Slicing a struct whose `ArrayData` already carries a non-zero offset
+        // over full-length children, which is how the C data interface hands 
us
+        // a sliced struct. The cumulative offset has to end up on the children
+        // only: anything left on the parent gets applied a second time by
+        // `From<ArrayData> for StructArray`.
+        let x = Int32Array::from(vec![Some(0), Some(1), Some(2), Some(3), 
None, Some(5)]);
+        let struct_array = StructArray::new(
+            Fields::from(vec![Field::new("x", DataType::Int32, true)]),
+            vec![Arc::new(x.clone())],
+            Some(NullBuffer::from(vec![true, true, true, false, true, true])),
+        );
+
+        let offset_data = struct_array
+            .to_data()
+            .into_builder()
+            .offset(1)
+            .len(5)
+            .nulls(Some(NullBuffer::from(vec![true, true, false, true, true])))
+            .build()
+            .unwrap();
+
+        let sliced = offset_data.slice(1, 3);
+        // The cumulative offset (1 + 1) lands on the child; the parent's own
+        // offset is reset to 0 so nothing re-applies it.
+        assert_eq!(sliced.offset(), 0);
+        assert_eq!(sliced.len(), 3);
+        assert_eq!(sliced.child_data()[0].offset(), 2);
+        assert_eq!(sliced.child_data()[0].len(), 3);
+
+        let arr = make_array(sliced);
+        assert_eq!(
+            arr.as_struct().column(0).as_primitive::<Int32Type>(),
+            &x.slice(2, 3)
+        );
+        assert_eq!(arr.as_struct(), &struct_array.slice(2, 3));
+    }
+
     #[test]
     #[should_panic(expected = "assertion failed: end <= self.len()")]
     fn test_struct_array_from_data_with_offset_and_length_error() {
diff --git a/arrow-data/src/data.rs b/arrow-data/src/data.rs
index ed73bf6273..0d46dbee62 100644
--- a/arrow-data/src/data.rs
+++ b/arrow-data/src/data.rs
@@ -696,19 +696,23 @@ impl ArrayData {
         assert!(end <= self.len());
 
         if let DataType::Struct(_) = self.data_type() {
-            // Slice into children
-            let new_offset = self.offset + offset;
+            // A struct has no buffers of its own, and reading child element 
`i`
+            // combines this array's offset with the child's own offset. 
Applying
+            // 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: new_offset,
+                offset: 0,
                 buffers: self.buffers.clone(),
-                // Slice child data, to propagate offsets down to them
                 child_data: self
                     .child_data()
                     .iter()
-                    .map(|data| data.slice(offset, length))
+                    .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)),
             }
         } else {

Reply via email to