Repository navigation
fix: include try_add suggestion in ANSI SUM overflow error #6732
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,7 @@ import java.util.concurrent.atomic.AtomicLong | |
| import scala.util.Random | ||
|
|
||
| import org.apache.hadoop.fs.Path | ||
| import org.apache.spark.{CometListenerBusUtils, SparkConf} | ||
| import org.apache.spark.{CometListenerBusUtils, SparkConf, SparkThrowable} | ||
| import org.apache.spark.scheduler.{SparkListener, SparkListenerTaskEnd} | ||
| import org.apache.spark.sql.{Column, CometTestBase, DataFrame, QueryTest, Row} | ||
| import org.apache.spark.sql.catalyst.expressions.Cast | ||
|
|
@@ -3297,20 +3297,40 @@ class CometAggregateSuite extends CometTestBase with AdaptiveSparkPlanHelper { | |
| (1 to 50).flatMap(_ => Seq((maxDec38_0, 1))) | ||
| } | ||
|
|
||
| /** | ||
| * Spark's integral SUM adds through `Add`, so an ANSI overflow carries the `try_add` | ||
| * suggestion. Compare the structured error, not just its error class. The overflow wording is | ||
| * left to the message parameters: Spark 4.2 normalizes `long overflow` to `overflow`. | ||
| */ | ||
| private def assertAnsiSumOverflowMatchesSpark(df: DataFrame): Unit = { | ||
| // Without this, a SUM that fell back to Spark would compare Spark's error with itself. | ||
| checkCometOperators(stripAQEPlan(df.queryExecution.executedPlan)) | ||
| val (sparkError, cometError) = checkSparkAnswerMaybeThrows(df) | ||
|
Comment on lines
+3305
to
+3308
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In ANSI mode nothing here checks that Comet ran the aggregate natively. From reading the code, if
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, thanks. The helper now starts with |
||
| def structured(error: Option[Throwable]): SparkThrowable with Throwable = { | ||
| val failure = error.getOrElse(fail("Expected SUM overflow in ANSI mode")) | ||
| causeChain(failure) | ||
| .collect { case e: SparkThrowable with Throwable => e } | ||
| .lastOption | ||
| .getOrElse(fail(s"Expected SparkThrowable: $failure")) | ||
| } | ||
| val expected = structured(sparkError) | ||
| val actual = structured(cometError) | ||
| assert(expected.getErrorClass == "ARITHMETIC_OVERFLOW") | ||
| assert(actual.getClass == expected.getClass) | ||
| assert(actual.getErrorClass == expected.getErrorClass) | ||
| assert(actual.getSqlState == expected.getSqlState) | ||
| assert(actual.getMessageParameters == expected.getMessageParameters) | ||
| assert(actual.getMessage.contains("try_add")) | ||
| } | ||
|
|
||
| test("ANSI support - SUM function") { | ||
| Seq(true, false).foreach { ansiEnabled => | ||
| withSQLConf(SQLConf.ANSI_ENABLED.key -> ansiEnabled.toString) { | ||
| // Test long overflow | ||
| withParquetTable(Seq((Long.MaxValue, 1L), (100L, 1L)), "tbl") { | ||
| val res = sql("SELECT SUM(_1) FROM tbl") | ||
| if (ansiEnabled) { | ||
| checkSparkAnswerMaybeThrows(res) match { | ||
| case (Some(sparkExc), Some(cometExc)) => | ||
| // make sure that the error message throws overflow exception only | ||
| assert(sparkExc.getMessage.contains("ARITHMETIC_OVERFLOW")) | ||
| assert(cometExc.getMessage.contains("ARITHMETIC_OVERFLOW")) | ||
| case _ => fail("Exception should be thrown for Long overflow in ANSI mode") | ||
| } | ||
| assertAnsiSumOverflowMatchesSpark(res) | ||
| } else { | ||
| checkSparkAnswerAndOperator(res) | ||
| } | ||
|
|
@@ -3319,16 +3339,42 @@ class CometAggregateSuite extends CometTestBase with AdaptiveSparkPlanHelper { | |
| withParquetTable(Seq((Long.MinValue, 1L), (-100L, 1L)), "tbl") { | ||
| val res = sql("SELECT SUM(_1) FROM tbl") | ||
| if (ansiEnabled) { | ||
| checkSparkAnswerMaybeThrows(res) match { | ||
| case (Some(sparkExc), Some(cometExc)) => | ||
| assert(sparkExc.getMessage.contains("ARITHMETIC_OVERFLOW")) | ||
| assert(cometExc.getMessage.contains("ARITHMETIC_OVERFLOW")) | ||
| case _ => fail("Exception should be thrown for Long underflow in ANSI mode") | ||
| } | ||
| assertAnsiSumOverflowMatchesSpark(res) | ||
| } else { | ||
| checkSparkAnswerAndOperator(res) | ||
| } | ||
| } | ||
| // Overflow only when the partial sums of two scan partitions are merged, ungrouped and | ||
| // grouped. A large open cost keeps the two files in separate scan partitions. | ||
| withSQLConf(SQLConf.FILES_OPEN_COST_IN_BYTES.key -> (128L * 1024 * 1024).toString) { | ||
| withTempView("tbl") { | ||
| withTempPath { dir => | ||
| Seq((Long.MaxValue, 1), (1L, 1)).foreach { row => | ||
| spark | ||
| .createDataFrame(Seq(row)) | ||
| .write | ||
| .mode("append") | ||
| .parquet(dir.getCanonicalPath) | ||
| } | ||
| spark.read.parquet(dir.getCanonicalPath).createOrReplaceTempView("tbl") | ||
| // One row per scan partition, so no partial update can overflow: the error | ||
| // below can only come from merging the two partial sums. | ||
| val rowsPerPartition = | ||
| spark.table("tbl").rdd.mapPartitions(rows => Iterator(rows.size)).collect() | ||
| assert(rowsPerPartition.toSeq == Seq(1, 1), rowsPerPartition.mkString(",")) | ||
| for (query <- Seq( | ||
| "SELECT SUM(_1) FROM tbl", | ||
| "SELECT _2, SUM(_1) FROM tbl GROUP BY _2")) { | ||
|
Comment on lines
+3365
to
+3367
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 61f490f. Both ANSI branches of the |
||
| val res = sql(query) | ||
| if (ansiEnabled) { | ||
| assertAnsiSumOverflowMatchesSpark(res) | ||
| } else { | ||
| checkSparkAnswerAndOperator(res) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // Test Int SUM (should not overflow) | ||
| withParquetTable(Seq((Int.MaxValue, 1), (Int.MaxValue, 1), (100, 1)), "tbl") { | ||
| val res = sql("SELECT SUM(_1) FROM tbl") | ||
|
|
@@ -3381,13 +3427,7 @@ class CometAggregateSuite extends CometTestBase with AdaptiveSparkPlanHelper { | |
| "tbl") { | ||
| val res = sql("SELECT _2, SUM(_1) FROM tbl GROUP BY _2").repartition(2) | ||
| if (ansiEnabled) { | ||
| checkSparkAnswerMaybeThrows(res) match { | ||
| case (Some(sparkExc), Some(cometExc)) => | ||
| assert(sparkExc.getMessage.contains("ARITHMETIC_OVERFLOW")) | ||
| assert(cometExc.getMessage.contains("ARITHMETIC_OVERFLOW")) | ||
| case _ => | ||
| fail("Exception should be thrown for Long overflow with GROUP BY in ANSI mode") | ||
| } | ||
| assertAnsiSumOverflowMatchesSpark(res) | ||
| } else { | ||
| checkSparkAnswerAndOperator(res) | ||
| } | ||
|
|
@@ -3398,13 +3438,7 @@ class CometAggregateSuite extends CometTestBase with AdaptiveSparkPlanHelper { | |
| "tbl") { | ||
| val res = sql("SELECT _2, SUM(_1) FROM tbl GROUP BY _2") | ||
| if (ansiEnabled) { | ||
| checkSparkAnswerMaybeThrows(res) match { | ||
| case (Some(sparkExc), Some(cometExc)) => | ||
| assert(sparkExc.getMessage.contains("ARITHMETIC_OVERFLOW")) | ||
| assert(cometExc.getMessage.contains("ARITHMETIC_OVERFLOW")) | ||
| case _ => | ||
| fail("Exception should be thrown for Long underflow with GROUP BY in ANSI mode") | ||
| } | ||
| assertAnsiSumOverflowMatchesSpark(res) | ||
| } else { | ||
| checkSparkAnswerAndOperator(res) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#6069 is open and approved, and it adds a fourth ANSI site in this file:
SlidingSumIntegerAccumulator::evaluatereturnsarithmetic_overflow_error("integer"). Git merges the two PRs without a conflict, but this line drops that import, so from reading the merged tree I expect whichever lands second not to compile. That sliding frame would also keep reportinginteger overflowwith notry_add, because Spark recomputes each sliding frame through the sameAdd. Could that site usesum_overflow_error()too, in whichever PR lands second? I have not built the merge.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I can't change that site here, because it only exists in #6069. Whichever PR lands second will handle it:
SlidingSumIntegerAccumulator::evaluatetosum_overflow_error(), which also fixes the import.