diff --git a/core/src/main/resources/error/error-classes.json b/core/src/main/resources/error/error-classes.json index 12c97c2108a4..afe08f044c7d 100644 --- a/core/src/main/resources/error/error-classes.json +++ b/core/src/main/resources/error/error-classes.json @@ -229,46 +229,6 @@ "Input to should all be the same type, but it's ." ] }, - "FORMAT_CONT_THOUSANDS_SEPS" : { - "message" : [ - "Thousands separators (, or G) must have digits in between them in the number format: ." - ] - }, - "FORMAT_CUR_MUST_BEFORE_DEC" : { - "message" : [ - "Currency characters must appear before any decimal point in the number format: ." - ] - }, - "FORMAT_CUR_MUST_BEFORE_DIGIT" : { - "message" : [ - "Currency characters must appear before digits in the number format: ." - ] - }, - "FORMAT_EMPTY" : { - "message" : [ - "The number format string cannot be empty." - ] - }, - "FORMAT_THOUSANDS_SEPS_MUST_BEFORE_DEC" : { - "message" : [ - "Thousands separators (, or G) may not appear after the decimal point in the number format: ." - ] - }, - "FORMAT_UNEXPECTED_TOKEN" : { - "message" : [ - "Unexpected found in the format string ; the structure of the format string must match: [MI|S] [$] [0|9|G|,]* [.|D] [0|9]* [$] [PR|MI|S]." - ] - }, - "FORMAT_WRONG_NUM_DIGIT" : { - "message" : [ - "The format string requires at least one number digit." - ] - }, - "FORMAT_WRONG_NUM_TOKEN" : { - "message" : [ - "At most one is allowed in the number format: ." - ] - }, "HASH_MAP_TYPE" : { "message" : [ "Input to the function cannot contain elements of the \"MAP\" type. In Spark, same maps may have different hashcode, thus hash expressions are prohibited on \"MAP\" elements. To restore previous behavior set \"spark.sql.legacy.allowHashOnMapType\" to \"true\"." @@ -687,6 +647,63 @@ ], "sqlState" : "42000" }, + "INVALID_FORMAT" : { + "message" : [ + "The format is invalid: ." + ], + "subClass" : { + "CONT_THOUSANDS_SEPS" : { + "message" : [ + "Thousands separators (, or G) must have digits in between them in the number format." + ] + }, + "CUR_MUST_BEFORE_DEC" : { + "message" : [ + "Currency characters must appear before any decimal point in the number format." + ] + }, + "CUR_MUST_BEFORE_DIGIT" : { + "message" : [ + "Currency characters must appear before digits in the number format." + ] + }, + "EMPTY" : { + "message" : [ + "The number format string cannot be empty." + ] + }, + "ESC_AT_THE_END" : { + "message" : [ + "The escape character is not allowed to end with." + ] + }, + "ESC_IN_THE_MIDDLE" : { + "message" : [ + "The escape character is not allowed to precede ." + ] + }, + "THOUSANDS_SEPS_MUST_BEFORE_DEC" : { + "message" : [ + "Thousands separators (, or G) may not appear after the decimal point in the number format." + ] + }, + "UNEXPECTED_TOKEN" : { + "message" : [ + "Found the unexpected in the format string; the structure of the format string must match: [MI|S] [$] [0|9|G|,]* [.|D] [0|9]* [$] [PR|MI|S]." + ] + }, + "WRONG_NUM_DIGIT" : { + "message" : [ + "The format string requires at least one number digit." + ] + }, + "WRONG_NUM_TOKEN" : { + "message" : [ + "At most one is allowed in the number format." + ] + } + } + }, "INVALID_FRACTION_OF_SECOND" : { "message" : [ "The fraction of sec must be zero. Valid range is [0, 60]. If necessary set to \"false\" to bypass this error." @@ -708,23 +725,6 @@ "The JOIN with LATERAL correlation is not allowed because an OUTER subquery cannot correlate to its join partner. Remove the LATERAL correlation or use an INNER JOIN, or LEFT OUTER JOIN instead." ] }, - "INVALID_LIKE_PATTERN" : { - "message" : [ - "The pattern is invalid." - ], - "subClass" : { - "ESC_AT_THE_END" : { - "message" : [ - "the escape character is not allowed to end with." - ] - }, - "ESC_IN_THE_MIDDLE" : { - "message" : [ - "the escape character is not allowed to precede ." - ] - } - } - }, "INVALID_PANDAS_UDF_PLACEMENT" : { "message" : [ "The group aggregate pandas UDF cannot be invoked together with as other, non-pandas aggregate functions." diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala index 86bb48410d27..12dac5c632a3 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala @@ -52,6 +52,7 @@ trait CheckAnalysis extends PredicateHelper with LookupCatalog with QueryErrorsB val extendedCheckRules: Seq[LogicalPlan => Unit] = Nil val DATA_TYPE_MISMATCH_ERROR = TreeNodeTag[Boolean]("dataTypeMismatchError") + val INVALID_FORMAT_ERROR = TreeNodeTag[Boolean]("invalidFormatError") /** * Fails the analysis at the point where a specific tree node was parsed using a provided @@ -221,6 +222,9 @@ trait CheckAnalysis extends PredicateHelper with LookupCatalog with QueryErrorsB hof.failAnalysis( errorClass = "_LEGACY_ERROR_TEMP_2314", messageParameters = Map("sqlExpr" -> hof.sql, "msg" -> message)) + case checkRes: TypeCheckResult.InvalidFormat => + hof.setTagValue(INVALID_FORMAT_ERROR, true) + hof.invalidFormat(checkRes) } // If an attribute can't be resolved as a map key of string type, either the key should be @@ -252,6 +256,9 @@ trait CheckAnalysis extends PredicateHelper with LookupCatalog with QueryErrorsB "sqlExpr" -> e.sql, "msg" -> message, "hint" -> extraHintForAnsiTypeCoercionExpression(operator))) + case checkRes: TypeCheckResult.InvalidFormat => + e.setTagValue(INVALID_FORMAT_ERROR, true) + e.invalidFormat(checkRes) } case c: Cast if !c.resolved => diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCheckResult.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCheckResult.scala index 852c948a6c70..05ad8046ea03 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCheckResult.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCheckResult.scala @@ -55,4 +55,17 @@ object TypeCheckResult { extends TypeCheckResult { def isSuccess: Boolean = false } + + /** + * Represents an error of invalid format with the `INVALID_FORMAT` error class. + * + * @param errorSubClass A sub-class of `INVALID_FORMAT`. + * @param messageParameters Parameters of the sub-class error message. + */ + case class InvalidFormat( + errorSubClass: String, + messageParameters: Map[String, String] = Map.empty) + extends TypeCheckResult { + def isSuccess: Boolean = false + } } diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/package.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/package.scala index c7d4f98be4b9..fc683e98b979 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/package.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/package.scala @@ -18,7 +18,7 @@ package org.apache.spark.sql.catalyst import org.apache.spark.sql.AnalysisException -import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.DataTypeMismatch +import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.{DataTypeMismatch, InvalidFormat} import org.apache.spark.sql.catalyst.expressions._ import org.apache.spark.sql.catalyst.trees.TreeNode import org.apache.spark.sql.catalyst.util.quoteNameParts @@ -73,6 +73,13 @@ package object analysis { origin = t.origin) } + def invalidFormat(invalidFormat: InvalidFormat): Nothing = { + throw new AnalysisException( + errorClass = s"INVALID_FORMAT.${invalidFormat.errorSubClass}", + messageParameters = invalidFormat.messageParameters, + origin = t.origin) + } + def tableNotFound(name: Seq[String]): Nothing = { throw new AnalysisException( errorClass = "TABLE_OR_VIEW_NOT_FOUND", diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/ToNumberParser.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/ToNumberParser.scala index 4bc147f32977..5099b3fdb4b1 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/ToNumberParser.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/ToNumberParser.scala @@ -20,7 +20,7 @@ package org.apache.spark.sql.catalyst.util import scala.collection.mutable import org.apache.spark.sql.catalyst.analysis.TypeCheckResult -import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.{DataTypeMismatch, TypeCheckSuccess} +import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.{InvalidFormat, TypeCheckSuccess} import org.apache.spark.sql.catalyst.expressions.Cast._ import org.apache.spark.sql.errors.QueryExecutionErrors import org.apache.spark.sql.types.{Decimal, DecimalType, StringType} @@ -281,37 +281,29 @@ class ToNumberParser(numberFormat: String, errorOnFail: Boolean) extends Seriali // Make sure the format string contains at least one token. if (numberFormat.isEmpty) { - return DataTypeMismatch( - errorSubClass = "FORMAT_EMPTY", - messageParameters = Map.empty - ) + return InvalidFormat( + errorSubClass = "EMPTY", + messageParameters = Map("format" -> toSQLValue(numberFormat, StringType))) } // Make sure the format string contains at least one digit. if (!formatTokens.exists( token => token.isInstanceOf[DigitGroups])) { - return DataTypeMismatch( - errorSubClass = "FORMAT_WRONG_NUM_DIGIT", - messageParameters = Map.empty - ) + return InvalidFormat( + errorSubClass = "WRONG_NUM_DIGIT", + messageParameters = Map("format" -> toSQLValue(numberFormat, StringType))) } // Make sure that any dollar sign in the format string occurs before any digits. if (firstDigitIndex < firstDollarSignIndex) { - return DataTypeMismatch( - errorSubClass = "FORMAT_CUR_MUST_BEFORE_DIGIT", - messageParameters = Map( - "format" -> toSQLValue(numberFormat, StringType) - ) - ) + return InvalidFormat( + errorSubClass = "CUR_MUST_BEFORE_DIGIT", + messageParameters = Map("format" -> toSQLValue(numberFormat, StringType))) } // Make sure that any dollar sign in the format string occurs before any decimal point. if (firstDecimalPointIndex != -1 && firstDecimalPointIndex < firstDollarSignIndex) { - return DataTypeMismatch( - errorSubClass = "FORMAT_CUR_MUST_BEFORE_DEC", - messageParameters = Map( - "format" -> toSQLValue(numberFormat, StringType) - ) - ) + return InvalidFormat( + errorSubClass = "CUR_MUST_BEFORE_DEC", + messageParameters = Map("format" -> toSQLValue(numberFormat, StringType))) } // Make sure that any thousands separators in the format string have digits before and after. if (digitGroupsBeforeDecimalPoint.exists { @@ -327,23 +319,18 @@ class ToNumberParser(numberFormat: String, errorOnFail: Boolean) extends Seriali false }) }) { - return DataTypeMismatch( - errorSubClass = "FORMAT_CONT_THOUSANDS_SEPS", - messageParameters = Map( - "format" -> toSQLValue(numberFormat, StringType) - ) - ) + return InvalidFormat( + errorSubClass = "CONT_THOUSANDS_SEPS", + messageParameters = Map("format" -> toSQLValue(numberFormat, StringType))) } // Make sure that thousands separators does not appear after the decimal point, if any. if (digitGroupsAfterDecimalPoint.exists { case DigitGroups(tokens, digits) => tokens.length > digits.length }) { - return DataTypeMismatch( - errorSubClass = "FORMAT_THOUSANDS_SEPS_MUST_BEFORE_DEC", - messageParameters = Map( - "format" -> toSQLValue(numberFormat, StringType) - ) + return InvalidFormat( + errorSubClass = "THOUSANDS_SEPS_MUST_BEFORE_DEC", + messageParameters = Map("format" -> toSQLValue(numberFormat, StringType)) ) } // Make sure that the format string does not contain any prohibited duplicate tokens. @@ -354,13 +341,11 @@ class ToNumberParser(numberFormat: String, errorOnFail: Boolean) extends Seriali DollarSign(), ClosingAngleBracket()).foreach { token => if (inputTokenCounts.getOrElse(token, 0) > 1) { - return DataTypeMismatch( - errorSubClass = "FORMAT_WRONG_NUM_TOKEN", + return InvalidFormat( + errorSubClass = "WRONG_NUM_TOKEN", messageParameters = Map( "token" -> token.toString, - "format" -> toSQLValue(numberFormat, StringType) - ) - ) + "format" -> toSQLValue(numberFormat, StringType))) } } // Enforce the ordering of tokens in the format string according to this specification: @@ -393,13 +378,11 @@ class ToNumberParser(numberFormat: String, errorOnFail: Boolean) extends Seriali } } if (formatTokenIndex < formatTokens.length) { - return DataTypeMismatch( - errorSubClass = "FORMAT_UNEXPECTED_TOKEN", + return InvalidFormat( + errorSubClass = "UNEXPECTED_TOKEN", messageParameters = Map( "token" -> formatTokens(formatTokenIndex).toString, - "format" -> toSQLValue(numberFormat, StringType) - ) - ) + "format" -> toSQLValue(numberFormat, StringType))) } // Validation of the format string finished successfully. TypeCheckSuccess diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala index 0f245597efd9..963f6c5025e0 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala @@ -2143,17 +2143,16 @@ private[sql] object QueryCompilationErrors extends QueryErrorsBase { def escapeCharacterInTheMiddleError(pattern: String, char: String): Throwable = { new AnalysisException( - errorClass = "INVALID_LIKE_PATTERN.ESC_IN_THE_MIDDLE", + errorClass = "INVALID_FORMAT.ESC_IN_THE_MIDDLE", messageParameters = Map( - "pattern" -> toSQLValue(pattern, StringType), + "format" -> toSQLValue(pattern, StringType), "char" -> toSQLValue(char, StringType))) } def escapeCharacterAtTheEndError(pattern: String): Throwable = { new AnalysisException( - errorClass = "INVALID_LIKE_PATTERN.ESC_AT_THE_END", - messageParameters = Map( - "pattern" -> toSQLValue(pattern, StringType))) + errorClass = "INVALID_FORMAT.ESC_AT_THE_END", + messageParameters = Map("format" -> toSQLValue(pattern, StringType))) } def tableIdentifierExistsError(tableIdentifier: TableIdentifier): Throwable = { diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/RegexpExpressionsSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/RegexpExpressionsSuite.scala index 095c2736ae07..95e2601f5532 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/RegexpExpressionsSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/RegexpExpressionsSuite.scala @@ -154,15 +154,18 @@ class RegexpExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper { // scalastyle:on nonascii // invalid escaping - val invalidEscape = intercept[AnalysisException] { - evaluateWithoutCodegen("""a""" like """\a""") - } - assert(invalidEscape.getMessage.contains("pattern")) - - val endEscape = intercept[AnalysisException] { - evaluateWithoutCodegen("""a""" like """a\""") - } - assert(endEscape.getMessage.contains("pattern")) + checkError( + exception = intercept[AnalysisException] { + evaluateWithoutCodegen("""a""" like """\a""") + }, + errorClass = "INVALID_FORMAT.ESC_IN_THE_MIDDLE", + parameters = Map("format" -> """'\\a'""", "char" -> "'a'")) + checkError( + exception = intercept[AnalysisException] { + evaluateWithoutCodegen("""a""" like """a\""") + }, + errorClass = "INVALID_FORMAT.ESC_AT_THE_END", + parameters = Map("format" -> """'a\\'""")) // case checkLiteralRow("A" like _, "a%", false) @@ -231,14 +234,12 @@ class RegexpExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper { // scalastyle:on nonascii // invalid escaping - val invalidEscape = intercept[AnalysisException] { - evaluateWithoutCodegen("""a""" like(s"""${escapeChar}a""", escapeChar)) - } - assert(invalidEscape.getMessage.contains("pattern")) - val endEscape = intercept[AnalysisException] { - evaluateWithoutCodegen("""a""" like(s"""a$escapeChar""", escapeChar)) - } - assert(endEscape.getMessage.contains("pattern")) + checkError( + exception = intercept[AnalysisException] { + evaluateWithoutCodegen("""a""" like(s"""${escapeChar}a""", escapeChar)) + }, + errorClass = "INVALID_FORMAT.ESC_IN_THE_MIDDLE", + parameters = Map("format" -> s"'${escapeChar}a'", "char" -> "'a'")) // case checkLiteralRow("A" like(_, escapeChar), "a%", false) diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/StringExpressionsSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/StringExpressionsSuite.scala index 8bdbcb26e83c..0585578571a4 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/StringExpressionsSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/StringExpressionsSuite.scala @@ -21,7 +21,7 @@ import java.math.{BigDecimal => JavaBigDecimal} import org.apache.spark.{SparkFunSuite, SparkIllegalArgumentException} import org.apache.spark.sql.catalyst.analysis.TypeCheckResult -import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.DataTypeMismatch +import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.{DataTypeMismatch, InvalidFormat} import org.apache.spark.sql.catalyst.dsl.expressions._ import org.apache.spark.sql.catalyst.expressions.Cast._ import org.apache.spark.sql.catalyst.expressions.codegen.GenerateUnsafeProjection @@ -1089,155 +1089,155 @@ class StringExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper { test("ToNumber and ToCharacter: negative tests (the format string is invalid)") { Seq( // The format string must not be empty. - ("454", "") -> DataTypeMismatch( - errorSubClass = "FORMAT_EMPTY", - messageParameters = Map.empty), + ("454", "") -> InvalidFormat( + errorSubClass = "EMPTY", + messageParameters = Map("format" -> "''")), // Make sure the format string does not contain any unrecognized characters. ("454", "999@") -> - DataTypeMismatch( - errorSubClass = "FORMAT_UNEXPECTED_TOKEN", + InvalidFormat( + errorSubClass = "UNEXPECTED_TOKEN", messageParameters = Map( "token" -> "character '@''", "format" -> toSQLValue("999@", StringType)) ), ("454", "999M") -> - DataTypeMismatch( - errorSubClass = "FORMAT_UNEXPECTED_TOKEN", + InvalidFormat( + errorSubClass = "UNEXPECTED_TOKEN", messageParameters = Map( "token" -> "character 'M''", "format" -> toSQLValue("999M", StringType) ) ), ("454", "999P") -> - DataTypeMismatch( - errorSubClass = "FORMAT_UNEXPECTED_TOKEN", + InvalidFormat( + errorSubClass = "UNEXPECTED_TOKEN", messageParameters = Map( "token" -> "character 'P''", "format" -> toSQLValue("999P", StringType)) ), // Make sure the format string contains at least one digit. - ("454", "$") -> DataTypeMismatch( - errorSubClass = "FORMAT_WRONG_NUM_DIGIT", - messageParameters = Map.empty), + ("454", "$") -> InvalidFormat( + errorSubClass = "WRONG_NUM_DIGIT", + messageParameters = Map("format" -> "'$'")), // Make sure the format string contains at most one decimal point. ("454", "99.99.99") -> - DataTypeMismatch( - errorSubClass = "FORMAT_WRONG_NUM_TOKEN", + InvalidFormat( + errorSubClass = "WRONG_NUM_TOKEN", messageParameters = Map( "token" -> ". or D", "format" -> toSQLValue("99.99.99", StringType)) ), // Make sure the format string contains at most one dollar sign. ("454", "$$99") -> - DataTypeMismatch( - errorSubClass = "FORMAT_WRONG_NUM_TOKEN", + InvalidFormat( + errorSubClass = "WRONG_NUM_TOKEN", messageParameters = Map( "token" -> "$", "" + "format" -> toSQLValue("$$99", StringType)) ), // Make sure the format string contains at most one minus sign at the beginning or end. ("$4-4", "$9MI9") -> - DataTypeMismatch( - errorSubClass = "FORMAT_UNEXPECTED_TOKEN", + InvalidFormat( + errorSubClass = "UNEXPECTED_TOKEN", messageParameters = Map( "token" -> "digit sequence", "format" -> toSQLValue("$9MI9", StringType)) ), ("--4", "SMI9") -> - DataTypeMismatch( - errorSubClass = "FORMAT_UNEXPECTED_TOKEN", + InvalidFormat( + errorSubClass = "UNEXPECTED_TOKEN", messageParameters = Map( "token" -> "digit sequence", "format" -> toSQLValue("SMI9", StringType)) ), ("--$54", "SS$99") -> - DataTypeMismatch( - errorSubClass = "FORMAT_WRONG_NUM_TOKEN", + InvalidFormat( + errorSubClass = "WRONG_NUM_TOKEN", messageParameters = Map( "token" -> "S", "format" -> toSQLValue("SS$99", StringType)) ), ("-$54", "MI$99MI") -> - DataTypeMismatch( - errorSubClass = "FORMAT_WRONG_NUM_TOKEN", + InvalidFormat( + errorSubClass = "WRONG_NUM_TOKEN", messageParameters = Map( "token" -> "MI", "format" -> toSQLValue("MI$99MI", StringType)) ), ("$4-4", "$9MI9MI") -> - DataTypeMismatch( - errorSubClass = "FORMAT_WRONG_NUM_TOKEN", + InvalidFormat( + errorSubClass = "WRONG_NUM_TOKEN", messageParameters = Map("token" -> "MI", "format" -> toSQLValue("$9MI9MI", StringType)) ), // Make sure the format string contains at most one closing angle bracket at the end. ("<$45>", "PR$99") -> - DataTypeMismatch( - errorSubClass = "FORMAT_UNEXPECTED_TOKEN", + InvalidFormat( + errorSubClass = "UNEXPECTED_TOKEN", messageParameters = Map( "token" -> "$", "format" -> toSQLValue("PR$99", StringType)) ), ("$4<4>", "$9PR9") -> - DataTypeMismatch( - errorSubClass = "FORMAT_UNEXPECTED_TOKEN", + InvalidFormat( + errorSubClass = "UNEXPECTED_TOKEN", messageParameters = Map( "token" -> "digit sequence", "format" -> toSQLValue("$9PR9", StringType)) ), ("<<454>>", "999PRPR") -> - DataTypeMismatch( - errorSubClass = "FORMAT_WRONG_NUM_TOKEN", + InvalidFormat( + errorSubClass = "WRONG_NUM_TOKEN", messageParameters = Map( "token" -> "PR", "format" -> toSQLValue("999PRPR", StringType)) ), // Make sure that any dollar sign in the format string occurs before any digits. - ("4$54", "9$99") -> DataTypeMismatch( - errorSubClass = "FORMAT_CUR_MUST_BEFORE_DIGIT", + ("4$54", "9$99") -> InvalidFormat( + errorSubClass = "CUR_MUST_BEFORE_DIGIT", messageParameters = Map("format" -> toSQLValue("9$99", StringType))), // Make sure that any dollar sign in the format string occurs before any decimal point. - (".$99", ".$99") -> DataTypeMismatch( - errorSubClass = "FORMAT_CUR_MUST_BEFORE_DEC", + (".$99", ".$99") -> InvalidFormat( + errorSubClass = "CUR_MUST_BEFORE_DEC", messageParameters = Map("format" -> toSQLValue(".$99", StringType))), // Thousands separators must have digits in between them. - (",123", ",099") -> DataTypeMismatch( - errorSubClass = "FORMAT_CONT_THOUSANDS_SEPS", + (",123", ",099") -> InvalidFormat( + errorSubClass = "CONT_THOUSANDS_SEPS", messageParameters = Map("format" -> toSQLValue(",099", StringType))), - (",123,456", ",999,099") -> DataTypeMismatch( - errorSubClass = "FORMAT_CONT_THOUSANDS_SEPS", + (",123,456", ",999,099") -> InvalidFormat( + errorSubClass = "CONT_THOUSANDS_SEPS", messageParameters = Map("format" -> toSQLValue(",999,099", StringType))), - (",,345", "9,,09.99") -> DataTypeMismatch( - errorSubClass = "FORMAT_CONT_THOUSANDS_SEPS", + (",,345", "9,,09.99") -> InvalidFormat( + errorSubClass = "CONT_THOUSANDS_SEPS", messageParameters = Map("format" -> toSQLValue("9,,09.99", StringType))), - (",,345", "9,99,.99") -> DataTypeMismatch( - errorSubClass = "FORMAT_CONT_THOUSANDS_SEPS", + (",,345", "9,99,.99") -> InvalidFormat( + errorSubClass = "CONT_THOUSANDS_SEPS", messageParameters = Map("format" -> toSQLValue("9,99,.99", StringType))), - (",,345", "9,99,") -> DataTypeMismatch( - errorSubClass = "FORMAT_CONT_THOUSANDS_SEPS", + (",,345", "9,99,") -> InvalidFormat( + errorSubClass = "CONT_THOUSANDS_SEPS", messageParameters = Map("format" -> toSQLValue("9,99,", StringType))), - (",,345", ",,999,099.99") -> DataTypeMismatch( - errorSubClass = "FORMAT_CONT_THOUSANDS_SEPS", + (",,345", ",,999,099.99") -> InvalidFormat( + errorSubClass = "CONT_THOUSANDS_SEPS", messageParameters = Map("format" -> toSQLValue(",,999,099.99", StringType))), // Thousands separators must not appear after the decimal point. - ("123.45,6", "099.99,9") -> DataTypeMismatch( - errorSubClass = "FORMAT_THOUSANDS_SEPS_MUST_BEFORE_DEC", + ("123.45,6", "099.99,9") -> InvalidFormat( + errorSubClass = "THOUSANDS_SEPS_MUST_BEFORE_DEC", messageParameters = Map("format" -> toSQLValue("099.99,9", StringType))) - ).foreach { case ((str: String, format: String), dataTypeMismatch: DataTypeMismatch) => + ).foreach { case ((str: String, format: String), invalidFormat: InvalidFormat) => val toNumberResult = ToNumber(Literal(str), Literal(format)).checkInputDataTypes() assert(toNumberResult != TypeCheckResult.TypeCheckSuccess, s"The format string should have been invalid: $format") - assert(toNumberResult == dataTypeMismatch) + assert(toNumberResult == invalidFormat) val tryToNumberResult = TryToNumber(Literal(str), Literal(format)).checkInputDataTypes() assert(tryToNumberResult != TypeCheckResult.TypeCheckSuccess, s"The format string should have been invalid: $format") - assert(tryToNumberResult == dataTypeMismatch) + assert(tryToNumberResult == invalidFormat) val toCharResult = ToCharacter(Decimal(456), Literal(format)).checkInputDataTypes() assert(toCharResult != TypeCheckResult.TypeCheckSuccess, s"The format string should have been invalid: $format") - assert(toCharResult == dataTypeMismatch) + assert(toCharResult == invalidFormat) } } diff --git a/sql/core/src/test/resources/sql-tests/results/postgreSQL/numeric.sql.out b/sql/core/src/test/resources/sql-tests/results/postgreSQL/numeric.sql.out index 608c7ba21b74..1f151036d1e9 100644 --- a/sql/core/src/test/resources/sql-tests/results/postgreSQL/numeric.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/postgreSQL/numeric.sql.out @@ -4712,10 +4712,9 @@ struct<> -- !query output org.apache.spark.sql.AnalysisException { - "errorClass" : "DATATYPE_MISMATCH.FORMAT_THOUSANDS_SEPS_MUST_BEFORE_DEC", + "errorClass" : "INVALID_FORMAT.THOUSANDS_SEPS_MUST_BEFORE_DEC", "messageParameters" : { - "format" : "'99G999G999D999G999'", - "sqlExpr" : "\"to_number(-34,338,492.654,878, 99G999G999D999G999)\"" + "format" : "'99G999G999D999G999'" }, "queryContext" : [ { "objectType" : "", @@ -4774,10 +4773,9 @@ struct<> -- !query output org.apache.spark.sql.AnalysisException { - "errorClass" : "DATATYPE_MISMATCH.FORMAT_CONT_THOUSANDS_SEPS", + "errorClass" : "INVALID_FORMAT.CONT_THOUSANDS_SEPS", "messageParameters" : { - "format" : "'999G'", - "sqlExpr" : "\"to_number(123,000, 999G)\"" + "format" : "'999G'" }, "queryContext" : [ { "objectType" : "", diff --git a/sql/core/src/test/resources/sql-tests/results/postgreSQL/strings.sql.out b/sql/core/src/test/resources/sql-tests/results/postgreSQL/strings.sql.out index a9c63e99653c..1f39669409d8 100644 --- a/sql/core/src/test/resources/sql-tests/results/postgreSQL/strings.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/postgreSQL/strings.sql.out @@ -444,10 +444,10 @@ struct<> -- !query output org.apache.spark.sql.AnalysisException { - "errorClass" : "INVALID_LIKE_PATTERN.ESC_IN_THE_MIDDLE", + "errorClass" : "INVALID_FORMAT.ESC_IN_THE_MIDDLE", "messageParameters" : { "char" : "'a'", - "pattern" : "'m%aca'" + "format" : "'m%aca'" } } @@ -459,10 +459,10 @@ struct<> -- !query output org.apache.spark.sql.AnalysisException { - "errorClass" : "INVALID_LIKE_PATTERN.ESC_IN_THE_MIDDLE", + "errorClass" : "INVALID_FORMAT.ESC_IN_THE_MIDDLE", "messageParameters" : { "char" : "'a'", - "pattern" : "'m%aca'" + "format" : "'m%aca'" } } @@ -474,10 +474,10 @@ struct<> -- !query output org.apache.spark.sql.AnalysisException { - "errorClass" : "INVALID_LIKE_PATTERN.ESC_IN_THE_MIDDLE", + "errorClass" : "INVALID_FORMAT.ESC_IN_THE_MIDDLE", "messageParameters" : { "char" : "'a'", - "pattern" : "'m%a%%a'" + "format" : "'m%a%%a'" } } @@ -489,10 +489,10 @@ struct<> -- !query output org.apache.spark.sql.AnalysisException { - "errorClass" : "INVALID_LIKE_PATTERN.ESC_IN_THE_MIDDLE", + "errorClass" : "INVALID_FORMAT.ESC_IN_THE_MIDDLE", "messageParameters" : { "char" : "'a'", - "pattern" : "'m%a%%a'" + "format" : "'m%a%%a'" } } @@ -504,10 +504,10 @@ struct<> -- !query output org.apache.spark.sql.AnalysisException { - "errorClass" : "INVALID_LIKE_PATTERN.ESC_IN_THE_MIDDLE", + "errorClass" : "INVALID_FORMAT.ESC_IN_THE_MIDDLE", "messageParameters" : { "char" : "'e'", - "pattern" : "'b_ear'" + "format" : "'b_ear'" } } @@ -519,10 +519,10 @@ struct<> -- !query output org.apache.spark.sql.AnalysisException { - "errorClass" : "INVALID_LIKE_PATTERN.ESC_IN_THE_MIDDLE", + "errorClass" : "INVALID_FORMAT.ESC_IN_THE_MIDDLE", "messageParameters" : { "char" : "'e'", - "pattern" : "'b_ear'" + "format" : "'b_ear'" } } @@ -534,10 +534,10 @@ struct<> -- !query output org.apache.spark.sql.AnalysisException { - "errorClass" : "INVALID_LIKE_PATTERN.ESC_IN_THE_MIDDLE", + "errorClass" : "INVALID_FORMAT.ESC_IN_THE_MIDDLE", "messageParameters" : { "char" : "'e'", - "pattern" : "'b_e__r'" + "format" : "'b_e__r'" } } @@ -549,10 +549,10 @@ struct<> -- !query output org.apache.spark.sql.AnalysisException { - "errorClass" : "INVALID_LIKE_PATTERN.ESC_IN_THE_MIDDLE", + "errorClass" : "INVALID_FORMAT.ESC_IN_THE_MIDDLE", "messageParameters" : { "char" : "'e'", - "pattern" : "'b_e__r'" + "format" : "'b_e__r'" } } diff --git a/sql/core/src/test/scala/org/apache/spark/sql/SQLQuerySuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/SQLQuerySuite.scala index cc703035b1ed..1ebdf7296053 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/SQLQuerySuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/SQLQuerySuite.scala @@ -3714,9 +3714,9 @@ class SQLQuerySuite extends QueryTest with SharedSparkSession with AdaptiveSpark exception = intercept[AnalysisException] { sql("SELECT s LIKE 'm%@ca' ESCAPE '%' FROM df").collect() }, - errorClass = "INVALID_LIKE_PATTERN.ESC_IN_THE_MIDDLE", + errorClass = "INVALID_FORMAT.ESC_IN_THE_MIDDLE", parameters = Map( - "pattern" -> toSQLValue("m%@ca", StringType), + "format" -> toSQLValue("m%@ca", StringType), "char" -> toSQLValue("@", StringType))) checkAnswer(sql("SELECT s LIKE 'm@@ca' ESCAPE '@' FROM df"), Row(true)) @@ -3731,8 +3731,8 @@ class SQLQuerySuite extends QueryTest with SharedSparkSession with AdaptiveSpark exception = intercept[AnalysisException] { sql("SELECT a LIKE 'jialiuping%' ESCAPE '%' FROM df").collect() }, - errorClass = "INVALID_LIKE_PATTERN.ESC_AT_THE_END", - parameters = Map("pattern" -> toSQLValue("jialiuping%", StringType))) + errorClass = "INVALID_FORMAT.ESC_AT_THE_END", + parameters = Map("format" -> toSQLValue("jialiuping%", StringType))) } }