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 1e8cf4566c97 [SPARK-56663][SQL][FOLLOWUP] Fix silent overflow in
date_trunc fast path.
1e8cf4566c97 is described below
commit 1e8cf4566c9788c2c28c859185dc2c0a87cf4bee
Author: chenhao-db <[email protected]>
AuthorDate: Thu Jun 4 22:32:17 2026 +0800
[SPARK-56663][SQL][FOLLOWUP] Fix silent overflow in date_trunc fast path.
### What changes were proposed in this pull request?
Fixes the bug introduced by https://github.com/apache/spark/pull/55610.
When `local` is a very large negative value, `Math.floorMod(local, unitMicros)`
is positive because `unitMicros` is positive, and the subtraction can overflow
to positive values. This produces a inconsistent result from the slow path.
In most common cases, this optimization still takes effect. The performance
loss is ignorable.
### Why are the changes needed?
Fix incorrect result.
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
New unit tests. It passes before
https://github.com/apache/spark/pull/55610, or after this change. But it fails
on the current master.
### Was this patch authored or co-authored using generative AI tooling?
No.
Closes #56313 from chenhao-db/fix_trunc_overflow.
Authored-by: chenhao-db <[email protected]>
Signed-off-by: Wenchen Fan <[email protected]>
(cherry picked from commit 94d23c3220f5b9eb0503d756e430b0371c79b58a)
Signed-off-by: Wenchen Fan <[email protected]>
---
.../apache/spark/sql/catalyst/util/DateTimeUtils.scala | 6 +++---
.../sql/catalyst/expressions/DateExpressionsSuite.scala | 15 +++++++++++++++
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/DateTimeUtils.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/DateTimeUtils.scala
index f4a47db0fe43..c5e6b5cbce93 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/DateTimeUtils.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/DateTimeUtils.scala
@@ -510,7 +510,7 @@ object DateTimeUtils extends SparkDateTimeUtils {
val offsetMicros = originalOffsetSec * MICROS_PER_SECOND
try {
val local = Math.addExact(micros, offsetMicros)
- val truncatedLocal = local - Math.floorMod(local, unitMicros)
+ val truncatedLocal = Math.subtractExact(local, Math.floorMod(local,
unitMicros))
val candidate = Math.subtractExact(truncatedLocal, offsetMicros)
if (!rules.isFixedOffset) {
val candidateSec = Math.floorDiv(candidate, MICROS_PER_SECOND)
@@ -537,9 +537,9 @@ object DateTimeUtils extends SparkDateTimeUtils {
level match {
case TRUNC_TO_MICROSECOND => micros
case TRUNC_TO_MILLISECOND =>
- micros - Math.floorMod(micros, MICROS_PER_MILLIS)
+ Math.subtractExact(micros, Math.floorMod(micros, MICROS_PER_MILLIS))
case TRUNC_TO_SECOND =>
- micros - Math.floorMod(micros, MICROS_PER_SECOND)
+ Math.subtractExact(micros, Math.floorMod(micros, MICROS_PER_SECOND))
case TRUNC_TO_MINUTE =>
truncToUnitFast(micros, zoneId, MICROS_PER_MINUTE, ChronoUnit.MINUTES)
case TRUNC_TO_HOUR =>
diff --git
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala
index 824d08d67c50..4a2b23fe059b 100644
---
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala
+++
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala
@@ -853,6 +853,21 @@ class DateExpressionsSuite extends SparkFunSuite with
ExpressionEvalHelper {
}
}
+ test("TruncTimestamp of Long.MinValue overflows with ArithmeticException") {
+ withDefaultTimeZone(UTC) {
+ // Long.MinValue is the smallest representable timestamp value (in
micros). Truncating it
+ // rounds the value down to an earlier instant, which falls below the
representable micros
+ // range. The overflow must surface as an ArithmeticException instead of
silently wrapping
+ // around to a bogus (positive) timestamp.
+ val minTimestamp = Literal.create(Long.MinValue, TimestampType)
+ Seq("YEAR", "QUARTER", "MONTH", "WEEK", "DAY", "HOUR", "MINUTE",
+ "SECOND", "MILLISECOND").foreach { fmt =>
+ checkExceptionInExpression[ArithmeticException](
+ TruncTimestamp(Literal.create(fmt, StringType), minTimestamp), "")
+ }
+ }
+ }
+
test("unsupported fmt fields for trunc/date_trunc results null") {
Seq("INVALID", "decade", "century", "millennium", "whatever",
null).foreach { field =>
testTruncDate(Date.valueOf("2000-03-08"), field, null)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]