in flink-table/flink-table-planner-blink/src/main/scala/org/apache/flink/table/planner/codegen/calls/ScalarOperatorGens.scala [860:1295]
def generateCast(
ctx: CodeGeneratorContext,
operand: GeneratedExpression,
targetType: LogicalType)
: GeneratedExpression = (operand.resultType.getTypeRoot, targetType.getTypeRoot) match {
// special case: cast from TimeIndicatorTypeInfo to SqlTimeTypeInfo
case (TIMESTAMP_WITHOUT_TIME_ZONE, TIMESTAMP_WITHOUT_TIME_ZONE)
if operand.resultType.asInstanceOf[TimestampType].getKind == TimestampKind.PROCTIME ||
operand.resultType.asInstanceOf[TimestampType].getKind == TimestampKind.ROWTIME ||
targetType.asInstanceOf[TimestampType].getKind == TimestampKind.PROCTIME ||
targetType.asInstanceOf[TimestampType].getKind == TimestampKind.ROWTIME =>
operand.copy(resultType = new TimestampType(3)) // just replace the DataType
case (TIMESTAMP_WITHOUT_TIME_ZONE, TIMESTAMP_WITHOUT_TIME_ZONE) =>
val fromType = operand.resultType.asInstanceOf[TimestampType]
val toType = targetType.asInstanceOf[TimestampType]
if (fromType.getPrecision <= toType.getPrecision) {
operand.copy(resultType = targetType)
} else {
val method = qualifyMethod(BuiltInMethods.TRUNCATE_SQL_TIMESTAMP)
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
s"$method($operandTerm, ${toType.getPrecision})"
}
}
case (TIMESTAMP_WITHOUT_TIME_ZONE, TIMESTAMP_WITH_LOCAL_TIME_ZONE) =>
val fromType = operand.resultType.asInstanceOf[TimestampType]
val toType = targetType.asInstanceOf[LocalZonedTimestampType]
val method = qualifyMethod(BuiltInMethods.TIMESTAMP_TO_TIMESTAMP_WITH_LOCAL_ZONE)
if (fromType.getPrecision < toType.getPrecision) {
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
val timeZone = ctx.addReusableSessionTimeZone()
s"$method($operandTerm, $timeZone)"
}
} else {
val truncate_method = qualifyMethod(BuiltInMethods.TRUNCATE_SQL_TIMESTAMP)
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
val timeZone = ctx.addReusableSessionTimeZone()
s"$truncate_method($method($operandTerm, $timeZone), ${toType.getPrecision})"
}
}
case (TIMESTAMP_WITH_LOCAL_TIME_ZONE, TIMESTAMP_WITHOUT_TIME_ZONE) =>
val fromType = operand.resultType.asInstanceOf[LocalZonedTimestampType]
val toType = targetType.asInstanceOf[TimestampType]
val method = qualifyMethod(BuiltInMethods.TIMESTAMP_WITH_LOCAL_ZONE_TO_TIMESTAMP)
if (fromType.getPrecision < toType.getPrecision) {
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
val zone = ctx.addReusableSessionTimeZone()
s"$method($operandTerm, $zone)"
}
} else {
val truncate_method = qualifyMethod(BuiltInMethods.TRUNCATE_SQL_TIMESTAMP)
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
val zone = ctx.addReusableSessionTimeZone()
s"$truncate_method($method($operandTerm, $zone), ${toType.getPrecision})"
}
}
case (TIMESTAMP_WITH_LOCAL_TIME_ZONE, TIMESTAMP_WITH_LOCAL_TIME_ZONE) =>
val fromType = operand.resultType.asInstanceOf[LocalZonedTimestampType]
val toType = targetType.asInstanceOf[LocalZonedTimestampType]
if (fromType.getPrecision <= toType.getPrecision) {
operand.copy(resultType = targetType)
} else {
val method = qualifyMethod(BuiltInMethods.TRUNCATE_SQL_TIMESTAMP)
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
s"$method($operandTerm, ${toType.getPrecision})"
}
}
// identity casting
case (_, _) if isInteroperable(operand.resultType, targetType) =>
operand.copy(resultType = targetType)
// Date/Time/Timestamp -> String
case (_, VARCHAR | CHAR) if TypeCheckUtils.isTimePoint(operand.resultType) =>
generateStringResultCallIfArgsNotNull(ctx, Seq(operand)) {
operandTerm =>
s"${localTimeToStringCode(ctx, operand.resultType, operandTerm.head)}"
}
// Interval Months -> String
case (INTERVAL_YEAR_MONTH, VARCHAR | CHAR) =>
val method = qualifyMethod(BuiltInMethod.INTERVAL_YEAR_MONTH_TO_STRING.method)
val timeUnitRange = qualifyEnum(TimeUnitRange.YEAR_TO_MONTH)
generateStringResultCallIfArgsNotNull(ctx, Seq(operand)) {
terms => s"$method(${terms.head}, $timeUnitRange)"
}
// Interval Millis -> String
case (INTERVAL_DAY_TIME, VARCHAR | CHAR) =>
val method = qualifyMethod(BuiltInMethod.INTERVAL_DAY_TIME_TO_STRING.method)
val timeUnitRange = qualifyEnum(TimeUnitRange.DAY_TO_SECOND)
generateStringResultCallIfArgsNotNull(ctx, Seq(operand)) {
terms => s"$method(${terms.head}, $timeUnitRange, 3)" // milli second precision
}
// Array -> String
case (ARRAY, VARCHAR | CHAR) =>
generateCastArrayToString(ctx, operand, operand.resultType.asInstanceOf[ArrayType])
// Byte array -> String UTF-8
case (VARBINARY, VARCHAR | CHAR) =>
val charset = classOf[StandardCharsets].getCanonicalName
generateStringResultCallIfArgsNotNull(ctx, Seq(operand)) {
terms => s"(new String(${terms.head}, $charset.UTF_8))"
}
// Map -> String
case (MAP, VARCHAR | CHAR) =>
generateCastMapToString(ctx, operand, operand.resultType.asInstanceOf[MapType])
// composite type -> String
case (ROW, VARCHAR | CHAR) =>
generateCastRowDataToString(ctx, operand, operand.resultType.asInstanceOf[RowType])
case (RAW, VARCHAR | CHAR) =>
generateStringResultCallIfArgsNotNull(ctx, Seq(operand)) {
terms =>
val converter = DataFormatConverters.getConverterForDataType(
fromLogicalTypeToDataType(operand.resultType))
val converterTerm = ctx.addReusableObject(converter, "converter")
s""" "" + $converterTerm.toExternal(${terms.head})"""
}
// * (not Date/Time/Timestamp) -> String
// TODO: GenericType with Date/Time/Timestamp -> String would call toString implicitly
case (_, VARCHAR | CHAR) =>
generateStringResultCallIfArgsNotNull(ctx, Seq(operand)) {
terms => s""" "" + ${terms.head}"""
}
// String -> Boolean
case (VARCHAR | CHAR, BOOLEAN) =>
generateUnaryOperatorIfNotNull(
ctx,
targetType,
operand,
resultNullable = true) {
operandTerm => s"$BINARY_STRING_UTIL.toBooleanSQL($operandTerm)"
}
// String -> NUMERIC TYPE (not Character)
case (VARCHAR | CHAR, _)
if TypeCheckUtils.isNumeric(targetType) =>
targetType match {
case dt: DecimalType =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) { operandTerm =>
s"$BINARY_STRING_UTIL.toDecimal($operandTerm, ${dt.getPrecision}, ${dt.getScale})"
}
case _ =>
val methodName = targetType.getTypeRoot match {
case TINYINT => "toByte"
case SMALLINT => "toShort"
case INTEGER => "toInt"
case BIGINT => "toLong"
case DOUBLE => "toDouble"
case FLOAT => "toFloat"
case _ => null
}
assert(methodName != null, "Unexpected data type.")
generateUnaryOperatorIfNotNull(
ctx,
targetType,
operand,
resultNullable = true) {
operandTerm => s"($BINARY_STRING_UTIL.$methodName($operandTerm.trim()))"
}
}
// String -> Date
case (VARCHAR | CHAR, DATE) =>
generateUnaryOperatorIfNotNull(
ctx,
targetType,
operand,
resultNullable = true) {
operandTerm =>
s"${qualifyMethod(BuiltInMethods.STRING_TO_DATE)}($operandTerm.toString())"
}
// String -> Time
case (VARCHAR | CHAR, TIME_WITHOUT_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(
ctx,
targetType,
operand,
resultNullable = true) {
operandTerm =>
s"${qualifyMethod(BuiltInMethods.STRING_TO_TIME)}($operandTerm.toString())"
}
// String -> Timestamp
case (VARCHAR | CHAR, TIMESTAMP_WITHOUT_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(
ctx,
targetType,
operand,
resultNullable = true) {
operandTerm =>
s"""
|${qualifyMethod(BuiltInMethods.STRING_TO_TIMESTAMP)}($operandTerm.toString())
""".stripMargin
}
case (VARCHAR | CHAR, TIMESTAMP_WITH_LOCAL_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(
ctx, targetType, operand, resultNullable = true) { operandTerm =>
val zone = ctx.addReusableSessionTimeZone()
val method = qualifyMethod(BuiltInMethods.STRING_TO_TIMESTAMP_TIME_ZONE)
s"$TIMESTAMP_DATA.fromEpochMillis($method($operandTerm.toString(), $zone))"
}
// String -> binary
case (VARCHAR | CHAR, VARBINARY | BINARY) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"$operandTerm.toBytes()"
}
// Note: SQL2003 $6.12 - casting is not allowed between boolean and numeric types.
// Calcite does not allow it either.
// Boolean -> DECIMAL
case (BOOLEAN, DECIMAL) =>
val dt = targetType.asInstanceOf[DecimalType]
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"$DECIMAL_UTIL.castFrom($operandTerm, ${dt.getPrecision}, ${dt.getScale})"
}
// Boolean -> NUMERIC TYPE
case (BOOLEAN, _) if TypeCheckUtils.isNumeric(targetType) =>
val targetTypeTerm = primitiveTypeTermForType(targetType)
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"($targetTypeTerm) ($operandTerm ? 1 : 0)"
}
// DECIMAL -> Boolean
case (DECIMAL, BOOLEAN) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"$DECIMAL_UTIL.castToBoolean($operandTerm)"
}
// DECIMAL -> Timestamp
case (DECIMAL, TIMESTAMP_WITHOUT_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
s"$TIMESTAMP_DATA.fromEpochMillis($DECIMAL_UTIL.castToTimestamp($operandTerm))"
}
// NUMERIC TYPE -> Boolean
case (_, BOOLEAN) if isNumeric(operand.resultType) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"$operandTerm != 0"
}
// between NUMERIC TYPE | Decimal
case (_, _) if isNumeric(operand.resultType) && isNumeric(targetType) =>
val operandCasting = numericCasting(operand.resultType, targetType)
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"${operandCasting(operandTerm)}"
}
// Date -> Timestamp
case (DATE, TIMESTAMP_WITHOUT_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
s"""
|$TIMESTAMP_DATA.fromEpochMillis(
| $operandTerm * ${classOf[DateTimeUtils].getCanonicalName}.MILLIS_PER_DAY)
""".stripMargin
}
// Timestamp -> Date
case (TIMESTAMP_WITHOUT_TIME_ZONE, DATE) =>
val targetTypeTerm = primitiveTypeTermForType(targetType)
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
s"""
|($targetTypeTerm) ($operandTerm.getMillisecond() /
| ${classOf[DateTimeUtils].getCanonicalName}.MILLIS_PER_DAY)
""".stripMargin
}
// Time -> Timestamp
case (TIME_WITHOUT_TIME_ZONE, TIMESTAMP_WITHOUT_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"$TIMESTAMP_DATA.fromEpochMillis($operandTerm)"
}
// Timestamp -> Time
case (TIMESTAMP_WITHOUT_TIME_ZONE, TIME_WITHOUT_TIME_ZONE) =>
val targetTypeTerm = primitiveTypeTermForType(targetType)
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
s"($targetTypeTerm) ($operandTerm.getMillisecond() % " +
s"${classOf[DateTimeUtils].getCanonicalName}.MILLIS_PER_DAY)"
}
// Date -> Timestamp with local time zone
case (DATE, TIMESTAMP_WITH_LOCAL_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) { operandTerm =>
val zone = ctx.addReusableSessionTimeZone()
val method = qualifyMethod(BuiltInMethods.DATE_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE)
s"$TIMESTAMP_DATA.fromEpochMillis($method($operandTerm, $zone))"
}
// Timestamp with local time zone -> Date
case (TIMESTAMP_WITH_LOCAL_TIME_ZONE, DATE) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) { operandTerm =>
val zone = ctx.addReusableSessionTimeZone()
val method = qualifyMethod(BuiltInMethods.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_DATE)
s"$method($operandTerm.getMillisecond(), $zone)"
}
// Time -> Timestamp with local time zone
case (TIME_WITHOUT_TIME_ZONE, TIMESTAMP_WITH_LOCAL_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) { operandTerm =>
val zone = ctx.addReusableSessionTimeZone()
val method = qualifyMethod(BuiltInMethods.TIME_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE)
s"$TIMESTAMP_DATA.fromEpochMillis($method($operandTerm, $zone))"
}
// Timestamp with local time zone -> Time
case (TIMESTAMP_WITH_LOCAL_TIME_ZONE, TIME_WITHOUT_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) { operandTerm =>
val zone = ctx.addReusableSessionTimeZone()
val method = qualifyMethod(BuiltInMethods.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME)
s"$method($operandTerm.getMillisecond(), $zone)"
}
// Timestamp -> Decimal
case (TIMESTAMP_WITHOUT_TIME_ZONE, DECIMAL) =>
val dt = targetType.asInstanceOf[DecimalType]
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm =>
s"""
|$DECIMAL_UTIL.castFrom(
| ((double) ($operandTerm.getMillisecond() / 1000.0)),
| ${dt.getPrecision}, ${dt.getScale})
""".stripMargin
}
// Tinyint -> Timestamp
// Smallint -> Timestamp
// Int -> Timestamp
// Bigint -> Timestamp
case (TINYINT, TIMESTAMP_WITHOUT_TIME_ZONE) |
(SMALLINT, TIMESTAMP_WITHOUT_TIME_ZONE) |
(INTEGER, TIMESTAMP_WITHOUT_TIME_ZONE) |
(BIGINT, TIMESTAMP_WITHOUT_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"$TIMESTAMP_DATA.fromEpochMillis(((long) $operandTerm) * 1000)"
}
// Float -> Timestamp
// Double -> Timestamp
case (FLOAT, TIMESTAMP_WITHOUT_TIME_ZONE) |
(DOUBLE, TIMESTAMP_WITHOUT_TIME_ZONE) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"$TIMESTAMP_DATA.fromEpochMillis((long) ($operandTerm * 1000))"
}
// Timestamp -> Tinyint
case (TIMESTAMP_WITHOUT_TIME_ZONE, TINYINT) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"((byte) ($operandTerm.getMillisecond() / 1000))"
}
// Timestamp -> Smallint
case (TIMESTAMP_WITHOUT_TIME_ZONE, SMALLINT) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"((short) ($operandTerm.getMillisecond() / 1000))"
}
// Timestamp -> Int
case (TIMESTAMP_WITHOUT_TIME_ZONE, INTEGER) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"((int) ($operandTerm.getMillisecond() / 1000))"
}
// Timestamp -> BigInt
case (TIMESTAMP_WITHOUT_TIME_ZONE, BIGINT) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"((long) ($operandTerm.getMillisecond() / 1000))"
}
// Timestamp -> Float
case (TIMESTAMP_WITHOUT_TIME_ZONE, FLOAT) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"((float) ($operandTerm.getMillisecond() / 1000.0))"
}
// Timestamp -> Double
case (TIMESTAMP_WITHOUT_TIME_ZONE, DOUBLE) =>
generateUnaryOperatorIfNotNull(ctx, targetType, operand) {
operandTerm => s"((double) ($operandTerm.getMillisecond() / 1000.0))"
}
// internal temporal casting
// Date -> Integer
// Time -> Integer
// Integer -> Date
// Integer -> Time
// Integer -> Interval Months
// Long -> Interval Millis
// Interval Months -> Integer
// Interval Millis -> Long
case (DATE, INTEGER) |
(TIME_WITHOUT_TIME_ZONE, INTEGER) |
(INTEGER, DATE) |
(INTEGER, TIME_WITHOUT_TIME_ZONE) |
(INTEGER, INTERVAL_YEAR_MONTH) |
(BIGINT, INTERVAL_DAY_TIME) |
(INTERVAL_YEAR_MONTH, INTEGER) |
(INTERVAL_DAY_TIME, BIGINT) =>
internalExprCasting(operand, targetType)
// internal reinterpretation of temporal types
// Date, Time, Interval Months -> Long
case (DATE, BIGINT)
| (TIME_WITHOUT_TIME_ZONE, BIGINT)
| (INTERVAL_YEAR_MONTH, BIGINT) =>
internalExprCasting(operand, targetType)
case (_, _) =>
throw new CodeGenException(s"Unsupported cast from '${operand.resultType}' to '$targetType'.")
}