MaxGekk commented on code in PR #59571:
URL: https://github.com/apache/spark/pull/59571#discussion_r4302997546


##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/literals.scala:
##########
@@ -688,6 +689,10 @@ case class Literal (value: Any, dataType: DataType) 
extends LeafExpression {
     case (v: Decimal, t: DecimalType) => s"${v}BD"
     case (v: Int, DateType) =>
       s"DATE '$toString'"
+    // toString strips trailing zeros, but the parser derives TIME(7)..TIME(9) 
from the number of
+    // fractional digits (6 or fewer give TIME(6)), so pad back to exactly 
`precision` digits.
+    case (_: Long, t: TimeType) if t.precision > TimeType.MICROS_PRECISION =>
+      s"TIME '${padToNanosPrecision(toString, t.precision)}'"
     case (_: Long, _: TimeType) =>

Review Comment:
   The TIME(6) branch has the same issue. `Literal(lt: LocalTime)` at line 92 
creates `TimeType()` from `localTimeToNanos` without truncating, so 
`LocalTime.of(23, 59, 59, 999999999)` prints 9 digits and parses back as 
TIME(9). There is a witness in 
`sql/connect/common/src/test/resources/query-tests/explain-results/function_lit.explain`,
 which shows a TIME(6) literal as `TIME '23:59:59.999999999'`.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/literals.scala:
##########
@@ -642,9 +642,10 @@ case class Literal (value: Any, dataType: DataType) 
extends LeafExpression {
       ts + "." + "0" * precision
     } else {
       val fracLen = ts.length - dotIdx - 1
-      // fracLen can never exceed precision: 
formatNanos/formatWithoutTimeZoneNanos truncate
-      // the value to `precision` digits before formatting, and the 
fractionFormatter only
-      // strips trailing zeros (never adds digits); the else branch handles 
fracLen == precision.
+      // fracLen can never exceed precision for the nanosecond timestamps: 
formatNanos/

Review Comment:
   This comment documents the root cause rather than fixing it: `toString` for 
`TimeType` calls `new FractionTimeFormatter().format(nanos)` with no precision, 
unlike the nanosecond timestamps where the formatter truncates to `precision`. 
If `toString` truncated to `t.precision` as well, the helper could keep its 
single invariant (`fracLen <= precision`), and the two TIME cases below could 
collapse into one precision-aware case.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/literals.scala:
##########
@@ -688,6 +689,10 @@ case class Literal (value: Any, dataType: DataType) 
extends LeafExpression {
     case (v: Decimal, t: DecimalType) => s"${v}BD"
     case (v: Int, DateType) =>
       s"DATE '$toString'"
+    // toString strips trailing zeros, but the parser derives TIME(7)..TIME(9) 
from the number of
+    // fractional digits (6 or fewer give TIME(6)), so pad back to exactly 
`precision` digits.
+    case (_: Long, t: TimeType) if t.precision > TimeType.MICROS_PRECISION =>
+      s"TIME '${padToNanosPrecision(toString, t.precision)}'"
     case (_: Long, _: TimeType) =>
       s"TIME '$toString'"

Review Comment:
   TIME(0)-TIME(5) do not survive the round trip either, and padding cannot 
help there because 6 or fewer digits always give TIME(6). A TIME(3) argument 
such as `expr("CAST(TIME '12:00:00.1' AS TIME(3))")` becomes `TIME 
'12:00:00.1'`, so `SELECT :t = '12:00:00.1005'` compares at microsecond 
precision and returns false, while at TIME(3) the string is truncated to `.100` 
and the result is true.
   
   If we want the type to be preserved for every precision, one option is to 
emit `CAST(TIME '<value>' AS TIME(p))` whenever `p` differs from what the 
parser would infer from the digits. If that is out of scope here, it is 
probably worth a note in the PR description.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/literals.scala:
##########
@@ -688,6 +689,10 @@ case class Literal (value: Any, dataType: DataType) 
extends LeafExpression {
     case (v: Decimal, t: DecimalType) => s"${v}BD"
     case (v: Int, DateType) =>
       s"DATE '$toString'"
+    // toString strips trailing zeros, but the parser derives TIME(7)..TIME(9) 
from the number of
+    // fractional digits (6 or fewer give TIME(6)), so pad back to exactly 
`precision` digits.
+    case (_: Long, t: TimeType) if t.precision > TimeType.MICROS_PRECISION =>

Review Comment:
   This pads a short fraction but does not cut a long one. 
`Literal.create(LocalTime.of(12, 0, 0, 123456789), TimeType(7))` is accepted 
(validation only checks for a `Long`, and neither `TimeConverter` nor 
`localTimeToNanos` truncates), `toString` prints all 9 digits, 
`padToNanosPrecision` returns them as is, and the parser infers TIME(9). So 
`spark.sql("SELECT :t", Map("t" -> thatLiteral))` still comes back as TIME(9) 
instead of TIME(7).
   
   I think the simplest fix is to truncate before formatting, e.g. 
`DateTimeUtils.truncateTimeToPrecision(value, t.precision)`, which is what 
`formatNanos` already does for the nanosecond timestamps.



##########
sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/LiteralExpressionSuite.scala:
##########
@@ -717,6 +717,22 @@ class LiteralExpressionSuite extends SparkFunSuite with 
ExpressionEvalHelper {
     }
   }
 
+  test("SPARK-60314: TimeType(7-9) sql keeps the precision on a round trip") {

Review Comment:
   All of these values have exactly `p` fractional digits, so the 
over-precision case (`Literal(nanos with 9 digits, TimeType(7))`) is not 
exercised and would pass today. Could you add a case for that, and maybe one 
for ARRAY/STRUCT/MAP of TIME(9), which are rendered element-wise via 
`Literal(_, elementType).sql` during parameter substitution?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to