[SPARK-58217][SQL] CAST(DECIMAL AS TIMESTAMP) silently produces garbage timestamps on overflow#57372
[SPARK-58217][SQL] CAST(DECIMAL AS TIMESTAMP) silently produces garbage timestamps on overflow#57372jiangxt2 wants to merge 3 commits into
Conversation
|
The null/CAST_OVERFLOW behavior for out-of-range decimal→timestamp is consistent with how existing overflow casts already handle this (e.g., One thing I noticed: this changes user-visible behavior (4.2 returns a silently-wrong wrapping value from unguarded
Should we add a bullet to the existing "Upgrading from Spark SQL 4.2 to 4.3" section. Something like:
WDYT? |
|
@vboo123 Agreed, this is a user-visible behavior change and should be documented. Added a migration guide entry in the latest commit — wording is close to what you suggested. |
…BigDecimal.longValue() When casting a large Decimal to Timestamp, the internal multiply by MICROS_PER_SECOND can exceed Long range. Per the Java specification, BigDecimal.longValue() returns only the low-order 64 bits when the value overflows, producing nonsensical timestamps like "-78449-06-15". Spark should detect this overflow and either throw CAST_OVERFLOW (ANSI mode) or return NULL (non-ANSI), rather than silently propagating a truncated value. This is consistent with existing overflow protection in the double-to-timestamp path (NaN/Infinite -> null) and the SecondsToTimestamp expression (DATETIME_OVERFLOW). The fix validates the multiplication result against Long range in both the interpreted path (decimalToTimestamp) and the codegen path (castToTimestampCode), and adds DecimalType to forceNullable. Signed-off-by: jiangxt2 <jiangxt2@vip.qq.com> Co-Authored-By: Zhang Dong <zdcheerful@hotmail.com> Co-Authored-By: ArtificialIdoit <bill.sea@hotmail.com> Co-Authored-By: cwq222 <15503804976@163.com>
… Cast sbt compileIncremental may not resolve private val symbols defined in the companion object when referenced from the companion class, causing four "not found" compilation errors in the Precompile Spark CI job. Move the three BigDecimal constants (MICROS_PER_SECOND_BD, LONG_MAX_BD, LONG_MIN_BD) from object Cast into case class Cast so that decimalToTimestamp and castToTimestampCode can access them directly. Signed-off-by: jiangxt2 <jiangxt2@vip.qq.com>
… overflow behavior change Since Spark 4.3, when casting a decimal value to timestamp overflows Long after scaling, Spark returns null (non-ANSI mode) or raises CAST_OVERFLOW error (ANSI mode) instead of a wrapping value. Signed-off-by: jiangxt2 <jiangxt2@vip.qq.com>
f9cd67e to
56e9910
Compare
What changes were proposed in this pull request?
Add Long range validation for the Decimal-to-Timestamp cast path in both interpreted and codegen execution.
When a Decimal value multiplied by
MICROS_PER_SECOND(1,000,000) exceedsLongrange,BigDecimal.longValue()silently truncates the low 64 bits per the Java specification. The fix detects this overflow and either throwsCAST_OVERFLOW(ANSI mode) or returnsNULL(non-ANSI mode).The cast implementation now validates the multiplication result against Long range before calling
longValue().DecimalType → TimestampTypeis also registered inforceNullablesince the cast can now return null.Why are the changes needed?
CAST(DECIMAL(38,0) AS TIMESTAMP)silently produces garbage timestamps when the Decimal value overflows Long after scaling. This affects both ANSI and non-ANSI modes.Root cause:
BigDecimal.longValue()silently returns the low 64 bits when the value exceeds Long range. The cast implementation had no range checking.This is consistent with existing overflow protection:
NaN/Infiniteby returning nullSecondsToTimestampusesDATETIME_OVERFLOWfor similar overflow protectionDoes this PR introduce any user-facing change?
Yes. Previously,
CAST(large_decimal AS TIMESTAMP)returned a nonsensical timestamp value when the Decimal overflows Long after scaling. Now:NULL(consistent with other overflow casts)CAST_OVERFLOWerrorThis is a behavior change for an edge case that previously produced silently wrong results.
How was this patch tested?
Added unit tests in
CastWithAnsiOffSuiteandCastWithAnsiOnSuite:Non-ANSI mode (
CastWithAnsiOffSuite):Decimal(99999999999999999999, 38, 0)→ nullDecimal(-99999999999999999999, 38, 0)→ nullDecimal(99999999999999999999.999999, 38, 6)→ nullDecimal(1, 10, 0)→MICROS_PER_SECONDDecimal(0, 10, 0)→0LDecimal(9223372036854, 20, 0)→ passes throughDecimal(9223372036855, 20, 0)→ nullANSI mode (
CastWithAnsiOnSuite):CAST_OVERFLOWwith correct error parametersCAST_OVERFLOWwith correct error parametersWas this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code with Claude Opus 4.8