diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercion.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercion.scala index 457dc10028..f03296fdee 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercion.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercion.scala @@ -120,6 +120,11 @@ object AnsiTypeCoercion extends TypeCoercionBase { case (_: TimestampType, _: DateType) | (_: DateType, _: TimestampType) => Some(TimestampType) + case (t1: DayTimeIntervalType, t2: DayTimeIntervalType) => + Some(DayTimeIntervalType(t1.startField.min(t2.startField), t1.endField.max(t2.endField))) + case (t1: YearMonthIntervalType, t2: YearMonthIntervalType) => + Some(YearMonthIntervalType(t1.startField.min(t2.startField), t1.endField.max(t2.endField))) + case (t1, t2) => findTypeForComplex(t1, t2, findTightestCommonType) } diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercion.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercion.scala index 42c10e8a11..db6f499f2b 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercion.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercion.scala @@ -867,6 +867,11 @@ object TypeCoercion extends TypeCoercionBase { case (_: TimestampType, _: DateType) | (_: DateType, _: TimestampType) => Some(TimestampType) + case (t1: DayTimeIntervalType, t2: DayTimeIntervalType) => + Some(DayTimeIntervalType(t1.startField.min(t2.startField), t1.endField.max(t2.endField))) + case (t1: YearMonthIntervalType, t2: YearMonthIntervalType) => + Some(YearMonthIntervalType(t1.startField.min(t2.startField), t1.endField.max(t2.endField))) + case (_: TimestampNTZType, _: DateType) | (_: DateType, _: TimestampNTZType) => Some(TimestampNTZType) diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala index 602daf8dde..6a7d7ef988 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala @@ -18,6 +18,7 @@ package org.apache.spark.sql.catalyst.analysis import java.sql.Timestamp +import java.time.{Duration, Period} import org.apache.spark.internal.config.Tests.IS_TESTING import org.apache.spark.sql.catalyst.analysis.TypeCoercion._ @@ -1604,6 +1605,52 @@ class TypeCoercionSuite extends AnalysisTest { ruleTest(TypeCoercion.IntegralDivision, IntegralDivide(2, 1L), IntegralDivide(Cast(2, LongType), 1L)) } + + test("SPARK-36431: Support TypeCoercion of ANSI intervals with different fields") { + DataTypeTestUtils.yearMonthIntervalTypes.foreach { ym1 => + DataTypeTestUtils.yearMonthIntervalTypes.foreach { ym2 => + val literal1 = Literal.create(Period.ofMonths(12), ym1) + val literal2 = Literal.create(Period.ofMonths(12), ym2) + val commonType = YearMonthIntervalType( + ym1.startField.min(ym2.startField), ym1.endField.max(ym2.endField)) + if (commonType == ym1 && commonType == ym2) { + ruleTest(TypeCoercion.ImplicitTypeCasts, EqualTo(literal1, literal2), + EqualTo(literal1, literal2)) + } else if (commonType == ym1) { + ruleTest(TypeCoercion.ImplicitTypeCasts, EqualTo(literal1, literal2), + EqualTo(literal1, Cast(literal2, commonType))) + } else if (commonType == ym2) { + ruleTest(TypeCoercion.ImplicitTypeCasts, EqualTo(literal1, literal2), + EqualTo(Cast(literal1, commonType), literal2)) + } else { + ruleTest(TypeCoercion.ImplicitTypeCasts, EqualTo(literal1, literal2), + EqualTo(Cast(literal1, commonType), Cast(literal2, commonType))) + } + } + } + + DataTypeTestUtils.dayTimeIntervalTypes.foreach { dt1 => + DataTypeTestUtils.dayTimeIntervalTypes.foreach { dt2 => + val literal1 = Literal.create(Duration.ofSeconds(1111), dt1) + val literal2 = Literal.create(Duration.ofSeconds(1111), dt2) + val commonType = DayTimeIntervalType( + dt1.startField.min(dt2.startField), dt1.endField.max(dt2.endField)) + if (commonType == dt1 && commonType == dt2) { + ruleTest(TypeCoercion.ImplicitTypeCasts, EqualTo(literal1, literal2), + EqualTo(literal1, literal2)) + } else if (commonType == dt1) { + ruleTest(TypeCoercion.ImplicitTypeCasts, EqualTo(literal1, literal2), + EqualTo(literal1, Cast(literal2, commonType))) + } else if (commonType == dt2) { + ruleTest(TypeCoercion.ImplicitTypeCasts, EqualTo(literal1, literal2), + EqualTo(Cast(literal1, commonType), literal2)) + } else { + ruleTest(TypeCoercion.ImplicitTypeCasts, EqualTo(literal1, literal2), + EqualTo(Cast(literal1, commonType), Cast(literal2, commonType))) + } + } + } + } } diff --git a/sql/core/src/test/resources/sql-tests/inputs/interval.sql b/sql/core/src/test/resources/sql-tests/inputs/interval.sql index 43d1e03fab..a16d152816 100644 --- a/sql/core/src/test/resources/sql-tests/inputs/interval.sql +++ b/sql/core/src/test/resources/sql-tests/inputs/interval.sql @@ -322,3 +322,16 @@ SELECT INTERVAL '153722867280' MINUTE; SELECT INTERVAL '-153722867280' MINUTE; SELECT INTERVAL '54.775807' SECOND; SELECT INTERVAL '-54.775807' SECOND; + +SELECT INTERVAL '1' DAY > INTERVAL '1' HOUR; +SELECT INTERVAL '1 02' DAY TO HOUR = INTERVAL '02:10:55' HOUR TO SECOND; +SELECT INTERVAL '1' YEAR < INTERVAL '1' MONTH; +SELECT INTERVAL '-1-1' YEAR TO MONTH = INTERVAL '-13' MONTH; +SELECT INTERVAL 1 MONTH > INTERVAL 20 DAYS; + +SELECT array(INTERVAL '1' YEAR, INTERVAL '1' MONTH); +SELECT array(INTERVAL '1' DAY, INTERVAL '01:01' HOUR TO MINUTE); +SELECT array(INTERVAL 1 MONTH, INTERVAL 20 DAYS); +SELECT coalesce(INTERVAL '1' YEAR, INTERVAL '1' MONTH); +SELECT coalesce(INTERVAL '1' DAY, INTERVAL '01:01' HOUR TO MINUTE); +SELECT coalesce(INTERVAL 1 MONTH, INTERVAL 20 DAYS); diff --git a/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out b/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out index 9bf492ef35..9ba5da322c 100644 --- a/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out @@ -1,5 +1,5 @@ -- Automatically generated by SQLQueryTestSuite --- Number of queries: 200 +-- Number of queries: 211 -- !query @@ -818,10 +818,9 @@ struct> -- !query select map(1, interval 1 year, 2, interval 2 month) -- !query schema -struct<> +struct> -- !query output -org.apache.spark.sql.AnalysisException -cannot resolve 'map(1, INTERVAL '1' YEAR, 2, INTERVAL '2' MONTH)' due to data type mismatch: The given values of function map should all be the same type, but they are [interval year, interval month]; line 1 pos 7 +{1:1-0,2:0-2} -- !query @@ -1985,3 +1984,94 @@ SELECT INTERVAL '-54.775807' SECOND struct -- !query output -0 00:00:54.775807000 + + +-- !query +SELECT INTERVAL '1' DAY > INTERVAL '1' HOUR +-- !query schema +struct<(INTERVAL '1' DAY > INTERVAL '01' HOUR):boolean> +-- !query output +true + + +-- !query +SELECT INTERVAL '1 02' DAY TO HOUR = INTERVAL '02:10:55' HOUR TO SECOND +-- !query schema +struct<(INTERVAL '1 02' DAY TO HOUR = INTERVAL '02:10:55' HOUR TO SECOND):boolean> +-- !query output +false + + +-- !query +SELECT INTERVAL '1' YEAR < INTERVAL '1' MONTH +-- !query schema +struct<(INTERVAL '1' YEAR < INTERVAL '1' MONTH):boolean> +-- !query output +false + + +-- !query +SELECT INTERVAL '-1-1' YEAR TO MONTH = INTERVAL '-13' MONTH +-- !query schema +struct<(INTERVAL '-1-1' YEAR TO MONTH = INTERVAL '-13' MONTH):boolean> +-- !query output +true + + +-- !query +SELECT INTERVAL 1 MONTH > INTERVAL 20 DAYS +-- !query schema +struct<> +-- !query output +org.apache.spark.sql.AnalysisException +cannot resolve '(INTERVAL '1' MONTH > INTERVAL '20' DAY)' due to data type mismatch: differing types in '(INTERVAL '1' MONTH > INTERVAL '20' DAY)' (interval month and interval day).; line 1 pos 7 + + +-- !query +SELECT array(INTERVAL '1' YEAR, INTERVAL '1' MONTH) +-- !query schema +struct> +-- !query output +[1-0,0-1] + + +-- !query +SELECT array(INTERVAL '1' DAY, INTERVAL '01:01' HOUR TO MINUTE) +-- !query schema +struct> +-- !query output +[1 00:00:00.000000000,0 01:01:00.000000000] + + +-- !query +SELECT array(INTERVAL 1 MONTH, INTERVAL 20 DAYS) +-- !query schema +struct<> +-- !query output +org.apache.spark.sql.AnalysisException +cannot resolve 'array(INTERVAL '1' MONTH, INTERVAL '20' DAY)' due to data type mismatch: input to function array should all be the same type, but it's [interval month, interval day]; line 1 pos 7 + + +-- !query +SELECT coalesce(INTERVAL '1' YEAR, INTERVAL '1' MONTH) +-- !query schema +struct +-- !query output +1-0 + + +-- !query +SELECT coalesce(INTERVAL '1' DAY, INTERVAL '01:01' HOUR TO MINUTE) +-- !query schema +struct +-- !query output +1 00:00:00.000000000 + + +-- !query +SELECT coalesce(INTERVAL 1 MONTH, INTERVAL 20 DAYS) +-- !query schema +struct<> +-- !query output +org.apache.spark.sql.AnalysisException +cannot resolve 'coalesce(INTERVAL '1' MONTH, INTERVAL '20' DAY)' due to data type mismatch: input to function coalesce should all be the same type, but it's [interval month, interval day]; line 1 pos 7 diff --git a/sql/core/src/test/resources/sql-tests/results/interval.sql.out b/sql/core/src/test/resources/sql-tests/results/interval.sql.out index 8780365f64..a15cc23672 100644 --- a/sql/core/src/test/resources/sql-tests/results/interval.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/interval.sql.out @@ -1,5 +1,5 @@ -- Automatically generated by SQLQueryTestSuite --- Number of queries: 200 +-- Number of queries: 211 -- !query @@ -817,10 +817,9 @@ struct> -- !query select map(1, interval 1 year, 2, interval 2 month) -- !query schema -struct<> +struct> -- !query output -org.apache.spark.sql.AnalysisException -cannot resolve 'map(1, INTERVAL '1' YEAR, 2, INTERVAL '2' MONTH)' due to data type mismatch: The given values of function map should all be the same type, but they are [interval year, interval month]; line 1 pos 7 +{1:1-0,2:0-2} -- !query @@ -1984,3 +1983,94 @@ SELECT INTERVAL '-54.775807' SECOND struct -- !query output -0 00:00:54.775807000 + + +-- !query +SELECT INTERVAL '1' DAY > INTERVAL '1' HOUR +-- !query schema +struct<(INTERVAL '1' DAY > INTERVAL '01' HOUR):boolean> +-- !query output +true + + +-- !query +SELECT INTERVAL '1 02' DAY TO HOUR = INTERVAL '02:10:55' HOUR TO SECOND +-- !query schema +struct<(INTERVAL '1 02' DAY TO HOUR = INTERVAL '02:10:55' HOUR TO SECOND):boolean> +-- !query output +false + + +-- !query +SELECT INTERVAL '1' YEAR < INTERVAL '1' MONTH +-- !query schema +struct<(INTERVAL '1' YEAR < INTERVAL '1' MONTH):boolean> +-- !query output +false + + +-- !query +SELECT INTERVAL '-1-1' YEAR TO MONTH = INTERVAL '-13' MONTH +-- !query schema +struct<(INTERVAL '-1-1' YEAR TO MONTH = INTERVAL '-13' MONTH):boolean> +-- !query output +true + + +-- !query +SELECT INTERVAL 1 MONTH > INTERVAL 20 DAYS +-- !query schema +struct<> +-- !query output +org.apache.spark.sql.AnalysisException +cannot resolve '(INTERVAL '1' MONTH > INTERVAL '20' DAY)' due to data type mismatch: differing types in '(INTERVAL '1' MONTH > INTERVAL '20' DAY)' (interval month and interval day).; line 1 pos 7 + + +-- !query +SELECT array(INTERVAL '1' YEAR, INTERVAL '1' MONTH) +-- !query schema +struct> +-- !query output +[1-0,0-1] + + +-- !query +SELECT array(INTERVAL '1' DAY, INTERVAL '01:01' HOUR TO MINUTE) +-- !query schema +struct> +-- !query output +[1 00:00:00.000000000,0 01:01:00.000000000] + + +-- !query +SELECT array(INTERVAL 1 MONTH, INTERVAL 20 DAYS) +-- !query schema +struct<> +-- !query output +org.apache.spark.sql.AnalysisException +cannot resolve 'array(INTERVAL '1' MONTH, INTERVAL '20' DAY)' due to data type mismatch: input to function array should all be the same type, but it's [interval month, interval day]; line 1 pos 7 + + +-- !query +SELECT coalesce(INTERVAL '1' YEAR, INTERVAL '1' MONTH) +-- !query schema +struct +-- !query output +1-0 + + +-- !query +SELECT coalesce(INTERVAL '1' DAY, INTERVAL '01:01' HOUR TO MINUTE) +-- !query schema +struct +-- !query output +1 00:00:00.000000000 + + +-- !query +SELECT coalesce(INTERVAL 1 MONTH, INTERVAL 20 DAYS) +-- !query schema +struct<> +-- !query output +org.apache.spark.sql.AnalysisException +cannot resolve 'coalesce(INTERVAL '1' MONTH, INTERVAL '20' DAY)' due to data type mismatch: input to function coalesce should all be the same type, but it's [interval month, interval day]; line 1 pos 7