This is an automated email from the ASF dual-hosted git repository.

maxgekk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 2ba0eab9db25 [SPARK-51562][SQL] Add `time` function
2ba0eab9db25 is described below

commit 2ba0eab9db25e1e5958f9bb5bcc58b97c8c6c505
Author: Uros Bojanic <uros.boja...@databricks.com>
AuthorDate: Fri Jul 18 21:01:28 2025 +0200

    [SPARK-51562][SQL] Add `time` function
    
    ### What changes were proposed in this pull request?
    Add new function `time`, which should cast an `expr` to TIME type.
    
    Syntax
    ```
    time(expr)
    ```
    
    Arguments
    - `expr`: Any expression that can be cast to TIMESTAMP.
    
    Returns
    - A TIME.
    
    Note that this function is a synonym for CAST(expr AS TIME).
    
    ### Why are the changes needed?
    Expanding function coverage for TIME type.
    
    ### Does this PR introduce _any_ user-facing change?
    Yes, this PR adds the new `time` function.
    
    ### How was this patch tested?
    Added new expression-level unit tests, and e2e SQL tests in both ANSI and 
non-ANSI modes.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    No.
    
    Closes #51551 from uros-db/time_func.
    
    Authored-by: Uros Bojanic <uros.boja...@databricks.com>
    Signed-off-by: Max Gekk <max.g...@gmail.com>
---
 .../sql/catalyst/analysis/FunctionRegistry.scala   |  1 +
 .../sql/catalyst/expressions/CastSuiteBase.scala   | 27 ++++++++
 .../sql-functions/sql-expression-schema.md         |  3 +-
 .../sql-tests/analyzer-results/time.sql.out        | 70 +++++++++++++++++++
 .../src/test/resources/sql-tests/inputs/time.sql   | 14 ++++
 .../test/resources/sql-tests/results/time.sql.out  | 80 ++++++++++++++++++++++
 6 files changed, 194 insertions(+), 1 deletion(-)

diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
index 76c3b1d80b29..d84379c223de 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
@@ -873,6 +873,7 @@ object FunctionRegistry {
     castAlias("decimal", DecimalType.USER_DEFAULT),
     castAlias("date", DateType),
     castAlias("timestamp", TimestampType),
+    castAlias("time", TimeType()),
     castAlias("binary", BinaryType),
     castAlias("string", StringType),
 
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala
index a02b4276628c..44638cede3ec 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala
@@ -1588,6 +1588,33 @@ abstract class CastSuiteBase extends SparkFunSuite with 
ExpressionEvalHelper {
     }
   }
 
+  test("SPARK-51562: cast alias - time function") {
+    import org.apache.spark.sql.catalyst.analysis.FunctionRegistry
+    import org.apache.spark.sql.catalyst.FunctionIdentifier
+    // Test that time() function is registered and works correctly.
+    val registry = FunctionRegistry.builtin
+    val timeFunction = 
registry.lookupFunctionBuilder(FunctionIdentifier("time"))
+    assert(timeFunction.isDefined, "time function should be registered in 
FunctionRegistry")
+    // Test that time() function creates a proper Cast expression.
+    val stringInput = Literal("12:34:56")
+    val timeExpr = timeFunction.get(Seq(stringInput))
+    assert(timeExpr.isInstanceOf[Cast])
+    // The return type of the cast expression should be TimeType().
+    val castExpr = timeExpr.asInstanceOf[Cast]
+    assert(castExpr.dataType === TimeType())
+
+    // Test basic string to time conversions using the alias.
+    checkEvaluation(timeExpr, localTime(12, 34, 56))
+    val timeExprWithMillis = timeFunction.get(Seq(Literal("12:34:56.789")))
+    checkEvaluation(timeExprWithMillis, localTime(12, 34, 56, 789000))
+    val timeExprWithMicros = timeFunction.get(Seq(Literal("12:34:56.789012")))
+    checkEvaluation(timeExprWithMicros, localTime(12, 34, 56, 789012))
+
+    // Test null inputs.
+    val timeExprNull = timeFunction.get(Seq(Literal.create(null, StringType)))
+    checkEvaluation(timeExprNull, null)
+  }
+
   test("cast time to time") {
     checkEvaluation(cast(Literal(localTime(), TimeType(0)), TimeType(0)), 0L)
     checkEvaluation(cast(Literal(localTime(0, 0, 0, 1), TimeType(6)), 
TimeType(6)),
diff --git a/sql/core/src/test/resources/sql-functions/sql-expression-schema.md 
b/sql/core/src/test/resources/sql-functions/sql-expression-schema.md
index 3b0b21b9cd77..7aefb5d58f59 100644
--- a/sql/core/src/test/resources/sql-functions/sql-expression-schema.md
+++ b/sql/core/src/test/resources/sql-functions/sql-expression-schema.md
@@ -74,6 +74,7 @@
 | org.apache.spark.sql.catalyst.expressions.Cast | int | N/A | N/A |
 | org.apache.spark.sql.catalyst.expressions.Cast | smallint | N/A | N/A |
 | org.apache.spark.sql.catalyst.expressions.Cast | string | N/A | N/A |
+| org.apache.spark.sql.catalyst.expressions.Cast | time | N/A | N/A |
 | org.apache.spark.sql.catalyst.expressions.Cast | timestamp | N/A | N/A |
 | org.apache.spark.sql.catalyst.expressions.Cast | tinyint | N/A | N/A |
 | org.apache.spark.sql.catalyst.expressions.Cbrt | cbrt | SELECT cbrt(27.0) | 
struct<CBRT(27.0):double> |
@@ -480,4 +481,4 @@
 | org.apache.spark.sql.catalyst.expressions.xml.XPathList | xpath | SELECT 
xpath('<a><b>b1</b><b>b2</b><b>b3</b><c>c1</c><c>c2</c></a>','a/b/text()') | 
struct<xpath(<a><b>b1</b><b>b2</b><b>b3</b><c>c1</c><c>c2</c></a>, 
a/b/text()):array<string>> |
 | org.apache.spark.sql.catalyst.expressions.xml.XPathLong | xpath_long | 
SELECT xpath_long('<a><b>1</b><b>2</b></a>', 'sum(a/b)') | 
struct<xpath_long(<a><b>1</b><b>2</b></a>, sum(a/b)):bigint> |
 | org.apache.spark.sql.catalyst.expressions.xml.XPathShort | xpath_short | 
SELECT xpath_short('<a><b>1</b><b>2</b></a>', 'sum(a/b)') | 
struct<xpath_short(<a><b>1</b><b>2</b></a>, sum(a/b)):smallint> |
-| org.apache.spark.sql.catalyst.expressions.xml.XPathString | xpath_string | 
SELECT xpath_string('<a><b>b</b><c>cc</c></a>','a/c') | 
struct<xpath_string(<a><b>b</b><c>cc</c></a>, a/c):string> |
+| org.apache.spark.sql.catalyst.expressions.xml.XPathString | xpath_string | 
SELECT xpath_string('<a><b>b</b><c>cc</c></a>','a/c') | 
struct<xpath_string(<a><b>b</b><c>cc</c></a>, a/c):string> |
\ No newline at end of file
diff --git 
a/sql/core/src/test/resources/sql-tests/analyzer-results/time.sql.out 
b/sql/core/src/test/resources/sql-tests/analyzer-results/time.sql.out
index c536fbcbe5b2..df6cef2f62f8 100644
--- a/sql/core/src/test/resources/sql-tests/analyzer-results/time.sql.out
+++ b/sql/core/src/test/resources/sql-tests/analyzer-results/time.sql.out
@@ -401,6 +401,34 @@ Project [extract(SECOND, cast(09:08:01.987654 as time(6))) 
AS extract(SECOND FRO
 +- OneRowRelation
 
 
+-- !query
+SELECT cast("12:34:56" as time)
+-- !query analysis
+Project [cast(12:34:56 as time(6)) AS CAST(12:34:56 AS TIME(6))#x]
++- OneRowRelation
+
+
+-- !query
+SELECT cast("12:34:56.789" as time(3))
+-- !query analysis
+Project [cast(12:34:56.789 as time(3)) AS CAST(12:34:56.789 AS TIME(3))#x]
++- OneRowRelation
+
+
+-- !query
+SELECT cast("12:34:56.789" as time(6))
+-- !query analysis
+Project [cast(12:34:56.789 as time(6)) AS CAST(12:34:56.789 AS TIME(6))#x]
++- OneRowRelation
+
+
+-- !query
+SELECT cast("12:34:56.789012" as time without time zone)
+-- !query analysis
+Project [cast(12:34:56.789012 as time(6)) AS CAST(12:34:56.789012 AS 
TIME(6))#x]
++- OneRowRelation
+
+
 -- !query
 SELECT cast(cast('12:00' as time(0)) as time(2))
 -- !query analysis
@@ -422,6 +450,48 @@ Project [cast(11:59:59.999999 as time(6)) AS CAST(TIME 
'11:59:59.999999' AS TIME
 +- OneRowRelation
 
 
+-- !query
+SELECT time("12:34:56")
+-- !query analysis
+Project [cast(12:34:56 as time(6)) AS 12:34:56#x]
++- OneRowRelation
+
+
+-- !query
+SELECT time("12:34:56.789")
+-- !query analysis
+Project [cast(12:34:56.789 as time(6)) AS 12:34:56.789#x]
++- OneRowRelation
+
+
+-- !query
+SELECT time("12:34:56.789012")
+-- !query analysis
+Project [cast(12:34:56.789012 as time(6)) AS 12:34:56.789012#x]
++- OneRowRelation
+
+
+-- !query
+SELECT time(cast('12:00' as time(0)))
+-- !query analysis
+Project [cast(cast(12:00 as time(0)) as time(6)) AS CAST(12:00 AS TIME(0))#x]
++- OneRowRelation
+
+
+-- !query
+SELECT time(('23:59:59.001001' :: time(6)))
+-- !query analysis
+Project [cast(cast(23:59:59.001001 as time(6)) as time(6)) AS 
CAST(23:59:59.001001 AS TIME(6))#x]
++- OneRowRelation
+
+
+-- !query
+SELECT time(time'11:59:59.999999')
+-- !query analysis
+Project [cast(11:59:59.999999 as time(6)) AS TIME '11:59:59.999999'#x]
++- OneRowRelation
+
+
 -- !query
 SELECT '12:43:33.1234' :: TIME(4) + INTERVAL '01:04:05.56' HOUR TO SECOND
 -- !query analysis
diff --git a/sql/core/src/test/resources/sql-tests/inputs/time.sql 
b/sql/core/src/test/resources/sql-tests/inputs/time.sql
index 5a6569f4a6f8..6fced5cc2f51 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/time.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/time.sql
@@ -68,11 +68,25 @@ select extract(SECOND FROM cast('09:08:01.987654' as 
time(4)));
 select extract(SECOND FROM cast('09:08:01.987654' as time(5)));
 select extract(SECOND FROM cast('09:08:01.987654' as time(6)));
 
+-- cast string to time
+SELECT cast("12:34:56" as time);
+SELECT cast("12:34:56.789" as time(3));
+SELECT cast("12:34:56.789" as time(6));
+SELECT cast("12:34:56.789012" as time without time zone);
+
 -- cast time to time
 SELECT cast(cast('12:00' as time(0)) as time(2));
 SELECT cast(('23:59:59.001001' :: time(6)) as time(4));
 SELECT cast(time'11:59:59.999999' as time without time zone);
 
+-- SPARK-51562: test time function (i.e. alias for casting to time type).
+SELECT time("12:34:56");
+SELECT time("12:34:56.789");
+SELECT time("12:34:56.789012");
+SELECT time(cast('12:00' as time(0)));
+SELECT time(('23:59:59.001001' :: time(6)));
+SELECT time(time'11:59:59.999999');
+
 -- +/- ANSI day-time intervals
 SELECT '12:43:33.1234' :: TIME(4) + INTERVAL '01:04:05.56' HOUR TO SECOND;
 SELECT TIME'08:30' + NULL;
diff --git a/sql/core/src/test/resources/sql-tests/results/time.sql.out 
b/sql/core/src/test/resources/sql-tests/results/time.sql.out
index 29ef0816c316..4a87c026e6df 100644
--- a/sql/core/src/test/resources/sql-tests/results/time.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/time.sql.out
@@ -500,6 +500,38 @@ struct<extract(SECOND FROM CAST(09:08:01.987654 AS 
TIME(6))):decimal(8,6)>
 1.987654
 
 
+-- !query
+SELECT cast("12:34:56" as time)
+-- !query schema
+struct<CAST(12:34:56 AS TIME(6)):time(6)>
+-- !query output
+12:34:56
+
+
+-- !query
+SELECT cast("12:34:56.789" as time(3))
+-- !query schema
+struct<CAST(12:34:56.789 AS TIME(3)):time(3)>
+-- !query output
+12:34:56.789
+
+
+-- !query
+SELECT cast("12:34:56.789" as time(6))
+-- !query schema
+struct<CAST(12:34:56.789 AS TIME(6)):time(6)>
+-- !query output
+12:34:56.789
+
+
+-- !query
+SELECT cast("12:34:56.789012" as time without time zone)
+-- !query schema
+struct<CAST(12:34:56.789012 AS TIME(6)):time(6)>
+-- !query output
+12:34:56.789012
+
+
 -- !query
 SELECT cast(cast('12:00' as time(0)) as time(2))
 -- !query schema
@@ -524,6 +556,54 @@ struct<CAST(TIME '11:59:59.999999' AS TIME(6)):time(6)>
 11:59:59.999999
 
 
+-- !query
+SELECT time("12:34:56")
+-- !query schema
+struct<12:34:56:time(6)>
+-- !query output
+12:34:56
+
+
+-- !query
+SELECT time("12:34:56.789")
+-- !query schema
+struct<12:34:56.789:time(6)>
+-- !query output
+12:34:56.789
+
+
+-- !query
+SELECT time("12:34:56.789012")
+-- !query schema
+struct<12:34:56.789012:time(6)>
+-- !query output
+12:34:56.789012
+
+
+-- !query
+SELECT time(cast('12:00' as time(0)))
+-- !query schema
+struct<CAST(12:00 AS TIME(0)):time(6)>
+-- !query output
+12:00:00
+
+
+-- !query
+SELECT time(('23:59:59.001001' :: time(6)))
+-- !query schema
+struct<CAST(23:59:59.001001 AS TIME(6)):time(6)>
+-- !query output
+23:59:59.001001
+
+
+-- !query
+SELECT time(time'11:59:59.999999')
+-- !query schema
+struct<TIME '11:59:59.999999':time(6)>
+-- !query output
+11:59:59.999999
+
+
 -- !query
 SELECT '12:43:33.1234' :: TIME(4) + INTERVAL '01:04:05.56' HOUR TO SECOND
 -- !query schema


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@spark.apache.org
For additional commands, e-mail: commits-h...@spark.apache.org

Reply via email to