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 40699a23ff Fix IPC roundtrip for empty RunArray slices (#11361)
40699a23ff is described below

commit 40699a23ff7bf65a70a707a726ef3ffe50b178df
Author: Liang-Chi Hsieh <[email protected]>
AuthorDate: Sun Oct 4 07:19:17 2026 +0000

    Fix IPC roundtrip for empty RunArray slices (#11361)
    
    # Which issue does this PR close?
    
    Closes #11364.
    
    # Rationale for this change
    
    Writing an empty slice of a nonempty `RunArray` succeeds, but reading
    the resulting IPC batch fails because the writer emits a run end of
    zero. For example, slicing run ends `[2, 5]` and values `[10, 20]` with
    `slice(2, 0)` produces this error. Run ends must be strictly positive.
    
    # What changes are included in this PR?
    
    Normalize empty run arrays to zero-offset arrays with empty run-end and
    value children before calculating physical run indices. Add regression
    tests for Int16, Int32, and Int64 run ends, every legal empty-slice
    offset in the example array, already-empty arrays, and both Stream and
    File IPC roundtrips.
    
    Implementation and regression tests were generated with OpenAI Codex and
    reviewed and validated with the checks below.
    
    # Are these changes tested?
    
    - Default arrow-ipc library tests: 134 passed.
    - All-features arrow-ipc library tests: 140 passed.
    - New regression tests with `arrow-data/force_validate`: 3 passed.
    - Clippy for arrow-ipc with all targets and all features, with warnings
    denied: passed.
    - Workspace formatting and diff whitespace checks: passed.
    
    The full all-features suite with `arrow-data/force_validate` has six
    failures in existing reader tests that intentionally construct invalid
    arrays. The same six failures were reproduced on the unchanged base
    commit; all new tests pass with forced validation.
    
    # Are there any user-facing changes?
    
    Empty RunArray slices now roundtrip through Stream and File IPC
    successfully. No public API changes.
---
 arrow-ipc/src/writer.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/arrow-ipc/src/writer.rs b/arrow-ipc/src/writer.rs
index 30555a738a..c435aa3147 100644
--- a/arrow-ipc/src/writer.rs
+++ b/arrow-ipc/src/writer.rs
@@ -1279,6 +1279,11 @@ pub(crate) fn unslice_run_array(arr: ArrayData) -> 
Result<ArrayData, ArrowError>
 fn into_zero_offset_run_array<R: RunEndIndexType>(
     run_array: RunArray<R>,
 ) -> Result<RunArray<R>, ArrowError> {
+    // Empty slices have no physical runs, regardless of their offset.
+    if run_array.is_empty() {
+        return Ok(ArrayData::new_empty(run_array.data_type()).into());
+    }
+
     let run_ends = run_array.run_ends();
     if run_ends.offset() == 0 && run_ends.max_value() == run_ends.len() {
         return Ok(run_array);
@@ -3764,6 +3769,56 @@ mod tests {
         assert_eq!(batch, batch2);
     }
 
+    fn assert_empty_run_array_roundtrip<R: RunEndIndexType>() {
+        let run_ends = PrimitiveArray::<R>::from_iter_values(
+            [2, 5]
+                .into_iter()
+                .map(|v| R::Native::from_usize(v).unwrap()),
+        );
+        let values = Int32Array::from(vec![10, 20]);
+        let array = RunArray::<R>::try_new(&run_ends, &values).unwrap();
+        let empty = 
RunArray::<R>::from(ArrayData::new_empty(array.data_type()));
+
+        for source in [&array, &empty] {
+            for offset in 0..=source.len() {
+                let sliced = source.slice(offset, 0);
+                let batch = RecordBatch::try_from_iter(vec![("run", 
Arc::new(sliced) as ArrayRef)])
+                    .unwrap();
+                for decoded in [
+                    deserialize_stream(serialize_stream(&batch)),
+                    deserialize_file(serialize_file(&batch)),
+                ] {
+                    assert_eq!(decoded, batch);
+                    let data = decoded.column(0).to_data();
+                    data.validate_full().unwrap();
+                    assert_eq!(data.offset(), 0);
+                    assert!(data.child_data().iter().all(ArrayData::is_empty));
+                }
+
+                let normalized = 
into_zero_offset_run_array(source.slice(offset, 0)).unwrap();
+                normalized.to_data().validate_full().unwrap();
+                assert_eq!(normalized.offset(), 0);
+                assert!(normalized.run_ends().values().is_empty());
+                assert!(normalized.values().is_empty());
+            }
+        }
+    }
+
+    #[test]
+    fn test_empty_run_array_roundtrip_i16() {
+        assert_empty_run_array_roundtrip::<Int16Type>();
+    }
+
+    #[test]
+    fn test_empty_run_array_roundtrip_i32() {
+        assert_empty_run_array_roundtrip::<Int32Type>();
+    }
+
+    #[test]
+    fn test_empty_run_array_roundtrip_i64() {
+        assert_empty_run_array_roundtrip::<Int64Type>();
+    }
+
     #[test]
     fn test_run_array_unslice() {
         let total_len = 80;

Reply via email to