blob: 9fff8c976a91f7034412879358bebd2c1586fb01 [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.calcite.test;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.test.SqlTestFactory;
import org.apache.calcite.sql.test.SqlTester;
import org.apache.calcite.sql.test.SqlValidatorTester;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.sql.validate.implicit.AbstractTypeCoercion;
import org.apache.calcite.sql.validate.implicit.TypeCoercion;
import org.apache.calcite.test.catalog.MockCatalogReader;
import org.apache.calcite.util.Pair;
import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
/**
* Test cases for implicit type coercion. see {@link TypeCoercion} doc
* or <a href="https://docs.google.com/spreadsheets/d/1GhleX5h5W8-kJKh7NMJ4vtoE78pwfaZRJl88ULX_MgU/edit?usp=sharing">CalciteImplicitCasts</a>
* for conversion details.
*/
class TypeCoercionTest extends SqlValidatorTestCase {
private TypeCoercion typeCoercion;
private RelDataTypeFactory dataTypeFactory;
private SqlTestFactory.MockCatalogReaderFactory catalogReaderFactory;
// type category.
private ImmutableList<RelDataType> numericTypes;
private ImmutableList<RelDataType> atomicTypes;
private ImmutableList<RelDataType> allTypes;
private ImmutableList<RelDataType> charTypes;
private ImmutableList<RelDataType> binaryTypes;
private ImmutableList<RelDataType> booleanTypes;
// single types
private RelDataType nullType;
private RelDataType booleanType;
private RelDataType tinyintType;
private RelDataType smallintType;
private RelDataType intType;
private RelDataType bigintType;
private RelDataType floatType;
private RelDataType doubleType;
private RelDataType decimalType;
private RelDataType dateType;
private RelDataType timeType;
private RelDataType timestampType;
private RelDataType binaryType;
private RelDataType varbinaryType;
private RelDataType charType;
private RelDataType varcharType;
private RelDataType varchar20Type;
TypeCoercionTest() {
// tool tester impl.
SqlTester tester1 = new SqlValidatorTester(SqlTestFactory.INSTANCE);
this.typeCoercion = tester1.getValidator().getTypeCoercion();
this.dataTypeFactory = tester1.getValidator().getTypeFactory();
initializeSingleTypes();
initializeCategoryTypes();
// sql validator tester.
catalogReaderFactory = (factory, caseSensitive) ->
new TCatalogReader(this.dataTypeFactory, caseSensitive).init();
tester = getTester();
}
//~ fields initialize ------------------------------------------------------
private void initializeSingleTypes() {
nullType = dataTypeFactory.createSqlType(SqlTypeName.NULL);
booleanType = dataTypeFactory.createSqlType(SqlTypeName.BOOLEAN);
tinyintType = dataTypeFactory.createSqlType(SqlTypeName.TINYINT);
smallintType = dataTypeFactory.createSqlType(SqlTypeName.SMALLINT);
intType = dataTypeFactory.createSqlType(SqlTypeName.INTEGER);
bigintType = dataTypeFactory.createSqlType(SqlTypeName.BIGINT);
floatType = dataTypeFactory.createSqlType(SqlTypeName.FLOAT);
doubleType = dataTypeFactory.createSqlType(SqlTypeName.DOUBLE);
decimalType = dataTypeFactory.createSqlType(SqlTypeName.DECIMAL);
dateType = dataTypeFactory.createSqlType(SqlTypeName.DATE);
timeType = dataTypeFactory.createSqlType(SqlTypeName.TIME);
timestampType = dataTypeFactory.createSqlType(SqlTypeName.TIMESTAMP);
binaryType = dataTypeFactory.createSqlType(SqlTypeName.BINARY);
varbinaryType = dataTypeFactory.createSqlType(SqlTypeName.VARBINARY);
charType = dataTypeFactory.createSqlType(SqlTypeName.CHAR);
varcharType = dataTypeFactory.createSqlType(SqlTypeName.VARCHAR);
varchar20Type = dataTypeFactory.createSqlType(SqlTypeName.VARCHAR, 20);
}
private void initializeCategoryTypes() {
// INT
ImmutableList.Builder<RelDataType> builder = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.INT_TYPES) {
builder.add(dataTypeFactory.createSqlType(typeName));
}
numericTypes = builder.build();
// ATOMIC
ImmutableList.Builder<RelDataType> builder3 = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.DATETIME_TYPES) {
builder3.add(dataTypeFactory.createSqlType(typeName));
}
builder3.addAll(numericTypes);
for (SqlTypeName typeName : SqlTypeName.STRING_TYPES) {
builder3.add(dataTypeFactory.createSqlType(typeName));
}
for (SqlTypeName typeName : SqlTypeName.BOOLEAN_TYPES) {
builder3.add(dataTypeFactory.createSqlType(typeName));
}
atomicTypes = builder3.build();
// COMPLEX
ImmutableList.Builder<RelDataType> builder4 = ImmutableList.builder();
builder4.add(dataTypeFactory.createArrayType(intType, -1));
builder4.add(dataTypeFactory.createArrayType(varcharType, -1));
builder4.add(dataTypeFactory.createMapType(varcharType, varcharType));
builder4.add(dataTypeFactory.createStructType(ImmutableList.of(Pair.of("a1", varcharType))));
List<? extends Map.Entry<String, RelDataType>> ll =
ImmutableList.of(Pair.of("a1", varbinaryType), Pair.of("a2", intType));
builder4.add(dataTypeFactory.createStructType(ll));
ImmutableList<RelDataType> complexTypes = builder4.build();
// ALL
SqlIntervalQualifier intervalQualifier =
new SqlIntervalQualifier(TimeUnit.DAY, TimeUnit.MINUTE, SqlParserPos.ZERO);
allTypes = combine(atomicTypes, complexTypes,
ImmutableList.of(nullType, dataTypeFactory.createSqlIntervalType(intervalQualifier)));
// CHARACTERS
ImmutableList.Builder<RelDataType> builder6 = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.CHAR_TYPES) {
builder6.add(dataTypeFactory.createSqlType(typeName));
}
charTypes = builder6.build();
// BINARY
ImmutableList.Builder<RelDataType> builder7 = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.BINARY_TYPES) {
builder7.add(dataTypeFactory.createSqlType(typeName));
}
binaryTypes = builder7.build();
// BOOLEAN
ImmutableList.Builder<RelDataType> builder8 = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.BOOLEAN_TYPES) {
builder8.add(dataTypeFactory.createSqlType(typeName));
}
booleanTypes = builder8.build();
}
//~ Tool methods -----------------------------------------------------------
private RelDataType arrayType(RelDataType type) {
return dataTypeFactory.createArrayType(type, -1);
}
private RelDataType mapType(RelDataType keyType, RelDataType valType) {
return dataTypeFactory.createMapType(keyType, valType);
}
private RelDataType recordType(String name, RelDataType type) {
return dataTypeFactory.createStructType(ImmutableList.of(Pair.of(name, type)));
}
private RelDataType recordType(List<? extends Map.Entry<String, RelDataType>> pairs) {
return dataTypeFactory.createStructType(pairs);
}
private RelDataType decimalType(int precision, int scale) {
return dataTypeFactory.createSqlType(SqlTypeName.DECIMAL, precision, scale);
}
/** Decision method for {@link AbstractTypeCoercion#implicitCast}. */
private void shouldCast(
RelDataType from,
SqlTypeFamily family,
RelDataType expected) {
if (family == null) {
// ROW type do not have a family.
return;
}
RelDataType castedType = ((AbstractTypeCoercion) typeCoercion).implicitCast(from, family);
boolean equals = castedType != null
&& (from.equals(castedType)
|| SqlTypeUtil.equalSansNullability(dataTypeFactory, castedType, expected)
|| expected.getSqlTypeName().getFamily().contains(castedType));
assert equals
: "Failed to cast from "
+ from.getSqlTypeName()
+ " to "
+ family;
}
private void shouldNotCast(
RelDataType from,
SqlTypeFamily family) {
if (family == null) {
// ROW type do not have a family.
return;
}
RelDataType castedType = ((AbstractTypeCoercion) typeCoercion).implicitCast(from, family);
assert castedType == null
: "Should not be able to cast from "
+ from.getSqlTypeName()
+ " to "
+ family;
}
private void checkShouldCast(RelDataType checked, List<RelDataType> types) {
for (RelDataType type : allTypes) {
if (contains(types, type)) {
shouldCast(checked, type.getSqlTypeName().getFamily(), type);
} else {
shouldNotCast(checked, type.getSqlTypeName().getFamily());
}
}
}
// some data types has the same type family, i.e. TIMESTAMP and
// TIMESTAMP_WITH_LOCAL_TIME_ZONE all have TIMESTAMP family.
private static boolean contains(List<RelDataType> types, RelDataType type) {
for (RelDataType type1 : types) {
if (type1.equals(type)
|| type1.getSqlTypeName().getFamily() == type.getSqlTypeName().getFamily()) {
return true;
}
}
return false;
}
private boolean equals(Object o1, Object o2) {
if (o1 == null && o2 != null
|| o1 != null && o2 == null) {
return false;
}
return o1 == o2;
}
private String toStringNullable(Object o1) {
if (o1 == null) {
return "NULL";
}
return o1.toString();
}
/** Decision method for finding a common type. */
private void checkCommonType(
RelDataType type1,
RelDataType type2,
RelDataType expected,
boolean isSymmetric) {
RelDataType result = typeCoercion.getTightestCommonType(type1, type2);
assert equals(result, expected)
: "Expected "
+ toStringNullable(expected)
+ " as common type for "
+ type1.toString()
+ " and "
+ type2.toString()
+ ", but found "
+ toStringNullable(result);
if (isSymmetric) {
RelDataType result1 = typeCoercion.getTightestCommonType(type2, type1);
assert equals(result1, expected)
: "Expected "
+ toStringNullable(expected)
+ " as common type for "
+ type2.toString()
+ " and "
+ type1.toString()
+ ", but found "
+ toStringNullable(result1);
}
}
/** Decision method for finding a wider type. */
private void checkWiderType(
RelDataType type1,
RelDataType type2,
RelDataType expected,
boolean stringPromotion,
boolean isSymmetric) {
RelDataType result = typeCoercion.getWiderTypeForTwo(type1, type2, stringPromotion);
assert equals(result, expected)
: "Expected "
+ toStringNullable(expected)
+ " as common type for " + type1.toString()
+ " and " + type2.toString()
+ ", but found " + toStringNullable(result);
if (isSymmetric) {
RelDataType result1 = typeCoercion.getWiderTypeForTwo(type2, type1, stringPromotion);
assert equals(result1, expected)
: "Expected "
+ toStringNullable(expected)
+ " as common type for " + type2.toString()
+ " and " + type1.toString()
+ ", but found " + toStringNullable(result1);
}
}
@Override public SqlTester getTester() {
return new SqlValidatorTester(SqlTestFactory.INSTANCE
.withCatalogReader(getCatalogReaderFactory()));
}
private static ImmutableList<RelDataType> combine(
List<RelDataType> list0,
List<RelDataType> list1) {
return ImmutableList.<RelDataType>builder()
.addAll(list0)
.addAll(list1)
.build();
}
private static ImmutableList<RelDataType> combine(
List<RelDataType> list0,
List<RelDataType> list1,
List<RelDataType> list2) {
return ImmutableList.<RelDataType>builder()
.addAll(list0)
.addAll(list1)
.addAll(list2)
.build();
}
SqlTestFactory.MockCatalogReaderFactory getCatalogReaderFactory() {
return catalogReaderFactory;
}
//~ Tests ------------------------------------------------------------------
/**
* Test case for {@link TypeCoercion#getTightestCommonType}.
*/
@Test void testGetTightestCommonType() {
// NULL
checkCommonType(nullType, nullType, nullType, true);
// BOOLEAN
checkCommonType(nullType, booleanType, booleanType, true);
checkCommonType(booleanType, booleanType, booleanType, true);
checkCommonType(intType, booleanType, null, true);
checkCommonType(bigintType, booleanType, null, true);
// INT
checkCommonType(nullType, tinyintType, tinyintType, true);
checkCommonType(nullType, intType, intType, true);
checkCommonType(nullType, bigintType, bigintType, true);
checkCommonType(smallintType, intType, intType, true);
checkCommonType(smallintType, bigintType, bigintType, true);
checkCommonType(intType, bigintType, bigintType, true);
checkCommonType(bigintType, bigintType, bigintType, true);
// FLOAT/DOUBLE
checkCommonType(nullType, floatType, floatType, true);
checkCommonType(nullType, doubleType, doubleType, true);
// Use RelDataTypeFactory#leastRestrictive to find the common type, it's not symmetric but
// it's ok because precision does not become lower.
checkCommonType(floatType, doubleType, floatType, false);
checkCommonType(floatType, floatType, floatType, true);
checkCommonType(doubleType, doubleType, doubleType, true);
// EXACT + FRACTIONAL
checkCommonType(intType, floatType, floatType, true);
checkCommonType(intType, doubleType, doubleType, true);
checkCommonType(bigintType, floatType, floatType, true);
checkCommonType(bigintType, doubleType, doubleType, true);
// Fixed precision decimal
RelDataType decimal54 = dataTypeFactory.createSqlType(SqlTypeName.DECIMAL, 5, 4);
RelDataType decimal71 = dataTypeFactory.createSqlType(SqlTypeName.DECIMAL, 7, 1);
checkCommonType(decimal54, decimal71, null, true);
checkCommonType(decimal54, doubleType, null, true);
checkCommonType(decimal54, intType, null, true);
// CHAR/VARCHAR
checkCommonType(nullType, charType, charType, true);
checkCommonType(charType, varcharType, varcharType, true);
checkCommonType(intType, charType, null, true);
checkCommonType(doubleType, charType, null, true);
// TIMESTAMP
checkCommonType(nullType, timestampType, timestampType, true);
checkCommonType(timestampType, timestampType, timestampType, true);
checkCommonType(dateType, timestampType, timestampType, true);
checkCommonType(intType, timestampType, null, true);
checkCommonType(varcharType, timestampType, null, true);
// STRUCT
checkCommonType(nullType, mapType(intType, charType), mapType(intType, charType), true);
checkCommonType(nullType, recordType(ImmutableList.of()), recordType(ImmutableList.of()),
true);
checkCommonType(charType, mapType(intType, charType), null, true);
checkCommonType(arrayType(intType), recordType(ImmutableList.of()), null, true);
checkCommonType(recordType("a", intType), recordType("b", intType), null, true);
checkCommonType(recordType("a", intType), recordType("a", intType),
recordType("a", intType), true);
checkCommonType(recordType("a", arrayType(intType)), recordType("a", arrayType(intType)),
recordType("a", arrayType(intType)), true);
}
/** Test case for {@link TypeCoercion#getWiderTypeForTwo}
* and {@link TypeCoercion#getWiderTypeFor}. */
@Test void testWiderTypeFor() {
// DECIMAL please see details in SqlTypeFactoryImpl#leastRestrictiveSqlType.
checkWiderType(decimalType(5, 4), decimalType(7, 1), decimalType(10, 4), true, true);
checkWiderType(decimalType(5, 4), doubleType, doubleType, true, true);
checkWiderType(decimalType(5, 4), intType, decimalType(14, 4), true, true);
checkWiderType(decimalType(5, 4), bigintType, decimalType(19, 0), true, true);
// Array
checkWiderType(arrayType(smallintType), arrayType(doubleType), arrayType(doubleType),
true, true);
checkWiderType(arrayType(timestampType), arrayType(varcharType), arrayType(varcharType),
true, true);
checkWiderType(arrayType(intType), arrayType(bigintType), arrayType(bigintType),
true, true);
// No string promotion
checkWiderType(intType, charType, null, false, true);
checkWiderType(timestampType, charType, null, false, true);
checkWiderType(arrayType(bigintType), arrayType(charType), null, false, true);
checkWiderType(arrayType(charType), arrayType(timestampType), null, false, true);
// String promotion
checkWiderType(intType, charType, varcharType, true, true);
checkWiderType(timestampType, charType, varcharType, true, true);
checkWiderType(arrayType(bigintType), arrayType(varcharType), arrayType(varcharType),
true, true);
checkWiderType(arrayType(charType), arrayType(timestampType), arrayType(varcharType),
true, true);
}
/** Test set operations: UNION, INTERSECT, EXCEPT type coercion. */
@Test void testSetOperations() {
// union
sql("select 1 from (values(true)) union select '2' from (values(true))")
.type("RecordType(VARCHAR NOT NULL EXPR$0) NOT NULL");
sql("select 1 from (values(true)) union select '2' from (values(true))"
+ "union select '3' from (values(true))")
.type("RecordType(VARCHAR NOT NULL EXPR$0) NOT NULL");
sql("select 1, '2' from (values(true, false)) union select '3', 4 from (values(true, false))")
.type("RecordType(VARCHAR NOT NULL EXPR$0, VARCHAR NOT NULL EXPR$1) NOT NULL");
sql("select '1' from (values(true)) union values 2")
.type("RecordType(VARCHAR NOT NULL EXPR$0) NOT NULL");
sql("select (select 1+2 from (values true)) tt from (values(true)) union values '2'")
.type("RecordType(VARCHAR NOT NULL TT) NOT NULL");
// union with star
sql("select * from (values(1, '3')) union select * from (values('2', 4))")
.type("RecordType(VARCHAR NOT NULL EXPR$0, VARCHAR NOT NULL EXPR$1) NOT NULL");
sql("select 1 from (values(true)) union values (select '1' from (values (true)) as tt)")
.type("RecordType(VARCHAR EXPR$0) NOT NULL");
// union with func
sql("select LOCALTIME from (values(true)) union values '1'")
.type("RecordType(VARCHAR NOT NULL LOCALTIME) NOT NULL");
sql("select t1_int, t1_decimal, t1_smallint, t1_double from t1 "
+ "union select t2_varchar20, t2_decimal, t2_float, t2_bigint from t2 "
+ "union select t1_varchar20, t1_decimal, t1_float, t1_double from t1 "
+ "union select t2_varchar20, t2_decimal, t2_smallint, t2_double from t2")
.type("RecordType(VARCHAR NOT NULL T1_INT,"
+ " DECIMAL(19, 0) NOT NULL T1_DECIMAL,"
+ " FLOAT NOT NULL T1_SMALLINT,"
+ " DOUBLE NOT NULL T1_DOUBLE) NOT NULL");
// (int) union (int) union (varchar(20))
sql("select t1_int from t1 "
+ "union select t2_int from t2 "
+ "union select t1_varchar20 from t1")
.columnType("VARCHAR NOT NULL");
// (varchar(20)) union (int) union (int)
sql("select t1_varchar20 from t1 "
+ "union select t2_int from t2 "
+ "union select t1_int from t1")
.columnType("VARCHAR NOT NULL");
// date union timestamp
sql("select t1_date, t1_timestamp from t1\n"
+ "union select t2_timestamp, t2_date from t2")
.type("RecordType(TIMESTAMP(0) NOT NULL T1_DATE,"
+ " TIMESTAMP(0) NOT NULL T1_TIMESTAMP) NOT NULL");
// intersect
sql("select t1_int, t1_decimal, t1_smallint, t1_double from t1 "
+ "intersect select t2_varchar20, t2_decimal, t2_float, t2_bigint from t2 ")
.type("RecordType(VARCHAR NOT NULL T1_INT,"
+ " DECIMAL(19, 0) NOT NULL T1_DECIMAL,"
+ " FLOAT NOT NULL T1_SMALLINT,"
+ " DOUBLE NOT NULL T1_DOUBLE) NOT NULL");
// except
sql("select t1_int, t1_decimal, t1_smallint, t1_double from t1 "
+ "except select t2_varchar20, t2_decimal, t2_float, t2_bigint from t2 ")
.type("RecordType(VARCHAR NOT NULL T1_INT,"
+ " DECIMAL(19, 0) NOT NULL T1_DECIMAL,"
+ " FLOAT NOT NULL T1_SMALLINT,"
+ " DOUBLE NOT NULL T1_DOUBLE) NOT NULL");
}
/** Test arithmetic expressions with string type arguments. */
@Test void testArithmeticExpressionsWithStrings() {
// for null type in binary arithmetic.
expr("1 + null").ok();
expr("1 - null").ok();
expr("1 / null").ok();
expr("1 * null").ok();
expr("MOD(1, null)").ok();
sql("select 1+'2', 2-'3', 2*'3', 2/'3', MOD(4,'3') "
+ "from (values (true, true, true, true, true))")
.type("RecordType(INTEGER NOT NULL EXPR$0, "
+ "INTEGER NOT NULL EXPR$1, "
+ "INTEGER NOT NULL EXPR$2, "
+ "INTEGER NOT NULL EXPR$3, "
+ "DECIMAL(19, 9) "
+ "NOT NULL EXPR$4) NOT NULL");
expr("select abs(t1_varchar20) from t1").ok();
expr("select sum(t1_varchar20) from t1").ok();
expr("select avg(t1_varchar20) from t1").ok();
tester.setFor(SqlStdOperatorTable.STDDEV_POP);
tester.setFor(SqlStdOperatorTable.STDDEV_SAMP);
expr("select STDDEV_POP(t1_varchar20) from t1").ok();
expr("select STDDEV_SAMP(t1_varchar20) from t1").ok();
expr("select -(t1_varchar20) from t1").ok();
expr("select +(t1_varchar20) from t1").ok();
tester.setFor(SqlStdOperatorTable.VAR_POP);
tester.setFor(SqlStdOperatorTable.VAR_SAMP);
expr("select VAR_POP(t1_varchar20) from t1").ok();
expr("select VAR_SAMP(t1_varchar20) from t1").ok();
// test divide with strings
expr("'12.3'/5")
.columnType("INTEGER NOT NULL");
expr("'12.3'/cast(5 as bigint)")
.columnType("BIGINT NOT NULL");
expr("'12.3'/cast(5 as float)")
.columnType("FLOAT NOT NULL");
expr("'12.3'/cast(5 as double)")
.columnType("DOUBLE NOT NULL");
expr("'12.3'/5.1")
.columnType("DECIMAL(19, 8) NOT NULL");
expr("12.3/'5.1'")
.columnType("DECIMAL(19, 8) NOT NULL");
// test binary arithmetic with two strings.
expr("'12.3' + '5'")
.columnType("DECIMAL(19, 9) NOT NULL");
expr("'12.3' - '5'")
.columnType("DECIMAL(19, 9) NOT NULL");
expr("'12.3' * '5'")
.columnType("DECIMAL(19, 18) NOT NULL");
expr("'12.3' / '5'")
.columnType("DECIMAL(19, 0) NOT NULL");
}
/** Test cases for binary comparison expressions. */
@Test void testBinaryComparisonCoercion() {
expr("'2' = 3").columnType("BOOLEAN NOT NULL");
expr("'2' > 3").columnType("BOOLEAN NOT NULL");
expr("'2' >= 3").columnType("BOOLEAN NOT NULL");
expr("'2' < 3").columnType("BOOLEAN NOT NULL");
expr("'2' <= 3").columnType("BOOLEAN NOT NULL");
expr("'2' is distinct from 3").columnType("BOOLEAN NOT NULL");
expr("'2' is not distinct from 3").columnType("BOOLEAN NOT NULL");
// NULL operand
expr("'2' = null").columnType("BOOLEAN");
expr("'2' > null").columnType("BOOLEAN");
expr("'2' >= null").columnType("BOOLEAN");
expr("'2' < null").columnType("BOOLEAN");
expr("'2' <= null").columnType("BOOLEAN");
expr("'2' is distinct from null").columnType("BOOLEAN NOT NULL");
expr("'2' is not distinct from null").columnType("BOOLEAN NOT NULL");
// BETWEEN operator
expr("'2' between 1 and 3").columnType("BOOLEAN NOT NULL");
expr("NULL between 1 and 3").columnType("BOOLEAN");
sql("select '2019-09-23' between t1_date and t1_timestamp from t1")
.columnType("BOOLEAN NOT NULL");
sql("select t1_date between '2019-09-23' and t1_timestamp from t1")
.columnType("BOOLEAN NOT NULL");
sql("select cast('2019-09-23' as date) between t1_date and t1_timestamp from t1")
.columnType("BOOLEAN NOT NULL");
sql("select t1_date between cast('2019-09-23' as date) and t1_timestamp from t1")
.columnType("BOOLEAN NOT NULL");
}
/** Test case for case when expression and COALESCE operator. */
@Test void testCaseWhen() {
// coalesce
// double int float
sql("select COALESCE(t1_double, t1_int, t1_float) from t1")
.type("RecordType(DOUBLE NOT NULL EXPR$0) NOT NULL");
// bigint int decimal
sql("select COALESCE(t1_bigint, t1_int, t1_decimal) from t1")
.type("RecordType(DECIMAL(19, 0) NOT NULL EXPR$0) NOT NULL");
// null int
sql("select COALESCE(null, t1_int) from t1")
.type("RecordType(INTEGER EXPR$0) NOT NULL");
// timestamp varchar
sql("select COALESCE(t1_varchar20, t1_timestamp) from t1")
.type("RecordType(VARCHAR NOT NULL EXPR$0) NOT NULL");
// null float int
sql("select COALESCE(null, t1_float, t1_int) from t1")
.type("RecordType(FLOAT EXPR$0) NOT NULL");
// null int decimal double
sql("select COALESCE(null, t1_int, t1_decimal, t1_double) from t1")
.type("RecordType(DOUBLE EXPR$0) NOT NULL");
// null float double varchar
sql("select COALESCE(null, t1_float, t1_double, t1_varchar20) from t1")
.type("RecordType(VARCHAR EXPR$0) NOT NULL");
// timestamp int varchar
sql("select COALESCE(t1_timestamp, t1_int, t1_varchar20) from t1")
.type("RecordType(TIMESTAMP(0) NOT NULL EXPR$0) NOT NULL");
// timestamp date
sql("select COALESCE(t1_timestamp, t1_date) from t1")
.type("RecordType(TIMESTAMP(0) NOT NULL EXPR$0) NOT NULL");
// date timestamp
sql("select COALESCE(t1_timestamp, t1_date) from t1")
.type("RecordType(TIMESTAMP(0) NOT NULL EXPR$0) NOT NULL");
// null date timestamp
sql("select COALESCE(t1_timestamp, t1_date) from t1")
.type("RecordType(TIMESTAMP(0) NOT NULL EXPR$0) NOT NULL");
// case when
// smallint int char
sql("select case "
+ "when 1 > 0 then t2_smallint "
+ "when 2 > 3 then t2_int "
+ "else t2_varchar20 end from t2")
.type("RecordType(VARCHAR NOT NULL EXPR$0) NOT NULL");
// boolean int char
sql("select case "
+ "when 1 > 0 then t2_boolean "
+ "when 2 > 3 then t2_int "
+ "else t2_varchar20 end from t2")
.type("RecordType(VARCHAR NOT NULL EXPR$0) NOT NULL");
// float decimal
sql("select case when 1 > 0 then t2_float else t2_decimal end from t2")
.type("RecordType(DOUBLE NOT NULL EXPR$0) NOT NULL");
// bigint decimal
sql("select case when 1 > 0 then t2_bigint else t2_decimal end from t2")
.type("RecordType(DECIMAL(19, 0) NOT NULL EXPR$0) NOT NULL");
// date timestamp
sql("select case when 1 > 0 then t2_date else t2_timestamp end from t2")
.type("RecordType(TIMESTAMP(0) NOT NULL EXPR$0) NOT NULL");
}
/** Test for {@link AbstractTypeCoercion#implicitCast}. */
@Test void testImplicitCasts() {
// TINYINT
RelDataType checkedType1 = dataTypeFactory.createSqlType(SqlTypeName.TINYINT);
checkShouldCast(checkedType1, combine(numericTypes, charTypes));
shouldCast(checkedType1, SqlTypeFamily.DECIMAL,
dataTypeFactory.decimalOf(checkedType1));
shouldCast(checkedType1, SqlTypeFamily.NUMERIC, checkedType1);
shouldCast(checkedType1, SqlTypeFamily.INTEGER, checkedType1);
shouldCast(checkedType1, SqlTypeFamily.EXACT_NUMERIC, checkedType1);
shouldNotCast(checkedType1, SqlTypeFamily.APPROXIMATE_NUMERIC);
// SMALLINT
RelDataType checkedType2 = smallintType;
checkShouldCast(checkedType2, combine(numericTypes, charTypes));
shouldCast(checkedType2, SqlTypeFamily.DECIMAL,
dataTypeFactory.decimalOf(checkedType2));
shouldCast(checkedType2, SqlTypeFamily.NUMERIC, checkedType2);
shouldCast(checkedType2, SqlTypeFamily.INTEGER, checkedType2);
shouldCast(checkedType2, SqlTypeFamily.EXACT_NUMERIC, checkedType2);
shouldNotCast(checkedType2, SqlTypeFamily.APPROXIMATE_NUMERIC);
// INT
RelDataType checkedType3 = intType;
checkShouldCast(checkedType3, combine(numericTypes, charTypes));
shouldCast(checkedType3, SqlTypeFamily.DECIMAL,
dataTypeFactory.decimalOf(checkedType3));
shouldCast(checkedType3, SqlTypeFamily.NUMERIC, checkedType3);
shouldCast(checkedType3, SqlTypeFamily.INTEGER, checkedType3);
shouldCast(checkedType3, SqlTypeFamily.EXACT_NUMERIC, checkedType3);
shouldNotCast(checkedType3, SqlTypeFamily.APPROXIMATE_NUMERIC);
// BIGINT
RelDataType checkedType4 = bigintType;
checkShouldCast(checkedType4, combine(numericTypes, charTypes));
shouldCast(checkedType4, SqlTypeFamily.DECIMAL,
dataTypeFactory.decimalOf(checkedType4));
shouldCast(checkedType4, SqlTypeFamily.NUMERIC, checkedType4);
shouldCast(checkedType4, SqlTypeFamily.INTEGER, checkedType4);
shouldCast(checkedType4, SqlTypeFamily.EXACT_NUMERIC, checkedType4);
shouldNotCast(checkedType4, SqlTypeFamily.APPROXIMATE_NUMERIC);
// FLOAT/REAL
RelDataType checkedType5 = floatType;
checkShouldCast(checkedType5, combine(numericTypes, charTypes));
shouldCast(checkedType5, SqlTypeFamily.DECIMAL,
dataTypeFactory.decimalOf(checkedType5));
shouldCast(checkedType5, SqlTypeFamily.NUMERIC, checkedType5);
shouldNotCast(checkedType5, SqlTypeFamily.INTEGER);
shouldCast(checkedType5, SqlTypeFamily.EXACT_NUMERIC,
dataTypeFactory.decimalOf(checkedType5));
shouldCast(checkedType5, SqlTypeFamily.APPROXIMATE_NUMERIC, checkedType5);
// DOUBLE
RelDataType checkedType6 = doubleType;
checkShouldCast(checkedType6, combine(numericTypes, charTypes));
shouldCast(checkedType6, SqlTypeFamily.DECIMAL,
dataTypeFactory.decimalOf(checkedType6));
shouldCast(checkedType6, SqlTypeFamily.NUMERIC, checkedType6);
shouldNotCast(checkedType6, SqlTypeFamily.INTEGER);
shouldCast(checkedType6, SqlTypeFamily.EXACT_NUMERIC,
dataTypeFactory.decimalOf(checkedType5));
shouldCast(checkedType6, SqlTypeFamily.APPROXIMATE_NUMERIC, checkedType6);
// DECIMAL(10, 2)
RelDataType checkedType7 = decimalType(10, 2);
checkShouldCast(checkedType7, combine(numericTypes, charTypes));
shouldCast(checkedType7, SqlTypeFamily.DECIMAL,
dataTypeFactory.decimalOf(checkedType7));
shouldCast(checkedType7, SqlTypeFamily.NUMERIC, checkedType7);
shouldNotCast(checkedType7, SqlTypeFamily.INTEGER);
shouldCast(checkedType7, SqlTypeFamily.EXACT_NUMERIC, checkedType7);
shouldNotCast(checkedType7, SqlTypeFamily.APPROXIMATE_NUMERIC);
// BINARY
RelDataType checkedType8 = binaryType;
checkShouldCast(checkedType8, combine(binaryTypes, charTypes));
shouldNotCast(checkedType8, SqlTypeFamily.DECIMAL);
shouldNotCast(checkedType8, SqlTypeFamily.NUMERIC);
shouldNotCast(checkedType8, SqlTypeFamily.INTEGER);
// BOOLEAN
RelDataType checkedType9 = booleanType;
checkShouldCast(checkedType9, combine(booleanTypes, charTypes));
shouldNotCast(checkedType9, SqlTypeFamily.DECIMAL);
shouldNotCast(checkedType9, SqlTypeFamily.NUMERIC);
shouldNotCast(checkedType9, SqlTypeFamily.INTEGER);
// CHARACTER
RelDataType checkedType10 = varcharType;
ImmutableList.Builder<RelDataType> builder = ImmutableList.builder();
for (RelDataType type : atomicTypes) {
if (!SqlTypeUtil.isBoolean(type)) {
builder.add(type);
}
}
checkShouldCast(checkedType10, builder.build());
shouldCast(checkedType10, SqlTypeFamily.DECIMAL,
SqlTypeUtil.getMaxPrecisionScaleDecimal(dataTypeFactory));
shouldCast(checkedType10, SqlTypeFamily.NUMERIC,
SqlTypeUtil.getMaxPrecisionScaleDecimal(dataTypeFactory));
shouldNotCast(checkedType10, SqlTypeFamily.BOOLEAN);
// DATE
RelDataType checkedType11 = dateType;
checkShouldCast(
checkedType11,
combine(ImmutableList.of(timestampType, checkedType11),
charTypes));
shouldNotCast(checkedType11, SqlTypeFamily.DECIMAL);
shouldNotCast(checkedType11, SqlTypeFamily.NUMERIC);
shouldNotCast(checkedType11, SqlTypeFamily.INTEGER);
// TIME
RelDataType checkedType12 = timeType;
checkShouldCast(
checkedType12,
combine(ImmutableList.of(checkedType12), charTypes));
shouldNotCast(checkedType12, SqlTypeFamily.DECIMAL);
shouldNotCast(checkedType12, SqlTypeFamily.NUMERIC);
shouldNotCast(checkedType12, SqlTypeFamily.INTEGER);
// TIMESTAMP
RelDataType checkedType13 = timestampType;
checkShouldCast(
checkedType13,
combine(ImmutableList.of(dateType, checkedType13),
charTypes));
shouldNotCast(checkedType13, SqlTypeFamily.DECIMAL);
shouldNotCast(checkedType13, SqlTypeFamily.NUMERIC);
shouldNotCast(checkedType13, SqlTypeFamily.INTEGER);
// NULL
RelDataType checkedType14 = nullType;
checkShouldCast(checkedType14, allTypes);
shouldCast(checkedType14, SqlTypeFamily.DECIMAL, decimalType);
shouldCast(checkedType14, SqlTypeFamily.NUMERIC, intType);
// INTERVAL
RelDataType checkedType15 = dataTypeFactory.createSqlIntervalType(
new SqlIntervalQualifier(TimeUnit.YEAR, TimeUnit.MONTH, SqlParserPos.ZERO));
checkShouldCast(checkedType15, ImmutableList.of(checkedType15));
shouldNotCast(checkedType15, SqlTypeFamily.DECIMAL);
shouldNotCast(checkedType15, SqlTypeFamily.NUMERIC);
shouldNotCast(checkedType15, SqlTypeFamily.INTEGER);
}
/** Test case for {@link TypeCoercion#builtinFunctionCoercion}. */
@Test void testBuiltinFunctionCoercion() {
// concat
expr("'ab'||'cde'")
.columnType("CHAR(5) NOT NULL");
expr("null||'cde'")
.columnType("VARCHAR");
expr("1||'234'")
.columnType("VARCHAR NOT NULL");
expr("select ^'a'||t1_binary^ from t1")
.fails("(?s).*Cannot apply.*");
// smallint int double
expr("select t1_smallint||t1_int||t1_double from t1")
.columnType("VARCHAR");
// boolean float smallint
expr("select t1_boolean||t1_float||t1_smallint from t1")
.columnType("VARCHAR");
// decimal
expr("select t1_decimal||t1_varchar20 from t1")
.columnType("VARCHAR");
// date timestamp
expr("select t1_timestamp||t1_date from t1")
.columnType("VARCHAR");
}
/** Test case for {@link TypeCoercion#querySourceCoercion}. */
@Test void testQuerySourceCoercion() {
final String expectRowType = "RecordType("
+ "VARCHAR(20) NOT NULL t1_varchar20, "
+ "SMALLINT NOT NULL t1_smallint, "
+ "INTEGER NOT NULL t1_int, "
+ "BIGINT NOT NULL t1_bigint, "
+ "FLOAT NOT NULL t1_float, "
+ "DOUBLE NOT NULL t1_double, "
+ "DECIMAL(19, 0) NOT NULL t1_decimal, "
+ "TIMESTAMP(0) NOT NULL t1_timestamp, "
+ "DATE NOT NULL t1_date, "
+ "BINARY(1) NOT NULL t1_binary, "
+ "BOOLEAN NOT NULL t1_boolean) NOT NULL";
final String sql = "insert into t1 select t2_smallint, t2_int, t2_bigint, t2_float,\n"
+ "t2_double, t2_decimal, t2_int, t2_date, t2_timestamp, t2_varchar20, t2_int from t2";
sql(sql).type(expectRowType);
final String sql1 = "insert into ^t1^(t1_varchar20, t1_date, t1_int)\n"
+ "select t2_smallint, t2_timestamp, t2_float from t2";
sql(sql1).fails("(?s).*Column 't1_smallint' has no default value and does not allow NULLs.*");
final String sql2 = "update t1 set t1_varchar20=123, "
+ "t1_date=TIMESTAMP '2020-01-03 10:14:34', t1_int=12.3";
sql(sql2).type(expectRowType);
}
//~ Inner Class ------------------------------------------------------------
/** A catalog reader with table t1 and t2 whose schema contains all the test data types. */
public class TCatalogReader extends MockCatalogReader {
private boolean isCaseSensitive;
TCatalogReader(RelDataTypeFactory typeFactory, boolean isCaseSensitive) {
super(typeFactory, false);
this.isCaseSensitive = isCaseSensitive;
}
public MockCatalogReader init() {
MockSchema tSchema = new MockSchema("SALES");
registerSchema(tSchema);
// Register "T1" table.
final MockTable t1 =
MockTable.create(this, tSchema, "T1", false, 7.0, null);
t1.addColumn("t1_varchar20", varchar20Type, true);
t1.addColumn("t1_smallint", smallintType);
t1.addColumn("t1_int", intType);
t1.addColumn("t1_bigint", bigintType);
t1.addColumn("t1_float", floatType);
t1.addColumn("t1_double", doubleType);
t1.addColumn("t1_decimal", decimalType);
t1.addColumn("t1_timestamp", timestampType);
t1.addColumn("t1_date", dateType);
t1.addColumn("t1_binary", binaryType);
t1.addColumn("t1_boolean", booleanType);
registerTable(t1);
final MockTable t2 =
MockTable.create(this, tSchema, "T2", false, 7.0, null);
t2.addColumn("t2_varchar20", varchar20Type, true);
t2.addColumn("t2_smallint", smallintType);
t2.addColumn("t2_int", intType);
t2.addColumn("t2_bigint", bigintType);
t2.addColumn("t2_float", floatType);
t2.addColumn("t2_double", doubleType);
t2.addColumn("t2_decimal", decimalType);
t2.addColumn("t2_timestamp", timestampType);
t2.addColumn("t2_date", dateType);
t2.addColumn("t2_binary", binaryType);
t2.addColumn("t2_boolean", booleanType);
registerTable(t2);
return this;
}
@Override public boolean isCaseSensitive() {
return isCaseSensitive;
}
}
}