/*
* Licensed 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 com.facebook.presto.hive;
import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.spi.type.NamedTypeSignature;
import com.facebook.presto.spi.type.StandardTypes;
import com.facebook.presto.spi.type.Type;
import com.facebook.presto.spi.type.TypeManager;
import com.facebook.presto.spi.type.TypeSignature;
import com.facebook.presto.spi.type.TypeSignatureParameter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.google.common.collect.ImmutableList;
import org.apache.hadoop.hive.serde2.typeinfo.CharTypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.ListTypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.MapTypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.PrimitiveTypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.StructTypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.VarcharTypeInfo;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Locale;
import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED;
import static com.facebook.presto.spi.type.BigintType.BIGINT;
import static com.facebook.presto.spi.type.BooleanType.BOOLEAN;
import static com.facebook.presto.spi.type.CharType.createCharType;
import static com.facebook.presto.spi.type.DateType.DATE;
import static com.facebook.presto.spi.type.DecimalType.createDecimalType;
import static com.facebook.presto.spi.type.DoubleType.DOUBLE;
import static com.facebook.presto.spi.type.IntegerType.INTEGER;
import static com.facebook.presto.spi.type.RealType.REAL;
import static com.facebook.presto.spi.type.SmallintType.SMALLINT;
import static com.facebook.presto.spi.type.TimestampType.TIMESTAMP;
import static com.facebook.presto.spi.type.TinyintType.TINYINT;
import static com.facebook.presto.spi.type.VarbinaryType.VARBINARY;
import static com.facebook.presto.spi.type.VarcharType.createUnboundedVarcharType;
import static com.facebook.presto.spi.type.VarcharType.createVarcharType;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.binaryTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.booleanTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.byteTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.dateTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.doubleTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.floatTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.intTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.longTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.shortTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.stringTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.timestampTypeInfo;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils.getTypeInfoFromTypeString;
import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils.getTypeInfosFromTypeString;
public final class HiveType
{
public static final HiveType HIVE_BOOLEAN = new HiveType(booleanTypeInfo);
public static final HiveType HIVE_BYTE = new HiveType(byteTypeInfo);
public static final HiveType HIVE_SHORT = new HiveType(shortTypeInfo);
public static final HiveType HIVE_INT = new HiveType(intTypeInfo);
public static final HiveType HIVE_LONG = new HiveType(longTypeInfo);
public static final HiveType HIVE_FLOAT = new HiveType(floatTypeInfo);
public static final HiveType HIVE_DOUBLE = new HiveType(doubleTypeInfo);
public static final HiveType HIVE_STRING = new HiveType(stringTypeInfo);
public static final HiveType HIVE_TIMESTAMP = new HiveType(timestampTypeInfo);
public static final HiveType HIVE_DATE = new HiveType(dateTypeInfo);
public static final HiveType HIVE_BINARY = new HiveType(binaryTypeInfo);
private final String hiveTypeName;
private final TypeInfo typeInfo;
private HiveType(TypeInfo typeInfo)
{
requireNonNull(typeInfo, "typeInfo is null");
this.hiveTypeName = typeInfo.getTypeName();
this.typeInfo = typeInfo;
}
@JsonValue
public String getHiveTypeName()
{
return hiveTypeName;
}
public Category getCategory()
{
return typeInfo.getCategory();
}
public TypeInfo getTypeInfo()
{
return typeInfo;
}
public TypeSignature getTypeSignature()
{
return getTypeSignature(typeInfo);
}
public Type getType(TypeManager typeManager)
{
return typeManager.getType(getTypeSignature());
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
HiveType hiveType = (HiveType) o;
if (!hiveTypeName.equals(hiveType.hiveTypeName)) {
return false;
}
return true;
}
@Override
public int hashCode()
{
return hiveTypeName.hashCode();
}
@Override
public String toString()
{
return hiveTypeName;
}
public boolean isSupportedType()
{
return isSupportedType(getTypeInfo());
}
public static boolean isSupportedType(TypeInfo typeInfo)
{
switch (typeInfo.getCategory()) {
case PRIMITIVE:
return getPrimitiveType((PrimitiveTypeInfo) typeInfo) != null;
case MAP:
MapTypeInfo mapTypeInfo = (MapTypeInfo) typeInfo;
return isSupportedType(mapTypeInfo.getMapKeyTypeInfo()) && isSupportedType(mapTypeInfo.getMapValueTypeInfo());
case LIST:
ListTypeInfo listTypeInfo = (ListTypeInfo) typeInfo;
return isSupportedType(listTypeInfo.getListElementTypeInfo());
case STRUCT:
StructTypeInfo structTypeInfo = (StructTypeInfo) typeInfo;
return structTypeInfo.getAllStructFieldTypeInfos().stream()
.allMatch(HiveType::isSupportedType);
}
return false;
}
@JsonCreator
@Nonnull
public static HiveType valueOf(String hiveTypeName)
{
requireNonNull(hiveTypeName, "hiveTypeName is null");
return toHiveType(getTypeInfoFromTypeString(hiveTypeName));
}
@Nonnull
public static List<HiveType> toHiveTypes(String hiveTypes)
{
requireNonNull(hiveTypes, "hiveTypes is null");
return ImmutableList.copyOf(getTypeInfosFromTypeString(hiveTypes).stream()
.map(HiveType::toHiveType)
.collect(toList()));
}
@Nonnull
private static HiveType toHiveType(TypeInfo typeInfo)
{
requireNonNull(typeInfo, "typeInfo is null");
return new HiveType(typeInfo);
}
@Nonnull
public static HiveType toHiveType(TypeTranslator typeTranslator, Type type)
{
requireNonNull(typeTranslator, "typeTranslator is null");
requireNonNull(type, "type is null");
return new HiveType(typeTranslator.translate(type));
}
@Nonnull
private static TypeSignature getTypeSignature(TypeInfo typeInfo)
{
switch (typeInfo.getCategory()) {
case PRIMITIVE:
Type primitiveType = getPrimitiveType((PrimitiveTypeInfo) typeInfo);
if (primitiveType == null) {
break;
}
return primitiveType.getTypeSignature();
case MAP:
MapTypeInfo mapTypeInfo = (MapTypeInfo) typeInfo;
TypeSignature keyType = getTypeSignature(mapTypeInfo.getMapKeyTypeInfo());
TypeSignature valueType = getTypeSignature(mapTypeInfo.getMapValueTypeInfo());
return new TypeSignature(
StandardTypes.MAP,
ImmutableList.of(TypeSignatureParameter.of(keyType), TypeSignatureParameter.of(valueType)));
case LIST:
ListTypeInfo listTypeInfo = (ListTypeInfo) typeInfo;
TypeSignature elementType = getTypeSignature(listTypeInfo.getListElementTypeInfo());
return new TypeSignature(
StandardTypes.ARRAY,
ImmutableList.of(TypeSignatureParameter.of(elementType)));
case STRUCT:
StructTypeInfo structTypeInfo = (StructTypeInfo) typeInfo;
List<TypeInfo> structFieldTypeInfos = structTypeInfo.getAllStructFieldTypeInfos();
List<String> structFieldNames = structTypeInfo.getAllStructFieldNames();
if (structFieldTypeInfos.size() != structFieldNames.size()) {
throw new PrestoException(HiveErrorCode.HIVE_INVALID_METADATA, format("Invalid Hive struct type: %s", typeInfo));
}
ImmutableList.Builder<TypeSignatureParameter> typeSignatureBuilder = ImmutableList.builder();
for (int i = 0; i < structFieldTypeInfos.size(); i++) {
TypeSignature typeSignature = getTypeSignature(structFieldTypeInfos.get(i));
// Lower case the struct field names.
// Otherwise, Presto will refuse to write to columns whose struct type has field names containing upper case characters.
// Users can't work around this by casting in their queries because Presto parser always lower case types.
// TODO: This is a hack. Presto engine should be able to handle identifiers in a case insensitive way where necessary.
String rowFieldName = structFieldNames.get(i).toLowerCase(Locale.US);
typeSignatureBuilder.add(TypeSignatureParameter.of(new NamedTypeSignature(rowFieldName, typeSignature)));
}
return new TypeSignature(StandardTypes.ROW, typeSignatureBuilder.build());
}
throw new PrestoException(NOT_SUPPORTED, format("Unsupported Hive type: %s", typeInfo));
}
public static Type getPrimitiveType(PrimitiveTypeInfo typeInfo)
{
switch (typeInfo.getPrimitiveCategory()) {
case BOOLEAN:
return BOOLEAN;
case BYTE:
return TINYINT;
case SHORT:
return SMALLINT;
case INT:
return INTEGER;
case LONG:
return BIGINT;
case FLOAT:
return REAL;
case DOUBLE:
return DOUBLE;
case STRING:
return createUnboundedVarcharType();
case VARCHAR:
return createVarcharType(((VarcharTypeInfo) typeInfo).getLength());
case CHAR:
return createCharType(((CharTypeInfo) typeInfo).getLength());
case DATE:
return DATE;
case TIMESTAMP:
return TIMESTAMP;
case BINARY:
return VARBINARY;
case DECIMAL:
DecimalTypeInfo decimalTypeInfo = (DecimalTypeInfo) typeInfo;
return createDecimalType(decimalTypeInfo.precision(), decimalTypeInfo.scale());
default:
return null;
}
}
}