From 743b7d76e27b0d7f9f941d09b49c66f55ade6797 Mon Sep 17 00:00:00 2001 From: ump45nose <52391318+ump45nose@users.noreply.github.com> Date: Wed, 21 Oct 2026 09:05:51 +0800 Subject: [PATCH] GH-830: Create Null children when building a struct writer from a Field NullableStructWriter/SingleStructWriter threw "Unknown type: NULL" when the struct field had a Null-typed child, which broke e.g. StructVector#getTransferPair(Field, ...). Add the NullVector child instead. --- .../main/codegen/templates/StructWriters.java | 7 +++ .../apache/arrow/vector/TestStructVector.java | 59 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/vector/src/main/codegen/templates/StructWriters.java b/vector/src/main/codegen/templates/StructWriters.java index 413f707c70..541047443d 100644 --- a/vector/src/main/codegen/templates/StructWriters.java +++ b/vector/src/main/codegen/templates/StructWriters.java @@ -86,6 +86,13 @@ public class ${mode}StructWriter extends AbstractFieldWriter { case EXTENSIONTYPE: extension(child.getName(), child.getType()); break; + case NULL: { + // There is no writer for the Null type, but the child vector still has to exist so that + // the container matches its field (e.g. when it is created from a Field by a TransferPair). + FieldType fieldType = new FieldType(addVectorAsNullable, MinorType.NULL.getType(), null, null); + container.addOrGet(child.getName(), fieldType, NullVector.class); + break; + } case UNION: FieldType fieldType = new FieldType(addVectorAsNullable, MinorType.UNION.getType(), null, null); UnionWriter writer = new UnionWriter(container.addOrGet(child.getName(), fieldType, UnionVector.class), getNullableStructWriterFactory()); diff --git a/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java b/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java index 8c8a45f588..352a201cc6 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java @@ -17,10 +17,12 @@ package org.apache.arrow.vector; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.HashMap; @@ -379,6 +381,63 @@ public void testStructVectorTransferPairWithExtensionType() { toVector.close(); } + @Test + public void testStructVectorTransferPairWithNullType() { + Field nullField = new Field("null_child", FieldType.nullable(ArrowType.Null.INSTANCE), null); + Field structField = + new Field("struct", FieldType.nullable(new ArrowType.Struct()), List.of(nullField)); + + try (StructVector s1 = (StructVector) structField.createVector(allocator)) { + NullVector nullVector = + s1.addOrGet("null_child", FieldType.nullable(ArrowType.Null.INSTANCE), NullVector.class); + s1.allocateNew(); + s1.setIndexDefined(0); + s1.setNull(1); + s1.setIndexDefined(2); + nullVector.setValueCount(3); + s1.setValueCount(3); + + TransferPair tp = s1.getTransferPair(structField, allocator); + try (StructVector toVector = (StructVector) tp.getTo()) { + assertEquals(s1.getField(), toVector.getField()); + + tp.transfer(); + assertEquals(3, toVector.getValueCount()); + assertFalse(toVector.isNull(0)); + assertTrue(toVector.isNull(1)); + assertFalse(toVector.isNull(2)); + NullVector toChild = toVector.getChild("null_child", NullVector.class); + assertNotNull(toChild); + assertEquals(3, toChild.getValueCount()); + assertTrue(toChild.isNull(0)); + } + } + } + + @Test + public void testStructVectorFromFieldWithNullChild() { + Field nullField = new Field("null_child", FieldType.nullable(ArrowType.Null.INSTANCE), null); + Field intField = new Field("int_child", FieldType.nullable(MinorType.INT.getType()), null); + Field structField = + new Field( + "struct", FieldType.nullable(new ArrowType.Struct()), List.of(nullField, intField)); + + try (StructVector vector = new StructVector(structField, allocator, null)) { + assertEquals(structField, vector.getField()); + assertEquals(2, vector.size()); + assertNotNull(vector.getChild("null_child", NullVector.class)); + + // The writer still works for the other children. + NullableStructWriter writer = vector.getWriter(); + writer.setPosition(0); + writer.start(); + writer.integer("int_child").writeInt(42); + writer.end(); + vector.setValueCount(1); + assertEquals(42, vector.getChild("int_child", IntVector.class).get(0)); + } + } + private StructVector simpleStructVector(String name, BufferAllocator allocator) { final String INT_COL = "struct_int_child"; final String FLT_COL = "struct_flt_child";