uros-b commented on code in PR #56616:
URL: https://github.com/apache/spark/pull/56616#discussion_r3444974540
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala:
##########
@@ -759,6 +759,92 @@ case class MicrosToTimestamp(child: Expression)
copy(child = newChild)
}
+// scalastyle:off line.size.limit line.contains.tab
+@ExpressionDescription(
+ usage = "_FUNC_(nanoseconds) - Creates timestamp with the local time zone
and nanosecond precision (TIMESTAMP_LTZ(9)) from the number of nanoseconds
since UTC epoch.",
+ examples = """
+ Examples:
+ > SET spark.sql.timestampNanosTypes.enabled=true;
+ spark.sql.timestampNanosTypes.enabled true
+ > SELECT _FUNC_(1230219000123456789);
+ 2008-12-25 07:30:00.123456789
+ """,
+ group = "datetime_funcs",
+ since = "4.3.0")
+// scalastyle:on line.size.limit line.contains.tab
+case class NanosToTimestamp(child: Expression)
+ extends UnaryExpression with ExpectsInputTypes {
+ override def nullIntolerant: Boolean = true
+
+ // Accepts an integral or DECIMAL nanosecond count only. DECIMAL is required
to span the full
+ // [0001, 9999] calendar range: nanos for year 9999 (~2.5e20) overflow a
64-bit BIGINT, the same
+ // reason the inverse `unix_nanos` returns DECIMAL(21, 0); an integral
argument is widened to
+ // BigInteger directly. FLOAT/DOUBLE/STRING are intentionally rejected at
analysis rather than
+ // implicitly coerced: a fractional or string nanosecond count is not
meaningful, and the implicit
+ // DECIMAL coercion (FLOAT -> DECIMAL(14, 7), DOUBLE -> DECIMAL(30, 15))
would silently overflow
+ // for realistic magnitudes.
+ override def inputTypes: Seq[AbstractDataType] =
Seq(TypeCollection(IntegralType, DecimalType))
+
+ override def dataType: DataType = TimestampLTZNanosType(9)
+
+ // Maps the integer nanosecond count to the (epochMicros, nanosWithinMicro)
pair with floor
+ // semantics, so the sub-microsecond remainder is always in [0, 999]
(matching the negative-input
+ // behavior of `floorDiv`/`floorMod`). When `epochMicros` overflows 64 bits
-- i.e. the input is
+ // outside the representable timestamp range -- `longValueExact` throws,
which is surfaced as a
+ // DATETIME_OVERFLOW error.
+ override def nullSafeEval(input: Any): Any = {
+ val n = child.dataType match {
+ case _: DecimalType =>
+ input.asInstanceOf[Decimal].toJavaBigDecimal
+ .setScale(0, java.math.RoundingMode.FLOOR).toBigInteger
+ case _: IntegralType =>
+ BigInteger.valueOf(input.asInstanceOf[Number].longValue())
+ }
+ val thousand = BigInteger.valueOf(NANOS_PER_MICROS)
+ val rem = n.mod(thousand)
+ val micros = try {
+ n.subtract(rem).divide(thousand).longValueExact()
+ } catch {
+ case _: ArithmeticException => throw
QueryExecutionErrors.timestampNanosOverflowError(n)
Review Comment:
One question here for my curiosity: Overflow guard only catches epochMicros
not fitting in a 64-bit long, not the documented calendar range. This is
consistent with timestamp_micros (which also does no calendar-range
validation); so I'm wondering - is it intentional?
Inputs whose epochMicros fits in a long but represents a year > 9999 (or <
0001) — up to ~year 292471 — silently produce an out-of-range
TimestampNanosVal, since fromParts validates only nanosWithinMicro.
--
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]