Skip to content

Commit b44389c

Browse files
MaxGekkdongjoon-hyun
authored andcommitted
[SPARK-52618][SQL] Casting TIME(n) to TIME(m)
### What changes were proposed in this pull request? In the PR, I propose to support casting from TIME(n) to TIME(m) for any n, m in the range [0, 6]. ### Why are the changes needed? Users can change precision of TIME values. ### Does this PR introduce _any_ user-facing change? No, the TIME data type hasn't been released yet. ### How was this patch tested? By running new tests: ``` $ build/sbt "sql/testOnly org.apache.spark.sql.SQLQueryTestSuite -- -z time.sql" ``` ### Was this patch authored or co-authored using generative AI tooling? No. Closes #51361 from MaxGekk/cast-time-to-time-2. Authored-by: Max Gekk <max.gekk@gmail.com> Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
1 parent 80ef244 commit b44389c

File tree

5 files changed

+88
-4
lines changed

5 files changed

+88
-4
lines changed

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ object Cast extends QueryErrorsBase {
134134
// to convert data of these types to Variant Objects.
135135
case (_, VariantType) => variant.VariantGet.checkDataType(from, allowStructsAndMaps = false)
136136

137+
case (_: TimeType, _: TimeType) => true
138+
137139
// non-null variants can generate nulls even in ANSI mode
138140
case (ArrayType(fromType, fn), ArrayType(toType, tn)) =>
139141
canAnsiCast(fromType, toType) && resolvableNullability(fn || (fromType == VariantType), tn)
@@ -251,6 +253,8 @@ object Cast extends QueryErrorsBase {
251253
// to convert data of these types to Variant Objects.
252254
case (_, VariantType) => variant.VariantGet.checkDataType(from, allowStructsAndMaps = false)
253255

256+
case (_: TimeType, _: TimeType) => true
257+
254258
case (ArrayType(fromType, fn), ArrayType(toType, tn)) =>
255259
canCast(fromType, toType) &&
256260
resolvableNullability(fn || forceNullable(fromType, toType), tn)
@@ -733,13 +737,15 @@ case class Cast(
733737
buildCast[Long](_, t => microsToDays(t, ZoneOffset.UTC))
734738
}
735739

736-
private[this] def castToTime(from: DataType): Any => Any = from match {
740+
private[this] def castToTime(from: DataType, to: TimeType): Any => Any = from match {
737741
case _: StringType =>
738742
if (ansiEnabled) {
739743
buildCast[UTF8String](_, s => DateTimeUtils.stringToTimeAnsi(s, getContextOrNull()))
740744
} else {
741745
buildCast[UTF8String](_, s => DateTimeUtils.stringToTime(s).orNull)
742746
}
747+
case _: TimeType =>
748+
buildCast[Long](_, nanos => DateTimeUtils.truncateTimeToPrecision(nanos, to.precision))
743749
}
744750

745751
// IntervalConverter
@@ -1149,7 +1155,7 @@ case class Cast(
11491155
case s: StringType => castToString(from, s.constraint)
11501156
case BinaryType => castToBinary(from)
11511157
case DateType => castToDate(from)
1152-
case _: TimeType => castToTime(from)
1158+
case it: TimeType => castToTime(from, it)
11531159
case decimal: DecimalType => castToDecimal(from, decimal)
11541160
case TimestampType => castToTimestamp(from)
11551161
case TimestampNTZType => castToTimestampNTZ(from)
@@ -1257,7 +1263,7 @@ case class Cast(
12571263
(c, evPrim, _) => castToStringCode(from, ctx, s.constraint).apply(c, evPrim)
12581264
case BinaryType => castToBinaryCode(from)
12591265
case DateType => castToDateCode(from, ctx)
1260-
case _: TimeType => castToTimeCode(from, ctx)
1266+
case it: TimeType => castToTimeCode(from, it, ctx)
12611267
case decimal: DecimalType => castToDecimalCode(from, decimal, ctx)
12621268
case TimestampType => castToTimestampCode(from, ctx)
12631269
case TimestampNTZType => castToTimestampNTZCode(from, ctx)
@@ -1354,6 +1360,7 @@ case class Cast(
13541360

13551361
private[this] def castToTimeCode(
13561362
from: DataType,
1363+
to: TimeType,
13571364
ctx: CodegenContext): CastFunction = {
13581365
from match {
13591366
case _: StringType =>
@@ -1374,7 +1381,11 @@ case class Cast(
13741381
}
13751382
"""
13761383
}
1377-
1384+
case _: TimeType =>
1385+
(nanos, evPrim, _) =>
1386+
code"""
1387+
$evPrim = $dateTimeUtilsCls.truncateTimeToPrecision($nanos, ${to.precision});
1388+
"""
13781389
case _ =>
13791390
(_, _, evNull) => code"$evNull = true;"
13801391
}

sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,4 +1484,27 @@ abstract class CastSuiteBase extends SparkFunSuite with ExpressionEvalHelper {
14841484
checkEvaluation(cast(Literal.create("23:59:59.000001 "),
14851485
TimeType(6)), localTime(23, 59, 59, 1))
14861486
}
1487+
1488+
test("cast time to time") {
1489+
checkEvaluation(cast(Literal(localTime(), TimeType(0)), TimeType(0)), 0L)
1490+
checkEvaluation(cast(Literal(localTime(0, 0, 0, 1), TimeType(6)), TimeType(6)),
1491+
localTime(0, 0, 0, 1))
1492+
checkEvaluation(cast(Literal(localTime(0, 0, 0, 19), TimeType(6)), TimeType(5)),
1493+
localTime(0, 0, 0, 10))
1494+
checkEvaluation(cast(Literal(localTime(23, 59, 59, 999990), TimeType(5)), TimeType(6)),
1495+
localTime(23, 59, 59, 999990))
1496+
checkEvaluation(cast(Literal(localTime(23, 59, 59, 999999), TimeType(6)), TimeType(5)),
1497+
localTime(23, 59, 59, 999990))
1498+
checkEvaluation(cast(Literal(localTime(11, 58, 59, 123400), TimeType(4)), TimeType(5)),
1499+
localTime(11, 58, 59, 123400))
1500+
checkEvaluation(cast(Literal(localTime(19, 2, 3, 765000), TimeType(3)), TimeType(2)),
1501+
localTime(19, 2, 3, 760000))
1502+
1503+
for (sp <- TimeType.MIN_PRECISION to TimeType.MAX_PRECISION) {
1504+
for (tp <- TimeType.MIN_PRECISION to TimeType.MAX_PRECISION) {
1505+
checkConsistencyBetweenInterpretedAndCodegen(
1506+
(child: Expression) => Cast(child, TimeType(sp)), TimeType(tp))
1507+
}
1508+
}
1509+
}
14871510
}

sql/core/src/test/resources/sql-tests/analyzer-results/time.sql.out

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,24 @@ select extract(SECOND FROM cast('09:08:01.987654' as time(6)))
399399
-- !query analysis
400400
Project [extract(SECOND, cast(09:08:01.987654 as time(6))) AS extract(SECOND FROM CAST(09:08:01.987654 AS TIME(6)))#x]
401401
+- OneRowRelation
402+
403+
404+
-- !query
405+
SELECT cast(cast('12:00' as time(0)) as time(2))
406+
-- !query analysis
407+
Project [cast(cast(12:00 as time(0)) as time(2)) AS CAST(CAST(12:00 AS TIME(0)) AS TIME(2))#x]
408+
+- OneRowRelation
409+
410+
411+
-- !query
412+
SELECT cast(('23:59:59.001001' :: time(6)) as time(4))
413+
-- !query analysis
414+
Project [cast(cast(23:59:59.001001 as time(6)) as time(4)) AS CAST(CAST(23:59:59.001001 AS TIME(6)) AS TIME(4))#x]
415+
+- OneRowRelation
416+
417+
418+
-- !query
419+
SELECT cast(time'11:59:59.999999' as time without time zone)
420+
-- !query analysis
421+
Project [cast(11:59:59.999999 as time(6)) AS CAST(TIME '11:59:59.999999' AS TIME(6))#x]
422+
+- OneRowRelation

sql/core/src/test/resources/sql-tests/inputs/time.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,8 @@ select extract(SECOND FROM cast('09:08:01.987654' as time(3)));
6767
select extract(SECOND FROM cast('09:08:01.987654' as time(4)));
6868
select extract(SECOND FROM cast('09:08:01.987654' as time(5)));
6969
select extract(SECOND FROM cast('09:08:01.987654' as time(6)));
70+
71+
-- cast time to time
72+
SELECT cast(cast('12:00' as time(0)) as time(2));
73+
SELECT cast(('23:59:59.001001' :: time(6)) as time(4));
74+
SELECT cast(time'11:59:59.999999' as time without time zone);

sql/core/src/test/resources/sql-tests/results/time.sql.out

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,3 +498,27 @@ select extract(SECOND FROM cast('09:08:01.987654' as time(6)))
498498
struct<extract(SECOND FROM CAST(09:08:01.987654 AS TIME(6))):decimal(8,6)>
499499
-- !query output
500500
1.987654
501+
502+
503+
-- !query
504+
SELECT cast(cast('12:00' as time(0)) as time(2))
505+
-- !query schema
506+
struct<CAST(CAST(12:00 AS TIME(0)) AS TIME(2)):time(2)>
507+
-- !query output
508+
12:00:00
509+
510+
511+
-- !query
512+
SELECT cast(('23:59:59.001001' :: time(6)) as time(4))
513+
-- !query schema
514+
struct<CAST(CAST(23:59:59.001001 AS TIME(6)) AS TIME(4)):time(4)>
515+
-- !query output
516+
23:59:59.001
517+
518+
519+
-- !query
520+
SELECT cast(time'11:59:59.999999' as time without time zone)
521+
-- !query schema
522+
struct<CAST(TIME '11:59:59.999999' AS TIME(6)):time(6)>
523+
-- !query output
524+
11:59:59.999999

0 commit comments

Comments
 (0)