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]

Reply via email to