Skip to content

Commit bc8bd9c

Browse files
committed
fix(vector): reallocate LargeListVector offsets that cannot hold [0]
A zero-length split of a ListVector only clears its target, so a LargeListVector below it keeps the allocator's shared empty buffer as its offsets. getFieldBuffers() then set the writer index of the shared buffer to 8: the LargeList offsets were written with no bytes when a later vector reset it, and when none did, every other buffer still on the shared empty buffer was sized 8 bytes and writing the batch threw IndexOutOfBoundsException. Reallocate the offsets buffer in getFieldBuffers() when it cannot hold [0], as ListVector now does.
1 parent d811f92 commit bc8bd9c

3 files changed

Lines changed: 55 additions & 1 deletion

File tree

‎vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,18 @@ private void setReaderAndWriterIndex() {
316316
// Both are set to 0 means 0 bytes are written to the IPC stream which will crash IPC readers
317317
// in other libraries. According to Arrow spec, we should still output the offset buffer which
318318
// is [0].
319-
offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH);
319+
final long requiredOffsetBufferSize = (long) (valueCount + 1) * OFFSET_WIDTH;
320+
if (offsetBuffer.capacity() < requiredOffsetBufferSize) {
321+
// An empty vector may still hold the allocator's shared empty buffer
322+
ArrowBuf newOffsetBuffer = allocator.buffer(requiredOffsetBufferSize);
323+
newOffsetBuffer.setZero(0, newOffsetBuffer.capacity());
324+
if (offsetBuffer.capacity() > 0) {
325+
newOffsetBuffer.setBytes(0, offsetBuffer, 0, offsetBuffer.capacity());
326+
}
327+
offsetBuffer.getReferenceManager().release();
328+
offsetBuffer = newOffsetBuffer;
329+
}
330+
offsetBuffer.writerIndex(requiredOffsetBufferSize);
320331
}
321332

322333
/**

‎vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,4 +1216,17 @@ public void testSetValueCountAcceptsMaxIntChildValueCount() {
12161216
}
12171217
}
12181218
}
1219+
1220+
@Test
1221+
public void testGetFieldBuffersOfEmptyVector() {
1222+
// An empty vector holds the allocator's shared empty buffer as its offsets until it allocates
1223+
try (LargeListVector vector = LargeListVector.empty("list", allocator)) {
1224+
vector.addOrGetVector(FieldType.nullable(MinorType.INT.getType()));
1225+
ArrowBuf offsets = vector.getFieldBuffers().get(1);
1226+
assertTrue(offsets.capacity() >= LargeListVector.OFFSET_WIDTH);
1227+
assertEquals(LargeListVector.OFFSET_WIDTH, offsets.readableBytes());
1228+
assertEquals(0L, offsets.getLong(0));
1229+
assertEquals(0, allocator.getEmpty().writerIndex());
1230+
}
1231+
}
12191232
}

‎vector/src/test/java/org/apache/arrow/vector/TestVectorUnloadNonZeroStartOffset.java‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,21 @@ private ListVector emptyRowsOverNestedList() {
185185
return vector;
186186
}
187187

188+
/** Two empty rows over an inner list that still holds two large lists, [[1]] and [[2]]. */
189+
private ListVector emptyRowsOverNestedLargeList() {
190+
ListVector vector = ListVector.empty("l", allocator);
191+
vector.addOrGetVector(FieldType.nullable(ArrowType.List.INSTANCE));
192+
ListVector inner = (ListVector) vector.getDataVector();
193+
inner.addOrGetVector(FieldType.nullable(ArrowType.LargeList.INSTANCE));
194+
LargeListVector largeList = (LargeListVector) inner.getDataVector();
195+
largeList.addOrGetVector(FieldType.nullable(MinorType.INT.getType()));
196+
load(vector, 2, allValid(), ints(0, 0, 0));
197+
load(inner, 2, allValid(), ints(0, 1, 2));
198+
load(largeList, 2, allValid(), longs(0, 1, 2));
199+
load(largeList.getDataVector(), 2, allValid(), ints(1, 2));
200+
return vector;
201+
}
202+
188203
/**
189204
* One row over a dense union whose int child skips an element: union slots 0..3 hold child
190205
* elements 0, 1, 3 and 4, that is 10, 11, 13 and 14.
@@ -359,6 +374,21 @@ public void emptyCoveredRangeOverNestedListKeepsItsFirstOffset() {
359374
}
360375
}
361376

377+
@Test
378+
public void emptyCoveredRangeOverNestedLargeListKeepsItsFirstOffset() {
379+
try (ListVector vector = emptyRowsOverNestedLargeList();
380+
VectorSchemaRoot root = VectorSchemaRoot.of(vector);
381+
ArrowRecordBatch batch = new VectorUnloader(root).getRecordBatch();
382+
VectorSchemaRoot loaded = reload(root, batch)) {
383+
assertEquals(Arrays.asList(2, 0, 0, 0), lengths(batch));
384+
assertEquals(Arrays.asList(1L, 12L, 0L, 4L, 0L, 8L, 0L, 0L), sizes(batch));
385+
assertEquals(0L, batch.getBuffers().get(5).getLong(0));
386+
ListVector read = (ListVector) loaded.getVector(0);
387+
assertEquals(Collections.emptyList(), read.getObject(0));
388+
assertEquals(Collections.emptyList(), read.getObject(1));
389+
}
390+
}
391+
362392
@Test
363393
public void denseUnionChildIsWrittenUntrimmed() {
364394
try (ListVector vector = listOfDenseUnion(0, 3);

0 commit comments

Comments
 (0)