-
Notifications
You must be signed in to change notification settings - Fork 45
Column projection of union type #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,8 @@ | |
| import org.apache.iceberg.types.Types; | ||
|
|
||
| public abstract class AvroSchemaWithTypeVisitor<T> { | ||
| private static final String UNION_TAG_FIELD_NAME = "tag"; | ||
|
|
||
| public static <T> T visit(org.apache.iceberg.Schema iSchema, Schema schema, AvroSchemaWithTypeVisitor<T> visitor) { | ||
| return visit(iSchema.asStruct(), schema, visitor); | ||
| } | ||
|
|
@@ -97,17 +99,65 @@ private static <T> T visitUnion(Type type, Schema union, AvroSchemaWithTypeVisit | |
| options.add(visit(type, branch, visitor)); | ||
| } | ||
| } else { // complex union case | ||
| int index = 1; | ||
| for (Schema branch : types) { | ||
| if (branch.getType() == Schema.Type.NULL) { | ||
| options.add(visit((Type) null, branch, visitor)); | ||
| } else { | ||
| options.add(visit(type.asStructType().fields().get(index).type(), branch, visitor)); | ||
| index += 1; | ||
| visitComplexUnion(type, union, visitor, options); | ||
| } | ||
| return visitor.union(type, union, options); | ||
| } | ||
|
|
||
| /* | ||
| A complex union with multiple types of Avro schema is converted into a struct with multiple fields of Iceberg schema. | ||
| A field is related to a type in the order defined in Avro schema. Also, an extra tag field is added into the struct of | ||
| Iceberg schema. The user can query the column of union type with the field projected (e.g. colUnion.field0) in which | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think Iceberg documentation tries to avoid describing scenarios referencing the user. Can we make this a factual statement about the function spec that does not involve referencing users? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure if we should make this function description revolve around projection pruning use case. Let us describe it in a more general sense. |
||
| the maximum number of the fields to be projected equals to the number of fields of the complete struct converted | ||
| from the union. The case of without field projection equals to the case of full fields projection. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| Therefore, this function visits the complex union by assuming the field projection always happens. | ||
| */ | ||
| private static <T> void visitComplexUnion(Type type, Schema union, | ||
| AvroSchemaWithTypeVisitor<T> visitor, List<T> options) { | ||
| boolean nullTypeFound = false; | ||
| int typeIndex = 0; | ||
| int fieldIndexInStruct = 0; | ||
| while (typeIndex < union.getTypes().size()) { | ||
| Schema schema = union.getTypes().get(typeIndex); | ||
| // in some cases, a NULL type exists in the union of Avro schema besides the actual types, | ||
| // and it affects the index of the actual types of the order in the union | ||
| if (schema.getType() == Schema.Type.NULL) { | ||
| nullTypeFound = true; | ||
| options.add(visit((Type) null, schema, visitor)); | ||
| typeIndex++; | ||
| continue; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to do |
||
| } | ||
|
|
||
| // If a NULL type is found before current type, the type index is one larger than the actual type index which | ||
| // can be used to track the corresponding field in the struct of Iceberg schema. | ||
| int actualTypeIndex = nullTypeFound ? typeIndex - 1 : typeIndex; | ||
| boolean relatedFieldInStructFound = false; | ||
| while (fieldIndexInStruct < type.asStructType().fields().size()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you need this loop or can you just keep two pointers and leverage the outer loop? |
||
| String structFieldName = type.asStructType().fields().get(fieldIndexInStruct).name(); | ||
| if (UNION_TAG_FIELD_NAME.equals(structFieldName)) { | ||
| fieldIndexInStruct++; | ||
| continue; | ||
| } | ||
|
|
||
| int indexFromStructFieldName = Integer.valueOf(structFieldName.substring(5)); | ||
| if (actualTypeIndex == indexFromStructFieldName) { | ||
| relatedFieldInStructFound = true; | ||
| options.add(visit(type.asStructType().fields().get(fieldIndexInStruct).type(), schema, visitor)); | ||
| fieldIndexInStruct++; | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| // If a field is not projected, a corresponding field in the struct of Iceberg schema cannot be found | ||
| // for current type of union in Avro schema, a reader for current type still needs to be created and | ||
| // used to make the reading of Avro file successfully. In this case, a null field type is used to | ||
| // create the option for the reader of the current type which still can read the corresponding content | ||
| // in Avro file successfully. | ||
| if (!relatedFieldInStructFound) { | ||
| options.add(visit((Type) null, schema, visitor)); | ||
| } | ||
| typeIndex++; | ||
| } | ||
| return visitor.union(type, union, options); | ||
| } | ||
|
|
||
| private static <T> T visitArray(Type type, Schema array, AvroSchemaWithTypeVisitor<T> visitor) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we frame
A field is related to a type in the order defined in Avro schemaas an expectation from the Iceberg schema to match the Avro schema?