Repository navigation
Conversation
… 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.
|
The "Ensure PR format" check is only waiting for a label. Could a maintainer add |
|
@ump45nose I'm reviewing this PR. I set the label. |
| 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; | ||
| } |
There was a problem hiding this comment.
addVectorAsNullable is lost further down: addOrGet -> FieldType.createNewSingleVector -> MinorType.NULL.getNewVector, which returns new NullVector(field.getName()). That always gives a nullable field with no metadata. So for a child like Field("n", FieldType.notNullable(ArrowType.Null.INSTANCE), null), the struct field doesn't match the input field.
Could we change Types.java (line 167) to return new NullVector(field); and pass the child own FieldType here? That keeps both nullability and metadata. It also fixes Field.createVector, which goes through the same getNewVector.
| 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 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). | |
| container.addOrGet(child.getName(), child.getFieldType(), NullVector.class); | |
| break; |
There was a problem hiding this comment.
Done in edf274a: StructWriters now passes child.getFieldType(), and Types.NULL.getNewVector uses new NullVector(field) so nullability and metadata survive addOrGet / Field.createVector.
TestStructVector (netty + unsafe): 15 tests, 0 failures.
|
|
||
| @Test | ||
| public void testStructVectorFromFieldWithNullChild() { | ||
| Field nullField = new Field("null_child", FieldType.nullable(ArrowType.Null.INSTANCE), null); |
There was a problem hiding this comment.
Could this child be non nullable and carry metadata? Then assertEquals(structField, vector.getField()) below checks that the Null child field is kept. As written, it passes even though a non nullable Null child comes back nullable.
| Field nullField = new Field("null_child", FieldType.nullable(ArrowType.Null.INSTANCE), null); | |
| Field nullField = | |
| new Field( | |
| "null_child", | |
| new FieldType(false, ArrowType.Null.INSTANCE, null, Map.of("k", "v")), | |
| null); |
There was a problem hiding this comment.
Updated both Null-child tests to use a non-nullable Null field with metadata (Map.of(\"k\", \"v\")), so assertEquals(structField, vector.getField()) actually exercises FieldType preservation.
| NullVector toChild = toVector.getChild("null_child", NullVector.class); | ||
| assertNotNull(toChild); | ||
| assertEquals(3, toChild.getValueCount()); | ||
| assertTrue(toChild.isNull(0)); |
There was a problem hiding this comment.
NullVector.isNull always returns true, so this always passes. Checking the child field against the input would be more helpful:
| assertTrue(toChild.isNull(0)); | |
| assertEquals(nullField, toChild.getField()); |
Same idea for line 402: s1 is built through the same getNewVector path, so assertEquals(s1.getField(), toVector.getField()) passes even when both are wrong. Comparing with structField would catch it.
There was a problem hiding this comment.
Switched to assertEquals(nullField, toChild.getField()) and assertEquals(structField, toVector.getField()) as suggested.
Address review: construct NullVector from the full Field in Types, pass child.getFieldType() in StructWriters, and assert non-nullable Null children with metadata round-trip through TransferPair.
|
Addressed @jbonofre's review in edf274a:
Local: AI-assisted reply. |
|
@ump45nose thanks! I'm doing a new pass 😄 |
| public FieldVector getNewVector( | ||
| Field field, BufferAllocator allocator, CallBack schemaChangeCallback) { | ||
| return new NullVector(field.getName()); | ||
| return new NullVector(field); |
There was a problem hiding this comment.
This change makes a NullVector created from a schema keep its nullability. But NullVector transfer pairs still create a plain nullable vector (TransferImpl(String ref) -> new NullVector(ref), and getTransferPair(Field, ...) only uses field.getName()). So the source and target of a transfer now have different fields.
That breaks code that worked before. VectorSchemaRoot.slice() uses getTransferPair(allocator), and VectorSchemaRootAppender compares nullability, so this now throws IllegalArgumentException: Vector schema roots have different schemas.:
Schema schema = new Schema(List.of(
new Field("n", FieldType.notNullable(ArrowType.Null.INSTANCE), null)));
try (VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator);
VectorSchemaRoot target = VectorSchemaRoot.create(schema, allocator)) {
root.setRowCount(3);
try (VectorSchemaRoot slice = root.slice(0, 2)) {
VectorSchemaRootAppender.append(target, slice);
}
}Before this PR, both sides were nullable, so this worked.
Could you also update NullVector so its transfer pairs keep the field? Something like:
@Override
public TransferPair getTransferPair(String ref, BufferAllocator allocator) {
return new TransferImpl(new NullVector(ref, field.getFieldType()));
}
@Override
public TransferPair getTransferPair(Field field, BufferAllocator allocator) {
return new TransferImpl(new NullVector(field));
}There was a problem hiding this comment.
Good catch, thanks. In 8fb9a80 NullVector.getTransferPair(String, ...) now builds the target with the source FieldType, and getTransferPair(Field, ...) uses the given Field, so slice() keeps nullability and metadata. I added your scenario as TestVectorSchemaRootAppender#testAppendSliceWithNonNullableNullVector, plus TestSplitAndTransfer#testNullVectorTransferPairKeepsField for the three transfer-pair entry points. Both fail without the change, and the full vector module tests pass locally.
Address review: NullVector transfer pairs rebuilt the target from the name only, so a non-nullable Null field came back nullable and VectorSchemaRoot.slice() + VectorSchemaRootAppender failed with "different schemas". Build the target from the source FieldType (or the given Field) instead.
What's Changed
NullableStructWriter/SingleStructWriter(generated fromStructWriters.java) walk the struct field's children in their constructor and create a child vector for each one. There was nocase NULL, so a struct whose field has aNull-typed child threwUnsupportedOperationException: Unknown type: NULL. BecauseStructVectorbuilds its writer as soon as it is constructed, this brokenew StructVector(field, ...)and anything that goes through it, such asStructVector#getTransferPair(Field, BufferAllocator)from the issue.There is no writer for the Null type, so the new case only adds the
NullVectorchild to the container (with the child's nullability). The resulting vector's field now matches the input field, and the other children's writers keep working as before.Tests added to
TestStructVector:testStructVectorTransferPairWithNullType: the reproducer from the issue, plus atransfer()that checks the value count, struct validity and theNullVectorchild.testStructVectorFromFieldWithNullChild: builds aStructVectorstraight from aFieldwith a Null child and an Int child, then writes through the struct writer.Both fail on
mainwithUnknown type: NULLand pass with this change. I also ranTestStructVector,TestComplexWriter,TestListVector,TestMapVector,TestUnionVector,TestDenseUnionVector,TestComplexCopier,TestPromotableWriterandTestSplitAndTransfer(all pass, both the default and netty runs), andmvn -pl vector spotless:check.AI assistance disclosure: this change, its tests and this description were prepared with the help of an AI coding assistant. The tests listed above were run locally against this branch.
Closes #830.