blob: d1833558a2f6f29e4e9bd44814a6304b2e6a2ef1 [file] [log] [blame]
/*
* 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.iceberg.types;
import java.util.List;
import java.util.function.Supplier;
import org.apache.iceberg.Schema;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
/**
* This is used to fix primitive types to match a table schema. This uses a reference schema to
* override types that were lost in round-trip conversion.
*/
public abstract class FixupTypes extends TypeUtil.CustomOrderSchemaVisitor<Type> {
private final Schema referenceSchema;
private Type sourceType;
protected FixupTypes(Schema referenceSchema) {
this.referenceSchema = referenceSchema;
this.sourceType = referenceSchema.asStruct();
}
@Override
public Type schema(Schema schema, Supplier<Type> future) {
this.sourceType = referenceSchema.asStruct();
return future.get();
}
@Override
public Type struct(Types.StructType struct, Iterable<Type> fieldTypes) {
Preconditions.checkArgument(sourceType.isStructType(), "Not a struct: %s", sourceType);
List<Types.NestedField> fields = struct.fields();
int length = fields.size();
List<Type> types = Lists.newArrayList(fieldTypes);
List<Types.NestedField> newFields = Lists.newArrayListWithExpectedSize(length);
boolean hasChange = false;
for (int i = 0; i < length; i += 1) {
Types.NestedField field = fields.get(i);
Type resultType = types.get(i);
if (field.type() == resultType) {
newFields.add(field);
} else if (field.isRequired()) {
hasChange = true;
newFields.add(Types.NestedField.required(field.fieldId(), field.name(), resultType));
} else {
hasChange = true;
newFields.add(Types.NestedField.optional(field.fieldId(), field.name(), resultType));
}
}
if (hasChange) {
return Types.StructType.of(newFields);
}
return struct;
}
@Override
public Type field(Types.NestedField field, Supplier<Type> future) {
Preconditions.checkArgument(sourceType.isStructType(), "Not a struct: %s", sourceType);
Types.StructType sourceStruct = sourceType.asStructType();
this.sourceType = sourceStruct.field(field.fieldId()).type();
try {
return future.get();
} finally {
sourceType = sourceStruct;
}
}
@Override
public Type list(Types.ListType list, Supplier<Type> elementTypeFuture) {
Preconditions.checkArgument(sourceType.isListType(), "Not a list: %s", sourceType);
Types.ListType sourceList = sourceType.asListType();
this.sourceType = sourceList.elementType();
try {
Type elementType = elementTypeFuture.get();
if (list.elementType() == elementType) {
return list;
}
if (list.isElementOptional()) {
return Types.ListType.ofOptional(list.elementId(), elementType);
} else {
return Types.ListType.ofRequired(list.elementId(), elementType);
}
} finally {
this.sourceType = sourceList;
}
}
@Override
public Type map(Types.MapType map, Supplier<Type> keyTypeFuture, Supplier<Type> valueTypeFuture) {
Preconditions.checkArgument(sourceType.isMapType(), "Not a map: %s", sourceType);
Types.MapType sourceMap = sourceType.asMapType();
try {
this.sourceType = sourceMap.keyType();
Type keyType = keyTypeFuture.get();
this.sourceType = sourceMap.valueType();
Type valueType = valueTypeFuture.get();
if (map.keyType() == keyType && map.valueType() == valueType) {
return map;
}
if (map.isValueOptional()) {
return Types.MapType.ofOptional(map.keyId(), map.valueId(), keyType, valueType);
} else {
return Types.MapType.ofRequired(map.keyId(), map.valueId(), keyType, valueType);
}
} finally {
this.sourceType = sourceMap;
}
}
@Override
public Type primitive(Type.PrimitiveType primitive) {
if (sourceType.equals(primitive)) {
return primitive; // already correct
}
if (fixupPrimitive(primitive, sourceType)) {
return sourceType;
}
// nothing to fix up, let validation catch promotion errors
return primitive;
}
protected abstract boolean fixupPrimitive(Type.PrimitiveType type, Type source);
}