Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1127,15 +1127,9 @@ class CometArrayExpressionSuite extends CometTestBase with AdaptiveSparkPlanHelp
sql("CREATE TABLE test_array_get_item(arr ARRAY<INT>) USING parquet")
sql("INSERT INTO test_array_get_item VALUES (array(1, 2, 3))")
// Try to access array with out-of-bounds index
val exception = intercept[Exception] {
sql("select arr[5] from test_array_get_item").collect()
}
val exception =
checkSparkError(sql("select arr[5] from test_array_get_item"), "INVALID_ARRAY_INDEX")
val errorMessage = exception.getMessage
// Verify error message contains the expected error code
assert(
errorMessage.contains("INVALID_ARRAY_INDEX"),
s"Error message should contain array index error: $errorMessage")

assert(errorMessage.contains("The index 5 is out of bounds. The array has 3 elements." +
" Use the SQL function `get()` to tolerate accessing element at invalid index and return NULL instead."))

Expand All @@ -1156,15 +1150,10 @@ class CometArrayExpressionSuite extends CometTestBase with AdaptiveSparkPlanHelp
sql("CREATE TABLE test_element_at_invalid(arr ARRAY<INT>) USING parquet")
sql("INSERT INTO test_element_at_invalid VALUES (array(1, 2, 3))")
// Try to access array with out-of-bounds index using element_at
val exception = intercept[Exception] {
sql("select element_at(arr, 10) from test_element_at_invalid").collect()
}
val exception = checkSparkError(
sql("select element_at(arr, 10) from test_element_at_invalid"),
"INVALID_ARRAY_INDEX_IN_ELEMENT_AT")
val errorMessage = exception.getMessage
// Verify error message contains the expected error code
assert(
errorMessage.contains("INVALID_ARRAY_INDEX_IN_ELEMENT_AT"),
s"Error message should contain array index error: $errorMessage")

assert(errorMessage.contains("The index 10 is out of bounds. The array has 3 elements." +
" Use `try_element_at` to tolerate accessing element at invalid index and return NULL instead"))

Expand All @@ -1185,15 +1174,10 @@ class CometArrayExpressionSuite extends CometTestBase with AdaptiveSparkPlanHelp
sql("CREATE TABLE test_element_at_zero(arr ARRAY<INT>) USING parquet")
sql("INSERT INTO test_element_at_zero VALUES (array(1, 2, 3))")
// Try to access array with zero index (invalid in Spark)
val exception = intercept[Exception] {
sql("select element_at(arr, 0) from test_element_at_zero").collect()
}
val exception = checkSparkError(
sql("select element_at(arr, 0) from test_element_at_zero"),
"INVALID_INDEX_OF_ZERO")
val errorMessage = exception.getMessage
// Verify error message contains the expected error code
assert(
errorMessage.contains("INVALID_INDEX_OF_ZERO"),
s"Error message should contain zero index error: $errorMessage")

assert(
errorMessage.contains("The index 0 is invalid. An index shall be either < 0 or > 0" +
" (the first element has index 1)"))
Expand Down
39 changes: 7 additions & 32 deletions spark/src/test/scala/org/apache/comet/CometExpressionSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ import org.apache.comet.testing.{DataGenOptions, FuzzDataGenerator}
class CometExpressionSuite extends CometTestBase with AdaptiveSparkPlanHelper {
import testImplicits._

val ARITHMETIC_OVERFLOW_EXCEPTION_MSG =
"""[ARITHMETIC_OVERFLOW] integer overflow. If necessary set "spark.sql.ansi.enabled" to "false" to bypass this error"""
val DIVIDE_BY_ZERO_EXCEPTION_MSG =
"""Division by zero. Use `try_divide` to tolerate divisor being 0 and return NULL instead"""

Expand Down Expand Up @@ -2944,12 +2942,7 @@ class CometExpressionSuite extends CometTestBase with AdaptiveSparkPlanHelper {
| from tbl
| """.stripMargin)

checkSparkAnswerMaybeThrows(res) match {
case (Some(sparkExc), Some(cometExc)) =>
assert(cometExc.getMessage.contains(ARITHMETIC_OVERFLOW_EXCEPTION_MSG))
assert(sparkExc.getMessage.contains("overflow"))
case _ => fail("Exception should be thrown")
}
checkSparkError(res, "ARITHMETIC_OVERFLOW")
}
}
}
Expand All @@ -2964,12 +2957,7 @@ class CometExpressionSuite extends CometTestBase with AdaptiveSparkPlanHelper {
| _1 - _2
| from tbl
| """.stripMargin)
checkSparkAnswerMaybeThrows(res) match {
case (Some(sparkExc), Some(cometExc)) =>
assert(cometExc.getMessage.contains(ARITHMETIC_OVERFLOW_EXCEPTION_MSG))
assert(sparkExc.getMessage.contains("overflow"))
case _ => fail("Exception should be thrown")
}
checkSparkError(res, "ARITHMETIC_OVERFLOW")
}
}
}
Expand All @@ -2985,12 +2973,7 @@ class CometExpressionSuite extends CometTestBase with AdaptiveSparkPlanHelper {
| from tbl
| """.stripMargin)

checkSparkAnswerMaybeThrows(res) match {
case (Some(sparkExc), Some(cometExc)) =>
assert(cometExc.getMessage.contains(ARITHMETIC_OVERFLOW_EXCEPTION_MSG))
assert(sparkExc.getMessage.contains("overflow"))
case _ => fail("Exception should be thrown")
}
checkSparkError(res, "ARITHMETIC_OVERFLOW")
}
}
}
Expand All @@ -3005,12 +2988,7 @@ class CometExpressionSuite extends CometTestBase with AdaptiveSparkPlanHelper {
| from tbl
| """.stripMargin)

checkSparkAnswerMaybeThrows(res) match {
case (Some(sparkExc), Some(cometExc)) =>
assert(cometExc.getMessage.contains(DIVIDE_BY_ZERO_EXCEPTION_MSG))
assert(sparkExc.getMessage.contains("Division by zero"))
case _ => fail("Exception should be thrown")
}
checkSparkError(res, "DIVIDE_BY_ZERO")
}
}
}
Expand All @@ -3025,12 +3003,7 @@ class CometExpressionSuite extends CometTestBase with AdaptiveSparkPlanHelper {
| from tbl
| """.stripMargin)

checkSparkAnswerMaybeThrows(res) match {
case (Some(sparkExc), Some(cometExc)) =>
assert(cometExc.getMessage.contains(DIVIDE_BY_ZERO_EXCEPTION_MSG))
assert(sparkExc.getMessage.contains("Division by zero"))
case _ => fail("Exception should be thrown")
}
checkSparkError(res, "DIVIDE_BY_ZERO")
}
}
}
Expand All @@ -3046,6 +3019,8 @@ class CometExpressionSuite extends CometTestBase with AdaptiveSparkPlanHelper {
| from tbl
| """.stripMargin)

// Integral divide still raises an unconverted Arrow error under ANSI.
// https://github.com/apache/datafusion-comet/issues/5072
checkSparkAnswerMaybeThrows(res) match {
case (Some(sparkException), Some(cometException)) =>
assert(sparkException.getMessage.contains(DIVIDE_BY_ZERO_EXCEPTION_MSG))
Expand Down
11 changes: 3 additions & 8 deletions spark/src/test/scala/org/apache/comet/CometNativeCastSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1382,15 +1382,9 @@ class CometNativeCastSuite extends CometTestBase with AdaptiveSparkPlanHelper {
// Create a table with string data using DataFrame API
Seq("a").toDF("s").write.format("parquet").saveAsTable("cast_error_msg")
// Try to cast invalid string to date - should throw exception with SQL context
val exception = intercept[Exception] {
sql("select cast(s as date) from cast_error_msg").collect()
}
val exception =
checkSparkError(sql("select cast(s as date) from cast_error_msg"), "CAST_INVALID_INPUT")
val errorMessage = exception.getMessage
// Verify error message contains the cast invalid input error
assert(
errorMessage.contains("CAST_INVALID_INPUT") ||
errorMessage.contains("cannot be cast to"),
s"Error message should contain cast error: $errorMessage")

assert(
errorMessage.contains("select cast(s as date) from cast_error_msg"),
Expand Down Expand Up @@ -2721,6 +2715,7 @@ class CometNativeCastSuite extends CometTestBase with AdaptiveSparkPlanHelper {
val cometMessage =
if (cometException.getCause != null) cometException.getCause.getMessage
else cometException.getMessage
// https://github.com/apache/datafusion-comet/issues/5072
// this if branch should only check decimal to decimal cast and errors when output precision, scale causes overflow.
if (df.schema("a").dataType.typeName.contains("decimal") && toType.typeName
.contains("decimal") && sparkMessage.contains("cannot be represented as")) {
Expand Down
27 changes: 27 additions & 0 deletions spark/src/test/scala/org/apache/spark/sql/CometTestBase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,33 @@ abstract class CometTestBase
}
}

/** Checks native execution and Spark exception type, error class and SQLSTATE parity. */
protected def checkSparkError(
df: DataFrame,
errorClass: String): SparkThrowable with Throwable = {
checkCometOperators(stripAQEPlan(df.queryExecution.executedPlan))
val (sparkError, cometError) = checkSparkAnswerMaybeThrows(df)

def structuredError(
error: Option[Throwable],
engine: String): SparkThrowable with Throwable = {
val failure = error.getOrElse(fail(s"$engine did not fail with $errorClass"))
val chain = causeChain(failure)
assert(!chain.exists(_.isInstanceOf[CometNativeException]), s"$engine: $failure")
chain.collect { case e: SparkThrowable with Throwable => e }.lastOption.getOrElse {
fail(s"$engine did not throw a SparkThrowable: $failure")
}
}

val expected = structuredError(sparkError, "Spark")
val actual = structuredError(cometError, "Comet")
assert(expected.getErrorClass == errorClass)
assert(actual.getClass == expected.getClass)
assert(actual.getErrorClass == errorClass)
assert(actual.getSqlState == expected.getSqlState)
actual
}

/**
* Compares the Comet DataFrame result against the expected Spark answer, using labels that
* correctly identify which side is Comet and which is Spark. This avoids the misleading "Spark
Expand Down
Loading