/*
* 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.type;
import com.facebook.presto.metadata.FunctionRegistry;
import com.facebook.presto.spi.function.OperatorType;
import com.facebook.presto.spi.type.CharType;
import com.facebook.presto.spi.type.DecimalType;
import com.facebook.presto.spi.type.ParametricType;
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.TypeParameter;
import com.facebook.presto.spi.type.TypeSignature;
import com.facebook.presto.spi.type.TypeSignatureParameter;
import com.facebook.presto.spi.type.VarcharType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import java.lang.invoke.MethodHandle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
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.HyperLogLogType.HYPER_LOG_LOG;
import static com.facebook.presto.spi.type.IntegerType.INTEGER;
import static com.facebook.presto.spi.type.P4HyperLogLogType.P4_HYPER_LOG_LOG;
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.TimeType.TIME;
import static com.facebook.presto.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE;
import static com.facebook.presto.spi.type.TimestampType.TIMESTAMP;
import static com.facebook.presto.spi.type.TimestampWithTimeZoneType.TIMESTAMP_WITH_TIME_ZONE;
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 com.facebook.presto.type.ArrayParametricType.ARRAY;
import static com.facebook.presto.type.CodePointsType.CODE_POINTS;
import static com.facebook.presto.type.ColorType.COLOR;
import static com.facebook.presto.type.FunctionParametricType.FUNCTION;
import static com.facebook.presto.type.IntervalDayTimeType.INTERVAL_DAY_TIME;
import static com.facebook.presto.type.IntervalYearMonthType.INTERVAL_YEAR_MONTH;
import static com.facebook.presto.type.JoniRegexpType.JONI_REGEXP;
import static com.facebook.presto.type.JsonPathType.JSON_PATH;
import static com.facebook.presto.type.JsonType.JSON;
import static com.facebook.presto.type.LikePatternType.LIKE_PATTERN;
import static com.facebook.presto.type.MapParametricType.MAP;
import static com.facebook.presto.type.Re2JRegexpType.RE2J_REGEXP;
import static com.facebook.presto.type.RowParametricType.ROW;
import static com.facebook.presto.type.UnknownType.UNKNOWN;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
@ThreadSafe
public final class TypeRegistry
implements TypeManager
{
private final ConcurrentMap<TypeSignature, Type> types = new ConcurrentHashMap<>();
private final ConcurrentMap<String, ParametricType> parametricTypes = new ConcurrentHashMap<>();
private FunctionRegistry functionRegistry;
public TypeRegistry()
{
this(ImmutableSet.of());
}
@Inject
public TypeRegistry(Set<Type> types)
{
requireNonNull(types, "types is null");
// Manually register UNKNOWN type without a verifyTypeClass call since it is a special type that can not be used by functions
this.types.put(UNKNOWN.getTypeSignature(), UNKNOWN);
// always add the built-in types; Presto will not function without these
addType(BOOLEAN);
addType(BIGINT);
addType(INTEGER);
addType(SMALLINT);
addType(TINYINT);
addType(DOUBLE);
addType(REAL);
addType(VARBINARY);
addType(DATE);
addType(TIME);
addType(TIME_WITH_TIME_ZONE);
addType(TIMESTAMP);
addType(TIMESTAMP_WITH_TIME_ZONE);
addType(INTERVAL_YEAR_MONTH);
addType(INTERVAL_DAY_TIME);
addType(HYPER_LOG_LOG);
addType(P4_HYPER_LOG_LOG);
addType(JONI_REGEXP);
addType(RE2J_REGEXP);
addType(LIKE_PATTERN);
addType(JSON_PATH);
addType(COLOR);
addType(JSON);
addType(CODE_POINTS);
addParametricType(VarcharParametricType.VARCHAR);
addParametricType(CharParametricType.CHAR);
addParametricType(DecimalParametricType.DECIMAL);
addParametricType(ROW);
addParametricType(ARRAY);
addParametricType(MAP);
addParametricType(FUNCTION);
for (Type type : types) {
addType(type);
}
}
public void setFunctionRegistry(FunctionRegistry functionRegistry)
{
checkState(this.functionRegistry == null, "TypeRegistry can only be associated with a single FunctionRegistry");
this.functionRegistry = requireNonNull(functionRegistry, "functionRegistry is null");
}
@Override
public Type getType(TypeSignature signature)
{
Type type = types.get(signature);
if (type == null) {
return instantiateParametricType(signature);
}
return type;
}
@Override
public Type getParameterizedType(String baseTypeName, List<TypeSignatureParameter> typeParameters)
{
return getType(new TypeSignature(baseTypeName, typeParameters));
}
private Type instantiateParametricType(TypeSignature signature)
{
List<TypeParameter> parameters = new ArrayList<>();
for (TypeSignatureParameter parameter : signature.getParameters()) {
TypeParameter typeParameter = TypeParameter.of(parameter, this);
if (typeParameter == null) {
return null;
}
parameters.add(typeParameter);
}
ParametricType parametricType = parametricTypes.get(signature.getBase().toLowerCase(Locale.ENGLISH));
if (parametricType == null) {
return null;
}
try {
Type instantiatedType = parametricType.createType(this, parameters);
// TODO: reimplement this check? Currently "varchar(Integer.MAX_VALUE)" fails with "varchar"
//checkState(instantiatedType.equalsSignature(signature), "Instantiated parametric type name (%s) does not match expected name (%s)", instantiatedType, signature);
return instantiatedType;
}
catch (IllegalArgumentException e) {
// TODO: check whether a type constructor actually exists rather than failing when it doesn't. This will be possible in the next version of the type system
return null;
}
}
@Override
public List<Type> getTypes()
{
return ImmutableList.copyOf(types.values());
}
@Override
public boolean isTypeOnlyCoercion(Type source, Type result)
{
if (source.equals(result)) {
return true;
}
if (!canCoerce(source, result)) {
return false;
}
if (source instanceof VarcharType && result instanceof VarcharType) {
return true;
}
if (source instanceof DecimalType && result instanceof DecimalType) {
DecimalType sourceDecimal = (DecimalType) source;
DecimalType resultDecimal = (DecimalType) result;
boolean sameDecimalSubtype = (sourceDecimal.isShort() && resultDecimal.isShort())
|| (!sourceDecimal.isShort() && !resultDecimal.isShort());
boolean sameScale = sourceDecimal.getScale() == resultDecimal.getScale();
boolean sourcePrecisionIsLessOrEqualToResultPrecision = sourceDecimal.getPrecision() <= resultDecimal.getPrecision();
return sameDecimalSubtype && sameScale && sourcePrecisionIsLessOrEqualToResultPrecision;
}
String sourceTypeBase = source.getTypeSignature().getBase();
String resultTypeBase = result.getTypeSignature().getBase();
if (sourceTypeBase.equals(resultTypeBase) && isCovariantParametrizedType(source)) {
List<Type> sourceTypeParameters = source.getTypeParameters();
List<Type> resultTypeParameters = result.getTypeParameters();
checkState(sourceTypeParameters.size() == resultTypeParameters.size());
for (int i = 0; i < sourceTypeParameters.size(); i++) {
if (!isTypeOnlyCoercion(sourceTypeParameters.get(i), resultTypeParameters.get(i))) {
return false;
}
}
return true;
}
return false;
}
@Override
public Optional<Type> getCommonSuperType(Type firstType, Type secondType)
{
if (firstType.equals(secondType)) {
return Optional.of(secondType);
}
if (firstType.equals(UnknownType.UNKNOWN)) {
return Optional.of(secondType);
}
if (secondType.equals(UnknownType.UNKNOWN)) {
return Optional.of(firstType);
}
String firstTypeBaseName = firstType.getTypeSignature().getBase();
String secondTypeBaseName = secondType.getTypeSignature().getBase();
if (firstTypeBaseName.equals(secondTypeBaseName)) {
if (firstTypeBaseName.equals(StandardTypes.DECIMAL)) {
return Optional.of(getCommonSuperTypeForDecimal(
(DecimalType) firstType, (DecimalType) secondType));
}
if (firstTypeBaseName.equals(StandardTypes.VARCHAR)) {
return Optional.of(getCommonSuperTypeForVarchar(
(VarcharType) firstType, (VarcharType) secondType));
}
if (isCovariantParametrizedType(firstType)) {
return getCommonSuperTypeForCovariantParametrizedType(firstType, secondType);
}
return Optional.empty();
}
Optional<Type> coercedType = coerceTypeBase(firstType, secondType.getTypeSignature().getBase());
if (coercedType.isPresent()) {
return getCommonSuperType(coercedType.get(), secondType);
}
coercedType = coerceTypeBase(secondType, firstType.getTypeSignature().getBase());
if (coercedType.isPresent()) {
return getCommonSuperType(firstType, coercedType.get());
}
return Optional.empty();
}
private static Type getCommonSuperTypeForDecimal(DecimalType firstType, DecimalType secondType)
{
int targetScale = Math.max(firstType.getScale(), secondType.getScale());
int targetPrecision = Math.max(firstType.getPrecision() - firstType.getScale(), secondType.getPrecision() - secondType.getScale()) + targetScale;
//we allow potential loss of precision here. Overflow checking is done in operators.
targetPrecision = Math.min(38, targetPrecision);
return createDecimalType(targetPrecision, targetScale);
}
private static Type getCommonSuperTypeForVarchar(VarcharType firstType, VarcharType secondType)
{
if (firstType.isUnbounded() || secondType.isUnbounded()) {
return createUnboundedVarcharType();
}
return createVarcharType(Math.max(firstType.getLength(), secondType.getLength()));
}
private Optional<Type> getCommonSuperTypeForCovariantParametrizedType(Type firstType, Type secondType)
{
checkState(firstType.getClass().equals(secondType.getClass()));
ImmutableList.Builder<TypeSignatureParameter> commonParameterTypes = ImmutableList.builder();
List<Type> firstTypeParameters = firstType.getTypeParameters();
List<Type> secondTypeParameters = secondType.getTypeParameters();
checkState(firstTypeParameters.size() == secondTypeParameters.size());
for (int i = 0; i < firstTypeParameters.size(); i++) {
Optional<Type> commonParameterType = getCommonSuperType(firstTypeParameters.get(i), secondTypeParameters.get(i));
if (!commonParameterType.isPresent()) {
return Optional.empty();
}
commonParameterTypes.add(TypeSignatureParameter.of(commonParameterType.get().getTypeSignature()));
}
String typeName = firstType.getTypeSignature().getBase();
return Optional.of(getType(new TypeSignature(typeName, commonParameterTypes.build())));
}
public void addType(Type type)
{
requireNonNull(type, "type is null");
Type existingType = types.putIfAbsent(type.getTypeSignature(), type);
checkState(existingType == null || existingType.equals(type), "Type %s is already registered", type);
}
public void addParametricType(ParametricType parametricType)
{
String name = parametricType.getName().toLowerCase(Locale.ENGLISH);
checkArgument(!parametricTypes.containsKey(name), "Parametric type already registered: %s", name);
parametricTypes.putIfAbsent(name, parametricType);
}
@Override
public Collection<ParametricType> getParametricTypes()
{
return ImmutableList.copyOf(parametricTypes.values());
}
/**
* coerceTypeBase and isCovariantParametrizedType defines all hand-coded rules for type coercion.
* Other methods should reference these two functions instead of hand-code new rules.
*/
@Override
public Optional<Type> coerceTypeBase(Type sourceType, String resultTypeBase)
{
String sourceTypeName = sourceType.getTypeSignature().getBase();
if (sourceTypeName.equals(resultTypeBase)) {
return Optional.of(sourceType);
}
switch (sourceTypeName) {
case UnknownType.NAME: {
switch (resultTypeBase) {
case StandardTypes.BOOLEAN:
case StandardTypes.BIGINT:
case StandardTypes.INTEGER:
case StandardTypes.DOUBLE:
case StandardTypes.REAL:
case StandardTypes.VARBINARY:
case StandardTypes.DATE:
case StandardTypes.TIME:
case StandardTypes.TIME_WITH_TIME_ZONE:
case StandardTypes.TIMESTAMP:
case StandardTypes.TIMESTAMP_WITH_TIME_ZONE:
case StandardTypes.HYPER_LOG_LOG:
case StandardTypes.P4_HYPER_LOG_LOG:
case StandardTypes.JSON:
case StandardTypes.INTERVAL_YEAR_TO_MONTH:
case StandardTypes.INTERVAL_DAY_TO_SECOND:
case JoniRegexpType.NAME:
case LikePatternType.NAME:
case JsonPathType.NAME:
case ColorType.NAME:
case CodePointsType.NAME:
return Optional.of(getType(new TypeSignature(resultTypeBase)));
case StandardTypes.VARCHAR:
return Optional.of(createVarcharType(0));
case StandardTypes.CHAR:
return Optional.of(createCharType(0));
case StandardTypes.DECIMAL:
return Optional.of(createDecimalType(1, 0));
default:
return Optional.empty();
}
}
case StandardTypes.TINYINT: {
switch (resultTypeBase) {
case StandardTypes.SMALLINT:
return Optional.of(SMALLINT);
case StandardTypes.INTEGER:
return Optional.of(INTEGER);
case StandardTypes.BIGINT:
return Optional.of(BIGINT);
case StandardTypes.REAL:
return Optional.of(REAL);
case StandardTypes.DOUBLE:
return Optional.of(DOUBLE);
case StandardTypes.DECIMAL:
return Optional.of(createDecimalType(3, 0));
default:
return Optional.empty();
}
}
case StandardTypes.SMALLINT: {
switch (resultTypeBase) {
case StandardTypes.INTEGER:
return Optional.of(INTEGER);
case StandardTypes.BIGINT:
return Optional.of(BIGINT);
case StandardTypes.REAL:
return Optional.of(REAL);
case StandardTypes.DOUBLE:
return Optional.of(DOUBLE);
case StandardTypes.DECIMAL:
return Optional.of(createDecimalType(5, 0));
default:
return Optional.empty();
}
}
case StandardTypes.INTEGER: {
switch (resultTypeBase) {
case StandardTypes.BIGINT:
return Optional.of(BIGINT);
case StandardTypes.REAL:
return Optional.of(REAL);
case StandardTypes.DOUBLE:
return Optional.of(DOUBLE);
case StandardTypes.DECIMAL:
return Optional.of(createDecimalType(10, 0));
default:
return Optional.empty();
}
}
case StandardTypes.BIGINT: {
switch (resultTypeBase) {
case StandardTypes.REAL:
return Optional.of(REAL);
case StandardTypes.DOUBLE:
return Optional.of(DOUBLE);
case StandardTypes.DECIMAL:
return Optional.of(createDecimalType(19, 0));
default:
return Optional.empty();
}
}
case StandardTypes.DECIMAL: {
switch (resultTypeBase) {
case StandardTypes.REAL:
return Optional.of(REAL);
case StandardTypes.DOUBLE:
return Optional.of(DOUBLE);
default:
return Optional.empty();
}
}
case StandardTypes.REAL: {
switch (resultTypeBase) {
case StandardTypes.DOUBLE:
return Optional.of(DOUBLE);
default:
return Optional.empty();
}
}
case StandardTypes.DATE: {
switch (resultTypeBase) {
case StandardTypes.TIMESTAMP:
return Optional.of(TIMESTAMP);
case StandardTypes.TIMESTAMP_WITH_TIME_ZONE:
return Optional.of(TIMESTAMP_WITH_TIME_ZONE);
default:
return Optional.empty();
}
}
case StandardTypes.TIME: {
switch (resultTypeBase) {
case StandardTypes.TIME_WITH_TIME_ZONE:
return Optional.of(TIME_WITH_TIME_ZONE);
default:
return Optional.empty();
}
}
case StandardTypes.TIMESTAMP: {
switch (resultTypeBase) {
case StandardTypes.TIMESTAMP_WITH_TIME_ZONE:
return Optional.of(TIMESTAMP_WITH_TIME_ZONE);
default:
return Optional.empty();
}
}
case StandardTypes.VARCHAR: {
switch (resultTypeBase) {
case JoniRegexpType.NAME:
return Optional.of(JONI_REGEXP);
case Re2JRegexpType.NAME:
return Optional.of(RE2J_REGEXP);
case LikePatternType.NAME:
return Optional.of(LIKE_PATTERN);
case JsonPathType.NAME:
return Optional.of(JSON_PATH);
case CodePointsType.NAME:
return Optional.of(CODE_POINTS);
default:
return Optional.empty();
}
}
case StandardTypes.CHAR: {
switch (resultTypeBase) {
case StandardTypes.VARCHAR:
CharType charType = (CharType) sourceType;
return Optional.of(createVarcharType(charType.getLength()));
case JoniRegexpType.NAME:
return Optional.of(JONI_REGEXP);
case Re2JRegexpType.NAME:
return Optional.of(RE2J_REGEXP);
case LikePatternType.NAME:
return Optional.of(LIKE_PATTERN);
case JsonPathType.NAME:
return Optional.of(JSON_PATH);
case CodePointsType.NAME:
return Optional.of(CODE_POINTS);
default:
return Optional.empty();
}
}
case StandardTypes.P4_HYPER_LOG_LOG: {
switch (resultTypeBase) {
case StandardTypes.HYPER_LOG_LOG:
return Optional.of(HYPER_LOG_LOG);
default:
return Optional.empty();
}
}
default:
return Optional.empty();
}
}
private static boolean isCovariantParametrizedType(Type type)
{
// if we ever introduce contravariant, this function should be changed to return an enumeration: INVARIANT, COVARIANT, CONTRAVARIANT
return type instanceof MapType || type instanceof ArrayType;
}
public static boolean isCovariantTypeBase(String typeBase)
{
return typeBase.equals(StandardTypes.ARRAY) || typeBase.equals(StandardTypes.MAP);
}
@Override
public MethodHandle resolveOperator(OperatorType operatorType, List<? extends Type> argumentTypes)
{
requireNonNull(functionRegistry, "functionRegistry is null");
return functionRegistry.getScalarFunctionImplementation(functionRegistry.resolveOperator(operatorType, argumentTypes)).getMethodHandle();
}
}