diff --git a/common/utils/src/main/resources/error/error-conditions.json b/common/utils/src/main/resources/error/error-conditions.json index 3786643125a9f..ebaa2cee7c8dd 100644 --- a/common/utils/src/main/resources/error/error-conditions.json +++ b/common/utils/src/main/resources/error/error-conditions.json @@ -1087,6 +1087,12 @@ ], "sqlState" : "42K03" }, + "DATETIME_FIELD_OUT_OF_BOUNDS" : { + "message" : [ + "The value you entered for is not valid. Please provide a value between . To disable strict validation, you can turn off ANSI mode by setting to \"false\"." + ], + "sqlState" : "22008" + }, "DATETIME_OVERFLOW" : { "message" : [ "Datetime operation overflow: ." diff --git a/common/utils/src/main/scala/org/apache/spark/SparkException.scala b/common/utils/src/main/scala/org/apache/spark/SparkException.scala index 398cb1fad6726..fcaee787fd8d3 100644 --- a/common/utils/src/main/scala/org/apache/spark/SparkException.scala +++ b/common/utils/src/main/scala/org/apache/spark/SparkException.scala @@ -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, @@ -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) ) } diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryExecutionErrors.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryExecutionErrors.scala index 4a23e9766fc5d..eeb9a2adaed8a 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryExecutionErrors.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryExecutionErrors.scala @@ -278,13 +278,49 @@ 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 valuePattern = "Invalid value for ([A-Za-z]+) \\(valid values (.+)\\): (.+)".r + val datePattern = "Invalid date '[A-Z]+ ([0-9]+)'".r + + errorMessage match { + case valuePattern(field, range, badValue) => + val unit = field match { + case "Year" => "YEAR" + case "MonthOfYear" => "MONTH" + case "DayOfMonth" => "DAY" + case "HourOfDay" => "HOUR" + case "MinuteOfHour" => "MINUTE" + case "SecondOfMinute" => "SECOND" + } + val formattedRange = range.replace(" - ", " ... ") + (unit, formattedRange, badValue) + case datePattern(badDate) => + ("DAY", "1 ... 28/31", badDate) + case _ => + throw new SparkDateTimeException( + errorClass = "_LEGACY_ERROR_TEMP_2000", + messageParameters = Map( + "message" -> errorMessage, + "ansiConfig" -> toSQLConf(SQLConf.ANSI_ENABLED.key)), + context = Array.empty, + summary = "") + } + } + val (unit, range, badValue) = extractDateTimeErrorInfo(e) new SparkDateTimeException( - errorClass = "_LEGACY_ERROR_TEMP_2000", + errorClass = "DATETIME_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" -> toSQLValue(badValue) + ), context = Array.empty, - summary = "") + summary = "", + cause = Some(e) + ) } def ansiIllegalArgumentError(message: String): SparkIllegalArgumentException = { diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala index 21ae35146282b..35d1eeee9442d 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala @@ -1140,12 +1140,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)), + "DATETIME_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)), + "DATETIME_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)), + "DATETIME_FIELD_OUT_OF_BOUNDS", + Map( + "ansiConfig" -> "\"spark.sql.ansi.enabled\"", + "unit" -> "DAY", + "range" -> "1 ... 28/31", + "badValue" -> "'32'") + ) } // non-ansi test @@ -1183,19 +1204,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)), + "DATETIME_FIELD_OUT_OF_BOUNDS", + Map( + "ansiConfig" -> "\"spark.sql.ansi.enabled\"", + "unit" -> "YEAR", + "range" -> "-999999999 ... 999999999", + "badValue" -> "'2147483647'") + ) + checkErrorInExpression[SparkDateTimeException]( + makeTimestampExpr.copy(month = Literal(13)), + "DATETIME_FIELD_OUT_OF_BOUNDS", + Map( + "ansiConfig" -> "\"spark.sql.ansi.enabled\"", + "unit" -> "MONTH", + "range" -> "1 ... 12", + "badValue" -> "'13'") + ) + checkErrorInExpression[SparkDateTimeException]( + makeTimestampExpr.copy(day = Literal(32)), + "DATETIME_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)), + "DATETIME_FIELD_OUT_OF_BOUNDS", + Map( + "ansiConfig" -> "\"spark.sql.ansi.enabled\"", + "unit" -> "HOUR", + "range" -> "0 ... 23", + "badValue" -> "'25'") + ) + checkErrorInExpression[SparkDateTimeException]( + makeTimestampExpr.copy(min = Literal(65)), + "DATETIME_FIELD_OUT_OF_BOUNDS", + Map( + "ansiConfig" -> "\"spark.sql.ansi.enabled\"", + "unit" -> "MINUTE", + "range" -> "0 ... 59", + "badValue" -> "'65'") + ) + checkErrorInExpression[SparkDateTimeException]( + makeTimestampExpr.copy(sec = Literal(Decimal(BigDecimal(70.0), 16, 6))), + "DATETIME_FIELD_OUT_OF_BOUNDS", + Map( + "ansiConfig" -> "\"spark.sql.ansi.enabled\"", + "unit" -> "SECOND", + "range" -> "0 ... 59", + "badValue" -> "'70'") + ) } makeTimestampExpr = MakeTimestamp(Literal(2019), Literal(6), Literal(30), diff --git a/sql/core/src/test/resources/sql-tests/results/ansi/date.sql.out b/sql/core/src/test/resources/sql-tests/results/ansi/date.sql.out index 67cd23faf2556..5fd834e5808dc 100644 --- a/sql/core/src/test/resources/sql-tests/results/ansi/date.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/ansi/date.sql.out @@ -53,10 +53,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_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" } } @@ -68,10 +71,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_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" } } diff --git a/sql/core/src/test/resources/sql-tests/results/ansi/timestamp.sql.out b/sql/core/src/test/resources/sql-tests/results/ansi/timestamp.sql.out index d7a58e321b0f0..24f235fdbaed4 100644 --- a/sql/core/src/test/resources/sql-tests/results/ansi/timestamp.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/ansi/timestamp.sql.out @@ -154,10 +154,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_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" : "SECOND" } } @@ -185,10 +188,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_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" : "SECOND" } } @@ -200,10 +206,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_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" : "SECOND" } } diff --git a/sql/core/src/test/resources/sql-tests/results/postgreSQL/date.sql.out b/sql/core/src/test/resources/sql-tests/results/postgreSQL/date.sql.out index 8caf8c54b9f39..a53336859f88e 100755 --- a/sql/core/src/test/resources/sql-tests/results/postgreSQL/date.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/postgreSQL/date.sql.out @@ -687,10 +687,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_FIELD_OUT_OF_BOUNDS", + "sqlState" : "22008", "messageParameters" : { "ansiConfig" : "\"spark.sql.ansi.enabled\"", - "message" : "Invalid date 'FEBRUARY 30'" + "badValue" : "'30'", + "range" : "1 ... 28/31", + "unit" : "DAY" } } @@ -702,10 +705,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_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" } } @@ -717,10 +723,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_FIELD_OUT_OF_BOUNDS", + "sqlState" : "22008", "messageParameters" : { "ansiConfig" : "\"spark.sql.ansi.enabled\"", - "message" : "Invalid value for DayOfMonth (valid values 1 - 28/31): -1" + "badValue" : "'-1'", + "range" : "1 ... 28/31", + "unit" : "DAY" } } diff --git a/sql/core/src/test/resources/sql-tests/results/timestampNTZ/timestamp-ansi.sql.out b/sql/core/src/test/resources/sql-tests/results/timestampNTZ/timestamp-ansi.sql.out index cd94674d2bf2b..dc7624b37a3a5 100644 --- a/sql/core/src/test/resources/sql-tests/results/timestampNTZ/timestamp-ansi.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/timestampNTZ/timestamp-ansi.sql.out @@ -154,10 +154,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_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" : "SECOND" } } @@ -185,10 +188,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_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" : "SECOND" } } @@ -200,10 +206,13 @@ struct<> -- !query output org.apache.spark.SparkDateTimeException { - "errorClass" : "_LEGACY_ERROR_TEMP_2000", + "errorClass" : "DATETIME_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" : "SECOND" } }