/*
* 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.block.BlockEncodingManager;
import com.facebook.presto.metadata.FunctionRegistry;
import com.facebook.presto.spi.function.OperatorType;
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.sql.analyzer.FeaturesConfig;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.testng.annotations.Test;
import java.util.Optional;
import java.util.Set;
import static com.facebook.presto.spi.type.BigintType.BIGINT;
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.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.TypeSignature.parseTypeSignature;
import static com.facebook.presto.spi.type.VarbinaryType.VARBINARY;
import static com.facebook.presto.spi.type.VarcharType.VARCHAR;
import static com.facebook.presto.spi.type.VarcharType.createUnboundedVarcharType;
import static com.facebook.presto.spi.type.VarcharType.createVarcharType;
import static com.facebook.presto.type.JoniRegexpType.JONI_REGEXP;
import static com.facebook.presto.type.JsonPathType.JSON_PATH;
import static com.facebook.presto.type.LikePatternType.LIKE_PATTERN;
import static com.facebook.presto.type.Re2JRegexpType.RE2J_REGEXP;
import static com.facebook.presto.type.UnknownType.UNKNOWN;
import static java.lang.String.format;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
public class TestTypeRegistry
{
private final TypeManager typeRegistry = new TypeRegistry();
private final FunctionRegistry functionRegistry = new FunctionRegistry(typeRegistry, new BlockEncodingManager(typeRegistry), new FeaturesConfig());
@Test
public void testNonexistentType()
{
TypeManager typeManager = new TypeRegistry();
assertNull(typeManager.getType(parseTypeSignature("not a real type")));
}
@Test
public void testIsTypeOnlyCoercion()
{
assertTrue(typeRegistry.isTypeOnlyCoercion(BIGINT, BIGINT));
assertTrue(isTypeOnlyCoercion("varchar(42)", "varchar(44)"));
assertFalse(isTypeOnlyCoercion("varchar(44)", "varchar(42)"));
assertFalse(isTypeOnlyCoercion("char(42)", "varchar(42)"));
assertTrue(isTypeOnlyCoercion("array(varchar(42))", "array(varchar(44))"));
assertFalse(isTypeOnlyCoercion("array(varchar(44))", "array(varchar(42))"));
assertTrue(isTypeOnlyCoercion("decimal(22,1)", "decimal(23,1)"));
assertTrue(isTypeOnlyCoercion("decimal(2,1)", "decimal(3,1)"));
assertFalse(isTypeOnlyCoercion("decimal(23,1)", "decimal(22,1)"));
assertFalse(isTypeOnlyCoercion("decimal(3,1)", "decimal(2,1)"));
assertFalse(isTypeOnlyCoercion("decimal(3,1)", "decimal(22,1)"));
assertTrue(isTypeOnlyCoercion("array(decimal(22,1))", "array(decimal(23,1))"));
assertTrue(isTypeOnlyCoercion("array(decimal(2,1))", "array(decimal(3,1))"));
assertFalse(isTypeOnlyCoercion("array(decimal(23,1))", "array(decimal(22,1))"));
assertFalse(isTypeOnlyCoercion("array(decimal(3,1))", "array(decimal(2,1))"));
assertTrue(isTypeOnlyCoercion("map(decimal(2,1), decimal(2,1))", "map(decimal(2,1), decimal(3,1))"));
assertFalse(isTypeOnlyCoercion("map(decimal(2,1), decimal(2,1))", "map(decimal(2,1), decimal(23,1))"));
assertFalse(isTypeOnlyCoercion("map(decimal(2,1), decimal(2,1))", "map(decimal(2,1), decimal(3,2))"));
assertTrue(isTypeOnlyCoercion("map(decimal(22,1), decimal(2,1))", "map(decimal(23,1), decimal(3,1))"));
assertFalse(isTypeOnlyCoercion("map(decimal(23,1), decimal(3,1))", "map(decimal(22,1), decimal(2,1))"));
}
@Test
public void testCanCoerce()
{
assertTrue(typeRegistry.canCoerce(BIGINT, BIGINT));
assertTrue(typeRegistry.canCoerce(UNKNOWN, BIGINT));
assertFalse(typeRegistry.canCoerce(BIGINT, UNKNOWN));
assertTrue(typeRegistry.canCoerce(BIGINT, DOUBLE));
assertTrue(typeRegistry.canCoerce(DATE, TIMESTAMP));
assertTrue(typeRegistry.canCoerce(DATE, TIMESTAMP_WITH_TIME_ZONE));
assertTrue(typeRegistry.canCoerce(TIME, TIME_WITH_TIME_ZONE));
assertTrue(typeRegistry.canCoerce(TIMESTAMP, TIMESTAMP_WITH_TIME_ZONE));
assertTrue(typeRegistry.canCoerce(VARCHAR, JONI_REGEXP));
assertTrue(typeRegistry.canCoerce(VARCHAR, RE2J_REGEXP));
assertTrue(typeRegistry.canCoerce(VARCHAR, LIKE_PATTERN));
assertTrue(typeRegistry.canCoerce(VARCHAR, JSON_PATH));
assertTrue(typeRegistry.canCoerce(REAL, DOUBLE));
assertTrue(typeRegistry.canCoerce(TINYINT, REAL));
assertTrue(typeRegistry.canCoerce(SMALLINT, REAL));
assertTrue(typeRegistry.canCoerce(INTEGER, REAL));
assertTrue(typeRegistry.canCoerce(BIGINT, REAL));
assertFalse(typeRegistry.canCoerce(DOUBLE, BIGINT));
assertFalse(typeRegistry.canCoerce(TIMESTAMP, TIME_WITH_TIME_ZONE));
assertFalse(typeRegistry.canCoerce(TIMESTAMP_WITH_TIME_ZONE, TIMESTAMP));
assertFalse(typeRegistry.canCoerce(VARBINARY, VARCHAR));
assertFalse(canCoerce("real", "decimal(37,1)"));
assertFalse(canCoerce("real", "decimal(37,37)"));
assertFalse(canCoerce("double", "decimal(37,1)"));
assertFalse(canCoerce("double", "decimal(37,37)"));
assertTrue(canCoerce("unknown", "array(bigint)"));
assertFalse(canCoerce("array(bigint)", "unknown"));
assertTrue(canCoerce("array(bigint)", "array(double)"));
assertFalse(canCoerce("array(double)", "array(bigint)"));
assertTrue(canCoerce("map(bigint,double)", "map(bigint,double)"));
assertTrue(canCoerce("map(bigint,double)", "map(double,double)"));
assertTrue(canCoerce("row(a bigint,b double,c varchar)", "row(a bigint,b double,c varchar)"));
assertTrue(canCoerce("varchar(42)", "varchar(42)"));
assertTrue(canCoerce("varchar(42)", "varchar(44)"));
assertFalse(canCoerce("varchar(44)", "varchar(42)"));
assertTrue(canCoerce("char(42)", "varchar(42)"));
assertTrue(canCoerce("char(42)", "varchar(44)"));
assertFalse(canCoerce("char(42)", "char(44)"));
assertFalse(canCoerce("char(42)", "char(40)"));
assertFalse(canCoerce("char(42)", "varchar(40)"));
assertTrue(typeRegistry.canCoerce(createType("char(42)"), JONI_REGEXP));
assertTrue(typeRegistry.canCoerce(createType("char(42)"), RE2J_REGEXP));
assertTrue(typeRegistry.canCoerce(createType("char(42)"), LIKE_PATTERN));
assertTrue(typeRegistry.canCoerce(createType("char(42)"), JSON_PATH));
assertTrue(canCoerce("decimal(22,1)", "decimal(23,1)"));
assertFalse(canCoerce("decimal(23,1)", "decimal(22,1)"));
assertFalse(canCoerce("bigint", "decimal(18,0)"));
assertTrue(canCoerce("bigint", "decimal(19,0)"));
assertTrue(canCoerce("bigint", "decimal(37,1)"));
assertTrue(canCoerce("array(bigint)", "array(decimal(20,1))"));
assertFalse(canCoerce("array(bigint)", "array(decimal(2,1))"));
assertTrue(canCoerce("decimal(3,2)", "double"));
assertTrue(canCoerce("decimal(22,1)", "double"));
assertTrue(canCoerce("decimal(3,2)", "real"));
assertTrue(canCoerce("decimal(22,1)", "real"));
assertFalse(canCoerce("integer", "decimal(9,0)"));
assertTrue(canCoerce("integer", "decimal(10,0)"));
assertTrue(canCoerce("integer", "decimal(37,1)"));
assertFalse(canCoerce("tinyint", "decimal(2,0)"));
assertTrue(canCoerce("tinyint", "decimal(3,0)"));
assertTrue(canCoerce("tinyint", "decimal(37,1)"));
assertFalse(canCoerce("smallint", "decimal(4,0)"));
assertTrue(canCoerce("smallint", "decimal(5,0)"));
assertTrue(canCoerce("smallint", "decimal(37,1)"));
}
@Test
public void testGetCommonSuperType()
{
assertCommonSuperType(UNKNOWN, UNKNOWN, UNKNOWN);
assertCommonSuperType(BIGINT, BIGINT, BIGINT);
assertCommonSuperType(UNKNOWN, BIGINT, BIGINT);
assertCommonSuperType(BIGINT, DOUBLE, DOUBLE);
assertCommonSuperType(DATE, TIMESTAMP, TIMESTAMP);
assertCommonSuperType(DATE, TIMESTAMP_WITH_TIME_ZONE, TIMESTAMP_WITH_TIME_ZONE);
assertCommonSuperType(TIME, TIME_WITH_TIME_ZONE, TIME_WITH_TIME_ZONE);
assertCommonSuperType(TIMESTAMP, TIMESTAMP_WITH_TIME_ZONE, TIMESTAMP_WITH_TIME_ZONE);
assertCommonSuperType(VARCHAR, JONI_REGEXP, JONI_REGEXP);
assertCommonSuperType(VARCHAR, RE2J_REGEXP, RE2J_REGEXP);
assertCommonSuperType(VARCHAR, LIKE_PATTERN, LIKE_PATTERN);
assertCommonSuperType(VARCHAR, JSON_PATH, JSON_PATH);
assertCommonSuperType(REAL, DOUBLE, DOUBLE);
assertCommonSuperType(REAL, TINYINT, REAL);
assertCommonSuperType(REAL, SMALLINT, REAL);
assertCommonSuperType(REAL, INTEGER, REAL);
assertCommonSuperType(REAL, BIGINT, REAL);
assertCommonSuperType(TIMESTAMP, TIME_WITH_TIME_ZONE, null);
assertCommonSuperType(VARBINARY, VARCHAR, null);
assertCommonSuperType("unknown", "array(bigint)", "array(bigint)");
assertCommonSuperType("array(bigint)", "array(double)", "array(double)");
assertCommonSuperType("array(bigint)", "array(unknown)", "array(bigint)");
assertCommonSuperType("map(bigint,double)", "map(bigint,double)", "map(bigint,double)");
assertCommonSuperType("map(bigint,double)", "map(double,double)", "map(double,double)");
assertCommonSuperType("row(a bigint,b double,c varchar)", "row(a bigint,b double,c varchar)", "row(a bigint,b double,c varchar)");
assertCommonSuperType("decimal(22,1)", "decimal(23,1)", "decimal(23,1)");
assertCommonSuperType("bigint", "decimal(23,1)", "decimal(23,1)");
assertCommonSuperType("bigint", "decimal(18,0)", "decimal(19,0)");
assertCommonSuperType("bigint", "decimal(19,0)", "decimal(19,0)");
assertCommonSuperType("bigint", "decimal(37,1)", "decimal(37,1)");
assertCommonSuperType("real", "decimal(37,1)", "real");
assertCommonSuperType("array(decimal(23,1))", "array(decimal(22,1))", "array(decimal(23,1))");
assertCommonSuperType("array(bigint)", "array(decimal(2,1))", "array(decimal(20,1))");
assertCommonSuperType("decimal(3,2)", "double", "double");
assertCommonSuperType("decimal(22,1)", "double", "double");
assertCommonSuperType("integer", "decimal(23,1)", "decimal(23,1)");
assertCommonSuperType("integer", "decimal(9,0)", "decimal(10,0)");
assertCommonSuperType("integer", "decimal(10,0)", "decimal(10,0)");
assertCommonSuperType("integer", "decimal(37,1)", "decimal(37,1)");
assertCommonSuperType("tinyint", "decimal(2,0)", "decimal(3,0)");
assertCommonSuperType("tinyint", "decimal(9,0)", "decimal(9,0)");
assertCommonSuperType("tinyint", "decimal(2,1)", "decimal(4,1)");
assertCommonSuperType("smallint", "decimal(2,0)", "decimal(5,0)");
assertCommonSuperType("smallint", "decimal(9,0)", "decimal(9,0)");
assertCommonSuperType("smallint", "decimal(2,1)", "decimal(6,1)");
}
@Test
public void testCoerceTypeBase()
throws Exception
{
assertEquals(typeRegistry.coerceTypeBase(createDecimalType(21, 1), "decimal"), Optional.of(createDecimalType(21, 1)));
assertEquals(typeRegistry.coerceTypeBase(BIGINT, "decimal"), Optional.of(createDecimalType(19, 0)));
assertEquals(typeRegistry.coerceTypeBase(INTEGER, "decimal"), Optional.of(createDecimalType(10, 0)));
assertEquals(typeRegistry.coerceTypeBase(TINYINT, "decimal"), Optional.of(createDecimalType(3, 0)));
assertEquals(typeRegistry.coerceTypeBase(SMALLINT, "decimal"), Optional.of(createDecimalType(5, 0)));
}
@Test
public void testCanCoerceIsTransitive()
throws Exception
{
Set<Type> types = getStandardPrimitiveTypes();
for (Type transitiveType : types) {
for (Type resultType : types) {
if (typeRegistry.canCoerce(transitiveType, resultType)) {
for (Type sourceType : types) {
if (typeRegistry.canCoerce(sourceType, transitiveType)) {
if (!typeRegistry.canCoerce(sourceType, resultType)) {
fail(format("'%s' -> '%s' coercion is missing when transitive coercion is possible: '%s' -> '%s' -> '%s'",
sourceType, resultType, sourceType, transitiveType, resultType));
}
}
}
}
}
}
}
@Test
public void testCastOperatorsExistForCoercions()
{
Set<Type> types = getStandardPrimitiveTypes();
for (Type sourceType : types) {
for (Type resultType : types) {
if (typeRegistry.canCoerce(sourceType, resultType) && sourceType != UNKNOWN && resultType != UNKNOWN) {
assertTrue(functionRegistry.canResolveOperator(OperatorType.CAST, resultType, ImmutableList.of(sourceType)),
format("'%s' -> '%s' coercion exists but there is no cast operator", sourceType, resultType));
}
}
}
}
private Set<Type> getStandardPrimitiveTypes()
{
ImmutableSet.Builder<Type> builder = ImmutableSet.builder();
// add unparametrized types
builder.addAll(typeRegistry.getTypes());
// add corner cases for parametrized types
builder.add(createDecimalType(1, 0));
builder.add(createDecimalType(17, 0));
builder.add(createDecimalType(38, 0));
builder.add(createDecimalType(17, 17));
builder.add(createDecimalType(38, 38));
builder.add(createVarcharType(0));
builder.add(createUnboundedVarcharType());
builder.add(createCharType(0));
builder.add(createCharType(42));
return builder.build();
}
private static void assertCommonSuperType(Type firstType, Type secondType, Type expected)
{
TypeRegistry typeManager = new TypeRegistry();
assertEquals(typeManager.getCommonSuperType(firstType, secondType), Optional.ofNullable(expected));
assertEquals(typeManager.getCommonSuperType(secondType, firstType), Optional.ofNullable(expected));
}
private void assertCommonSuperType(String firstType, String secondType, String expected)
{
assertEquals(typeRegistry.getCommonSuperType(createType(firstType), createType(secondType)), Optional.ofNullable(expected).map(this::createType));
assertEquals(typeRegistry.getCommonSuperType(createType(secondType), createType(firstType)), Optional.ofNullable(expected).map(this::createType));
}
private boolean canCoerce(String actual, String expected)
{
return typeRegistry.canCoerce(createType(actual), createType(expected));
}
private boolean isTypeOnlyCoercion(String actual, String expected)
{
return typeRegistry.isTypeOnlyCoercion(createType(actual), createType(expected));
}
private Type createType(String signature)
{
return typeRegistry.getType(TypeSignature.parseTypeSignature(signature));
}
}