diff --git a/core/src/main/resources/error/error-classes.json b/core/src/main/resources/error/error-classes.json index 999a9b0a4aec6..22b47c979c65a 100644 --- a/core/src/main/resources/error/error-classes.json +++ b/core/src/main/resources/error/error-classes.json @@ -95,6 +95,23 @@ "message" : [ "the binary operator requires the input type , not ." ] + }, + "CAST_WITHOUT_SUGGESTION" : { + "message" : [ + "cannot cast to ." + ] + }, + "CAST_WITH_CONF_SUGGESTION" : { + "message" : [ + "cannot cast to with ANSI mode on.", + "If you have to cast to , you can set as ." + ] + }, + "CAST_WITH_FUN_SUGGESTION" : { + "message" : [ + "cannot cast to .", + "To convert values from to , you can use the functions instead." + ] } } }, diff --git a/core/src/main/scala/org/apache/spark/SparkThrowableHelper.scala b/core/src/main/scala/org/apache/spark/SparkThrowableHelper.scala index 497fd91d77ac7..86337205d31c8 100644 --- a/core/src/main/scala/org/apache/spark/SparkThrowableHelper.scala +++ b/core/src/main/scala/org/apache/spark/SparkThrowableHelper.scala @@ -187,9 +187,10 @@ private[spark] object SparkThrowableHelper { val messageParameters = e.getMessageParameters if (!messageParameters.isEmpty) { g.writeObjectFieldStart("messageParameters") - messageParameters.asScala.toSeq.sortBy(_._1).foreach { case (name, value) => - g.writeStringField(name, value) - } + messageParameters.asScala + .toMap // To remove duplicates + .toSeq.sortBy(_._1) + .foreach { case (name, value) => g.writeStringField(name, value) } g.writeEndObject() } val queryContext = e.getQueryContext diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala index d8642d22af002..78cac4143d3e1 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit._ import org.apache.spark.SparkArithmeticException import org.apache.spark.sql.catalyst.InternalRow import org.apache.spark.sql.catalyst.analysis.{TypeCheckResult, TypeCoercion} +import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.DataTypeMismatch import org.apache.spark.sql.catalyst.expressions.codegen._ import org.apache.spark.sql.catalyst.expressions.codegen.Block._ import org.apache.spark.sql.catalyst.trees.{SQLQueryContext, TreeNodeTag} @@ -33,14 +34,14 @@ import org.apache.spark.sql.catalyst.util.DateTimeConstants._ import org.apache.spark.sql.catalyst.util.DateTimeUtils._ import org.apache.spark.sql.catalyst.util.IntervalStringStyles.ANSI_STYLE import org.apache.spark.sql.catalyst.util.IntervalUtils.{dayTimeIntervalToByte, dayTimeIntervalToDecimal, dayTimeIntervalToInt, dayTimeIntervalToLong, dayTimeIntervalToShort, yearMonthIntervalToByte, yearMonthIntervalToInt, yearMonthIntervalToShort} -import org.apache.spark.sql.errors.QueryExecutionErrors +import org.apache.spark.sql.errors.{QueryErrorsBase, QueryExecutionErrors} import org.apache.spark.sql.internal.SQLConf import org.apache.spark.sql.types._ import org.apache.spark.unsafe.UTF8StringBuilder import org.apache.spark.unsafe.types.{CalendarInterval, UTF8String} import org.apache.spark.unsafe.types.UTF8String.{IntWrapper, LongWrapper} -object Cast { +object Cast extends QueryErrorsBase { /** * As per section 6.13 "cast specification" in "Information technology — Database languages " + * "- SQL — Part 2: Foundation (SQL/Foundation)": @@ -412,47 +413,48 @@ object Cast { } } - // Show suggestion on how to complete the disallowed explicit casting with built-in type - // conversion functions. - private def suggestionOnConversionFunctions ( - from: DataType, - to: DataType, - functionNames: String): String = { - // scalastyle:off line.size.limit - s"""cannot cast ${from.catalogString} to ${to.catalogString}. - |To convert values from ${from.catalogString} to ${to.catalogString}, you can use $functionNames instead. - |""".stripMargin - // scalastyle:on line.size.limit - } - def typeCheckFailureMessage( from: DataType, to: DataType, - fallbackConf: Option[(String, String)]): String = + fallbackConf: Option[(String, String)]): DataTypeMismatch = { + def withFunSuggest(names: String*): DataTypeMismatch = { + DataTypeMismatch( + errorSubClass = "CAST_WITH_FUN_SUGGESTION", + messageParameters = Map( + "srcType" -> toSQLType(from), + "targetType" -> toSQLType(to), + "functionNames" -> names.map(toSQLId).mkString("/"))) + } (from, to) match { case (_: NumericType, TimestampType) => - suggestionOnConversionFunctions(from, to, - "functions TIMESTAMP_SECONDS/TIMESTAMP_MILLIS/TIMESTAMP_MICROS") + withFunSuggest("TIMESTAMP_SECONDS", "TIMESTAMP_MILLIS", "TIMESTAMP_MICROS") case (TimestampType, _: NumericType) => - suggestionOnConversionFunctions(from, to, "functions UNIX_SECONDS/UNIX_MILLIS/UNIX_MICROS") + withFunSuggest("UNIX_SECONDS", "UNIX_MILLIS", "UNIX_MICROS") case (_: NumericType, DateType) => - suggestionOnConversionFunctions(from, to, "function DATE_FROM_UNIX_DATE") + withFunSuggest("DATE_FROM_UNIX_DATE") case (DateType, _: NumericType) => - suggestionOnConversionFunctions(from, to, "function UNIX_DATE") + withFunSuggest("UNIX_DATE") - // scalastyle:off line.size.limit case _ if fallbackConf.isDefined && Cast.canCast(from, to) => - s""" - | cannot cast ${from.catalogString} to ${to.catalogString} with ANSI mode on. - | If you have to cast ${from.catalogString} to ${to.catalogString}, you can set ${fallbackConf.get._1} as ${fallbackConf.get._2}. - |""".stripMargin - // scalastyle:on line.size.limit + DataTypeMismatch( + errorSubClass = "CAST_WITH_CONF_SUGGESTION", + messageParameters = Map( + "srcType" -> toSQLType(from), + "targetType" -> toSQLType(to), + "config" -> toSQLConf(fallbackConf.get._1), + "configVal" -> toSQLValue(fallbackConf.get._2, StringType))) - case _ => s"cannot cast ${from.catalogString} to ${to.catalogString}" + case _ => + DataTypeMismatch( + errorSubClass = "CAST_WITHOUT_SUGGESTION", + messageParameters = Map( + "srcType" -> toSQLType(from), + "targetType" -> toSQLType(to))) } + } def apply( child: Expression, @@ -487,8 +489,12 @@ case class Cast( child: Expression, dataType: DataType, timeZoneId: Option[String] = None, - evalMode: EvalMode.Value = EvalMode.fromSQLConf(SQLConf.get)) extends UnaryExpression - with TimeZoneAwareExpression with NullIntolerant with SupportQueryContext { + evalMode: EvalMode.Value = EvalMode.fromSQLConf(SQLConf.get)) + extends UnaryExpression + with TimeZoneAwareExpression + with NullIntolerant + with SupportQueryContext + with QueryErrorsBase { def this(child: Expression, dataType: DataType, timeZoneId: Option[String]) = this(child, dataType, timeZoneId, evalMode = EvalMode.fromSQLConf(SQLConf.get)) @@ -509,7 +515,7 @@ case class Cast( evalMode == EvalMode.TRY } - private def typeCheckFailureMessage: String = evalMode match { + private def typeCheckFailureInCast: DataTypeMismatch = evalMode match { case EvalMode.ANSI => if (getTagValue(Cast.BY_TABLE_INSERTION).isDefined) { Cast.typeCheckFailureMessage(child.dataType, dataType, @@ -522,7 +528,11 @@ case class Cast( case EvalMode.TRY => Cast.typeCheckFailureMessage(child.dataType, dataType, None) case _ => - s"cannot cast ${child.dataType.catalogString} to ${dataType.catalogString}" + DataTypeMismatch( + errorSubClass = "CAST_WITHOUT_SUGGESTION", + messageParameters = Map( + "srcType" -> toSQLType(child.dataType), + "targetType" -> toSQLType(dataType))) } override def checkInputDataTypes(): TypeCheckResult = { @@ -535,7 +545,7 @@ case class Cast( if (canCast) { TypeCheckResult.TypeCheckSuccess } else { - TypeCheckResult.TypeCheckFailure(typeCheckFailureMessage) + typeCheckFailureInCast } } diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala index f627a2a835a4e..a60491b0ab8c2 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala @@ -27,7 +27,7 @@ import scala.collection.parallel.immutable.ParVector import org.apache.spark.SparkFunSuite import org.apache.spark.sql.Row import org.apache.spark.sql.catalyst.InternalRow -import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.TypeCheckFailure +import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.DataTypeMismatch import org.apache.spark.sql.catalyst.analysis.TypeCoercion.numericPrecedence import org.apache.spark.sql.catalyst.expressions.codegen.CodegenContext import org.apache.spark.sql.catalyst.util.DateTimeConstants._ @@ -66,21 +66,12 @@ abstract class CastSuiteBase extends SparkFunSuite with ExpressionEvalHelper { checkEvaluation(cast(Literal.create(null, from), to, UTC_OPT), null) } - protected def verifyCastFailure(c: Cast, optionalExpectedMsg: Option[String] = None): Unit = { + protected def verifyCastFailure(c: Cast, expected: DataTypeMismatch): Unit = { val typeCheckResult = c.checkInputDataTypes() assert(typeCheckResult.isFailure) - assert(typeCheckResult.isInstanceOf[TypeCheckFailure]) - val message = typeCheckResult.asInstanceOf[TypeCheckFailure].message - - if (optionalExpectedMsg.isDefined) { - assert(message.contains(optionalExpectedMsg.get)) - } else { - assert("cannot cast [a-zA-Z]+ to [a-zA-Z]+".r.findFirstIn(message).isDefined) - if (evalMode == EvalMode.ANSI) { - assert(message.contains("with ANSI mode on")) - assert(message.contains(s"set ${SQLConf.ANSI_ENABLED.key} as false")) - } - } + assert(typeCheckResult.isInstanceOf[DataTypeMismatch]) + val mismatch = typeCheckResult.asInstanceOf[DataTypeMismatch] + assert(mismatch === expected) } test("null cast") { @@ -936,13 +927,19 @@ abstract class CastSuiteBase extends SparkFunSuite with ExpressionEvalHelper { test("disallow type conversions between Numeric types and Timestamp without time zone type") { import DataTypeTestUtils.numericTypes checkInvalidCastFromNumericType(TimestampNTZType) - var errorMsg = "cannot cast bigint to timestamp_ntz" - verifyCastFailure(cast(Literal(0L), TimestampNTZType), Some(errorMsg)) + verifyCastFailure( + cast(Literal(0L), TimestampNTZType), + DataTypeMismatch( + "CAST_WITHOUT_SUGGESTION", + Map("srcType" -> "\"BIGINT\"", "targetType" -> "\"TIMESTAMP_NTZ\""))) val timestampNTZLiteral = Literal.create(LocalDateTime.now(), TimestampNTZType) - errorMsg = "cannot cast timestamp_ntz to" numericTypes.foreach { numericType => - verifyCastFailure(cast(timestampNTZLiteral, numericType), Some(errorMsg)) + verifyCastFailure( + cast(timestampNTZLiteral, numericType), + DataTypeMismatch( + "CAST_WITHOUT_SUGGESTION", + Map("srcType" -> "\"TIMESTAMP_NTZ\"", "targetType" -> s""""${numericType.sql}""""))) } } diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastWithAnsiOnSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastWithAnsiOnSuite.scala index 94d466617862e..3de4eb6815984 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastWithAnsiOnSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastWithAnsiOnSuite.scala @@ -23,6 +23,7 @@ import java.time.DateTimeException import org.apache.spark.{SparkArithmeticException, SparkRuntimeException} import org.apache.spark.sql.Row import org.apache.spark.sql.catalyst.InternalRow +import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.DataTypeMismatch import org.apache.spark.sql.catalyst.util.DateTimeConstants.MILLIS_PER_SECOND import org.apache.spark.sql.catalyst.util.DateTimeTestUtils import org.apache.spark.sql.catalyst.util.DateTimeTestUtils.{withDefaultTimeZone, UTC} @@ -141,12 +142,26 @@ class CastWithAnsiOnSuite extends CastSuiteBase with QueryErrorsBase { test("ANSI mode: disallow type conversions between Numeric types and Date type") { import DataTypeTestUtils.numericTypes checkInvalidCastFromNumericType(DateType) - var errorMsg = "you can use function DATE_FROM_UNIX_DATE instead" - verifyCastFailure(cast(Literal(0L), DateType), Some(errorMsg)) + verifyCastFailure( + cast(Literal(0L), DateType), + DataTypeMismatch( + "CAST_WITH_FUN_SUGGESTION", + Map( + "srcType" -> "\"BIGINT\"", + "targetType" -> "\"DATE\"", + "functionNames" -> "`DATE_FROM_UNIX_DATE`"))) val dateLiteral = Literal(1, DateType) - errorMsg = "you can use function UNIX_DATE instead" numericTypes.foreach { numericType => - verifyCastFailure(cast(dateLiteral, numericType), Some(errorMsg)) + withClue(s"numericType = ${numericType.sql}") { + verifyCastFailure( + cast(dateLiteral, numericType), + DataTypeMismatch( + "CAST_WITH_FUN_SUGGESTION", + Map( + "srcType" -> "\"DATE\"", + "targetType" -> s""""${numericType.sql}"""", + "functionNames" -> "`UNIX_DATE`"))) + } } } diff --git a/sql/core/src/test/resources/sql-tests/results/ansi/cast.sql.out b/sql/core/src/test/resources/sql-tests/results/ansi/cast.sql.out index deaece6e7e1ac..35d60255aba26 100644 --- a/sql/core/src/test/resources/sql-tests/results/ansi/cast.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/ansi/cast.sql.out @@ -611,10 +611,24 @@ SELECT HEX(CAST(CAST(123 AS byte) AS binary)) struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 'CAST(CAST(123 AS TINYINT) AS BINARY)' due to data type mismatch: - cannot cast tinyint to binary with ANSI mode on. - If you have to cast tinyint to binary, you can set spark.sql.ansi.enabled as false. -; line 1 pos 11 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITH_CONF_SUGGESTION", + "messageParameters" : { + "config" : "\"spark.sql.ansi.enabled\"", + "configVal" : "'false'", + "sqlExpr" : "\"CAST(CAST(123 AS TINYINT) AS BINARY)\"", + "srcType" : "\"TINYINT\"", + "targetType" : "\"BINARY\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 12, + "stopIndex" : 44, + "fragment" : "CAST(CAST(123 AS byte) AS binary)" + } ] +} -- !query @@ -623,10 +637,24 @@ SELECT HEX(CAST(CAST(-123 AS byte) AS binary)) struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 'CAST(CAST(-123 AS TINYINT) AS BINARY)' due to data type mismatch: - cannot cast tinyint to binary with ANSI mode on. - If you have to cast tinyint to binary, you can set spark.sql.ansi.enabled as false. -; line 1 pos 11 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITH_CONF_SUGGESTION", + "messageParameters" : { + "config" : "\"spark.sql.ansi.enabled\"", + "configVal" : "'false'", + "sqlExpr" : "\"CAST(CAST(-123 AS TINYINT) AS BINARY)\"", + "srcType" : "\"TINYINT\"", + "targetType" : "\"BINARY\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 12, + "stopIndex" : 45, + "fragment" : "CAST(CAST(-123 AS byte) AS binary)" + } ] +} -- !query @@ -635,10 +663,24 @@ SELECT HEX(CAST(123S AS binary)) struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 'CAST(123S AS BINARY)' due to data type mismatch: - cannot cast smallint to binary with ANSI mode on. - If you have to cast smallint to binary, you can set spark.sql.ansi.enabled as false. -; line 1 pos 11 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITH_CONF_SUGGESTION", + "messageParameters" : { + "config" : "\"spark.sql.ansi.enabled\"", + "configVal" : "'false'", + "sqlExpr" : "\"CAST(123 AS BINARY)\"", + "srcType" : "\"SMALLINT\"", + "targetType" : "\"BINARY\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 12, + "stopIndex" : 31, + "fragment" : "CAST(123S AS binary)" + } ] +} -- !query @@ -647,10 +689,24 @@ SELECT HEX(CAST(-123S AS binary)) struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 'CAST(-123S AS BINARY)' due to data type mismatch: - cannot cast smallint to binary with ANSI mode on. - If you have to cast smallint to binary, you can set spark.sql.ansi.enabled as false. -; line 1 pos 11 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITH_CONF_SUGGESTION", + "messageParameters" : { + "config" : "\"spark.sql.ansi.enabled\"", + "configVal" : "'false'", + "sqlExpr" : "\"CAST(-123 AS BINARY)\"", + "srcType" : "\"SMALLINT\"", + "targetType" : "\"BINARY\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 12, + "stopIndex" : 32, + "fragment" : "CAST(-123S AS binary)" + } ] +} -- !query @@ -659,10 +715,24 @@ SELECT HEX(CAST(123 AS binary)) struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 'CAST(123 AS BINARY)' due to data type mismatch: - cannot cast int to binary with ANSI mode on. - If you have to cast int to binary, you can set spark.sql.ansi.enabled as false. -; line 1 pos 11 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITH_CONF_SUGGESTION", + "messageParameters" : { + "config" : "\"spark.sql.ansi.enabled\"", + "configVal" : "'false'", + "sqlExpr" : "\"CAST(123 AS BINARY)\"", + "srcType" : "\"INT\"", + "targetType" : "\"BINARY\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 12, + "stopIndex" : 30, + "fragment" : "CAST(123 AS binary)" + } ] +} -- !query @@ -671,10 +741,24 @@ SELECT HEX(CAST(-123 AS binary)) struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 'CAST(-123 AS BINARY)' due to data type mismatch: - cannot cast int to binary with ANSI mode on. - If you have to cast int to binary, you can set spark.sql.ansi.enabled as false. -; line 1 pos 11 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITH_CONF_SUGGESTION", + "messageParameters" : { + "config" : "\"spark.sql.ansi.enabled\"", + "configVal" : "'false'", + "sqlExpr" : "\"CAST(-123 AS BINARY)\"", + "srcType" : "\"INT\"", + "targetType" : "\"BINARY\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 12, + "stopIndex" : 31, + "fragment" : "CAST(-123 AS binary)" + } ] +} -- !query @@ -683,10 +767,24 @@ SELECT HEX(CAST(123L AS binary)) struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 'CAST(123L AS BINARY)' due to data type mismatch: - cannot cast bigint to binary with ANSI mode on. - If you have to cast bigint to binary, you can set spark.sql.ansi.enabled as false. -; line 1 pos 11 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITH_CONF_SUGGESTION", + "messageParameters" : { + "config" : "\"spark.sql.ansi.enabled\"", + "configVal" : "'false'", + "sqlExpr" : "\"CAST(123 AS BINARY)\"", + "srcType" : "\"BIGINT\"", + "targetType" : "\"BINARY\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 12, + "stopIndex" : 31, + "fragment" : "CAST(123L AS binary)" + } ] +} -- !query @@ -695,10 +793,24 @@ SELECT HEX(CAST(-123L AS binary)) struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 'CAST(-123L AS BINARY)' due to data type mismatch: - cannot cast bigint to binary with ANSI mode on. - If you have to cast bigint to binary, you can set spark.sql.ansi.enabled as false. -; line 1 pos 11 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITH_CONF_SUGGESTION", + "messageParameters" : { + "config" : "\"spark.sql.ansi.enabled\"", + "configVal" : "'false'", + "sqlExpr" : "\"CAST(-123 AS BINARY)\"", + "srcType" : "\"BIGINT\"", + "targetType" : "\"BINARY\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 12, + "stopIndex" : 32, + "fragment" : "CAST(-123L AS binary)" + } ] +} -- !query diff --git a/sql/core/src/test/resources/sql-tests/results/typeCoercion/native/stringCastAndExpressions.sql.out b/sql/core/src/test/resources/sql-tests/results/typeCoercion/native/stringCastAndExpressions.sql.out index bd5e33ef9f7f9..c9ff8087042a1 100644 --- a/sql/core/src/test/resources/sql-tests/results/typeCoercion/native/stringCastAndExpressions.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/typeCoercion/native/stringCastAndExpressions.sql.out @@ -101,7 +101,22 @@ select cast(a as array) from t struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 't.a' due to data type mismatch: cannot cast string to array; line 1 pos 7 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITHOUT_SUGGESTION", + "messageParameters" : { + "sqlExpr" : "\"a\"", + "srcType" : "\"STRING\"", + "targetType" : "\"ARRAY\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 8, + "stopIndex" : 31, + "fragment" : "cast(a as array)" + } ] +} -- !query @@ -110,7 +125,22 @@ select cast(a as struct) from t struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 't.a' due to data type mismatch: cannot cast string to struct; line 1 pos 7 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITHOUT_SUGGESTION", + "messageParameters" : { + "sqlExpr" : "\"a\"", + "srcType" : "\"STRING\"", + "targetType" : "\"STRUCT\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 8, + "stopIndex" : 34, + "fragment" : "cast(a as struct)" + } ] +} -- !query @@ -119,7 +149,22 @@ select cast(a as map) from t struct<> -- !query output org.apache.spark.sql.AnalysisException -cannot resolve 't.a' due to data type mismatch: cannot cast string to map; line 1 pos 7 +{ + "errorClass" : "DATATYPE_MISMATCH", + "errorSubClass" : "CAST_WITHOUT_SUGGESTION", + "messageParameters" : { + "sqlExpr" : "\"a\"", + "srcType" : "\"STRING\"", + "targetType" : "\"MAP\"" + }, + "queryContext" : [ { + "objectType" : "", + "objectName" : "", + "startIndex" : 8, + "stopIndex" : 37, + "fragment" : "cast(a as map)" + } ] +} -- !query diff --git a/sql/core/src/test/scala/org/apache/spark/sql/DatasetSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/DatasetSuite.scala index 5be6d53f6e10c..7420ef32d4d9f 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/DatasetSuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/DatasetSuite.scala @@ -900,10 +900,16 @@ class DatasetSuite extends QueryTest test("Kryo encoder: check the schema mismatch when converting DataFrame to Dataset") { implicit val kryoEncoder = Encoders.kryo[KryoData] val df = Seq((1.0)).toDF("a") - val e = intercept[AnalysisException] { - df.as[KryoData] - }.message - assert(e.contains("cannot cast double to binary")) + checkError( + exception = intercept[AnalysisException] { + df.as[KryoData] + }, + errorClass = "DATATYPE_MISMATCH", + errorSubClass = Some("CAST_WITHOUT_SUGGESTION"), + parameters = Map( + "sqlExpr" -> "\"a\"", + "srcType" -> "\"DOUBLE\"", + "targetType" -> "\"BINARY\"")) } test("Java encoder") {