blob: 26829f8b5d51dd6adc649dd77ec8f813c40e2b47 [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.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.implicit.AbstractTypeCoercion;
import org.apache.calcite.sql.validate.implicit.TypeCoercion;
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;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* 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 {
public static final Fixture DEFAULT_FIXTURE =
Fixture.create(SqlTestFactory.INSTANCE);
//~ Helper methods ---------------------------------------------------------
public Fixture fixture() {
return DEFAULT_FIXTURE;
}
public static SqlValidatorFixture sql(String sql) {
return validatorFixture()
.withSql(sql);
}
public static SqlValidatorFixture expr(String sql) {
return validatorFixture()
.withExpr(sql);
}
private static SqlValidatorFixture validatorFixture() {
return SqlValidatorTestCase.FIXTURE
.withCatalogReader(TCatalogReader::create);
}
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();
}
//~ Tests ------------------------------------------------------------------
/**
* Test case for {@link TypeCoercion#getTightestCommonType}.
*/
@Test void testGetTightestCommonType() {
// NULL
final Fixture f = fixture();
f.checkCommonType(f.nullType, f.nullType, f.nullType, true);
// BOOLEAN
f.checkCommonType(f.nullType, f.booleanType, f.booleanType, true);
f.checkCommonType(f.booleanType, f.booleanType, f.booleanType, true);
f.checkCommonType(f.intType, f.booleanType, null, true);
f.checkCommonType(f.bigintType, f.booleanType, null, true);
// INT
f.checkCommonType(f.nullType, f.tinyintType, f.tinyintType, true);
f.checkCommonType(f.nullType, f.intType, f.intType, true);
f.checkCommonType(f.nullType, f.bigintType, f.bigintType, true);
f.checkCommonType(f.smallintType, f.intType, f.intType, true);
f.checkCommonType(f.smallintType, f.bigintType, f.bigintType, true);
f.checkCommonType(f.intType, f.bigintType, f.bigintType, true);
f.checkCommonType(f.bigintType, f.bigintType, f.bigintType, true);
// FLOAT/DOUBLE
f.checkCommonType(f.nullType, f.floatType, f.floatType, true);
f.checkCommonType(f.nullType, f.doubleType, f.doubleType, true);
// Use RelDataTypeFactory#leastRestrictive to find the common type; it's not
// symmetric but it's ok because precision does not become lower.
f.checkCommonType(f.floatType, f.doubleType, f.floatType, false);
f.checkCommonType(f.floatType, f.floatType, f.floatType, true);
f.checkCommonType(f.doubleType, f.doubleType, f.doubleType, true);
// EXACT + FRACTIONAL
f.checkCommonType(f.intType, f.floatType, f.floatType, true);
f.checkCommonType(f.intType, f.doubleType, f.doubleType, true);
f.checkCommonType(f.bigintType, f.floatType, f.floatType, true);
f.checkCommonType(f.bigintType, f.doubleType, f.doubleType, true);
// Fixed precision decimal
RelDataType decimal54 =
f.typeFactory.createSqlType(SqlTypeName.DECIMAL, 5, 4);
RelDataType decimal71 =
f.typeFactory.createSqlType(SqlTypeName.DECIMAL, 7, 1);
f.checkCommonType(decimal54, decimal71, null, true);
f.checkCommonType(decimal54, f.doubleType, null, true);
f.checkCommonType(decimal54, f.intType, null, true);
// CHAR/VARCHAR
f.checkCommonType(f.nullType, f.charType, f.charType, true);
f.checkCommonType(f.charType, f.varcharType, f.varcharType, true);
f.checkCommonType(f.intType, f.charType, null, true);
f.checkCommonType(f.doubleType, f.charType, null, true);
// TIMESTAMP
f.checkCommonType(f.nullType, f.timestampType, f.timestampType, true);
f.checkCommonType(f.timestampType, f.timestampType, f.timestampType, true);
f.checkCommonType(f.dateType, f.timestampType, f.timestampType, true);
f.checkCommonType(f.intType, f.timestampType, null, true);
f.checkCommonType(f.varcharType, f.timestampType, null, true);
// STRUCT
f.checkCommonType(f.nullType, f.mapType(f.intType, f.charType),
f.mapType(f.intType, f.charType), true);
f.checkCommonType(f.nullType, f.recordType(ImmutableList.of()),
f.recordType(ImmutableList.of()), true);
f.checkCommonType(f.charType, f.mapType(f.intType, f.charType), null, true);
f.checkCommonType(f.arrayType(f.intType), f.recordType(ImmutableList.of()),
null, true);
f.checkCommonType(f.recordType("a", f.intType),
f.recordType("b", f.intType), null, true);
f.checkCommonType(f.recordType("a", f.intType),
f.recordType("a", f.intType), f.recordType("a", f.intType), true);
f.checkCommonType(f.recordType("a", f.arrayType(f.intType)),
f.recordType("a", f.arrayType(f.intType)),
f.recordType("a", f.arrayType(f.intType)), true);
}
/** Test case for {@link TypeCoercion#getWiderTypeForTwo}
* and {@link TypeCoercion#getWiderTypeFor}. */
@Test void testWiderTypeFor() {
final Fixture f = fixture();
// DECIMAL please see details in SqlTypeFactoryImpl#leastRestrictiveSqlType.
f.checkWiderType(f.decimalType(5, 4), f.decimalType(7, 1),
f.decimalType(10, 4), true, true);
f.checkWiderType(f.decimalType(5, 4), f.doubleType, f.doubleType, true,
true);
f.checkWiderType(f.decimalType(5, 4), f.intType, f.decimalType(14, 4), true,
true);
f.checkWiderType(f.decimalType(5, 4), f.bigintType, f.decimalType(19, 0),
true, true);
// Array
f.checkWiderType(f.arrayType(f.smallintType), f.arrayType(f.doubleType),
f.arrayType(f.doubleType), true, true);
f.checkWiderType(f.arrayType(f.timestampType), f.arrayType(f.varcharType),
f.arrayType(f.varcharType), true, true);
f.checkWiderType(f.arrayType(f.intType), f.arrayType(f.bigintType),
f.arrayType(f.bigintType), true, true);
// No string promotion
f.checkWiderType(f.intType, f.charType, null, false, true);
f.checkWiderType(f.timestampType, f.charType, null, false, true);
f.checkWiderType(f.arrayType(f.bigintType), f.arrayType(f.charType), null,
false, true);
f.checkWiderType(f.arrayType(f.charType), f.arrayType(f.timestampType), null,
false, true);
// String promotion
f.checkWiderType(f.intType, f.charType, f.varcharType, true, true);
f.checkWiderType(f.timestampType, f.charType, f.varcharType, true, true);
f.checkWiderType(f.arrayType(f.bigintType), f.arrayType(f.varcharType),
f.arrayType(f.varcharType), true, true);
f.checkWiderType(f.arrayType(f.charType), f.arrayType(f.timestampType),
f.arrayType(f.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() {
SqlValidatorFixture f = validatorFixture();
// 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();
f.setFor(SqlStdOperatorTable.STDDEV_POP);
f.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();
f.setFor(SqlStdOperatorTable.VAR_POP);
f.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() {
final Fixture f = fixture();
// TINYINT
ImmutableList<RelDataType> charTypes = f.charTypes;
RelDataType checkedType1 = f.typeFactory.createSqlType(SqlTypeName.TINYINT);
f.checkShouldCast(checkedType1, combine(f.numericTypes, charTypes));
f.shouldCast(checkedType1, SqlTypeFamily.DECIMAL,
f.typeFactory.decimalOf(checkedType1));
f.shouldCast(checkedType1, SqlTypeFamily.NUMERIC, checkedType1);
f.shouldCast(checkedType1, SqlTypeFamily.INTEGER, checkedType1);
f.shouldCast(checkedType1, SqlTypeFamily.EXACT_NUMERIC, checkedType1);
f.shouldNotCast(checkedType1, SqlTypeFamily.APPROXIMATE_NUMERIC);
// SMALLINT
RelDataType checkedType2 = f.smallintType;
f.checkShouldCast(checkedType2, combine(f.numericTypes, charTypes));
f.shouldCast(checkedType2, SqlTypeFamily.DECIMAL,
f.typeFactory.decimalOf(checkedType2));
f.shouldCast(checkedType2, SqlTypeFamily.NUMERIC, checkedType2);
f.shouldCast(checkedType2, SqlTypeFamily.INTEGER, checkedType2);
f.shouldCast(checkedType2, SqlTypeFamily.EXACT_NUMERIC, checkedType2);
f.shouldNotCast(checkedType2, SqlTypeFamily.APPROXIMATE_NUMERIC);
// INT
RelDataType checkedType3 = f.intType;
f.checkShouldCast(checkedType3, combine(f.numericTypes, charTypes));
f.shouldCast(checkedType3, SqlTypeFamily.DECIMAL,
f.typeFactory.decimalOf(checkedType3));
f.shouldCast(checkedType3, SqlTypeFamily.NUMERIC, checkedType3);
f.shouldCast(checkedType3, SqlTypeFamily.INTEGER, checkedType3);
f.shouldCast(checkedType3, SqlTypeFamily.EXACT_NUMERIC, checkedType3);
f.shouldNotCast(checkedType3, SqlTypeFamily.APPROXIMATE_NUMERIC);
// BIGINT
RelDataType checkedType4 = f.bigintType;
f.checkShouldCast(checkedType4, combine(f.numericTypes, charTypes));
f.shouldCast(checkedType4, SqlTypeFamily.DECIMAL,
f.typeFactory.decimalOf(checkedType4));
f.shouldCast(checkedType4, SqlTypeFamily.NUMERIC, checkedType4);
f.shouldCast(checkedType4, SqlTypeFamily.INTEGER, checkedType4);
f.shouldCast(checkedType4, SqlTypeFamily.EXACT_NUMERIC, checkedType4);
f.shouldNotCast(checkedType4, SqlTypeFamily.APPROXIMATE_NUMERIC);
// FLOAT/REAL
RelDataType checkedType5 = f.floatType;
f.checkShouldCast(checkedType5, combine(f.numericTypes, charTypes));
f.shouldCast(checkedType5, SqlTypeFamily.DECIMAL,
f.typeFactory.decimalOf(checkedType5));
f.shouldCast(checkedType5, SqlTypeFamily.NUMERIC, checkedType5);
f.shouldNotCast(checkedType5, SqlTypeFamily.INTEGER);
f.shouldCast(checkedType5, SqlTypeFamily.EXACT_NUMERIC,
f.typeFactory.decimalOf(checkedType5));
f.shouldCast(checkedType5, SqlTypeFamily.APPROXIMATE_NUMERIC, checkedType5);
// DOUBLE
RelDataType checkedType6 = f.doubleType;
f.checkShouldCast(checkedType6, combine(f.numericTypes, charTypes));
f.shouldCast(checkedType6, SqlTypeFamily.DECIMAL,
f.typeFactory.decimalOf(checkedType6));
f.shouldCast(checkedType6, SqlTypeFamily.NUMERIC, checkedType6);
f.shouldNotCast(checkedType6, SqlTypeFamily.INTEGER);
f.shouldCast(checkedType6, SqlTypeFamily.EXACT_NUMERIC,
f.typeFactory.decimalOf(checkedType5));
f.shouldCast(checkedType6, SqlTypeFamily.APPROXIMATE_NUMERIC, checkedType6);
// DECIMAL(10, 2)
RelDataType checkedType7 = f.decimalType(10, 2);
f.checkShouldCast(checkedType7, combine(f.numericTypes, charTypes));
f.shouldCast(checkedType7, SqlTypeFamily.DECIMAL,
f.typeFactory.decimalOf(checkedType7));
f.shouldCast(checkedType7, SqlTypeFamily.NUMERIC, checkedType7);
f.shouldNotCast(checkedType7, SqlTypeFamily.INTEGER);
f.shouldCast(checkedType7, SqlTypeFamily.EXACT_NUMERIC, checkedType7);
f.shouldNotCast(checkedType7, SqlTypeFamily.APPROXIMATE_NUMERIC);
// BINARY
RelDataType checkedType8 = f.binaryType;
f.checkShouldCast(checkedType8, combine(f.binaryTypes, charTypes));
f.shouldNotCast(checkedType8, SqlTypeFamily.DECIMAL);
f.shouldNotCast(checkedType8, SqlTypeFamily.NUMERIC);
f.shouldNotCast(checkedType8, SqlTypeFamily.INTEGER);
// BOOLEAN
RelDataType checkedType9 = f.booleanType;
f.checkShouldCast(checkedType9, combine(f.booleanTypes, charTypes));
f.shouldNotCast(checkedType9, SqlTypeFamily.DECIMAL);
f.shouldNotCast(checkedType9, SqlTypeFamily.NUMERIC);
f.shouldNotCast(checkedType9, SqlTypeFamily.INTEGER);
// CHARACTER
RelDataType checkedType10 = f.varcharType;
ImmutableList.Builder<RelDataType> builder = ImmutableList.builder();
for (RelDataType type : f.atomicTypes) {
if (!SqlTypeUtil.isBoolean(type)) {
builder.add(type);
}
}
builder.addAll(f.geometryTypes);
f.checkShouldCast(checkedType10, builder.build());
f.shouldCast(checkedType10, SqlTypeFamily.DECIMAL,
SqlTypeUtil.getMaxPrecisionScaleDecimal(f.typeFactory));
f.shouldCast(checkedType10, SqlTypeFamily.NUMERIC,
SqlTypeUtil.getMaxPrecisionScaleDecimal(f.typeFactory));
f.shouldNotCast(checkedType10, SqlTypeFamily.BOOLEAN);
// DATE
RelDataType checkedType11 = f.dateType;
f.checkShouldCast(
checkedType11,
combine(ImmutableList.of(f.timestampType, checkedType11),
charTypes));
f.shouldNotCast(checkedType11, SqlTypeFamily.DECIMAL);
f.shouldNotCast(checkedType11, SqlTypeFamily.NUMERIC);
f.shouldNotCast(checkedType11, SqlTypeFamily.INTEGER);
// TIME
RelDataType checkedType12 = f.timeType;
f.checkShouldCast(
checkedType12,
combine(ImmutableList.of(checkedType12), charTypes));
f.shouldNotCast(checkedType12, SqlTypeFamily.DECIMAL);
f.shouldNotCast(checkedType12, SqlTypeFamily.NUMERIC);
f.shouldNotCast(checkedType12, SqlTypeFamily.INTEGER);
// TIMESTAMP
RelDataType checkedType13 = f.timestampType;
f.checkShouldCast(
checkedType13,
combine(ImmutableList.of(f.dateType, checkedType13),
charTypes));
f.shouldNotCast(checkedType13, SqlTypeFamily.DECIMAL);
f.shouldNotCast(checkedType13, SqlTypeFamily.NUMERIC);
f.shouldNotCast(checkedType13, SqlTypeFamily.INTEGER);
// NULL
RelDataType checkedType14 = f.nullType;
f.checkShouldCast(checkedType14, f.allTypes);
f.shouldCast(checkedType14, SqlTypeFamily.DECIMAL, f.decimalType);
f.shouldCast(checkedType14, SqlTypeFamily.NUMERIC, f.intType);
// INTERVAL
RelDataType checkedType15 = f.typeFactory.createSqlIntervalType(
new SqlIntervalQualifier(TimeUnit.YEAR, TimeUnit.MONTH, SqlParserPos.ZERO));
f.checkShouldCast(checkedType15, ImmutableList.of(checkedType15));
f.shouldNotCast(checkedType15, SqlTypeFamily.DECIMAL);
f.shouldNotCast(checkedType15, SqlTypeFamily.NUMERIC);
f.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 ------------------------------------------------------------
/** Everything you need to run a test. */
static class Fixture {
final TypeCoercion typeCoercion;
final RelDataTypeFactory typeFactory;
// type category.
final ImmutableList<RelDataType> numericTypes;
final ImmutableList<RelDataType> atomicTypes;
final ImmutableList<RelDataType> allTypes;
final ImmutableList<RelDataType> charTypes;
final ImmutableList<RelDataType> binaryTypes;
final ImmutableList<RelDataType> booleanTypes;
final ImmutableList<RelDataType> geometryTypes;
// single types
final RelDataType nullType;
final RelDataType booleanType;
final RelDataType tinyintType;
final RelDataType smallintType;
final RelDataType intType;
final RelDataType bigintType;
final RelDataType floatType;
final RelDataType doubleType;
final RelDataType decimalType;
final RelDataType dateType;
final RelDataType timeType;
final RelDataType timestampType;
final RelDataType binaryType;
final RelDataType varbinaryType;
final RelDataType charType;
final RelDataType varcharType;
final RelDataType varchar20Type;
final RelDataType geometryType;
/** Creates a Fixture. */
public static Fixture create(SqlTestFactory testFactory) {
final SqlValidator validator = testFactory.createValidator();
return new Fixture(validator.getTypeFactory(), validator.getTypeCoercion());
}
protected Fixture(RelDataTypeFactory typeFactory,
TypeCoercion typeCoercion) {
this.typeFactory = typeFactory;
this.typeCoercion = typeCoercion;
// Initialize single types
nullType = this.typeFactory.createSqlType(SqlTypeName.NULL);
booleanType = this.typeFactory.createSqlType(SqlTypeName.BOOLEAN);
tinyintType = this.typeFactory.createSqlType(SqlTypeName.TINYINT);
smallintType = this.typeFactory.createSqlType(SqlTypeName.SMALLINT);
intType = this.typeFactory.createSqlType(SqlTypeName.INTEGER);
bigintType = this.typeFactory.createSqlType(SqlTypeName.BIGINT);
floatType = this.typeFactory.createSqlType(SqlTypeName.FLOAT);
doubleType = this.typeFactory.createSqlType(SqlTypeName.DOUBLE);
decimalType = this.typeFactory.createSqlType(SqlTypeName.DECIMAL);
dateType = this.typeFactory.createSqlType(SqlTypeName.DATE);
timeType = this.typeFactory.createSqlType(SqlTypeName.TIME);
timestampType = this.typeFactory.createSqlType(SqlTypeName.TIMESTAMP);
binaryType = this.typeFactory.createSqlType(SqlTypeName.BINARY);
varbinaryType = this.typeFactory.createSqlType(SqlTypeName.VARBINARY);
charType = this.typeFactory.createSqlType(SqlTypeName.CHAR);
varcharType = this.typeFactory.createSqlType(SqlTypeName.VARCHAR);
varchar20Type = this.typeFactory.createSqlType(SqlTypeName.VARCHAR, 20);
geometryType = this.typeFactory.createSqlType(SqlTypeName.GEOMETRY);
// Initialize category types
// INT
ImmutableList.Builder<RelDataType> builder = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.INT_TYPES) {
builder.add(this.typeFactory.createSqlType(typeName));
}
numericTypes = builder.build();
// ATOMIC
ImmutableList.Builder<RelDataType> builder3 = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.DATETIME_TYPES) {
builder3.add(this.typeFactory.createSqlType(typeName));
}
builder3.addAll(numericTypes);
for (SqlTypeName typeName : SqlTypeName.STRING_TYPES) {
builder3.add(this.typeFactory.createSqlType(typeName));
}
for (SqlTypeName typeName : SqlTypeName.BOOLEAN_TYPES) {
builder3.add(this.typeFactory.createSqlType(typeName));
}
for (SqlTypeName typeName : SqlTypeName.GEOMETRY_TYPES) {
builder3.add(this.typeFactory.createSqlType(typeName));
}
atomicTypes = builder3.build();
// COMPLEX
ImmutableList.Builder<RelDataType> builder4 = ImmutableList.builder();
builder4.add(this.typeFactory.createArrayType(intType, -1));
builder4.add(this.typeFactory.createArrayType(varcharType, -1));
builder4.add(this.typeFactory.createMapType(varcharType, varcharType));
builder4.add(this.typeFactory.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(this.typeFactory.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, this.typeFactory.createSqlIntervalType(intervalQualifier)));
// CHARACTERS
ImmutableList.Builder<RelDataType> builder6 = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.CHAR_TYPES) {
builder6.add(this.typeFactory.createSqlType(typeName));
}
charTypes = builder6.build();
// BINARY
ImmutableList.Builder<RelDataType> builder7 = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.BINARY_TYPES) {
builder7.add(this.typeFactory.createSqlType(typeName));
}
binaryTypes = builder7.build();
// BOOLEAN
ImmutableList.Builder<RelDataType> builder8 = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.BOOLEAN_TYPES) {
builder8.add(this.typeFactory.createSqlType(typeName));
}
booleanTypes = builder8.build();
// GEOMETRY
ImmutableList.Builder<RelDataType> builder9 = ImmutableList.builder();
for (SqlTypeName typeName : SqlTypeName.GEOMETRY_TYPES) {
builder9.add(this.typeFactory.createSqlType(typeName));
}
geometryTypes = builder9.build();
}
public Fixture withTypeFactory(RelDataTypeFactory typeFactory) {
return new Fixture(typeFactory, typeCoercion);
}
//~ Tool methods -----------------------------------------------------------
RelDataType arrayType(RelDataType type) {
return typeFactory.createArrayType(type, -1);
}
RelDataType mapType(RelDataType keyType, RelDataType valType) {
return typeFactory.createMapType(keyType, valType);
}
RelDataType recordType(String name, RelDataType type) {
return typeFactory.createStructType(ImmutableList.of(Pair.of(name, type)));
}
RelDataType recordType(List<? extends Map.Entry<String, RelDataType>> pairs) {
return typeFactory.createStructType(pairs);
}
RelDataType decimalType(int precision, int scale) {
return typeFactory.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);
String reason = "Failed to cast from " + from.getSqlTypeName()
+ " to " + family;
assertThat(reason, castedType, notNullValue());
assertThat(reason,
from.equals(castedType)
|| SqlTypeUtil.equalSansNullability(typeFactory, castedType, expected)
|| expected.getSqlTypeName().getFamily().contains(castedType),
is(true));
}
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);
assertThat("Should not be able to cast from " + from.getSqlTypeName()
+ " to " + family,
castedType, nullValue());
}
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 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);
assertThat("Expected " + toStringNullable(expected)
+ " as common type for " + type1.toString()
+ " and " + type2.toString()
+ ", but found " + toStringNullable(result),
result,
sameInstance(expected));
if (isSymmetric) {
RelDataType result1 = typeCoercion.getTightestCommonType(type2, type1);
assertThat("Expected " + toStringNullable(expected)
+ " as common type for " + type2
+ " and " + type1
+ ", but found " + toStringNullable(result1),
result1, sameInstance(expected));
}
}
/** Decision method for finding a wider type. */
private void checkWiderType(
RelDataType type1,
RelDataType type2,
RelDataType expected,
boolean stringPromotion,
boolean symmetric) {
RelDataType result =
typeCoercion.getWiderTypeForTwo(type1, type2, stringPromotion);
assertThat("Expected "
+ toStringNullable(expected)
+ " as common type for " + type1.toString()
+ " and " + type2.toString()
+ ", but found " + toStringNullable(result),
result, sameInstance(expected));
if (symmetric) {
RelDataType result1 =
typeCoercion.getWiderTypeForTwo(type2, type1, stringPromotion);
assertThat("Expected " + toStringNullable(expected)
+ " as common type for " + type2
+ " and " + type1
+ ", but found " + toStringNullable(result1),
result1, sameInstance(expected));
}
}
}
}