From b88d12a00459329038fb5176fa735a1be8a0d12e Mon Sep 17 00:00:00 2001 From: Uros Bojanic Date: Mon, 21 Jul 2025 00:47:00 +0200 Subject: [PATCH 1/3] Initial commit --- .../apache/spark/sql/catalyst/analysis/AnsiTypeCoercion.scala | 4 ++-- .../org/apache/spark/sql/catalyst/analysis/TypeCoercion.scala | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) 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 13b554eb53d4d..4a6e9e14d2aeb 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 @@ -185,8 +185,8 @@ object AnsiTypeCoercion extends TypeCoercionBase { // Ideally the implicit cast rule should be the same as `Cast.canANSIStoreAssign` so that it's // consistent with table insertion. To avoid breaking too many existing Spark SQL queries, // we make the system to allow implicitly converting String type as other primitive types. - case (_: StringType, a @ (_: AtomicType | NumericType | DecimalType | AnyTimestampType)) => - Some(a.defaultConcreteType) + case (_: StringType, a @ (_: AtomicType | NumericType | DecimalType | AnyTimestampType | + AnyTimeType)) => Some(a.defaultConcreteType) case (ArrayType(fromType, _), AbstractArrayType(toType)) => implicitCast(fromType, toType).map(ArrayType(_, true)) 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 3e5f14810935b..be6a9ef29b031 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 @@ -221,6 +221,7 @@ object TypeCoercion extends TypeCoercionBase { case (_: StringType, target: NumericType) => target case (_: StringType, datetime: DatetimeType) => datetime case (_: StringType, AnyTimestampType) => AnyTimestampType.defaultConcreteType + case (_: StringType, AnyTimeType) => AnyTimeType.defaultConcreteType case (_: StringType, BinaryType) => BinaryType // Cast any atomic type to string except if there are strings with different collations. case (any: AtomicType, st: StringType) if !any.isInstanceOf[StringType] => st From 91e78ec81e58fe6dd22ae2583255f00013dae155 Mon Sep 17 00:00:00 2001 From: Uros Bojanic Date: Fri, 29 Aug 2025 15:49:15 +0200 Subject: [PATCH 2/3] Add tests --- .../catalyst/analysis/TypeCoercionSuite.scala | 11 +++++++++- .../native/implicitTypeCasts.sql.out | 22 +++++++++++++++++++ .../typeCoercion/native/implicitTypeCasts.sql | 4 ++++ .../native/implicitTypeCasts.sql.out | 16 ++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) 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 250f20fd09571..0f81ffbdfbb4b 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 @@ -217,6 +217,15 @@ abstract class TypeCoercionSuiteBase extends AnalysisTest { shouldNotCast(checkedType, IntegralType) } + test("implicit type cast - TimeType") { + val checkedType = TimeType() + checkTypeCasting(checkedType, castableTypes = Seq(checkedType, StringType) ++ datetimeTypes) + shouldCast(checkedType, AnyTimeType, AnyTimeType.defaultConcreteType) + shouldNotCast(checkedType, DecimalType) + shouldNotCast(checkedType, NumericType) + shouldNotCast(checkedType, IntegralType) + } + test("implicit type cast between two Map types") { val sourceType = MapType(IntegerType, IntegerType, true) val castableTypes = numericTypes ++ Seq(StringType).filter(!Cast.forceNullable(IntegerType, _)) @@ -1787,7 +1796,7 @@ object TypeCoercionSuite { val fractionalTypes: Seq[DataType] = Seq(DoubleType, FloatType, DecimalType.SYSTEM_DEFAULT, DecimalType(10, 2)) val numericTypes: Seq[DataType] = integralTypes ++ fractionalTypes - val datetimeTypes: Seq[DataType] = Seq(DateType, TimestampType, TimestampNTZType) + val datetimeTypes: Seq[DataType] = Seq(DateType, TimestampType, TimestampNTZType, TimeType()) val intervalTypes: Seq[DataType] = Seq(CalendarIntervalType, DayTimeIntervalType.defaultConcreteType, YearMonthIntervalType.defaultConcreteType) val atomicTypes: Seq[DataType] = diff --git a/sql/core/src/test/resources/sql-tests/analyzer-results/typeCoercion/native/implicitTypeCasts.sql.out b/sql/core/src/test/resources/sql-tests/analyzer-results/typeCoercion/native/implicitTypeCasts.sql.out index 977b1e1459c3e..910a320ae8772 100644 --- a/sql/core/src/test/resources/sql-tests/analyzer-results/typeCoercion/native/implicitTypeCasts.sql.out +++ b/sql/core/src/test/resources/sql-tests/analyzer-results/typeCoercion/native/implicitTypeCasts.sql.out @@ -359,6 +359,28 @@ Project [length(cast(cast(1996-09-10 10:11:12.4 as timestamp) as string)) AS len +- OneRowRelation +-- !query +SELECT '12:00:00' = TIME'12:00:00' FROM t +-- !query analysis +Project [(cast(12:00:00 as time(6)) = 12:00:00) AS (12:00:00 = TIME '12:00:00')#x] ++- SubqueryAlias t + +- View (`t`, [1#x]) + +- Project [cast(1#x as int) AS 1#x] + +- Project [1 AS 1#x] + +- OneRowRelation + + +-- !query +SELECT '12:00:01' > TIME'12:00:00' FROM t +-- !query analysis +Project [(cast(12:00:01 as time(6)) > 12:00:00) AS (12:00:01 > TIME '12:00:00')#x] ++- SubqueryAlias t + +- View (`t`, [1#x]) + +- Project [cast(1#x as int) AS 1#x] + +- Project [1 AS 1#x] + +- OneRowRelation + + -- !query SELECT year( '1996-01-10') FROM t -- !query analysis diff --git a/sql/core/src/test/resources/sql-tests/inputs/typeCoercion/native/implicitTypeCasts.sql b/sql/core/src/test/resources/sql-tests/inputs/typeCoercion/native/implicitTypeCasts.sql index 6de22b8b7c3de..21c4856d07397 100644 --- a/sql/core/src/test/resources/sql-tests/inputs/typeCoercion/native/implicitTypeCasts.sql +++ b/sql/core/src/test/resources/sql-tests/inputs/typeCoercion/native/implicitTypeCasts.sql @@ -56,6 +56,10 @@ SELECT length('four') FROM t; SELECT length(date('1996-09-10')) FROM t; SELECT length(timestamp('1996-09-10 10:11:12.4')) FROM t; +-- string to time +SELECT '12:00:00' = TIME'12:00:00' FROM t; +SELECT '12:00:01' > TIME'12:00:00' FROM t; + -- extract SELECT year( '1996-01-10') FROM t; SELECT month( '1996-01-10') FROM t; diff --git a/sql/core/src/test/resources/sql-tests/results/typeCoercion/native/implicitTypeCasts.sql.out b/sql/core/src/test/resources/sql-tests/results/typeCoercion/native/implicitTypeCasts.sql.out index bb75fe5991acf..726588623381c 100644 --- a/sql/core/src/test/resources/sql-tests/results/typeCoercion/native/implicitTypeCasts.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/typeCoercion/native/implicitTypeCasts.sql.out @@ -263,6 +263,22 @@ struct 21 +-- !query +SELECT '12:00:00' = TIME'12:00:00' FROM t +-- !query schema +struct<(12:00:00 = TIME '12:00:00'):boolean> +-- !query output +true + + +-- !query +SELECT '12:00:01' > TIME'12:00:00' FROM t +-- !query schema +struct<(12:00:01 > TIME '12:00:00'):boolean> +-- !query output +true + + -- !query SELECT year( '1996-01-10') FROM t -- !query schema From d004429f2398e226d82555bbe94c706a68a7910e Mon Sep 17 00:00:00 2001 From: Uros Bojanic Date: Mon, 23 Mar 2026 10:42:11 +0100 Subject: [PATCH 3/3] Update test name --- .../apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 eec80e5d1629a..9b313511010f3 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 @@ -217,7 +217,7 @@ abstract class TypeCoercionSuiteBase extends AnalysisTest { shouldNotCast(checkedType, IntegralType) } - test("implicit type cast - TimeType") { + test("SPARK-56152: implicit type cast - TimeType") { val checkedType = TimeType() checkTypeCasting(checkedType, castableTypes = Seq(checkedType, StringType) ++ datetimeTypes) shouldCast(checkedType, AnyTimeType, AnyTimeType.defaultConcreteType)