viirya commented on code in PR #59335:
URL: https://github.com/apache/spark/pull/59335#discussion_r4238665056
##########
sql/api/src/main/scala/org/apache/spark/sql/types/Decimal.scala:
##########
@@ -458,6 +458,19 @@ final class Decimal extends Ordered[Decimal] with
Serializable {
if (dv.ne(null)) {
// We get here if either we started with a BigDecimal, or we switched to
one because we would
// have overflowed our Long; in either case we must rescale dv to the
new scale.
+ if (math.abs(dv.scale.toLong - scale) > DecimalType.MAX_PRECISION) {
Review Comment:
Thanks. This is the Int overflow in `numDigitsInIntegralPart`, which is
fixed separately in #59334 (SPARK-60118) together with non-legacy
`CastWithAnsiOffSuite`/`CastWithAnsiOnSuite` cases for `1e2147483647`,
`12e2147483647`, etc. This PR covers the remaining cases that reach
`changePrecision`.
##########
sql/api/src/main/scala/org/apache/spark/sql/types/Decimal.scala:
##########
@@ -458,6 +458,19 @@ final class Decimal extends Ordered[Decimal] with
Serializable {
if (dv.ne(null)) {
// We get here if either we started with a BigDecimal, or we switched to
one because we would
// have overflowed our Long; in either case we must rescale dv to the
new scale.
+ if (math.abs(dv.scale.toLong - scale) > DecimalType.MAX_PRECISION) {
+ // setScale takes time proportional to the scale change and fails once
the change
+ // exceeds the Int range, e.g. for 1e-2147483647. Skip it when the
result is known.
+ val numIntegralDigits = dv.precision.toLong - dv.scale
+ if (dv.signum != 0 && numIntegralDigits > precision.toLong - scale) {
+ return false
+ }
+ if (dv.signum == 0 || numIntegralDigits < -scale.toLong - 1) {
+ // |dv| is below 0.01 ulp of the new scale, so the result depends
only on the sign
+ // and the rounding mode. Round a value of the same sign and 0.01
ulp instead.
+ dv = BigDecimal(dv.signum, scale + 2)
+ }
+ }
dv = dv.setScale(scale, roundMode)
Review Comment:
Agreed that `set(decimal, precision, scale)` and `set(decimal)` have the
same issue. I'd like to keep this PR focused on `changePrecision`. The parser
path also needs a decision on how to report overflow there
(`NUMERIC_VALUE_OUT_OF_RANGE.WITHOUT_SUGGESTION` carries a `roundedValue`).
Filed SPARK-60133 for it.
##########
sql/api/src/main/scala/org/apache/spark/sql/errors/DataTypeErrors.scala:
##########
@@ -210,7 +210,9 @@ private[sql] object DataTypeErrors extends
DataTypeErrorsBase {
new SparkArithmeticException(
errorClass = "NUMERIC_VALUE_OUT_OF_RANGE.WITH_SUGGESTION",
messageParameters = Map(
- "value" -> value.toPlainString,
+ // A negative scale (legacy mode only) can make the plain string
arbitrarily long,
+ // e.g. 1E+2147483647.
+ "value" -> (if (value.scale < 0) value.toString else
value.toPlainString),
Review Comment:
Good point. It now uses `toString` only when the scale is below -1000, so
the message for ordinary values like `1E+2` and `1E+40` is unchanged.
##########
sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastWithAnsiOnSuite.scala:
##########
@@ -481,6 +481,17 @@ class CastWithAnsiOnSuite extends CastSuiteBase with
QueryErrorsBase {
castErrMsg("abcd", DecimalType(38, 1)))
}
+ test("SPARK-60119: cast string with a huge exponent to decimal with negative
scale allowed") {
+ withSQLConf(SQLConf.LEGACY_ALLOW_NEGATIVE_SCALE_OF_DECIMAL_ENABLED.key ->
"true") {
+ Seq("1e2147483647", "-1e2147483646", "12e2147483647",
"1e100000000").foreach { str =>
+ checkExceptionInExpression[ArithmeticException](
+ cast(str, DecimalType(10, 2)),
+ "cannot be represented as Decimal(10, 2)")
Review Comment:
Added a `checkError` with the expected `value` for `1e2147483647`
(`1E+2147483647`) and for `1e40` (plain notation).
##########
sql/catalyst/src/test/scala/org/apache/spark/sql/types/DecimalSuite.scala:
##########
@@ -492,4 +492,57 @@ class DecimalSuite extends SparkFunSuite with
PrivateMethodTester with SQLHelper
s"unscaled: $unscaled, scaleFrom: $scaleFrom, scaleTo: $scaleTo, mode:
$roundMode")
}
}
+
+ test("SPARK-60119: changePrecision with a source scale far from the target
scale") {
+ // A value below 0.1 ulp of the target scale rounds to 0 or +/-1 ulp
depending only on its
Review Comment:
Done.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryExecutionErrors.scala:
##########
@@ -108,15 +108,8 @@ private[sql] object QueryExecutionErrors extends
QueryErrorsBase with ExecutionE
decimalPrecision: Int,
decimalScale: Int,
context: QueryContext = null): ArithmeticException = {
- new SparkArithmeticException(
- errorClass = "NUMERIC_VALUE_OUT_OF_RANGE.WITH_SUGGESTION",
- messageParameters = Map(
- "value" -> value.toPlainString,
- "precision" -> decimalPrecision.toString,
- "scale" -> decimalScale.toString,
- "config" -> toSQLConf(SQLConf.ANSI_ENABLED.key)),
- context = getQueryContext(context),
- summary = getSummary(context))
+ DataTypeErrors.cannotChangeDecimalPrecisionError(
Review Comment:
Done.
--
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]