This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-java.git
The following commit(s) were added to refs/heads/main by this push:
new 8ce39730f GH-1179: Correct the size of var-width vector with >0 start
offset during vector append (#1180)
8ce39730f is described below
commit 8ce39730fe969b41c8620b02f3fa77c974d90784
Author: Jordan Epstein <[email protected]>
AuthorDate: Thu Jul 2 18:16:18 2026 -0500
GH-1179: Correct the size of var-width vector with >0 start offset during
vector append (#1180)
## What's Changed
Fix VectorAppender data size computation for variable-width vectors with
non-zero start offsets
When appending a variable width offset vector in DataFusion comet I was
receiving exceptions
due to allocating too much memory. This is because Comet passes variable
width arrays back
to Java where the initial offset vector entry is greater than 0. Prior
to this change, arrow-java
determines how many bytes to copy by just looking at the last offset
entry in the buffer,
completely disregarding the value of the first. If first = 100 and last
= 200, Java will still
copy 200 bytes instead of 100. In this change we fix that.
Closes #1179
---------
Co-authored-by: Jordan Epstein <[email protected]>
---
.../apache/arrow/vector/util/VectorAppender.java | 92 +++++++---
.../arrow/vector/util/TestVectorAppender.java | 189 +++++++++++++++++++++
2 files changed, 257 insertions(+), 24 deletions(-)
diff --git
a/vector/src/main/java/org/apache/arrow/vector/util/VectorAppender.java
b/vector/src/main/java/org/apache/arrow/vector/util/VectorAppender.java
index e7c0d11cb..2cfeb0a04 100644
--- a/vector/src/main/java/org/apache/arrow/vector/util/VectorAppender.java
+++ b/vector/src/main/java/org/apache/arrow/vector/util/VectorAppender.java
@@ -125,10 +125,15 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
targetVector
.getOffsetBuffer()
.getInt((long) targetVector.getValueCount() *
BaseVariableWidthVector.OFFSET_WIDTH);
+ // The delta vector's offset buffer need not start at zero (e.g. a vector
imported through
+ // the C data interface from a sliced array), so the amount of data to
append is the
+ // distance between its first and last offsets, not the last offset itself.
+ int deltaDataStart = deltaVector.getOffsetBuffer().getInt(0);
int deltaDataSize =
deltaVector
- .getOffsetBuffer()
- .getInt((long) deltaVector.getValueCount() *
BaseVariableWidthVector.OFFSET_WIDTH);
+ .getOffsetBuffer()
+ .getInt((long) deltaVector.getValueCount() *
BaseVariableWidthVector.OFFSET_WIDTH)
+ - deltaDataStart;
int newValueCapacity = targetDataSize + deltaDataSize;
// make sure there is enough capacity
@@ -149,7 +154,7 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
// append data buffer
MemoryUtil.copyMemory(
- deltaVector.getDataBuffer().memoryAddress(),
+ deltaVector.getDataBuffer().memoryAddress() + deltaDataStart,
targetVector.getDataBuffer().memoryAddress() + targetDataSize,
deltaDataSize);
@@ -160,7 +165,7 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
+ (targetVector.getValueCount() + 1) *
BaseVariableWidthVector.OFFSET_WIDTH,
deltaVector.getValueCount() * BaseVariableWidthVector.OFFSET_WIDTH);
- // increase each offset from the second buffer
+ // rebase each appended offset to the target's data, accounting for the
delta's start offset
for (int i = 0; i < deltaVector.getValueCount(); i++) {
int oldOffset =
targetVector
@@ -172,7 +177,7 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
.getOffsetBuffer()
.setInt(
(long) (targetVector.getValueCount() + 1 + i) *
BaseVariableWidthVector.OFFSET_WIDTH,
- oldOffset + targetDataSize);
+ oldOffset - deltaDataStart + targetDataSize);
}
((BaseVariableWidthVector) targetVector).setLastSet(newValueCount - 1);
targetVector.setValueCount(newValueCount);
@@ -196,11 +201,15 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
.getOffsetBuffer()
.getLong(
(long) targetVector.getValueCount() *
BaseLargeVariableWidthVector.OFFSET_WIDTH);
+ // see the corresponding comment in visit(BaseVariableWidthVector, Void):
the delta's
+ // offset buffer need not start at zero
+ long deltaDataStart = deltaVector.getOffsetBuffer().getLong(0);
long deltaDataSize =
deltaVector
- .getOffsetBuffer()
- .getLong(
- (long) deltaVector.getValueCount() *
BaseLargeVariableWidthVector.OFFSET_WIDTH);
+ .getOffsetBuffer()
+ .getLong(
+ (long) deltaVector.getValueCount() *
BaseLargeVariableWidthVector.OFFSET_WIDTH)
+ - deltaDataStart;
long newValueCapacity = targetDataSize + deltaDataSize;
// make sure there is enough capacity
@@ -221,7 +230,7 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
// append data buffer
MemoryUtil.copyMemory(
- deltaVector.getDataBuffer().memoryAddress(),
+ deltaVector.getDataBuffer().memoryAddress() + deltaDataStart,
targetVector.getDataBuffer().memoryAddress() + targetDataSize,
deltaDataSize);
@@ -232,7 +241,7 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
+ (targetVector.getValueCount() + 1) *
BaseLargeVariableWidthVector.OFFSET_WIDTH,
deltaVector.getValueCount() *
BaseLargeVariableWidthVector.OFFSET_WIDTH);
- // increase each offset from the second buffer
+ // rebase each appended offset to the target's data, accounting for the
delta's start offset
for (int i = 0; i < deltaVector.getValueCount(); i++) {
long oldOffset =
targetVector
@@ -245,7 +254,7 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
.setLong(
(long) (targetVector.getValueCount() + 1 + i)
* BaseLargeVariableWidthVector.OFFSET_WIDTH,
- oldOffset + targetDataSize);
+ oldOffset - deltaDataStart + targetDataSize);
}
((BaseLargeVariableWidthVector) targetVector).setLastSet(newValueCount -
1);
targetVector.setValueCount(newValueCount);
@@ -331,16 +340,20 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
targetVector
.getOffsetBuffer()
.getInt((long) targetVector.getValueCount() *
ListVector.OFFSET_WIDTH);
- int deltaListSize =
+ // see the corresponding comment in visit(BaseVariableWidthVector, Void):
the delta's
+ // offset buffer need not start at zero
+ int deltaListStart = deltaVector.getOffsetBuffer().getInt(0);
+ int deltaListEnd =
deltaVector
.getOffsetBuffer()
.getInt((long) deltaVector.getValueCount() *
ListVector.OFFSET_WIDTH);
+ int deltaListSize = deltaListEnd - deltaListStart;
ListVector targetListVector = (ListVector) targetVector;
// make sure the underlying vector has value count set
targetListVector.getDataVector().setValueCount(targetListSize);
- deltaVector.getDataVector().setValueCount(deltaListSize);
+ deltaVector.getDataVector().setValueCount(deltaListEnd);
// make sure there is enough capacity
while (targetVector.getValueCapacity() < newValueCount) {
@@ -372,13 +385,16 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
.getOffsetBuffer()
.setInt(
(long) (targetVector.getValueCount() + 1 + i) *
ListVector.OFFSET_WIDTH,
- oldOffset + targetListSize);
+ oldOffset - deltaListStart + targetListSize);
}
targetListVector.setLastSet(newValueCount - 1);
// append underlying vectors
- VectorAppender innerAppender = new
VectorAppender(targetListVector.getDataVector());
- deltaVector.getDataVector().accept(innerAppender, null);
+ appendDataVector(
+ targetListVector.getDataVector(),
+ deltaVector.getDataVector(),
+ deltaListStart,
+ deltaListSize);
targetVector.setValueCount(newValueCount);
return targetVector;
@@ -400,17 +416,21 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
targetVector
.getOffsetBuffer()
.getLong((long) targetVector.getValueCount() *
LargeListVector.OFFSET_WIDTH);
- long deltaListSize =
+ // see the corresponding comment in visit(BaseVariableWidthVector, Void):
the delta's
+ // offset buffer need not start at zero
+ long deltaListStart = deltaVector.getOffsetBuffer().getLong(0);
+ long deltaListEnd =
deltaVector
.getOffsetBuffer()
.getLong((long) deltaVector.getValueCount() *
LargeListVector.OFFSET_WIDTH);
+ long deltaListSize = deltaListEnd - deltaListStart;
- ListVector targetListVector = (ListVector) targetVector;
+ LargeListVector targetListVector = (LargeListVector) targetVector;
// make sure the underlying vector has value count set
// todo recheck these casts when int64 vectors are supported
targetListVector.getDataVector().setValueCount(checkedCastToInt(targetListSize));
- deltaVector.getDataVector().setValueCount(checkedCastToInt(deltaListSize));
+ deltaVector.getDataVector().setValueCount(checkedCastToInt(deltaListEnd));
// make sure there is enough capacity
while (targetVector.getValueCapacity() < newValueCount) {
@@ -427,10 +447,10 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
// append offset buffer
MemoryUtil.copyMemory(
- deltaVector.getOffsetBuffer().memoryAddress() +
ListVector.OFFSET_WIDTH,
+ deltaVector.getOffsetBuffer().memoryAddress() +
LargeListVector.OFFSET_WIDTH,
targetVector.getOffsetBuffer().memoryAddress()
+ (targetVector.getValueCount() + 1) *
LargeListVector.OFFSET_WIDTH,
- (long) deltaVector.getValueCount() * ListVector.OFFSET_WIDTH);
+ (long) deltaVector.getValueCount() * LargeListVector.OFFSET_WIDTH);
// increase each offset from the second buffer
for (int i = 0; i < deltaVector.getValueCount(); i++) {
@@ -443,18 +463,42 @@ public class VectorAppender implements
VectorVisitor<ValueVector, Void> {
.getOffsetBuffer()
.setLong(
(long) (targetVector.getValueCount() + 1 + i) *
LargeListVector.OFFSET_WIDTH,
- oldOffset + targetListSize);
+ oldOffset - deltaListStart + targetListSize);
}
targetListVector.setLastSet(newValueCount - 1);
// append underlying vectors
- VectorAppender innerAppender = new
VectorAppender(targetListVector.getDataVector());
- deltaVector.getDataVector().accept(innerAppender, null);
+ appendDataVector(
+ targetListVector.getDataVector(),
+ deltaVector.getDataVector(),
+ checkedCastToInt(deltaListStart),
+ checkedCastToInt(deltaListSize));
targetVector.setValueCount(newValueCount);
return targetVector;
}
+ /**
+ * Appends the range [start, start + length) of the delta vector's data
vector to the target
+ * vector's data vector. The range may not cover the whole delta data vector
when the delta's
+ * offset buffer does not start at zero.
+ */
+ private static void appendDataVector(
+ ValueVector targetDataVector, ValueVector deltaDataVector, int start,
int length) {
+ if (start == 0 && length == deltaDataVector.getValueCount()) {
+ VectorAppender innerAppender = new VectorAppender(targetDataVector);
+ deltaDataVector.accept(innerAppender, null);
+ return;
+ }
+ TransferPair transferPair =
+ deltaDataVector.getTransferPair(deltaDataVector.getField(),
deltaDataVector.getAllocator());
+ transferPair.splitAndTransfer(start, length);
+ try (ValueVector slicedDeltaDataVector = transferPair.getTo()) {
+ VectorAppender innerAppender = new VectorAppender(targetDataVector);
+ slicedDeltaDataVector.accept(innerAppender, null);
+ }
+ }
+
@Override
public ValueVector visit(FixedSizeListVector deltaVector, Void value) {
Preconditions.checkArgument(
diff --git
a/vector/src/test/java/org/apache/arrow/vector/util/TestVectorAppender.java
b/vector/src/test/java/org/apache/arrow/vector/util/TestVectorAppender.java
index df5521a1a..9a8143f51 100644
--- a/vector/src/test/java/org/apache/arrow/vector/util/TestVectorAppender.java
+++ b/vector/src/test/java/org/apache/arrow/vector/util/TestVectorAppender.java
@@ -26,10 +26,13 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
+import org.apache.arrow.memory.ArrowBuf;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.memory.util.CommonUtil;
+import org.apache.arrow.vector.BaseLargeVariableWidthVector;
import org.apache.arrow.vector.BaseValueVector;
+import org.apache.arrow.vector.BaseVariableWidthVector;
import org.apache.arrow.vector.BaseVariableWidthViewVector;
import org.apache.arrow.vector.BigIntVector;
import org.apache.arrow.vector.BitVector;
@@ -53,6 +56,7 @@ import org.apache.arrow.vector.complex.UnionVector;
import org.apache.arrow.vector.holders.NullableBigIntHolder;
import org.apache.arrow.vector.holders.NullableFloat4Holder;
import org.apache.arrow.vector.holders.NullableIntHolder;
+import org.apache.arrow.vector.ipc.message.ArrowFieldNode;
import org.apache.arrow.vector.testing.ValueVectorDataPopulator;
import org.apache.arrow.vector.types.Types;
import org.apache.arrow.vector.types.pojo.ArrowType;
@@ -178,6 +182,82 @@ public class TestVectorAppender {
}
}
+ @Test
+ public void testAppendVariableWidthVectorWithNonZeroStartOffset() {
+ try (VarCharVector target = new VarCharVector("", allocator);
+ VarCharVector delta = new VarCharVector("", allocator)) {
+
+ target.allocateNew(64, 4);
+ ValueVectorDataPopulator.setVector(target, "a0", "a1");
+
+ // Build a delta vector whose offset buffer does not start at zero, as
produced e.g. by
+ // importing a sliced array through the C data interface. The values are
"BBBB" and
+ // "CCCC"; the data buffer additionally holds 4 bytes of unreferenced
prefix ("AAAA").
+ try (ArrowBuf validity = allocator.buffer(1);
+ ArrowBuf offsets = allocator.buffer(12);
+ ArrowBuf data = allocator.buffer(12)) {
+ validity.setByte(0, 0b11);
+ offsets.setInt(0, 4);
+ offsets.setInt(4, 8);
+ offsets.setInt(8, 12);
+ data.setBytes(0, "AAAABBBBCCCC".getBytes(StandardCharsets.UTF_8));
+ delta.loadFieldBuffers(new ArrowFieldNode(2, 0),
Arrays.asList(validity, offsets, data));
+ }
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ // the unreferenced prefix must not be appended
+ assertEquals(
+ 4 + 8,
+ target
+ .getOffsetBuffer()
+ .getInt((long) target.getValueCount() *
BaseVariableWidthVector.OFFSET_WIDTH));
+
+ try (VarCharVector expected = new VarCharVector("expected", allocator)) {
+ expected.allocateNew();
+ ValueVectorDataPopulator.setVector(expected, "a0", "a1", "BBBB",
"CCCC");
+ assertVectorsEqual(expected, target);
+ }
+ }
+ }
+
+ @Test
+ public void testAppendLargeVariableWidthVectorWithNonZeroStartOffset() {
+ try (LargeVarCharVector target = new LargeVarCharVector("", allocator);
+ LargeVarCharVector delta = new LargeVarCharVector("", allocator)) {
+
+ target.allocateNew(64, 4);
+ ValueVectorDataPopulator.setVector(target, "a0", "a1");
+
+ try (ArrowBuf validity = allocator.buffer(1);
+ ArrowBuf offsets = allocator.buffer(24);
+ ArrowBuf data = allocator.buffer(12)) {
+ validity.setByte(0, 0b11);
+ offsets.setLong(0, 4);
+ offsets.setLong(8, 8);
+ offsets.setLong(16, 12);
+ data.setBytes(0, "AAAABBBBCCCC".getBytes(StandardCharsets.UTF_8));
+ delta.loadFieldBuffers(new ArrowFieldNode(2, 0),
Arrays.asList(validity, offsets, data));
+ }
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ assertEquals(
+ 4 + 8,
+ target
+ .getOffsetBuffer()
+ .getLong((long) target.getValueCount() *
BaseLargeVariableWidthVector.OFFSET_WIDTH));
+
+ try (LargeVarCharVector expected = new LargeVarCharVector("expected",
allocator)) {
+ expected.allocateNew();
+ ValueVectorDataPopulator.setVector(expected, "a0", "a1", "BBBB",
"CCCC");
+ assertVectorsEqual(expected, target);
+ }
+ }
+ }
+
@Test
public void testAppendVariableWidthViewVector() {
final int length1 = 10;
@@ -431,6 +511,115 @@ public class TestVectorAppender {
}
}
+ @Test
+ public void testAppendListVectorWithNonZeroStartOffset() {
+ try (ListVector target = ListVector.empty("target", allocator);
+ ListVector delta = ListVector.empty("delta", allocator)) {
+
+ target.allocateNew();
+ ValueVectorDataPopulator.setVector(target, Arrays.asList(0, 1),
Arrays.asList(2, 3));
+
+ // Build a delta vector whose offset buffer does not start at zero, as
produced e.g. by
+ // importing a sliced array through the C data interface: lists [10, 11]
and [12, 13],
+ // with one unreferenced prefix element (9) in the data vector.
+ delta.addOrGetVector(FieldType.nullable(Types.MinorType.INT.getType()));
+ IntVector deltaDataVector = (IntVector) delta.getDataVector();
+ deltaDataVector.allocateNew(5);
+ for (int i = 0; i < 5; i++) {
+ deltaDataVector.set(i, 9 + i);
+ }
+ deltaDataVector.setValueCount(5);
+ try (ArrowBuf validity = allocator.buffer(1);
+ ArrowBuf offsets = allocator.buffer(12)) {
+ validity.setByte(0, 0b11);
+ offsets.setInt(0, 1);
+ offsets.setInt(4, 3);
+ offsets.setInt(8, 5);
+ delta.loadFieldBuffers(new ArrowFieldNode(2, 0),
Arrays.asList(validity, offsets));
+ }
+ assertEquals(Arrays.asList(10, 11), delta.getObject(0));
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ assertEquals(4, target.getValueCount());
+ // the unreferenced prefix element must not be appended
+ assertEquals(
+ 4 + 4,
+ target.getOffsetBuffer().getInt((long) target.getValueCount() *
ListVector.OFFSET_WIDTH));
+ assertEquals(Arrays.asList(0, 1), target.getObject(0));
+ assertEquals(Arrays.asList(2, 3), target.getObject(1));
+ assertEquals(Arrays.asList(10, 11), target.getObject(2));
+ assertEquals(Arrays.asList(12, 13), target.getObject(3));
+ }
+ }
+
+ @Test
+ public void testAppendLargeListVector() {
+ try (LargeListVector target = LargeListVector.empty("target", allocator);
+ LargeListVector delta = LargeListVector.empty("delta", allocator)) {
+
+ target.allocateNew();
+ ValueVectorDataPopulator.setVector(target, Arrays.asList(0, 1), null,
Arrays.asList(4, 5));
+
+ delta.allocateNew();
+ ValueVectorDataPopulator.setVector(delta, Arrays.asList(10, 11, 12),
Arrays.asList(13, 14));
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ assertEquals(5, target.getValueCount());
+ assertEquals(Arrays.asList(0, 1), target.getObject(0));
+ assertTrue(target.isNull(1));
+ assertEquals(Arrays.asList(4, 5), target.getObject(2));
+ assertEquals(Arrays.asList(10, 11, 12), target.getObject(3));
+ assertEquals(Arrays.asList(13, 14), target.getObject(4));
+ }
+ }
+
+ @Test
+ public void testAppendLargeListVectorWithNonZeroStartOffset() {
+ try (LargeListVector target = LargeListVector.empty("target", allocator);
+ LargeListVector delta = LargeListVector.empty("delta", allocator)) {
+
+ target.allocateNew();
+ ValueVectorDataPopulator.setVector(target, Arrays.asList(0, 1),
Arrays.asList(2, 3));
+
+ // same as testAppendListVectorWithNonZeroStartOffset, with 8-byte
offsets
+ delta.addOrGetVector(FieldType.nullable(Types.MinorType.INT.getType()));
+ IntVector deltaDataVector = (IntVector) delta.getDataVector();
+ deltaDataVector.allocateNew(5);
+ for (int i = 0; i < 5; i++) {
+ deltaDataVector.set(i, 9 + i);
+ }
+ deltaDataVector.setValueCount(5);
+ try (ArrowBuf validity = allocator.buffer(1);
+ ArrowBuf offsets = allocator.buffer(24)) {
+ validity.setByte(0, 0b11);
+ offsets.setLong(0, 1);
+ offsets.setLong(8, 3);
+ offsets.setLong(16, 5);
+ delta.loadFieldBuffers(new ArrowFieldNode(2, 0),
Arrays.asList(validity, offsets));
+ }
+ assertEquals(Arrays.asList(10, 11), delta.getObject(0));
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ assertEquals(4, target.getValueCount());
+ // the unreferenced prefix element must not be appended
+ assertEquals(
+ 4 + 4,
+ target
+ .getOffsetBuffer()
+ .getLong((long) target.getValueCount() *
LargeListVector.OFFSET_WIDTH));
+ assertEquals(Arrays.asList(0, 1), target.getObject(0));
+ assertEquals(Arrays.asList(2, 3), target.getObject(1));
+ assertEquals(Arrays.asList(10, 11), target.getObject(2));
+ assertEquals(Arrays.asList(12, 13), target.getObject(3));
+ }
+ }
+
@Test
public void testAppendEmptyListVector() {
try (ListVector target = ListVector.empty("target", allocator);