This is an automated email from the ASF dual-hosted git repository.
cloud-fan pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.x by this push:
new 6ef75d9c4003 [SPARK-56152][SQL] Enable implicit cast from STRING to
TIME type
6ef75d9c4003 is described below
commit 6ef75d9c40037a65957208666efa5b34b758cd6e
Author: Uros Bojanic <[email protected]>
AuthorDate: Fri May 15 01:45:55 2026 +0800
[SPARK-56152][SQL] Enable implicit cast from STRING to TIME type
### What changes were proposed in this pull request?
Enable implicit casting from StringType to TimeType.
### Why are the changes needed?
Ensure proper interoperability of TIME with other data types.
### Does this PR introduce _any_ user-facing change?
Yes, strings can now be implicitly casted to TimeType.
### How was this patch tested?
Added appropriate unit tests and end-to-end SQL tests.
### Was this patch authored or co-authored using generative AI tooling?
Yes, Claude Opus 4.7.
Closes #54950 from uros-db/cast-string-to-time.
Authored-by: Uros Bojanic <[email protected]>
Signed-off-by: Wenchen Fan <[email protected]>
(cherry picked from commit b74b9f737239292e75a0fad4a085b032a8ae6906)
Signed-off-by: Wenchen Fan <[email protected]>
---
.../sql/catalyst/analysis/AnsiTypeCoercion.scala | 4 +--
.../spark/sql/catalyst/analysis/TypeCoercion.scala | 1 +
.../catalyst/analysis/AnsiTypeCoercionSuite.scala | 1 +
.../sql/catalyst/analysis/TypeCoercionSuite.scala | 10 +++++++
.../typeCoercion/native/implicitTypeCasts.sql.out | 33 ++++++++++++++++++++++
.../typeCoercion/native/implicitTypeCasts.sql | 5 ++++
.../typeCoercion/native/implicitTypeCasts.sql.out | 24 ++++++++++++++++
7 files changed, 76 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 e23e7561f0e3..23c416dd4b38 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
@@ -192,8 +192,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 ce387ef397ac..53de166e69ed 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
@@ -228,6 +228,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
diff --git
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercionSuite.scala
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercionSuite.scala
index fa5027ce259d..1f415c5ede44 100644
---
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercionSuite.scala
+++
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercionSuite.scala
@@ -88,6 +88,7 @@ class AnsiTypeCoercionSuite extends TypeCoercionSuiteBase {
shouldCast(checkedType, DecimalType, DecimalType.SYSTEM_DEFAULT)
shouldCast(checkedType, NumericType, NumericType.defaultConcreteType)
shouldCast(checkedType, AnyTimestampType,
AnyTimestampType.defaultConcreteType)
+ shouldCast(checkedType, AnyTimeType, AnyTimeType.defaultConcreteType)
shouldNotCast(checkedType, IntegralType)
}
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 e6a9690ad757..c59b687dc6ed 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("SPARK-56152: 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, _))
@@ -523,6 +532,7 @@ class TypeCoercionSuite extends TypeCoercionSuiteBase {
shouldCast(checkedType, DecimalType, DecimalType.SYSTEM_DEFAULT)
shouldCast(checkedType, NumericType, NumericType.defaultConcreteType)
shouldCast(checkedType, AnyTimestampType,
AnyTimestampType.defaultConcreteType)
+ shouldCast(checkedType, AnyTimeType, AnyTimeType.defaultConcreteType)
shouldNotCast(checkedType, IntegralType)
}
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 977b1e1459c3..11c24c8cc340 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,39 @@ 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 time_trunc('HOUR', '12:34:56') FROM t
+-- !query analysis
+Project [time_trunc(HOUR, cast(12:34:56 as time(6))) AS time_trunc(HOUR,
12:34:56)#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 6de22b8b7c3d..86efa8fa338b 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,11 @@ 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;
+SELECT time_trunc('HOUR', '12:34:56') 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 bb75fe5991ac..f9c32cfa7fab 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,30 @@ struct<length(1996-09-10 10:11:12.4):int>
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 time_trunc('HOUR', '12:34:56') FROM t
+-- !query schema
+struct<time_trunc(HOUR, 12:34:56):time(6)>
+-- !query output
+12:00:00
+
+
-- !query
SELECT year( '1996-01-10') FROM t
-- !query schema
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]