visit(
LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation vectorLogicalType) {
- TypeUtil.checkValidFiniteVectorSchema(type);
- return visit(LogicalTypeAnnotation.listType());
+ List3Levels list3Levels = new List3Levels(type);
+ Type element = list3Levels.getElement();
+ TypeMapping child = fromParquet(element, null, element.getRepetition());
+ ArrowType.FixedSizeList fixedSizeList =
+ new ArrowType.FixedSizeList(vectorLogicalType.getNumElements());
+ Field arrowField = new Field(
+ name,
+ new FieldType(type.isRepetition(OPTIONAL), fixedSizeList, null),
+ Collections.singletonList(child.getArrowField()));
+ return of(new ListTypeMapping(arrowField, list3Levels, child));
}
@Override
diff --git a/parquet-arrow/src/test/java/org/apache/parquet/arrow/schema/TestSchemaConverter.java b/parquet-arrow/src/test/java/org/apache/parquet/arrow/schema/TestSchemaConverter.java
index a513adfdd7..f697b53a29 100644
--- a/parquet-arrow/src/test/java/org/apache/parquet/arrow/schema/TestSchemaConverter.java
+++ b/parquet-arrow/src/test/java/org/apache/parquet/arrow/schema/TestSchemaConverter.java
@@ -67,20 +67,21 @@
public class TestSchemaConverter {
@ParameterizedTest
@ValueSource(strings = {"required", "optional"})
- public void testVectorAsList(String repetition) {
+ public void testVectorAsFixedSizeList(String repetition) {
String children = "repeated group list { required float element; }";
MessageType vector = MessageTypeParser.parseMessageType(
"message m { " + repetition + " group v (FINITE_VECTOR(5)) { " + children + " } }");
- MessageType list = MessageTypeParser.parseMessageType(
- "message m { " + repetition + " group v (LIST) { " + children + " } }");
SchemaMapping mapping = converter.fromParquet(vector);
- assertThat(mapping.getArrowSchema())
- .isEqualTo(converter.fromParquet(list).getArrowSchema());
Field field = mapping.getArrowSchema().findField("v");
- assertThat(field.getType()).isEqualTo(new ArrowType.List());
+ assertThat(field.getType()).isEqualTo(new ArrowType.FixedSizeList(5));
+ assertThat(field.isNullable()).isEqualTo(repetition.equals("optional"));
assertThat(field.getChildren()).hasSize(1);
assertThat(field.getChildren().get(0).getType())
.isEqualTo(new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE));
+ assertThat(field.getChildren().get(0).isNullable()).isFalse();
+ assertThat(mapping.getParquetSchema()).isEqualTo(vector);
+ assertThat(converter.map(mapping.getArrowSchema(), vector).getParquetSchema())
+ .isEqualTo(vector);
}
private static Field field(String name, boolean nullable, ArrowType type, Field... children) {
diff --git a/parquet-avro/src/main/java/org/apache/parquet/avro/AvroReadSupport.java b/parquet-avro/src/main/java/org/apache/parquet/avro/AvroReadSupport.java
index ff66932ec6..9823fe4c4c 100644
--- a/parquet-avro/src/main/java/org/apache/parquet/avro/AvroReadSupport.java
+++ b/parquet-avro/src/main/java/org/apache/parquet/avro/AvroReadSupport.java
@@ -281,9 +281,8 @@ private static Boolean allListStructuresAreThreeLevel(Type type) {
return null;
}
GroupType group = type.asGroupType();
- if (group.getLogicalTypeAnnotation() instanceof LogicalTypeAnnotation.ListLogicalTypeAnnotation
- || group.getLogicalTypeAnnotation()
- instanceof LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation) {
+ LogicalTypeAnnotation logicalType = group.getLogicalTypeAnnotation();
+ if (logicalType instanceof LogicalTypeAnnotation.ListLogicalTypeAnnotation) {
if (group.isRepetition(Type.Repetition.REPEATED) || group.getFieldCount() != 1) {
return false;
}
diff --git a/parquet-avro/src/main/java/org/apache/parquet/avro/AvroRecordConverter.java b/parquet-avro/src/main/java/org/apache/parquet/avro/AvroRecordConverter.java
index 9f1c012624..f3d3d53c95 100644
--- a/parquet-avro/src/main/java/org/apache/parquet/avro/AvroRecordConverter.java
+++ b/parquet-avro/src/main/java/org/apache/parquet/avro/AvroRecordConverter.java
@@ -1000,7 +1000,7 @@ public void end() {
* determined by checking whether the type can be a synthetic group and by
* checking whether a potential synthetic group matches the expected schema.
*
- * Unlike {@link AvroSchemaConverter#isElementType(Type, String)}, this
+ * Unlike AvroSchemaConverter.isElementType, this
* method never guesses because the expected schema is known.
*
* @param repeatedType a type that may be the element type
diff --git a/parquet-avro/src/main/java/org/apache/parquet/avro/AvroSchemaConverter.java b/parquet-avro/src/main/java/org/apache/parquet/avro/AvroSchemaConverter.java
index 221f9dbc5a..fe4f2bb98f 100644
--- a/parquet-avro/src/main/java/org/apache/parquet/avro/AvroSchemaConverter.java
+++ b/parquet-avro/src/main/java/org/apache/parquet/avro/AvroSchemaConverter.java
@@ -71,7 +71,6 @@
import org.apache.parquet.schema.PrimitiveType;
import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
import org.apache.parquet.schema.Type;
-import org.apache.parquet.schema.TypeUtil;
import org.apache.parquet.schema.Types;
/**
@@ -471,9 +470,7 @@ public Optional visit(
if (!repeatedType.isRepetition(REPEATED)) {
throw new UnsupportedOperationException("Invalid list type " + parquetGroupType);
}
- if (!(logicalTypeAnnotation
- instanceof LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation)
- && isElementType(repeatedType, parquetGroupType.getName())) {
+ if (isElementType(repeatedType, parquetGroupType)) {
// repeated element types are always required
return of(Schema.createArray(convertField(repeatedType, names)));
} else {
@@ -490,7 +487,6 @@ && isElementType(repeatedType, parquetGroupType.getName())) {
@Override
public Optional visit(
LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation vectorLogicalType) {
- TypeUtil.checkValidFiniteVectorSchema(parquetGroupType);
return visit(LogicalTypeAnnotation.listType());
}
@@ -664,7 +660,9 @@ public Optional visit(UUIDLogicalTypeAnnotation uuidLogicalType) {
* the same. This ensures that the LIST rules are followed when there is no
* schema and that a schema can be provided to override the default behavior.
*/
- private boolean isElementType(Type repeatedType, String parentName) {
+ private boolean isElementType(Type repeatedType, GroupType parent) {
+ if (parent.getLogicalTypeAnnotation() instanceof LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation)
+ return false;
return (
// can't be a synthetic layer because it would be invalid
repeatedType.isPrimitive()
@@ -673,7 +671,7 @@ private boolean isElementType(Type repeatedType, String parentName) {
||
// known patterns without the synthetic layer
repeatedType.getName().equals("array")
- || repeatedType.getName().equals(parentName + "_tuple")
+ || repeatedType.getName().equals(parent.getName() + "_tuple")
||
// default assumption
assumeRepeatedIsListElement);
diff --git a/parquet-avro/src/test/java/org/apache/parquet/avro/TestArrayCompatibility.java b/parquet-avro/src/test/java/org/apache/parquet/avro/TestArrayCompatibility.java
index 790f35d244..60bf7315fa 100644
--- a/parquet-avro/src/test/java/org/apache/parquet/avro/TestArrayCompatibility.java
+++ b/parquet-avro/src/test/java/org/apache/parquet/avro/TestArrayCompatibility.java
@@ -1651,6 +1651,10 @@ public void testVectorWithoutAvroSchema(@TempDir java.nio.file.Path dir) throws
field("v", array(primitive(Schema.Type.FLOAT))),
optionalField("o", array(primitive(Schema.Type.FLOAT))));
assertThat(new AvroSchemaConverter().convert(schema)).isEqualTo(expectedSchema);
+ for (boolean assumeRepeatedIsListElement : new boolean[] {true, false}) {
+ assertThat(new AvroSchemaConverter(assumeRepeatedIsListElement, false).convert(schema))
+ .isEqualTo(expectedSchema);
+ }
try (AvroParquetReader reader = autoDetectReader(path)) {
assertReaderContains(
reader,
diff --git a/parquet-column/src/main/java/org/apache/parquet/io/ColumnIOFactory.java b/parquet-column/src/main/java/org/apache/parquet/io/ColumnIOFactory.java
index ba9a805ffd..24868acaf1 100644
--- a/parquet-column/src/main/java/org/apache/parquet/io/ColumnIOFactory.java
+++ b/parquet-column/src/main/java/org/apache/parquet/io/ColumnIOFactory.java
@@ -24,7 +24,6 @@
import org.apache.parquet.schema.MessageType;
import org.apache.parquet.schema.PrimitiveType;
import org.apache.parquet.schema.Type;
-import org.apache.parquet.schema.TypeUtil;
import org.apache.parquet.schema.TypeVisitor;
/**
@@ -54,7 +53,6 @@ private ColumnIOCreatorVisitor(
@Override
public void visit(MessageType messageType) {
- TypeUtil.checkValidFiniteVectorSchema(messageType);
columnIO = new MessageColumnIO(requestedSchema, validating, createdBy);
visitChildren(columnIO, messageType, requestedSchema);
columnIO.setLevels();
diff --git a/parquet-column/src/main/java/org/apache/parquet/io/FilteredRecordReader.java b/parquet-column/src/main/java/org/apache/parquet/io/FilteredRecordReader.java
index 3c52368c9b..9658c76fcd 100644
--- a/parquet-column/src/main/java/org/apache/parquet/io/FilteredRecordReader.java
+++ b/parquet-column/src/main/java/org/apache/parquet/io/FilteredRecordReader.java
@@ -84,12 +84,12 @@ private void skipToMatch() {
State currentState = getState(0);
do {
ColumnReader columnReader = currentState.column;
- int d = columnReader.getCurrentDefinitionLevel();
- currentState.validateVector(d);
+ int depth = columnReader.getCurrentDefinitionLevel();
+ currentState.validateVector(depth);
// currentLevel = depth + 1 at this point
// set the current value
- if (d >= currentState.maxDefinitionLevel) {
+ if (depth >= currentState.maxDefinitionLevel) {
columnReader.skip();
}
columnReader.consume();
diff --git a/parquet-column/src/main/java/org/apache/parquet/io/FiniteVectorValidator.java b/parquet-column/src/main/java/org/apache/parquet/io/FiniteVectorValidator.java
new file mode 100644
index 0000000000..f1c2108ebd
--- /dev/null
+++ b/parquet-column/src/main/java/org/apache/parquet/io/FiniteVectorValidator.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.parquet.io;
+
+import java.util.Arrays;
+import org.apache.parquet.column.ColumnReader;
+import org.apache.parquet.io.api.Binary;
+import org.apache.parquet.schema.LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation;
+import org.apache.parquet.schema.LogicalTypeAnnotation.Float16LogicalTypeAnnotation;
+import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
+
+final class FiniteVectorValidator {
+ private final ColumnReader column;
+ private final String[] fieldPath;
+ private final PrimitiveTypeName primitive;
+ private final int maxDefinitionLevel;
+ private final int maxRepetitionLevel;
+ private final int length;
+ private final boolean float16;
+ private long count;
+
+ FiniteVectorValidator(PrimitiveColumnIO columnIO, ColumnReader column, FiniteVectorLogicalTypeAnnotation vector) {
+ this.column = column;
+ this.fieldPath = columnIO.getFieldPath();
+ this.primitive = columnIO.getType().asPrimitiveType().getPrimitiveTypeName();
+ this.maxDefinitionLevel = columnIO.getDefinitionLevel();
+ this.maxRepetitionLevel = columnIO.getRepetitionLevel();
+ this.length = vector.getNumElements();
+ this.float16 = columnIO.getType().getLogicalTypeAnnotation() instanceof Float16LogicalTypeAnnotation;
+ }
+
+ void validate(int definitionLevel) {
+ int repetitionLevel = column.getCurrentRepetitionLevel();
+ if (definitionLevel < 0
+ || definitionLevel > maxDefinitionLevel
+ || repetitionLevel < 0
+ || repetitionLevel > maxRepetitionLevel) {
+ throw invalidVector("invalid levels");
+ }
+ if (count == 0 && repetitionLevel == maxRepetitionLevel) {
+ throw invalidVector("invalid continuation");
+ }
+ if (definitionLevel < maxDefinitionLevel) {
+ if (count > 0) throw invalidVector("null within vector");
+ if (definitionLevel == maxDefinitionLevel - 1) throw invalidVector("empty vector");
+ return;
+ }
+ count++;
+ if (count > length) {
+ throw invalidVector("expected " + length + " elements, got " + count);
+ }
+ if (primitive == PrimitiveTypeName.FLOAT && !Float.isFinite(column.getFloat())
+ || primitive == PrimitiveTypeName.DOUBLE && !Double.isFinite(column.getDouble())) {
+ throw invalidVector("non-finite element");
+ }
+ if (float16) {
+ Binary value = column.getBinary();
+ if (value.length() != 2) {
+ throw invalidVector("invalid FLOAT16 element length: " + value.length());
+ }
+ if ((value.get2BytesLittleEndian() & 0x7c00) == 0x7c00) {
+ throw invalidVector("non-finite element");
+ }
+ }
+ }
+
+ void end(int nextRepetitionLevel) {
+ if (nextRepetitionLevel < 0 || nextRepetitionLevel > maxRepetitionLevel) {
+ throw invalidVector("invalid levels");
+ }
+ if (nextRepetitionLevel == maxRepetitionLevel && count == 0) {
+ throw invalidVector("null within vector");
+ }
+ if (nextRepetitionLevel < maxRepetitionLevel && count > 0) {
+ if (count != length) {
+ throw invalidVector("expected " + length + " elements, got " + count);
+ }
+ count = 0;
+ }
+ }
+
+ private ParquetDecodingException invalidVector(String reason) {
+ return new ParquetDecodingException(
+ "Invalid FINITE_VECTOR data for " + Arrays.toString(fieldPath) + ": " + reason);
+ }
+}
diff --git a/parquet-column/src/main/java/org/apache/parquet/io/MessageColumnIO.java b/parquet-column/src/main/java/org/apache/parquet/io/MessageColumnIO.java
index 0c9fabec34..2428a279ee 100644
--- a/parquet-column/src/main/java/org/apache/parquet/io/MessageColumnIO.java
+++ b/parquet-column/src/main/java/org/apache/parquet/io/MessageColumnIO.java
@@ -509,7 +509,7 @@ public void flush() {
public RecordConsumer getRecordWriter(ColumnWriteStore columns) {
RecordConsumer recordWriter = new MessageColumnIORecordConsumer(columns);
if (DEBUG) recordWriter = new RecordConsumerLoggingWrapper(recordWriter);
- return validating || TypeUtil.checkValidFiniteVectorSchema(getType())
+ return validating || TypeUtil.hasFiniteVector(getType())
? new ValidatingRecordConsumer(recordWriter, getType())
: recordWriter;
}
diff --git a/parquet-column/src/main/java/org/apache/parquet/io/RecordReaderImplementation.java b/parquet-column/src/main/java/org/apache/parquet/io/RecordReaderImplementation.java
index df1501ae3f..dd98a6d98a 100644
--- a/parquet-column/src/main/java/org/apache/parquet/io/RecordReaderImplementation.java
+++ b/parquet-column/src/main/java/org/apache/parquet/io/RecordReaderImplementation.java
@@ -27,7 +27,6 @@
import java.util.Map;
import org.apache.parquet.column.ColumnReader;
import org.apache.parquet.column.impl.ColumnReadStoreImpl;
-import org.apache.parquet.io.api.Binary;
import org.apache.parquet.io.api.Converter;
import org.apache.parquet.io.api.GroupConverter;
import org.apache.parquet.io.api.PrimitiveConverter;
@@ -35,7 +34,6 @@
import org.apache.parquet.io.api.RecordMaterializer;
import org.apache.parquet.schema.LogicalTypeAnnotation;
import org.apache.parquet.schema.LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation;
-import org.apache.parquet.schema.LogicalTypeAnnotation.Float16LogicalTypeAnnotation;
import org.apache.parquet.schema.MessageType;
import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
import org.slf4j.Logger;
@@ -196,9 +194,7 @@ public static class State {
private Case[][][] caseLookup;
private List definedCases;
private List undefinedCases;
- private final int vectorLength;
- private final boolean vectorFloat16;
- private long vectorCount;
+ private final FiniteVectorValidator vectorValidator;
private State(
int id,
@@ -228,61 +224,15 @@ private State(
vector = (FiniteVectorLogicalTypeAnnotation) logical;
}
}
- this.vectorLength = vector == null ? 0 : vector.getNumElements();
- this.vectorFloat16 = vector != null
- && primitiveColumnIO.getType().getLogicalTypeAnnotation() instanceof Float16LogicalTypeAnnotation;
+ this.vectorValidator = vector == null ? null : new FiniteVectorValidator(primitiveColumnIO, column, vector);
}
void validateVector(int d) {
- if (vectorLength == 0) return;
- int r = column.getCurrentRepetitionLevel();
- if (d < 0 || d > maxDefinitionLevel || r < 0 || r > maxRepetitionLevel) {
- throw invalidVector("invalid levels");
- }
- if (vectorCount == 0 && r == maxRepetitionLevel) {
- throw invalidVector("invalid continuation");
- }
- if (d < maxDefinitionLevel) {
- if (vectorCount > 0) throw invalidVector("null within vector");
- if (d == maxDefinitionLevel - 1) throw invalidVector("empty vector");
- return;
- }
- vectorCount++;
- if (vectorCount > vectorLength) {
- throw invalidVector("expected " + vectorLength + " elements, got " + vectorCount);
- }
- if (primitive == PrimitiveTypeName.FLOAT && !Float.isFinite(column.getFloat())
- || primitive == PrimitiveTypeName.DOUBLE && !Double.isFinite(column.getDouble())) {
- throw invalidVector("non-finite element");
- }
- if (vectorFloat16) {
- Binary value = column.getBinary();
- if (value.length() != 2) {
- throw invalidVector("invalid FLOAT16 element length: " + value.length());
- }
- if ((value.get2BytesLittleEndian() & 0x7c00) == 0x7c00) {
- throw invalidVector("non-finite element");
- }
- }
+ if (vectorValidator != null) vectorValidator.validate(d);
}
void endVector(int nextR) {
- if (vectorLength == 0) return;
- if (nextR < 0 || nextR > maxRepetitionLevel) throw invalidVector("invalid levels");
- if (nextR == maxRepetitionLevel && vectorCount == 0) {
- throw invalidVector("null within vector");
- }
- if (nextR < maxRepetitionLevel && vectorCount > 0) {
- if (vectorCount != vectorLength) {
- throw invalidVector("expected " + vectorLength + " elements, got " + vectorCount);
- }
- vectorCount = 0;
- }
- }
-
- private ParquetDecodingException invalidVector(String reason) {
- return new ParquetDecodingException(
- "Invalid FINITE_VECTOR data for " + Arrays.toString(fieldPath) + ": " + reason);
+ if (vectorValidator != null) vectorValidator.end(nextR);
}
public int getDepth(int definitionLevel) {
diff --git a/parquet-column/src/main/java/org/apache/parquet/io/ValidatingRecordConsumer.java b/parquet-column/src/main/java/org/apache/parquet/io/ValidatingRecordConsumer.java
index cf34785e0e..e49cbcd802 100644
--- a/parquet-column/src/main/java/org/apache/parquet/io/ValidatingRecordConsumer.java
+++ b/parquet-column/src/main/java/org/apache/parquet/io/ValidatingRecordConsumer.java
@@ -38,7 +38,6 @@
import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
import org.apache.parquet.schema.Type;
import org.apache.parquet.schema.Type.Repetition;
-import org.apache.parquet.schema.TypeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,7 +64,6 @@ public class ValidatingRecordConsumer extends RecordConsumer {
* @param schema the schema to validate against
*/
public ValidatingRecordConsumer(RecordConsumer delegate, MessageType schema) {
- TypeUtil.checkValidFiniteVectorSchema(schema);
this.delegate = delegate;
this.types.push(schema);
}
diff --git a/parquet-column/src/main/java/org/apache/parquet/schema/LogicalTypeAnnotation.java b/parquet-column/src/main/java/org/apache/parquet/schema/LogicalTypeAnnotation.java
index beb159d327..5dadfb7b36 100644
--- a/parquet-column/src/main/java/org/apache/parquet/schema/LogicalTypeAnnotation.java
+++ b/parquet-column/src/main/java/org/apache/parquet/schema/LogicalTypeAnnotation.java
@@ -514,7 +514,7 @@ LogicalTypeToken getType() {
@Override
public boolean equals(Object obj) {
- return obj instanceof ListLogicalTypeAnnotation;
+ return obj != null && getClass() == obj.getClass();
}
@Override
@@ -524,7 +524,7 @@ public int hashCode() {
}
}
- public static class FiniteVectorLogicalTypeAnnotation extends LogicalTypeAnnotation {
+ public static class FiniteVectorLogicalTypeAnnotation extends ListLogicalTypeAnnotation {
private final int numElements;
private FiniteVectorLogicalTypeAnnotation(int numElements) {
diff --git a/parquet-column/src/main/java/org/apache/parquet/schema/MessageType.java b/parquet-column/src/main/java/org/apache/parquet/schema/MessageType.java
index 1aba0cc6af..7901745574 100644
--- a/parquet-column/src/main/java/org/apache/parquet/schema/MessageType.java
+++ b/parquet-column/src/main/java/org/apache/parquet/schema/MessageType.java
@@ -34,6 +34,7 @@ public final class MessageType extends GroupType {
*/
public MessageType(String name, Type... fields) {
super(Repetition.REPEATED, name, fields);
+ TypeUtil.checkValidFiniteVectorSchema(this);
}
/**
@@ -42,6 +43,7 @@ public MessageType(String name, Type... fields) {
*/
public MessageType(String name, List fields) {
super(Repetition.REPEATED, name, fields);
+ TypeUtil.checkValidFiniteVectorSchema(this);
}
/**
diff --git a/parquet-column/src/main/java/org/apache/parquet/schema/TypeUtil.java b/parquet-column/src/main/java/org/apache/parquet/schema/TypeUtil.java
index b307bcf7d8..c18a5cef79 100644
--- a/parquet-column/src/main/java/org/apache/parquet/schema/TypeUtil.java
+++ b/parquet-column/src/main/java/org/apache/parquet/schema/TypeUtil.java
@@ -41,6 +41,16 @@ public static boolean checkValidFiniteVectorSchema(Type type) {
return found;
}
+ public static boolean hasFiniteVector(Type type) {
+ if (type.getLogicalTypeAnnotation() instanceof FiniteVectorLogicalTypeAnnotation) return true;
+ if (!type.isPrimitive()) {
+ for (Type child : type.asGroupType().getFields()) {
+ if (hasFiniteVector(child)) return true;
+ }
+ }
+ return false;
+ }
+
private static void checkVector(GroupType vector) {
if (vector.getRepetition() == Repetition.REPEATED || vector.getFieldCount() != 1) {
throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
@@ -65,26 +75,16 @@ private static void checkVector(GroupType vector) {
PrimitiveType element = elementType.asPrimitiveType();
PrimitiveTypeName physical = element.getPrimitiveTypeName();
LogicalTypeAnnotation logical = element.getLogicalTypeAnnotation();
- boolean allowed;
- switch (physical) {
- case BOOLEAN:
- case FLOAT:
- case DOUBLE:
- allowed = logical == null;
- break;
- case INT32:
- case INT64:
- allowed = logical == null
+ boolean allowed = switch (physical) {
+ case BOOLEAN, FLOAT, DOUBLE -> logical == null;
+ case INT32, INT64 ->
+ logical == null
|| logical instanceof IntLogicalTypeAnnotation
|| logical instanceof DecimalLogicalTypeAnnotation;
- break;
- case FIXED_LEN_BYTE_ARRAY:
- allowed = logical instanceof Float16LogicalTypeAnnotation
- || logical instanceof DecimalLogicalTypeAnnotation;
- break;
- default:
- allowed = false;
- }
+ case FIXED_LEN_BYTE_ARRAY ->
+ logical instanceof Float16LogicalTypeAnnotation || logical instanceof DecimalLogicalTypeAnnotation;
+ default -> false;
+ };
if (!allowed) {
throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
}
@@ -117,7 +117,6 @@ public static FiniteVectorLogicalTypeAnnotation getFiniteVectorType(GroupType sc
}
public static void checkValidWriteSchema(GroupType schema) {
- checkValidFiniteVectorSchema(schema);
schema.accept(new TypeVisitor() {
@Override
public void visit(GroupType groupType) {
diff --git a/parquet-column/src/test/java/org/apache/parquet/schema/TestTypeBuildersWithLogicalTypes.java b/parquet-column/src/test/java/org/apache/parquet/schema/TestTypeBuildersWithLogicalTypes.java
index 0885bfa445..e6b8d74112 100644
--- a/parquet-column/src/test/java/org/apache/parquet/schema/TestTypeBuildersWithLogicalTypes.java
+++ b/parquet-column/src/test/java/org/apache/parquet/schema/TestTypeBuildersWithLogicalTypes.java
@@ -28,6 +28,7 @@
import static org.apache.parquet.schema.LogicalTypeAnnotation.float16Type;
import static org.apache.parquet.schema.LogicalTypeAnnotation.intType;
import static org.apache.parquet.schema.LogicalTypeAnnotation.jsonType;
+import static org.apache.parquet.schema.LogicalTypeAnnotation.listType;
import static org.apache.parquet.schema.LogicalTypeAnnotation.stringType;
import static org.apache.parquet.schema.LogicalTypeAnnotation.timeType;
import static org.apache.parquet.schema.LogicalTypeAnnotation.timestampType;
@@ -46,11 +47,14 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Stream;
import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
import org.apache.parquet.schema.Type.Repetition;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -75,11 +79,35 @@ public void testVectorAnnotation(int length) {
assertThat(finiteVectorType(length)).asString().isEqualTo("FINITE_VECTOR(" + length + ")");
assertThat(finiteVectorType(length).getNumElements()).isEqualTo(length);
assertThat(finiteVectorType(length).toOriginalType()).isEqualTo(OriginalType.LIST);
+ assertThat(finiteVectorType(length)).isInstanceOf(LogicalTypeAnnotation.ListLogicalTypeAnnotation.class);
assertThat(finiteVectorType(length))
.isEqualTo(finiteVectorType(length))
.hasSameHashCodeAs(finiteVectorType(length));
assertThat(finiteVectorType(length)).isNotEqualTo(finiteVectorType(length == 1 ? 5 : 1));
assertThat(finiteVectorType(length)).isNotEqualTo(LogicalTypeAnnotation.listType());
+ assertThat(listType()).isNotEqualTo(finiteVectorType(length));
+ }
+
+ @Test
+ public void testVectorVisitorDispatch() {
+ LogicalTypeAnnotation.LogicalTypeAnnotationVisitor listVisitor =
+ new LogicalTypeAnnotation.LogicalTypeAnnotationVisitor() {
+ @Override
+ public Optional visit(LogicalTypeAnnotation.ListLogicalTypeAnnotation list) {
+ return Optional.of("list");
+ }
+ };
+ assertThat(listType().accept(listVisitor)).contains("list");
+ assertThat(finiteVectorType(5).accept(listVisitor)).isEmpty();
+
+ LogicalTypeAnnotation.LogicalTypeAnnotationVisitor vectorVisitor =
+ new LogicalTypeAnnotation.LogicalTypeAnnotationVisitor() {
+ @Override
+ public Optional visit(LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation vector) {
+ return Optional.of(vector.getNumElements());
+ }
+ };
+ assertThat(finiteVectorType(5).accept(vectorVisitor)).contains(5);
}
@ParameterizedTest
@@ -127,11 +155,52 @@ private static Stream invalidVectors() {
@ParameterizedTest
@MethodSource("invalidVectors")
public void testInvalidVectorSchema(GroupType vector) {
- assertThatThrownBy(() -> TypeUtil.checkValidFiniteVectorSchema(new MessageType("m", vector)))
+ assertThatThrownBy(() -> new MessageType("m", vector))
.isInstanceOf(InvalidSchemaException.class)
.hasMessage("Invalid FINITE_VECTOR schema: " + vector);
}
+ private static Stream vectorConstructionBoundaries() {
+ Type element = Types.optional(FLOAT).named("element");
+ GroupType invalid = vector(element);
+ GroupType bad = new GroupType(
+ REQUIRED, "bad", finiteVectorType(5), new GroupType(Repetition.REPEATED, "list", element));
+ return Stream.of(
+ () -> new MessageType("m", Collections.singletonList(invalid)),
+ () -> Types.buildMessage().addField(invalid).named("m"),
+ () -> MessageTypeParser.parseMessageType("message m { required group v (FINITE_VECTOR(5)) { "
+ + "repeated group list { optional float element; } } }"),
+ () -> new MessageType(
+ "m",
+ new GroupType(
+ REQUIRED, "parent", vector(Types.required(FLOAT).named("element")), bad)));
+ }
+
+ @ParameterizedTest
+ @MethodSource("vectorConstructionBoundaries")
+ public void testVectorConstructionBoundaries(Executable construction) {
+ assertThatThrownBy(construction::execute)
+ .isInstanceOf(InvalidSchemaException.class)
+ .hasMessageContaining("Invalid FINITE_VECTOR schema");
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testHasFiniteVector(boolean hasVector) {
+ GroupType field = hasVector
+ ? vector(Types.required(FLOAT).named("element"))
+ : new GroupType(
+ REQUIRED,
+ "v",
+ listType(),
+ new GroupType(
+ Repetition.REPEATED,
+ "list",
+ Types.required(FLOAT).named("element")));
+ MessageType schema = new MessageType("m", new GroupType(REQUIRED, "parent", field));
+ assertThat(TypeUtil.hasFiniteVector(schema)).isEqualTo(hasVector);
+ }
+
@Test
public void testVectorOnPrimitive() {
assertThatThrownBy(() -> Types.required(FLOAT).as(finiteVectorType(5)).named("v"))
diff --git a/parquet-hadoop/src/main/java/org/apache/parquet/format/converter/ParquetMetadataConverter.java b/parquet-hadoop/src/main/java/org/apache/parquet/format/converter/ParquetMetadataConverter.java
index 4e94edf5b8..88801d5175 100644
--- a/parquet-hadoop/src/main/java/org/apache/parquet/format/converter/ParquetMetadataConverter.java
+++ b/parquet-hadoop/src/main/java/org/apache/parquet/format/converter/ParquetMetadataConverter.java
@@ -141,7 +141,6 @@
import org.apache.parquet.schema.PrimitiveType;
import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
import org.apache.parquet.schema.Type.Repetition;
-import org.apache.parquet.schema.TypeUtil;
import org.apache.parquet.schema.TypeVisitor;
import org.apache.parquet.schema.Types;
import org.slf4j.Logger;
@@ -1346,8 +1345,8 @@ LogicalTypeAnnotation getLogicalTypeAnnotation(LogicalType type) {
case ENUM -> LogicalTypeAnnotation.enumType();
case JSON -> LogicalTypeAnnotation.jsonType();
case LIST -> LogicalTypeAnnotation.listType();
- case FINITE__VECTOR -> LogicalTypeAnnotation.finiteVectorType(
- type.getFINITE_VECTOR().getNum_elements());
+ case FINITE__VECTOR ->
+ LogicalTypeAnnotation.finiteVectorType(type.getFINITE_VECTOR().getNum_elements());
case TIME -> {
TimeType time = type.getTIME();
yield LogicalTypeAnnotation.timeType(time.isAdjustedToUTC, convertTimeUnit(time.unit));
@@ -2041,13 +2040,11 @@ MessageType fromParquetSchema(List schema, List colu
}
Iterator columnOrderIterator = columnOrders == null ? null : columnOrders.iterator();
buildChildren(builder, iterator, root.getNum_children(), columnOrderIterator);
- MessageType messageType = builder.named(root.name);
try {
- TypeUtil.checkValidFiniteVectorSchema(messageType);
+ return builder.named(root.name);
} catch (InvalidSchemaException e) {
throw new ParquetDecodingException("Invalid FINITE_VECTOR schema in file metadata", e);
}
- return messageType;
}
private void buildChildren(
diff --git a/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestVectorReadWrite.java b/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestVectorReadWrite.java
index abe8350cef..6e70f81be0 100644
--- a/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestVectorReadWrite.java
+++ b/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestVectorReadWrite.java
@@ -159,10 +159,12 @@ public void testVectorRoundTrip(WriterVersion version, boolean dictionary) throw
if (version == WriterVersion.PARQUET_1_0 && !dictionary) {
Path legacy = new Path(tempDir.resolve("legacy-list.parquet").toUri());
Files.copy(java.nio.file.Path.of(path.toUri()), java.nio.file.Path.of(legacy.toUri()));
- rewriteFooter(legacy, footer -> footer.getSchema().stream()
- .filter(element -> element.isSetLogicalType()
- && element.getLogicalType().isSetFINITE_VECTOR())
- .forEach(org.apache.parquet.format.SchemaElement::unsetLogicalType));
+ rewriteFooter(
+ legacy,
+ footer -> footer.getSchema().stream()
+ .filter(element -> element.isSetLogicalType()
+ && element.getLogicalType().isSetFINITE_VECTOR())
+ .forEach(org.apache.parquet.format.SchemaElement::unsetLogicalType));
List vectors = readFooter(legacy).getSchema().stream()
.filter(element -> element.getConverted_type() == org.apache.parquet.format.ConvertedType.LIST)
.toList();
@@ -334,6 +336,13 @@ public void testMandatoryVectorValidation() throws IOException {
v.addGroup("list").append("element", 2.0f);
assertInvalidWrite(missingId, missingRow, "required field is missing required int32 id");
+ MessageType missingElement = schema("required", 2, false);
+ Group missingElementRow = new SimpleGroupFactory(missingElement).newGroup();
+ Group missingElementVector = missingElementRow.addGroup("v");
+ missingElementVector.addGroup("list").append("element", 1.0f);
+ missingElementVector.addGroup("list");
+ assertInvalidWrite(missingElement, missingElementRow, "required field is missing required float element");
+
MessageType duplicate = schema("required", 2, false);
Group duplicateRow = new SimpleGroupFactory(duplicate).newGroup();
for (int i = 0; i < 2; i++) {
diff --git a/parquet-protobuf/src/main/java/org/apache/parquet/proto/ProtoMessageConverter.java b/parquet-protobuf/src/main/java/org/apache/parquet/proto/ProtoMessageConverter.java
index 48c076fcac..a3cf08ec3f 100644
--- a/parquet-protobuf/src/main/java/org/apache/parquet/proto/ProtoMessageConverter.java
+++ b/parquet-protobuf/src/main/java/org/apache/parquet/proto/ProtoMessageConverter.java
@@ -67,7 +67,6 @@
import org.apache.parquet.schema.LogicalTypeAnnotation;
import org.apache.parquet.schema.PrimitiveType;
import org.apache.parquet.schema.Type;
-import org.apache.parquet.schema.TypeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -272,7 +271,6 @@ public Optional visit(LogicalTypeAnnotation.ListLogicalTypeAnnotation
@Override
public Optional visit(
LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation vectorLogicalType) {
- TypeUtil.checkValidFiniteVectorSchema(parquetType);
if (!fieldDescriptor.isRepeated()) {
throw new IncompatibleSchemaModificationException("FINITE_VECTOR field "
+ fieldDescriptor.getName() + " requires a repeated protobuf field");
@@ -897,8 +895,6 @@ public ListConverter(
Message.Builder parentBuilder, Descriptors.FieldDescriptor fieldDescriptor, Type parquetType) {
LogicalTypeAnnotation logicalTypeAnnotation = parquetType.getLogicalTypeAnnotation();
if (!(logicalTypeAnnotation instanceof LogicalTypeAnnotation.ListLogicalTypeAnnotation)
- && !(logicalTypeAnnotation
- instanceof LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation)
|| parquetType.isPrimitive()) {
throw new ParquetDecodingException(
"Expected LIST wrapper. Found: " + logicalTypeAnnotation + " instead.");
From 869eb6ce98d45b563fe2478d2a2fe860e6cc349c Mon Sep 17 00:00:00 2001
From: Divjot Arora
Date: Wed, 4 Nov 2026 13:33:38 +0000
Subject: [PATCH 15/15] GH-3877: Validate FINITE_VECTOR schemas in the group
builder
Co-authored-by: Isaac
---
.../apache/parquet/schema/MessageType.java | 2 -
.../org/apache/parquet/schema/TypeUtil.java | 71 -------------------
.../java/org/apache/parquet/schema/Types.java | 48 +++++++++++--
.../TestTypeBuildersWithLogicalTypes.java | 46 +++++++-----
.../converter/ParquetMetadataConverter.java | 2 +-
5 files changed, 75 insertions(+), 94 deletions(-)
diff --git a/parquet-column/src/main/java/org/apache/parquet/schema/MessageType.java b/parquet-column/src/main/java/org/apache/parquet/schema/MessageType.java
index 7901745574..1aba0cc6af 100644
--- a/parquet-column/src/main/java/org/apache/parquet/schema/MessageType.java
+++ b/parquet-column/src/main/java/org/apache/parquet/schema/MessageType.java
@@ -34,7 +34,6 @@ public final class MessageType extends GroupType {
*/
public MessageType(String name, Type... fields) {
super(Repetition.REPEATED, name, fields);
- TypeUtil.checkValidFiniteVectorSchema(this);
}
/**
@@ -43,7 +42,6 @@ public MessageType(String name, Type... fields) {
*/
public MessageType(String name, List fields) {
super(Repetition.REPEATED, name, fields);
- TypeUtil.checkValidFiniteVectorSchema(this);
}
/**
diff --git a/parquet-column/src/main/java/org/apache/parquet/schema/TypeUtil.java b/parquet-column/src/main/java/org/apache/parquet/schema/TypeUtil.java
index c18a5cef79..b32efce395 100644
--- a/parquet-column/src/main/java/org/apache/parquet/schema/TypeUtil.java
+++ b/parquet-column/src/main/java/org/apache/parquet/schema/TypeUtil.java
@@ -18,29 +18,9 @@
*/
package org.apache.parquet.schema;
-import org.apache.parquet.schema.LogicalTypeAnnotation.DecimalLogicalTypeAnnotation;
import org.apache.parquet.schema.LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation;
-import org.apache.parquet.schema.LogicalTypeAnnotation.Float16LogicalTypeAnnotation;
-import org.apache.parquet.schema.LogicalTypeAnnotation.IntLogicalTypeAnnotation;
-import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
-import org.apache.parquet.schema.Type.Repetition;
public class TypeUtil {
- public static boolean checkValidFiniteVectorSchema(Type type) {
- boolean found = type.getLogicalTypeAnnotation() instanceof FiniteVectorLogicalTypeAnnotation;
- if (found) {
- if (type.isPrimitive()) {
- throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + type);
- }
- checkVector(type.asGroupType());
- } else if (!type.isPrimitive()) {
- for (Type child : type.asGroupType().getFields()) {
- found |= checkValidFiniteVectorSchema(child);
- }
- }
- return found;
- }
-
public static boolean hasFiniteVector(Type type) {
if (type.getLogicalTypeAnnotation() instanceof FiniteVectorLogicalTypeAnnotation) return true;
if (!type.isPrimitive()) {
@@ -51,57 +31,6 @@ public static boolean hasFiniteVector(Type type) {
return false;
}
- private static void checkVector(GroupType vector) {
- if (vector.getRepetition() == Repetition.REPEATED || vector.getFieldCount() != 1) {
- throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
- }
- Type listType = vector.getType(0);
- if (listType.isPrimitive()
- || !listType.getName().equals("list")
- || listType.getRepetition() != Repetition.REPEATED
- || listType.getLogicalTypeAnnotation() != null) {
- throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
- }
- GroupType list = listType.asGroupType();
- if (list.getFieldCount() != 1) {
- throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
- }
- Type elementType = list.getType(0);
- if (!elementType.isPrimitive()
- || !elementType.getName().equals("element")
- || elementType.getRepetition() != Repetition.REQUIRED) {
- throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
- }
- PrimitiveType element = elementType.asPrimitiveType();
- PrimitiveTypeName physical = element.getPrimitiveTypeName();
- LogicalTypeAnnotation logical = element.getLogicalTypeAnnotation();
- boolean allowed = switch (physical) {
- case BOOLEAN, FLOAT, DOUBLE -> logical == null;
- case INT32, INT64 ->
- logical == null
- || logical instanceof IntLogicalTypeAnnotation
- || logical instanceof DecimalLogicalTypeAnnotation;
- case FIXED_LEN_BYTE_ARRAY ->
- logical instanceof Float16LogicalTypeAnnotation || logical instanceof DecimalLogicalTypeAnnotation;
- default -> false;
- };
- if (!allowed) {
- throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
- }
- try {
- Types.PrimitiveBuilder builder = Types.primitive(physical, element.getRepetition());
- if (physical == PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY) {
- builder.length(element.getTypeLength());
- }
- if (logical != null) {
- builder.as(logical);
- }
- builder.named(element.getName());
- } catch (IllegalArgumentException | IllegalStateException e) {
- throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector, e);
- }
- }
-
public static FiniteVectorLogicalTypeAnnotation getFiniteVectorType(GroupType schema, String[] path) {
if (path.length < 3) {
return null;
diff --git a/parquet-column/src/main/java/org/apache/parquet/schema/Types.java b/parquet-column/src/main/java/org/apache/parquet/schema/Types.java
index 2d6f0cbf8f..90205fe569 100644
--- a/parquet-column/src/main/java/org/apache/parquet/schema/Types.java
+++ b/parquet-column/src/main/java/org/apache/parquet/schema/Types.java
@@ -29,6 +29,10 @@
import java.util.stream.Collectors;
import org.apache.parquet.Preconditions;
import org.apache.parquet.schema.ColumnOrder.ColumnOrderName;
+import org.apache.parquet.schema.LogicalTypeAnnotation.DecimalLogicalTypeAnnotation;
+import org.apache.parquet.schema.LogicalTypeAnnotation.FiniteVectorLogicalTypeAnnotation;
+import org.apache.parquet.schema.LogicalTypeAnnotation.Float16LogicalTypeAnnotation;
+import org.apache.parquet.schema.LogicalTypeAnnotation.IntLogicalTypeAnnotation;
import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
import org.apache.parquet.schema.Type.ID;
import org.slf4j.Logger;
@@ -846,10 +850,46 @@ public THIS addFields(Type... types) {
@Override
protected GroupType build(String name) {
- if (newLogicalTypeSet) {
- return new GroupType(repetition, name, logicalTypeAnnotation, fields, id);
- } else {
- return new GroupType(repetition, name, getOriginalType(), fields, id);
+ GroupType group = newLogicalTypeSet
+ ? new GroupType(repetition, name, logicalTypeAnnotation, fields, id)
+ : new GroupType(repetition, name, getOriginalType(), fields, id);
+ if (logicalTypeAnnotation instanceof FiniteVectorLogicalTypeAnnotation) {
+ checkFiniteVector(group);
+ }
+ return group;
+ }
+
+ private static void checkFiniteVector(GroupType vector) {
+ if (vector.isRepetition(Type.Repetition.REPEATED) || vector.getFieldCount() != 1) {
+ throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
+ }
+ Type list = vector.getType(0);
+ if (list.isPrimitive()
+ || !list.getName().equals("list")
+ || !list.isRepetition(Type.Repetition.REPEATED)
+ || list.getLogicalTypeAnnotation() != null
+ || list.asGroupType().getFieldCount() != 1) {
+ throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
+ }
+ Type element = list.asGroupType().getType(0);
+ if (!element.isPrimitive()
+ || !element.getName().equals("element")
+ || !element.isRepetition(Type.Repetition.REQUIRED)) {
+ throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
+ }
+ LogicalTypeAnnotation logical = element.getLogicalTypeAnnotation();
+ boolean allowed = switch (element.asPrimitiveType().getPrimitiveTypeName()) {
+ case BOOLEAN, FLOAT, DOUBLE -> logical == null;
+ case INT32, INT64 ->
+ logical == null
+ || logical instanceof IntLogicalTypeAnnotation
+ || logical instanceof DecimalLogicalTypeAnnotation;
+ case FIXED_LEN_BYTE_ARRAY ->
+ logical instanceof Float16LogicalTypeAnnotation || logical instanceof DecimalLogicalTypeAnnotation;
+ default -> false;
+ };
+ if (!allowed) {
+ throw new InvalidSchemaException("Invalid FINITE_VECTOR schema: " + vector);
}
}
diff --git a/parquet-column/src/test/java/org/apache/parquet/schema/TestTypeBuildersWithLogicalTypes.java b/parquet-column/src/test/java/org/apache/parquet/schema/TestTypeBuildersWithLogicalTypes.java
index e6b8d74112..4bc47f3c48 100644
--- a/parquet-column/src/test/java/org/apache/parquet/schema/TestTypeBuildersWithLogicalTypes.java
+++ b/parquet-column/src/test/java/org/apache/parquet/schema/TestTypeBuildersWithLogicalTypes.java
@@ -47,7 +47,6 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
@@ -147,33 +146,48 @@ private static Stream invalidVectors() {
vector(Types.required(FIXED_LEN_BYTE_ARRAY).length(2).named("element"))),
Arguments.of(vector(Types.required(INT96).named("element"))),
Arguments.of(vector(Types.required(BINARY).as(stringType()).named("element"))),
- Arguments.of(vector(Types.required(INT32).as(dateType()).named("element"))),
- Arguments.of(
- vector(Types.required(INT32).named("element").withLogicalTypeAnnotation(intType(64, true)))));
+ Arguments.of(vector(Types.required(INT32).as(dateType()).named("element"))));
}
@ParameterizedTest
@MethodSource("invalidVectors")
public void testInvalidVectorSchema(GroupType vector) {
- assertThatThrownBy(() -> new MessageType("m", vector))
+ assertThatThrownBy(() -> Types.buildGroup(vector.getRepetition())
+ .as(vector.getLogicalTypeAnnotation())
+ .addFields(vector.getFields().toArray(new Type[0]))
+ .named(vector.getName()))
.isInstanceOf(InvalidSchemaException.class)
.hasMessage("Invalid FINITE_VECTOR schema: " + vector);
}
private static Stream vectorConstructionBoundaries() {
- Type element = Types.optional(FLOAT).named("element");
- GroupType invalid = vector(element);
- GroupType bad = new GroupType(
- REQUIRED, "bad", finiteVectorType(5), new GroupType(Repetition.REPEATED, "list", element));
+ GroupType list =
+ new GroupType(Repetition.REPEATED, "list", Types.optional(FLOAT).named("element"));
return Stream.of(
- () -> new MessageType("m", Collections.singletonList(invalid)),
- () -> Types.buildMessage().addField(invalid).named("m"),
+ () -> Types.requiredGroup()
+ .as(finiteVectorType(5))
+ .addField(list)
+ .named("v"),
+ () -> Types.buildMessage()
+ .requiredGroup()
+ .as(finiteVectorType(5))
+ .repeatedGroup()
+ .optional(FLOAT)
+ .named("element")
+ .named("list")
+ .named("v")
+ .named("m"),
+ () -> Types.buildMessage()
+ .requiredGroup()
+ .addField(vector(Types.required(FLOAT).named("element")))
+ .requiredGroup()
+ .as(finiteVectorType(5))
+ .addField(list)
+ .named("bad")
+ .named("parent")
+ .named("m"),
() -> MessageTypeParser.parseMessageType("message m { required group v (FINITE_VECTOR(5)) { "
- + "repeated group list { optional float element; } } }"),
- () -> new MessageType(
- "m",
- new GroupType(
- REQUIRED, "parent", vector(Types.required(FLOAT).named("element")), bad)));
+ + "repeated group list { optional float element; } } }"));
}
@ParameterizedTest
diff --git a/parquet-hadoop/src/main/java/org/apache/parquet/format/converter/ParquetMetadataConverter.java b/parquet-hadoop/src/main/java/org/apache/parquet/format/converter/ParquetMetadataConverter.java
index 88801d5175..0dc348ec2d 100644
--- a/parquet-hadoop/src/main/java/org/apache/parquet/format/converter/ParquetMetadataConverter.java
+++ b/parquet-hadoop/src/main/java/org/apache/parquet/format/converter/ParquetMetadataConverter.java
@@ -2039,8 +2039,8 @@ MessageType fromParquetSchema(List schema, List colu
builder.id(root.field_id);
}
Iterator columnOrderIterator = columnOrders == null ? null : columnOrders.iterator();
- buildChildren(builder, iterator, root.getNum_children(), columnOrderIterator);
try {
+ buildChildren(builder, iterator, root.getNum_children(), columnOrderIterator);
return builder.named(root.name);
} catch (InvalidSchemaException e) {
throw new ParquetDecodingException("Invalid FINITE_VECTOR schema in file metadata", e);