Skip to content
Closed
Show file tree
Hide file tree
Changes from 10 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
11 changes: 6 additions & 5 deletions common/utils/src/main/resources/error/error-conditions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,12 @@
],
"sqlState" : "22008"
},
"DATE_TIME_FIELD_OUT_OF_BOUNDS" : {
"message" : [
"The value '<badValue>' you entered for <unit> is not valid. Please provide a value between <range>. To disable strict validation, you can turn off ANSI mode by setting <ansiConfig> to false."
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @srielau updated the error message including more parameters such as unit, range and badValue

],
"sqlState" : "22008"
},
"DECIMAL_PRECISION_EXCEEDS_MAX_PRECISION" : {
"message" : [
"Decimal precision <precision> exceeds max precision <maxPrecision>."
Expand Down Expand Up @@ -6691,11 +6697,6 @@
"Sinks cannot request distribution and ordering in continuous execution mode."
]
},
"_LEGACY_ERROR_TEMP_2000" : {
"message" : [
"<message>. If necessary set <ansiConfig> to false to bypass this error."
]
},
"_LEGACY_ERROR_TEMP_2003" : {
"message" : [
"Unsuccessful try to zip maps with <size> unique keys due to exceeding the array size limit <maxRoundedArrayLength>."
Expand Down
23 changes: 20 additions & 3 deletions common/utils/src/main/scala/org/apache/spark/SparkException.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,9 @@ private[spark] class SparkDateTimeException private(
message: String,
errorClass: Option[String],
messageParameters: Map[String, String],
context: Array[QueryContext])
extends DateTimeException(message) with SparkThrowable {
context: Array[QueryContext],
cause: Option[Throwable])
extends DateTimeException(message, cause.orNull) with SparkThrowable {

def this(
errorClass: String,
Expand All @@ -318,7 +319,23 @@ private[spark] class SparkDateTimeException private(
SparkThrowableHelper.getMessage(errorClass, messageParameters, summary),
Option(errorClass),
messageParameters,
context
context,
cause = None
)
}

def this(
errorClass: String,
messageParameters: Map[String, String],
context: Array[QueryContext],
summary: String,
cause: Option[Throwable]) = {
this(
SparkThrowableHelper.getMessage(errorClass, messageParameters, summary),
Option(errorClass),
messageParameters,
context,
cause.orElse(None)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,7 @@ object DateTimeUtils extends SparkDateTimeUtils {
start: Int,
interval: CalendarInterval): Int = {
if (interval.microseconds != 0) {
throw QueryExecutionErrors.ansiIllegalArgumentError(
"Cannot add hours, minutes or seconds, milliseconds, microseconds to a date")
throw QueryExecutionErrors.ansiIllegalArgumentError()
}
val ld = daysToLocalDate(start).plusMonths(interval.months).plusDays(interval.days)
localDateToDays(ld)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,21 +278,45 @@ private[sql] object QueryExecutionErrors extends QueryErrorsBase with ExecutionE
}

def ansiDateTimeError(e: Exception): SparkDateTimeException = {
def extractDateTimeErrorInfo(e: Exception): (String, String, String) = {
val errorMessage = e.getMessage

val pattern = "Invalid value for ([A-Za-z]+) \\(valid values (.+)\\): (.+)".r

errorMessage match {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srielau If I understood @MaxGekk we want to get away from this message creation in the specific calls of the error, but actually keep all the info in error-conditions.json. This will make it really hard to sync with other APIs, especially as we are expanding these endless number of exception messages that we can get from the java library. Also, since this is external dependancy, who is to guarantee that this message format will stay the same when java updates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guarantee is the tests we write against them. I'm not opposed to having catch all for the "unforeseen".
Simply put: We should be able to rip out Java and replace it with C and messages should stay the same.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, but are we really going for this over getting as much information as we can provide to the user? For example there is a specific exception that is returning leap year error, but we will just say this should be in range. If I got a message like this, tbh I would be confused as to why it is not working when my value actually is in 1...28/31 range (29th february). We could do this changing for errors that we know for sure how they behave, but for all others, I lean to leaving the java message, as it provides sufficient info on why it is failing and end goal should be providing user with sufficient info to fix the error imo.

case pattern(field, range, badValue) =>
val unit = field match {
case "Year" => "YEAR"
case "MonthOfYear" => "MONTH"
case "DayOfMonth" => "DAY"
case "Hour" => "HOUR"
case "Minute" => "MINUTE"
case "Second" => "SECOND"
case _ => field
}
(unit, range, badValue)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@itholic Do you expect that it should match to both the cases above. What if not? I would add a default case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, toInstant can raise:

        if (seconds < MIN_SECOND || seconds > MAX_SECOND) {
            throw new DateTimeException("Instant exceeds minimum or maximum instant");
        }

How does your code handles this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

I think DATETIME_FIELD_OUT_OF_BOUNDS is not very proper error class to cover the example case, so let me leave the existing _LEGACY_ERROR_TEMP_2000 as it is for default case.

We may need introduce several new error classes to cover the all potential exception cases along with more test cases in a separate ticket.

}
val (unit, range, badValue) = extractDateTimeErrorInfo(e)
new SparkDateTimeException(
errorClass = "_LEGACY_ERROR_TEMP_2000",
errorClass = "DATE_TIME_FIELD_OUT_OF_BOUNDS",
messageParameters = Map(
"message" -> e.getMessage,
"ansiConfig" -> toSQLConf(SQLConf.ANSI_ENABLED.key)),
"ansiConfig" -> toSQLConf(SQLConf.ANSI_ENABLED.key),
"unit" -> unit,
"range" -> range,
"badValue" -> badValue
),
context = Array.empty,
summary = "")
summary = "",
cause = Some(e)
)
}

def ansiIllegalArgumentError(message: String): SparkIllegalArgumentException = {
new SparkIllegalArgumentException(
errorClass = "_LEGACY_ERROR_TEMP_2000",
messageParameters = Map(
"message" -> message,
"ansiConfig" -> toSQLConf(SQLConf.ANSI_ENABLED.key)))
def ansiIllegalArgumentError(): SparkException = {
SparkException.internalError(
"Cannot add hours, minutes or seconds, milliseconds, microseconds to a date. " +
s"If necessary set ${toSQLConf(SQLConf.ANSI_ENABLED.key)} to false to bypass this error"
)
}

def overflowInSumOfDecimalError(context: QueryContext): ArithmeticException = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,12 +434,14 @@ class DateExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper {
}

withSQLConf((SQLConf.ANSI_ENABLED.key, "true")) {
checkErrorInExpression[SparkIllegalArgumentException](
checkErrorInExpression[SparkException](
DateAddInterval(Literal(d), Literal(new CalendarInterval(1, 1, 25 * MICROS_PER_HOUR))),
"_LEGACY_ERROR_TEMP_2000",
Map("message" ->
"Cannot add hours, minutes or seconds, milliseconds, microseconds to a date",
"ansiConfig" -> "\"spark.sql.ansi.enabled\""))
condition = "INTERNAL_ERROR",
parameters = Map(
"message" ->
("Cannot add hours, minutes or seconds, milliseconds, microseconds to a date. " +
"If necessary set \"spark.sql.ansi.enabled\" to false to bypass this error"))
)
}

withSQLConf((SQLConf.ANSI_ENABLED.key, "false")) {
Expand Down Expand Up @@ -1140,12 +1142,33 @@ class DateExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper {

// ansi test
withSQLConf(SQLConf.ANSI_ENABLED.key -> "true") {
checkExceptionInExpression[DateTimeException](MakeDate(Literal(Int.MaxValue), Literal(13),
Literal(19)), EmptyRow, "Invalid value for Year")
checkExceptionInExpression[DateTimeException](MakeDate(Literal(2019),
Literal(13), Literal(19)), EmptyRow, "Invalid value for Month")
checkExceptionInExpression[DateTimeException](MakeDate(Literal(2019), Literal(7),
Literal(32)), EmptyRow, "Invalid value for Day")
checkErrorInExpression[SparkDateTimeException](
MakeDate(Literal(Int.MaxValue), Literal(13), Literal(19)),
"DATE_TIME_FIELD_OUT_OF_BOUNDS",
Map(
"ansiConfig" -> "\"spark.sql.ansi.enabled\"",
"unit" -> "YEAR",
"range" -> "-999999999 - 999999999",
"badValue" -> "2147483647")
)
checkErrorInExpression[SparkDateTimeException](
MakeDate(Literal(2019), Literal(13), Literal(19)),
"DATE_TIME_FIELD_OUT_OF_BOUNDS",
Map(
"ansiConfig" -> "\"spark.sql.ansi.enabled\"",
"unit" -> "MONTH",
"range" -> "1 - 12",
"badValue" -> "13")
)
checkErrorInExpression[SparkDateTimeException](
MakeDate(Literal(2019), Literal(7), Literal(32)),
"DATE_TIME_FIELD_OUT_OF_BOUNDS",
Map(
"ansiConfig" -> "\"spark.sql.ansi.enabled\"",
"unit" -> "DAY",
"range" -> "1 - 28/31",
"badValue" -> "32")
)
}

// non-ansi test
Expand Down Expand Up @@ -1183,19 +1206,72 @@ class DateExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper {
checkEvaluation(makeTimestampExpr.copy(timezone = None), expected)

Seq(
(makeTimestampExpr.copy(year = Literal(Int.MaxValue)), "Invalid value for Year"),
(makeTimestampExpr.copy(month = Literal(13)), "Invalid value for Month"),
(makeTimestampExpr.copy(day = Literal(32)), "Invalid value for Day"),
(makeTimestampExpr.copy(hour = Literal(25)), "Invalid value for Hour"),
(makeTimestampExpr.copy(min = Literal(65)), "Invalid value for Min"),
(makeTimestampExpr.copy(sec = Literal(Decimal(
BigDecimal(70.0), 16, 6))), "Invalid value for Second")
makeTimestampExpr.copy(year = Literal(Int.MaxValue)),
makeTimestampExpr.copy(month = Literal(13)),
makeTimestampExpr.copy(day = Literal(32)),
makeTimestampExpr.copy(hour = Literal(25)),
makeTimestampExpr.copy(min = Literal(65)),
makeTimestampExpr.copy(sec = Literal(Decimal(
BigDecimal(70.0), 16, 6)))
).foreach { entry =>
if (ansi) {
checkExceptionInExpression[DateTimeException](entry._1, EmptyRow, entry._2)
} else {
checkEvaluation(entry._1, null)
}
if (!ansi) checkEvaluation(entry, null)
}

if (ansi) {
checkErrorInExpression[SparkDateTimeException](
makeTimestampExpr.copy(year = Literal(Int.MaxValue)),
"DATE_TIME_FIELD_OUT_OF_BOUNDS",
Map(
"ansiConfig" -> "\"spark.sql.ansi.enabled\"",
"unit" -> "YEAR",
"range" -> "-999999999 - 999999999",
"badValue" -> "2147483647")
)
checkErrorInExpression[SparkDateTimeException](
makeTimestampExpr.copy(month = Literal(13)),
"DATE_TIME_FIELD_OUT_OF_BOUNDS",
Map(
"ansiConfig" -> "\"spark.sql.ansi.enabled\"",
"unit" -> "MONTH",
"range" -> "1 - 12",
"badValue" -> "13")
)
checkErrorInExpression[SparkDateTimeException](
makeTimestampExpr.copy(day = Literal(32)),
"DATE_TIME_FIELD_OUT_OF_BOUNDS",
Map(
"ansiConfig" -> "\"spark.sql.ansi.enabled\"",
"unit" -> "DAY",
"range" -> "1 - 28/31",
"badValue" -> "32")
)
checkErrorInExpression[SparkDateTimeException](
makeTimestampExpr.copy(hour = Literal(25)),
"DATE_TIME_FIELD_OUT_OF_BOUNDS",
Map(
"ansiConfig" -> "\"spark.sql.ansi.enabled\"",
"unit" -> "HourOfDay",
"range" -> "0 - 23",
"badValue" -> "25")
)
checkErrorInExpression[SparkDateTimeException](
makeTimestampExpr.copy(min = Literal(65)),
"DATE_TIME_FIELD_OUT_OF_BOUNDS",
Map(
"ansiConfig" -> "\"spark.sql.ansi.enabled\"",
"unit" -> "MinuteOfHour",
"range" -> "0 - 59",
"badValue" -> "65")
)
checkErrorInExpression[SparkDateTimeException](
makeTimestampExpr.copy(sec = Literal(Decimal(BigDecimal(70.0), 16, 6))),
"DATE_TIME_FIELD_OUT_OF_BOUNDS",
Map(
"ansiConfig" -> "\"spark.sql.ansi.enabled\"",
"unit" -> "SecondOfMinute",
"range" -> "0 - 59",
"badValue" -> "70")
)
}

makeTimestampExpr = MakeTimestamp(Literal(2019), Literal(6), Literal(30),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit
import org.scalatest.matchers.must.Matchers
import org.scalatest.matchers.should.Matchers._

import org.apache.spark.{SparkFunSuite, SparkIllegalArgumentException}
import org.apache.spark.{SparkException, SparkFunSuite, SparkIllegalArgumentException}
import org.apache.spark.sql.catalyst.plans.SQLHelper
import org.apache.spark.sql.catalyst.util.DateTimeConstants._
import org.apache.spark.sql.catalyst.util.DateTimeTestUtils._
Expand Down Expand Up @@ -540,12 +540,14 @@ class DateTimeUtilsSuite extends SparkFunSuite with Matchers with SQLHelper {
assert(dateAddInterval(input, new CalendarInterval(36, 47, 0)) === days(2000, 4, 15))
assert(dateAddInterval(input, new CalendarInterval(-13, 0, 0)) === days(1996, 1, 28))
checkError(
exception = intercept[SparkIllegalArgumentException](
exception = intercept[SparkException](
dateAddInterval(input, new CalendarInterval(36, 47, 1))),
condition = "_LEGACY_ERROR_TEMP_2000",
condition = "INTERNAL_ERROR",
parameters = Map(
"message" -> "Cannot add hours, minutes or seconds, milliseconds, microseconds to a date",
"ansiConfig" -> "\"spark.sql.ansi.enabled\""))
"message" ->
("Cannot add hours, minutes or seconds, milliseconds, microseconds to a date. " +
"If necessary set \"spark.sql.ansi.enabled\" to false to bypass this error"))
)
}

test("timestamp add interval") {
Expand Down
14 changes: 10 additions & 4 deletions sql/core/src/test/resources/sql-tests/results/ansi/date.sql.out
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,13 @@ struct<>
-- !query output
org.apache.spark.SparkDateTimeException
{
"errorClass" : "_LEGACY_ERROR_TEMP_2000",
"errorClass" : "DATE_TIME_FIELD_OUT_OF_BOUNDS",
"sqlState" : "22008",
"messageParameters" : {
"ansiConfig" : "\"spark.sql.ansi.enabled\"",
"message" : "Invalid value for MonthOfYear (valid values 1 - 12): 13"
"badValue" : "13",
"range" : "1 - 12",
"unit" : "MONTH"
}
}

Expand All @@ -68,10 +71,13 @@ struct<>
-- !query output
org.apache.spark.SparkDateTimeException
{
"errorClass" : "_LEGACY_ERROR_TEMP_2000",
"errorClass" : "DATE_TIME_FIELD_OUT_OF_BOUNDS",
"sqlState" : "22008",
"messageParameters" : {
"ansiConfig" : "\"spark.sql.ansi.enabled\"",
"message" : "Invalid value for DayOfMonth (valid values 1 - 28/31): 33"
"badValue" : "33",
"range" : "1 - 28/31",
"unit" : "DAY"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,13 @@ struct<>
-- !query output
org.apache.spark.SparkDateTimeException
{
"errorClass" : "_LEGACY_ERROR_TEMP_2000",
"errorClass" : "DATE_TIME_FIELD_OUT_OF_BOUNDS",
"sqlState" : "22008",
"messageParameters" : {
"ansiConfig" : "\"spark.sql.ansi.enabled\"",
"message" : "Invalid value for SecondOfMinute (valid values 0 - 59): 61"
"badValue" : "61",
"range" : "0 - 59",
"unit" : "SecondOfMinute"
}
}

Expand Down Expand Up @@ -185,10 +188,13 @@ struct<>
-- !query output
org.apache.spark.SparkDateTimeException
{
"errorClass" : "_LEGACY_ERROR_TEMP_2000",
"errorClass" : "DATE_TIME_FIELD_OUT_OF_BOUNDS",
"sqlState" : "22008",
"messageParameters" : {
"ansiConfig" : "\"spark.sql.ansi.enabled\"",
"message" : "Invalid value for SecondOfMinute (valid values 0 - 59): 99"
"badValue" : "99",
"range" : "0 - 59",
"unit" : "SecondOfMinute"
}
}

Expand All @@ -200,10 +206,13 @@ struct<>
-- !query output
org.apache.spark.SparkDateTimeException
{
"errorClass" : "_LEGACY_ERROR_TEMP_2000",
"errorClass" : "DATE_TIME_FIELD_OUT_OF_BOUNDS",
"sqlState" : "22008",
"messageParameters" : {
"ansiConfig" : "\"spark.sql.ansi.enabled\"",
"message" : "Invalid value for SecondOfMinute (valid values 0 - 59): 999"
"badValue" : "999",
"range" : "0 - 59",
"unit" : "SecondOfMinute"
}
}

Expand Down
Loading