Skip to content

Commit bb5b219

Browse files
committed
improve error msg
1 parent 19f3b89 commit bb5b219

2 files changed

Lines changed: 82 additions & 7 deletions

File tree

  • sql/catalyst/src
    • main/scala/org/apache/spark/sql/catalyst/expressions
    • test/scala/org/apache/spark/sql/catalyst/expressions

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@ abstract class CastBase extends UnaryExpression with TimeZoneAwareExpression wit
262262
*/
263263
def canCast(from: DataType, to: DataType): Boolean
264264

265+
/**
266+
* Returns the error message if casting from one type to another one is invalid.
267+
*/
268+
def typeCheckFailureMessage: String
269+
265270
override def toString: String = {
266271
val ansi = if (ansiEnabled) "ansi_" else ""
267272
s"${ansi}cast($child as ${dataType.simpleString})"
@@ -271,8 +276,7 @@ abstract class CastBase extends UnaryExpression with TimeZoneAwareExpression wit
271276
if (canCast(child.dataType, dataType)) {
272277
TypeCheckResult.TypeCheckSuccess
273278
} else {
274-
TypeCheckResult.TypeCheckFailure(
275-
s"cannot cast ${child.dataType.catalogString} to ${dataType.catalogString}")
279+
TypeCheckResult.TypeCheckFailure(typeCheckFailureMessage)
276280
}
277281
}
278282

@@ -1755,6 +1759,12 @@ case class Cast(child: Expression, dataType: DataType, timeZoneId: Option[String
17551759
} else {
17561760
Cast.canCast(from, to)
17571761
}
1762+
1763+
override def typeCheckFailureMessage: String = if (ansiEnabled) {
1764+
AnsiCast.typeCheckFailureMessage(child.dataType, dataType, SQLConf.ANSI_ENABLED.key, "false")
1765+
} else {
1766+
s"cannot cast ${child.dataType.catalogString} to ${dataType.catalogString}"
1767+
}
17581768
}
17591769

17601770
/**
@@ -1774,6 +1784,14 @@ case class AnsiCast(child: Expression, dataType: DataType, timeZoneId: Option[St
17741784
override protected val ansiEnabled: Boolean = true
17751785

17761786
override def canCast(from: DataType, to: DataType): Boolean = AnsiCast.canCast(from, to)
1787+
1788+
// For now, this expression is only used in table insertion.
1789+
// If there are more scenarios for this expression, we should update the error message on type
1790+
// check failure.
1791+
override def typeCheckFailureMessage: String =
1792+
AnsiCast.typeCheckFailureMessage(child.dataType, dataType,
1793+
SQLConf.STORE_ASSIGNMENT_POLICY.key, SQLConf.StoreAssignmentPolicy.LEGACY.toString)
1794+
17771795
}
17781796

17791797
object AnsiCast {
@@ -1876,6 +1894,35 @@ object AnsiCast {
18761894

18771895
case _ => false
18781896
}
1897+
1898+
def typeCheckFailureMessage(
1899+
from: DataType,
1900+
to: DataType,
1901+
fallbackConfKey: String,
1902+
fallbackConfValue: String): String =
1903+
(from, to) match {
1904+
case (_: NumericType, TimestampType) =>
1905+
// scalastyle:off line.size.limit
1906+
s"""
1907+
| cannot cast ${from.catalogString} to ${to.catalogString}.
1908+
| To convert values from ${from.catalogString} to ${to.catalogString}, you can use functions TIMESTAMP_SECONDS/TIMESTAMP_MILLIS/TIMESTAMP_MICROS instead.
1909+
|""".stripMargin
1910+
1911+
case (_: ArrayType, StringType) =>
1912+
s"""
1913+
| cannot cast ${from.catalogString} to ${to.catalogString} with ANSI mode on.
1914+
| If you have to cast ${from.catalogString} to ${to.catalogString}, you can use the function array_join or set $fallbackConfKey as $fallbackConfValue.
1915+
|""".stripMargin
1916+
1917+
case _ if Cast.canCast(from, to) =>
1918+
s"""
1919+
| cannot cast ${from.catalogString} to ${to.catalogString} with ANSI mode on.
1920+
| If you have to cast ${from.catalogString} to ${to.catalogString}, you can set $fallbackConfKey as $fallbackConfValue.
1921+
|""".stripMargin
1922+
1923+
case _ => s"cannot cast ${from.catalogString} to ${to.catalogString}"
1924+
// scalastyle:on line.size.limit
1925+
}
18791926
}
18801927

18811928
/**

sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuite.scala

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import scala.collection.parallel.immutable.ParVector
2525
import org.apache.spark.SparkFunSuite
2626
import org.apache.spark.sql.Row
2727
import org.apache.spark.sql.catalyst.InternalRow
28+
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.TypeCheckFailure
2829
import org.apache.spark.sql.catalyst.analysis.TypeCoercion.numericPrecedence
2930
import org.apache.spark.sql.catalyst.analysis.TypeCoercionSuite
3031
import org.apache.spark.sql.catalyst.expressions.aggregate.{CollectList, CollectSet}
@@ -841,12 +842,28 @@ abstract class AnsiCastSuiteBase extends CastSuiteBase {
841842
cast(Literal(134.12), DecimalType(3, 2)), "cannot be represented")
842843
}
843844

845+
protected def setConfigurationHint: String
846+
847+
private def verifyCastFailure(c: CastBase, optionalExpectedMsg: Option[String] = None): Unit = {
848+
val typeCheckResult = c.checkInputDataTypes()
849+
assert(typeCheckResult.isFailure)
850+
assert(typeCheckResult.isInstanceOf[TypeCheckFailure])
851+
val message = typeCheckResult.asInstanceOf[TypeCheckFailure].message
852+
853+
if (optionalExpectedMsg.isDefined) {
854+
assert(message.contains(optionalExpectedMsg.get))
855+
} else {
856+
assert(message.contains("with ANSI mode on"))
857+
assert(message.contains(setConfigurationHint))
858+
}
859+
}
860+
844861
test("ANSI mode: disallow type conversions between Numeric types and Timestamp type") {
845862
import DataTypeTestUtils.numericTypes
846863
checkInvalidCastFromNumericType(TimestampType)
847864
val timestampLiteral = Literal(1L, TimestampType)
848865
numericTypes.foreach { numericType =>
849-
assert(cast(timestampLiteral, numericType).checkInputDataTypes().isFailure)
866+
verifyCastFailure(cast(timestampLiteral, numericType))
850867
}
851868
}
852869

@@ -855,7 +872,7 @@ abstract class AnsiCastSuiteBase extends CastSuiteBase {
855872
checkInvalidCastFromNumericType(DateType)
856873
val dateLiteral = Literal(1, DateType)
857874
numericTypes.foreach { numericType =>
858-
assert(cast(dateLiteral, numericType).checkInputDataTypes().isFailure)
875+
verifyCastFailure(cast(dateLiteral, numericType))
859876
}
860877
}
861878

@@ -880,9 +897,9 @@ abstract class AnsiCastSuiteBase extends CastSuiteBase {
880897
}
881898

882899
test("ANSI mode: disallow casting complex types as String type") {
883-
assert(cast(Literal.create(Array(1, 2, 3, 4, 5)), StringType).checkInputDataTypes().isFailure)
884-
assert(cast(Literal.create(Map(1 -> "a")), StringType).checkInputDataTypes().isFailure)
885-
assert(cast(Literal.create((1, "a", 0.1)), StringType).checkInputDataTypes().isFailure)
900+
verifyCastFailure(cast(Literal.create(Array(1, 2, 3, 4, 5)), StringType))
901+
verifyCastFailure(cast(Literal.create(Map(1 -> "a")), StringType))
902+
verifyCastFailure(cast(Literal.create((1, "a", 0.1)), StringType))
886903
}
887904

888905
test("cast from invalid string to numeric should throw NumberFormatException") {
@@ -1489,6 +1506,9 @@ class CastSuiteWithAnsiModeOn extends AnsiCastSuiteBase {
14891506
case _ => Cast(Literal(v), targetType, timeZoneId)
14901507
}
14911508
}
1509+
1510+
override def setConfigurationHint: String =
1511+
s"set ${SQLConf.ANSI_ENABLED.key} as false"
14921512
}
14931513

14941514
/**
@@ -1511,6 +1531,10 @@ class AnsiCastSuiteWithAnsiModeOn extends AnsiCastSuiteBase {
15111531
case _ => AnsiCast(Literal(v), targetType, timeZoneId)
15121532
}
15131533
}
1534+
1535+
override def setConfigurationHint: String =
1536+
s"set ${SQLConf.STORE_ASSIGNMENT_POLICY.key} as" +
1537+
s" ${SQLConf.StoreAssignmentPolicy.LEGACY.toString}"
15141538
}
15151539

15161540
/**
@@ -1533,4 +1557,8 @@ class AnsiCastSuiteWithAnsiModeOff extends AnsiCastSuiteBase {
15331557
case _ => AnsiCast(Literal(v), targetType, timeZoneId)
15341558
}
15351559
}
1560+
1561+
override def setConfigurationHint: String =
1562+
s"set ${SQLConf.STORE_ASSIGNMENT_POLICY.key} as" +
1563+
s" ${SQLConf.StoreAssignmentPolicy.LEGACY.toString}"
15361564
}

0 commit comments

Comments
 (0)