From 3c0bc51c2a6dd6dfc12971c2253ad690abe45cf3 Mon Sep 17 00:00:00 2001 From: Robert Kruszewski Date: Wed, 19 Aug 2026 11:44:01 +0100 Subject: [PATCH 1/5] Reject non zero offset Signed-off-by: Robert Kruszewski --- .../org/apache/arrow/c/ArrayImporter.java | 5 +++++ c/src/main/java/org/apache/arrow/c/Data.java | 3 ++- .../org/apache/arrow/c/RoundtripTest.java | 21 +++++++++++++++++++ docs/source/cdata.rst | 5 +++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/c/src/main/java/org/apache/arrow/c/ArrayImporter.java b/c/src/main/java/org/apache/arrow/c/ArrayImporter.java index f31a8a1faa..18ae7591b1 100644 --- a/c/src/main/java/org/apache/arrow/c/ArrayImporter.java +++ b/c/src/main/java/org/apache/arrow/c/ArrayImporter.java @@ -83,6 +83,11 @@ private void importChild(ArrayImporter parent, ArrowArray src) { } private void doImport(ArrowArray.Snapshot snapshot) { + checkState( + snapshot.offset == 0, + "ArrowArray struct has non-zero offset (%s), which is not supported", + snapshot.offset); + // First import children (required for reconstituting parent array data) long[] children = NativeUtil.toJavaArray(snapshot.children, checkedCastToInt(snapshot.n_children)); diff --git a/c/src/main/java/org/apache/arrow/c/Data.java b/c/src/main/java/org/apache/arrow/c/Data.java index f9d2ee4542..d870b5884d 100644 --- a/c/src/main/java/org/apache/arrow/c/Data.java +++ b/c/src/main/java/org/apache/arrow/c/Data.java @@ -35,7 +35,8 @@ /** * Functions for working with the C data interface. * - *

This API is EXPERIMENTAL. Note that currently only 64bit systems are supported. + *

This API is EXPERIMENTAL. Note that currently only 64bit systems are supported. Importing + * {@link ArrowArray ArrowArrays} with a non-zero offset is not supported. */ public final class Data { diff --git a/c/src/test/java/org/apache/arrow/c/RoundtripTest.java b/c/src/test/java/org/apache/arrow/c/RoundtripTest.java index f6ff88571e..efdf580aeb 100644 --- a/c/src/test/java/org/apache/arrow/c/RoundtripTest.java +++ b/c/src/test/java/org/apache/arrow/c/RoundtripTest.java @@ -1051,6 +1051,27 @@ public void testImportReleasedArray() { } } + @Test + public void testImportArrayWithNonZeroOffset() { + try (IntVector source = new IntVector("source", allocator); + IntVector destination = new IntVector("destination", allocator); + ArrowArray array = ArrowArray.allocateNew(allocator)) { + setVector(source, 1, 2, 3); + Data.exportVector(allocator, source, null, array); + + ArrowArray.Snapshot snapshot = array.snapshot(); + snapshot.offset = 1; + array.save(snapshot); + + Exception e = + assertThrows( + IllegalStateException.class, + () -> Data.importIntoVector(allocator, array, destination, null)); + assertEquals( + "ArrowArray struct has non-zero offset (1), which is not supported", e.getMessage()); + } + } + @Test public void testArrayStructReuse() { // Consumer allocates empty structures diff --git a/docs/source/cdata.rst b/docs/source/cdata.rst index 7b2924d259..d2b5ab0cc3 100644 --- a/docs/source/cdata.rst +++ b/docs/source/cdata.rst @@ -22,6 +22,11 @@ C Data Interface Arrow supports exchanging data without copying or serialization within the same process through :external+arrow:ref:`c-data-interface`, even between different language runtimes. +.. note:: + + The Arrow Java C Data Interface implementation does not support importing arrays with + a non-zero offset. + Java to Python -------------- From f4728fde15b427903d35e3a6345c7fcc07b5c430 Mon Sep 17 00:00:00 2001 From: Robert Kruszewski Date: Wed, 19 Aug 2026 14:22:46 +0100 Subject: [PATCH 2/5] move --- c/src/main/java/org/apache/arrow/c/ArrayImporter.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/c/src/main/java/org/apache/arrow/c/ArrayImporter.java b/c/src/main/java/org/apache/arrow/c/ArrayImporter.java index 18ae7591b1..7c1cfca4f7 100644 --- a/c/src/main/java/org/apache/arrow/c/ArrayImporter.java +++ b/c/src/main/java/org/apache/arrow/c/ArrayImporter.java @@ -53,6 +53,10 @@ final class ArrayImporter { void importArray(ArrowArray src) { ArrowArray.Snapshot snapshot = src.snapshot(); checkState(snapshot.release != NULL, "Cannot import released ArrowArray"); + checkState( + snapshot.offset == 0, + "ArrowArray struct has non-zero offset (%s), which is not supported", + snapshot.offset); // Move imported array ArrowArray ownedArray = ArrowArray.allocateNew(allocator); @@ -83,11 +87,6 @@ private void importChild(ArrayImporter parent, ArrowArray src) { } private void doImport(ArrowArray.Snapshot snapshot) { - checkState( - snapshot.offset == 0, - "ArrowArray struct has non-zero offset (%s), which is not supported", - snapshot.offset); - // First import children (required for reconstituting parent array data) long[] children = NativeUtil.toJavaArray(snapshot.children, checkedCastToInt(snapshot.n_children)); From 8c7fd35f531d3119e2a641797acc4ac7945f49de Mon Sep 17 00:00:00 2001 From: Robert Kruszewski Date: Thu, 27 Aug 2026 17:53:30 +0100 Subject: [PATCH 3/5] Address comments Signed-off-by: Robert Kruszewski --- c/src/main/java/org/apache/arrow/c/ArrayImporter.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/c/src/main/java/org/apache/arrow/c/ArrayImporter.java b/c/src/main/java/org/apache/arrow/c/ArrayImporter.java index 7c1cfca4f7..d3c7e02398 100644 --- a/c/src/main/java/org/apache/arrow/c/ArrayImporter.java +++ b/c/src/main/java/org/apache/arrow/c/ArrayImporter.java @@ -53,10 +53,6 @@ final class ArrayImporter { void importArray(ArrowArray src) { ArrowArray.Snapshot snapshot = src.snapshot(); checkState(snapshot.release != NULL, "Cannot import released ArrowArray"); - checkState( - snapshot.offset == 0, - "ArrowArray struct has non-zero offset (%s), which is not supported", - snapshot.offset); // Move imported array ArrowArray ownedArray = ArrowArray.allocateNew(allocator); @@ -87,6 +83,11 @@ private void importChild(ArrayImporter parent, ArrowArray src) { } private void doImport(ArrowArray.Snapshot snapshot) { + checkState( + snapshot.offset == 0 || snapshot.length == 0, + "ArrowArray struct has non-zero offset (%s), which is not supported", + snapshot.offset); + // First import children (required for reconstituting parent array data) long[] children = NativeUtil.toJavaArray(snapshot.children, checkedCastToInt(snapshot.n_children)); From 520040f697cf3e84a12dc41364c32103aa00abb7 Mon Sep 17 00:00:00 2001 From: Robert Kruszewski Date: Thu, 27 Aug 2026 17:56:20 +0100 Subject: [PATCH 4/5] test --- .../java/org/apache/arrow/c/RoundtripTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/c/src/test/java/org/apache/arrow/c/RoundtripTest.java b/c/src/test/java/org/apache/arrow/c/RoundtripTest.java index efdf580aeb..4061768fa2 100644 --- a/c/src/test/java/org/apache/arrow/c/RoundtripTest.java +++ b/c/src/test/java/org/apache/arrow/c/RoundtripTest.java @@ -1072,6 +1072,24 @@ public void testImportArrayWithNonZeroOffset() { } } + @Test + public void testImportEmptyArrayWithNonZeroOffset() { + try (IntVector source = new IntVector("source", allocator); + IntVector destination = new IntVector("destination", allocator); + ArrowArray array = ArrowArray.allocateNew(allocator)) { + setVector(source, 1, 2, 3); + Data.exportVector(allocator, source, null, array); + + ArrowArray.Snapshot snapshot = array.snapshot(); + snapshot.offset = source.getValueCount(); + snapshot.length = 0; + array.save(snapshot); + + Data.importIntoVector(allocator, array, destination, null); + assertEquals(0, destination.getValueCount()); + } + } + @Test public void testArrayStructReuse() { // Consumer allocates empty structures From 10144281275c0a165c9f5da2ff274a0997396a0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Mon, 31 Aug 2026 09:46:42 +0200 Subject: [PATCH 5/5] Allow non-zero offset when importing null arrays Null arrays export no buffers (n_buffers == 0), so the offset has nothing to apply to and never enters the import: doImport short-circuits to an empty buffer list and NullVector.loadFieldBuffers only reads the field node length. Arrow C++ still propagates a slice offset onto the struct for these arrays, so rejecting them regressed imports that previously succeeded, e.g. a sliced all-null column. --- .../org/apache/arrow/c/ArrayImporter.java | 7 ++++++- .../org/apache/arrow/c/RoundtripTest.java | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/c/src/main/java/org/apache/arrow/c/ArrayImporter.java b/c/src/main/java/org/apache/arrow/c/ArrayImporter.java index d3c7e02398..e92e832991 100644 --- a/c/src/main/java/org/apache/arrow/c/ArrayImporter.java +++ b/c/src/main/java/org/apache/arrow/c/ArrayImporter.java @@ -30,6 +30,7 @@ import org.apache.arrow.vector.dictionary.Dictionary; import org.apache.arrow.vector.dictionary.DictionaryProvider; import org.apache.arrow.vector.ipc.message.ArrowFieldNode; +import org.apache.arrow.vector.types.pojo.ArrowType; import org.apache.arrow.vector.types.pojo.DictionaryEncoding; /** Importer for {@link ArrowArray}. */ @@ -83,8 +84,12 @@ private void importChild(ArrayImporter parent, ArrowArray src) { } private void doImport(ArrowArray.Snapshot snapshot) { + // A non-zero offset is only meaningful for arrays that have buffers to offset into. + // Null arrays carry no buffers, so their offset is inert and safe to ignore. checkState( - snapshot.offset == 0 || snapshot.length == 0, + snapshot.offset == 0 + || snapshot.length == 0 + || vector.getField().getType().getTypeID() == ArrowType.ArrowTypeID.Null, "ArrowArray struct has non-zero offset (%s), which is not supported", snapshot.offset); diff --git a/c/src/test/java/org/apache/arrow/c/RoundtripTest.java b/c/src/test/java/org/apache/arrow/c/RoundtripTest.java index 4061768fa2..7a301cc3e4 100644 --- a/c/src/test/java/org/apache/arrow/c/RoundtripTest.java +++ b/c/src/test/java/org/apache/arrow/c/RoundtripTest.java @@ -1090,6 +1090,26 @@ public void testImportEmptyArrayWithNonZeroOffset() { } } + @Test + public void testImportNullArrayWithNonZeroOffset() { + try (NullVector source = new NullVector("source", 10); + NullVector destination = new NullVector("destination"); + ArrowArray array = ArrowArray.allocateNew(allocator)) { + Data.exportVector(allocator, source, null, array); + + // Mimic a sliced null array. Arrow C++ propagates the slice offset onto the struct + // even for null arrays, which export no buffers at all (n_buffers == 0), so there is + // nothing for the offset to apply to and the import is safe. + ArrowArray.Snapshot snapshot = array.snapshot(); + snapshot.offset = 2; + snapshot.length = 8; + array.save(snapshot); + + Data.importIntoVector(allocator, array, destination, null); + assertEquals(8, destination.getValueCount()); + } + } + @Test public void testArrayStructReuse() { // Consumer allocates empty structures