You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by ge...@apache.org on 2021/11/23 06:11:48 UTC
[spark] branch master updated: [SPARK-37438][SQL] ANSI mode: Use store assignment rules for resolving function invocation
This is an automated email from the ASF dual-hosted git repository.
gengliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 5d3a657 [SPARK-37438][SQL] ANSI mode: Use store assignment rules for resolving function invocation
5d3a657 is described below
commit 5d3a6573a56f9c00ccc513c8131c037de7d29000
Author: Gengliang Wang <ge...@apache.org>
AuthorDate: Tue Nov 23 14:10:44 2021 +0800
[SPARK-37438][SQL] ANSI mode: Use store assignment rules for resolving function invocation
### What changes were proposed in this pull request?
Under ANSI mode(spark.sql.ansi.enabled=true), the function invocation of Spark SQL:
- In general, it follows the `Store assignment` rules as storing the input values as the declared parameter type of the SQL functions
- Special rules apply for string literals and untyped NULL. A NULL can be promoted to any other type, while a string literal can be promoted to any simple data type.
### Why are the changes needed?
Currently, the ANSI SQL mode resolves the function invocation with `Least Common Type Resolution` based on`Type precedence list`. After a closer look at the ANSI SQL standard, the "store assignment" syntax rules should be used for resolving the type coercion between the input and parameters of SQL function, while the `Type precedence list` is used for "Subject routine determination"(SQL function overloads).
![image](https://user-images.githubusercontent.com/1097932/142833343-25151c1b-ccda-407c-a831-6b6ec4cf642f.png)
I have also done some data science among real-world SQL queries, the following implicit function casts are not allowed as per `Least Common Type Resolution` but they are commonly seen:
- Numeric/Date/Timestamp => String, e.g. tableau generated query `CONCAT(DATE_ADD(%1, CAST(%2 AS INT)), SUBSTR(CAST(%1 AS TIMESTAMP), 11)) AS TIMESTAMP)`
- Timestamp => Date, e.g `date_sub(now(), 7) < ...`
- Double => Long, e.g. `from_unixtime(updated/1000)`, note that `updated` and `1000` will be converted as Double first.
The changes in this PR is ANSI compatible and it is good for the adoption of ANSI SQL mode.
### Does this PR introduce _any_ user-facing change?
Yes, Use store assignment rules for resolving function invocation under ANSI mode.
### How was this patch tested?
Unit tests
Closes #34681 from gengliangwang/functionImplicitCast.
Lead-authored-by: Gengliang Wang <ge...@apache.org>
Co-authored-by: Karen Feng <ka...@databricks.com>
Signed-off-by: Gengliang Wang <ge...@apache.org>
---
docs/sql-ref-ansi-compliance.md | 40 +-
.../sql/catalyst/analysis/AnsiTypeCoercion.scala | 83 +---
.../spark/sql/catalyst/analysis/TypeCoercion.scala | 4 +-
.../sql/catalyst/rules/RuleIdCollection.scala | 3 +-
.../catalyst/analysis/AnsiTypeCoercionSuite.scala | 387 +---------------
.../sql/catalyst/analysis/TypeCoercionSuite.scala | 502 +++++++++++----------
.../resources/sql-tests/results/ansi/date.sql.out | 25 +-
.../sql-tests/results/ansi/interval.sql.out | 4 +-
.../results/ansi/string-functions.sql.out | 54 +--
.../test/resources/sql-tests/results/date.sql.out | 10 +-
.../sql-tests/results/datetime-legacy.sql.out | 10 +-
.../sql-tests/results/postgreSQL/numeric.sql.out | 38 +-
.../sql-tests/results/postgreSQL/strings.sql.out | 20 +-
.../sql-tests/results/postgreSQL/text.sql.out | 60 +--
14 files changed, 433 insertions(+), 807 deletions(-)
diff --git a/docs/sql-ref-ansi-compliance.md b/docs/sql-ref-ansi-compliance.md
index b93f8b7..9ad7ad6 100644
--- a/docs/sql-ref-ansi-compliance.md
+++ b/docs/sql-ref-ansi-compliance.md
@@ -221,7 +221,6 @@ This is a graphical depiction of the precedence list as a directed tree:
The least common type from a set of types is the narrowest type reachable from the precedence list by all elements of the set of types.
The least common type resolution is used to:
-- Decide whether a function expecting a parameter of a type can be invoked using an argument of a narrower type.
- Derive the argument type for functions which expect a shared argument type for multiple parameters, such as coalesce, least, or greatest.
- Derive the operand types for operators such as arithmetic operations or comparisons.
- Derive the result type for expressions such as the case expression.
@@ -246,19 +245,40 @@ DOUBLE
> SELECT (typeof(coalesce(1BD, 1F)));
DOUBLE
--- The substring function expects arguments of type INT for the start and length parameters.
-> SELECT substring('hello', 1Y, 2);
-he
-> SELECT substring('hello', '1', 2);
-he
-> SELECT substring('hello', 1L, 2);
-Error: Argument 2 requires an INT type.
-> SELECT substring('hello', str, 2) FROM VALUES(CAST('1' AS STRING)) AS T(str);
-Error: Argument 2 requires an INT type.
```
### SQL Functions
+#### Function invocation
+Under ANSI mode(spark.sql.ansi.enabled=true), the function invocation of Spark SQL:
+- In general, it follows the `Store assignment` rules as storing the input values as the declared parameter type of the SQL functions
+- Special rules apply for string literals and untyped NULL. A NULL can be promoted to any other type, while a string literal can be promoted to any simple data type.
+```sql
+> SET spark.sql.ansi.enabled=true;
+-- implicitly cast Int to String type
+> SELECT concat('total number: ', 1);
+total number: 1
+-- implicitly cast Timestamp to Date type
+> select datediff(now(), current_date);
+0
+
+-- specialrule: implicitly cast String literal to Double type
+> SELECT ceil('0.1');
+1
+-- specialrule: implicitly cast NULL to Date type
+> SELECT year(null);
+NULL
+
+> CREATE TABLE t(s string);
+-- Can't store String column as Numeric types.
+> SELECT ceil(s) from t;
+Error in query: cannot resolve 'CEIL(spark_catalog.default.t.s)' due to data type mismatch
+-- Can't store String column as Date type.
+> select year(s) from t;
+Error in query: cannot resolve 'year(spark_catalog.default.t.s)' due to data type mismatch
+```
+
+#### Functions with different behaviors
The behavior of some SQL functions can be different under ANSI mode (`spark.sql.ansi.enabled=true`).
- `size`: This function returns null for null input.
- `element_at`:
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercion.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercion.scala
index e8bf2ae..debc13b 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercion.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercion.scala
@@ -159,6 +159,10 @@ object AnsiTypeCoercion extends TypeCoercionBase {
// If the expected type equals the input type, no need to cast.
case _ if expectedType.acceptsType(inType) => Some(inType)
+ // If input is a numeric type but not decimal, and we expect a decimal type,
+ // cast the input to decimal.
+ case (n: NumericType, DecimalType) => Some(DecimalType.forType(n))
+
// Cast null type (usually from null literals) into target types
// By default, the result type is `target.defaultConcreteType`. When the target type is
// `TypeCollection`, there is another branch to find the "closet convertible data type" below.
@@ -178,79 +182,17 @@ object AnsiTypeCoercion extends TypeCoercionBase {
case (StringType, DecimalType) if isInputFoldable =>
Some(DecimalType.SYSTEM_DEFAULT)
- // If input is a numeric type but not decimal, and we expect a decimal type,
- // cast the input to decimal.
- case (d: NumericType, DecimalType) => Some(DecimalType.forType(d))
-
- case (n1: NumericType, n2: NumericType) =>
- val widerType = findWiderTypeForTwo(n1, n2)
- widerType match {
- // if the expected type is Float type, we should still return Float type.
- case Some(DoubleType) if n1 != DoubleType && n2 == FloatType => Some(FloatType)
-
- case Some(dt) if dt == n2 => Some(dt)
-
- case _ => None
+ case (_, target: DataType) =>
+ if (Cast.canANSIStoreAssign(inType, target)) {
+ Some(target)
+ } else {
+ None
}
- case (DateType, TimestampType) => Some(TimestampType)
- case (DateType, AnyTimestampType) => Some(AnyTimestampType.defaultConcreteType)
-
// When we reach here, input type is not acceptable for any types in this type collection,
- // first try to find the all the expected types we can implicitly cast:
- // 1. if there is no convertible data types, return None;
- // 2. if there is only one convertible data type, cast input as it;
- // 3. otherwise if there are multiple convertible data types, find the closet convertible
- // data type among them. If there is no such a data type, return None.
+ // try to find the first one we can implicitly cast.
case (_, TypeCollection(types)) =>
- // Since Spark contains special objects like `NumericType` and `DecimalType`, which accepts
- // multiple types and they are `AbstractDataType` instead of `DataType`, here we use the
- // conversion result their representation.
- val convertibleTypes = types.flatMap(implicitCast(inType, _, isInputFoldable))
- if (convertibleTypes.isEmpty) {
- None
- } else {
- // find the closet convertible data type, which can be implicit cast to all other
- // convertible types.
- val closestConvertibleType = convertibleTypes.find { dt =>
- convertibleTypes.forall { target =>
- implicitCast(dt, target, isInputFoldable = false).isDefined
- }
- }
- // If the closet convertible type is Float type and the convertible types contains Double
- // type, simply return Double type as the closet convertible type to avoid potential
- // precision loss on converting the Integral type as Float type.
- if (closestConvertibleType.contains(FloatType) && convertibleTypes.contains(DoubleType)) {
- Some(DoubleType)
- } else {
- closestConvertibleType
- }
- }
-
- // Implicit cast between array types.
- //
- // Compare the nullabilities of the from type and the to type, check whether the cast of
- // the nullability is resolvable by the following rules:
- // 1. If the nullability of the to type is true, the cast is always allowed;
- // 2. If the nullabilities of both the from type and the to type are false, the cast is
- // allowed.
- // 3. Otherwise, the cast is not allowed
- case (ArrayType(fromType, containsNullFrom), ArrayType(toType: DataType, containsNullTo))
- if Cast.resolvableNullability(containsNullFrom, containsNullTo) =>
- implicitCast(fromType, toType, isInputFoldable).map(ArrayType(_, containsNullTo))
-
- // Implicit cast between Map types.
- // Follows the same semantics of implicit casting between two array types.
- // Refer to documentation above.
- case (MapType(fromKeyType, fromValueType, fn), MapType(toKeyType, toValueType, tn))
- if Cast.resolvableNullability(fn, tn) =>
- val newKeyType = implicitCast(fromKeyType, toKeyType, isInputFoldable)
- val newValueType = implicitCast(fromValueType, toValueType, isInputFoldable)
- if (newKeyType.isDefined && newValueType.isDefined) {
- Some(MapType(newKeyType.get, newValueType.get, tn))
- } else {
- None
- }
+ types.flatMap(implicitCast(inType, _, isInputFoldable)).headOption
case _ => None
}
@@ -348,6 +290,9 @@ object AnsiTypeCoercion extends TypeCoercionBase {
// Skip nodes who's children have not been resolved yet.
case e if !e.childrenResolved => e
+ case d @ DateAdd(AnyTimestampType(), _) => d.copy(startDate = Cast(d.startDate, DateType))
+ case d @ DateSub(AnyTimestampType(), _) => d.copy(startDate = Cast(d.startDate, DateType))
+
case s @ SubtractTimestamps(DateType(), AnyTimestampType(), _, _) =>
s.copy(left = Cast(s.left, s.right.dataType))
case s @ SubtractTimestamps(AnyTimestampType(), DateType(), _, _) =>
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercion.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercion.scala
index 90cbe56..5066674 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercion.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercion.scala
@@ -1157,9 +1157,9 @@ object TypeCoercion extends TypeCoercionBase {
override val transform: PartialFunction[Expression, Expression] = {
// Skip nodes who's children have not been resolved yet.
case e if !e.childrenResolved => e
- case d @ DateAdd(TimestampType(), _) => d.copy(startDate = Cast(d.startDate, DateType))
+ case d @ DateAdd(AnyTimestampType(), _) => d.copy(startDate = Cast(d.startDate, DateType))
case d @ DateAdd(StringType(), _) => d.copy(startDate = Cast(d.startDate, DateType))
- case d @ DateSub(TimestampType(), _) => d.copy(startDate = Cast(d.startDate, DateType))
+ case d @ DateSub(AnyTimestampType(), _) => d.copy(startDate = Cast(d.startDate, DateType))
case d @ DateSub(StringType(), _) => d.copy(startDate = Cast(d.startDate, DateType))
case s @ SubtractTimestamps(DateType(), AnyTimestampType(), _, _) =>
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/rules/RuleIdCollection.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/rules/RuleIdCollection.scala
index 9792545..5ec303d 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/rules/RuleIdCollection.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/rules/RuleIdCollection.scala
@@ -162,12 +162,13 @@ object RuleIdCollection {
// In the production code path, the following rules are run in CombinedTypeCoercionRule, and
// hence we only need to add them for unit testing.
"org.apache.spark.sql.catalyst.analysis.AnsiTypeCoercion$PromoteStringLiterals" ::
+ "org.apache.spark.sql.catalyst.analysis.AnsiTypeCoercion$DateTimeOperations" ::
"org.apache.spark.sql.catalyst.analysis.AnsiTypeCoercion$GetDateFieldOperations" ::
"org.apache.spark.sql.catalyst.analysis.DecimalPrecision" ::
"org.apache.spark.sql.catalyst.analysis.TypeCoercion$BooleanEquality" ::
+ "org.apache.spark.sql.catalyst.analysis.TypeCoercion$DateTimeOperations" ::
"org.apache.spark.sql.catalyst.analysis.TypeCoercionBase$CaseWhenCoercion" ::
"org.apache.spark.sql.catalyst.analysis.TypeCoercionBase$ConcatCoercion" ::
- "org.apache.spark.sql.catalyst.analysis.TypeCoercionBase$DateTimeOperations" ::
"org.apache.spark.sql.catalyst.analysis.TypeCoercionBase$Division" ::
"org.apache.spark.sql.catalyst.analysis.TypeCoercionBase$EltCoercion" ::
"org.apache.spark.sql.catalyst.analysis.TypeCoercionBase$FunctionArgumentConversion" ::
diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercionSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercionSuite.scala
index ab8d9d9..809cbb2 100644
--- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercionSuite.scala
+++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnsiTypeCoercionSuite.scala
@@ -19,42 +19,34 @@ package org.apache.spark.sql.catalyst.analysis
import java.sql.Timestamp
-import org.apache.spark.internal.config.Tests.IS_TESTING
import org.apache.spark.sql.catalyst.analysis.AnsiTypeCoercion._
import org.apache.spark.sql.catalyst.dsl.expressions._
import org.apache.spark.sql.catalyst.dsl.plans._
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.catalyst.plans.logical._
-import org.apache.spark.sql.catalyst.rules.{Rule, RuleExecutor}
import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.types._
-import org.apache.spark.util.Utils
-class AnsiTypeCoercionSuite extends AnalysisTest {
+class AnsiTypeCoercionSuite extends TypeCoercionSuiteBase {
import TypeCoercionSuite._
- // When Utils.isTesting is true, RuleIdCollection adds individual type coercion rules. Otherwise,
- // RuleIdCollection doesn't add them because they are called in a train inside
- // CombinedTypeCoercionRule.
- assert(Utils.isTesting, s"${IS_TESTING.key} is not set to true")
-
// scalastyle:off line.size.limit
// The following table shows all implicit data type conversions that are not visible to the user.
// +----------------------+----------+-----------+-------------+----------+------------+------------+------------+------------+-------------+------------+----------+---------------+------------+----------+-------------+----------+----------------------+---------------------+-------------+--------------+
// | Source Type\CAST TO | ByteType | ShortType | IntegerType | LongType | FloatType | DoubleType | Dec(10, 2) | BinaryType | BooleanType | StringType | DateType | TimestampType | ArrayType | MapType | StructType | NullType | CalendarIntervalType | DecimalType | NumericType | IntegralType |
// +----------------------+----------+-----------+-------------+----------+------------+------------+------------+------------+-------------+------------+----------+---------------+------------+----------+-------------+----------+----------------------+---------------------+-------------+--------------+
- // | ByteType | ByteType | ShortType | IntegerType | LongType | DoubleType | DoubleType | Dec(10, 2) | X | X | X | X | X | X | X | X | X | X | DecimalType(3, 0) | ByteType | ByteType |
- // | ShortType | X | ShortType | IntegerType | LongType | DoubleType | DoubleType | Dec(10, 2) | X | X | X | X | X | X | X | X | X | X | DecimalType(5, 0) | ShortType | ShortType |
- // | IntegerType | X | X | IntegerType | LongType | DoubleType | DoubleType | X | X | X | X | X | X | X | X | X | X | X | DecimalType(10, 0) | IntegerType | IntegerType |
- // | LongType | X | X | X | LongType | DoubleType | DoubleType | X | X | X | X | X | X | X | X | X | X | X | DecimalType(20, 0) | LongType | LongType |
- // | FloatType | X | X | X | X | FloatType | DoubleType | X | X | X | X | X | X | X | X | X | X | X | DecimalType(30, 15) | DoubleType | X |
- // | DoubleType | X | X | X | X | X | DoubleType | X | X | X | X | X | X | X | X | X | X | X | DecimalType(14, 7) | FloatType | X |
- // | Dec(10, 2) | X | X | X | X | DoubleType | DoubleType | Dec(10, 2) | X | X | X | X | X | X | X | X | X | X | DecimalType(10, 2) | Dec(10, 2) | X |
- // | BinaryType | X | X | X | X | X | X | X | BinaryType | X | X | X | X | X | X | X | X | X | X | X | X |
- // | BooleanType | X | X | X | X | X | X | X | X | BooleanType | X | X | X | X | X | X | X | X | X | X | X |
- // | StringType | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
- // | DateType | X | X | X | X | X | X | X | X | X | X | DateType | TimestampType | X | X | X | X | X | X | X | X |
- // | TimestampType | X | X | X | X | X | X | X | X | X | X | X | TimestampType | X | X | X | X | X | X | X | X |
+ // | ByteType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(3, 0) | ByteType | ByteType |
+ // | ShortType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(5, 0) | ShortType | ShortType |
+ // | IntegerType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(10, 0) | IntegerType | IntegerType |
+ // | LongType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(20, 0) | LongType | LongType |
+ // | DoubleType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(30, 15) | DoubleType | IntegerType |
+ // | FloatType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(14, 7) | FloatType | IntegerType |
+ // | Dec(10, 2) | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(10, 2) | Dec(10, 2) | IntegerType |
+ // | BinaryType | X | X | X | X | X | X | X | BinaryType | X | StringType | X | X | X | X | X | X | X | X | X | X |
+ // | BooleanType | X | X | X | X | X | X | X | X | BooleanType | StringType | X | X | X | X | X | X | X | X | X | X |
+ // | StringType | X | X | X | X | X | X | X | X | X | StringType | X | X | X | X | X | X | X | X | X | X |
+ // | DateType | X | X | X | X | X | X | X | X | X | StringType | DateType | TimestampType | X | X | X | X | X | X | X | X |
+ // | TimestampType | X | X | X | X | X | X | X | X | X | StringType | DateType | TimestampType | X | X | X | X | X | X | X | X |
// | ArrayType | X | X | X | X | X | X | X | X | X | X | X | X | ArrayType* | X | X | X | X | X | X | X |
// | MapType | X | X | X | X | X | X | X | X | X | X | X | X | X | MapType* | X | X | X | X | X | X |
// | StructType | X | X | X | X | X | X | X | X | X | X | X | X | X | X | StructType* | X | X | X | X | X |
@@ -65,30 +57,10 @@ class AnsiTypeCoercionSuite extends AnalysisTest {
// Note: ArrayType* is castable when the element type is castable according to the table.
// Note: MapType* is castable when both the key type and the value type are castable according to the table.
// scalastyle:on line.size.limit
+ override def implicitCast(e: Expression, expectedType: AbstractDataType): Option[Expression] =
+ AnsiTypeCoercion.implicitCast(e, expectedType)
- private def shouldCast(from: DataType, to: AbstractDataType, expected: DataType): Unit = {
- // Check default value
- val castDefault = AnsiTypeCoercion.implicitCast(default(from), to)
- assert(DataType.equalsIgnoreCompatibleNullability(
- castDefault.map(_.dataType).getOrElse(null), expected),
- s"Failed to cast $from to $to")
-
- // Check null value
- val castNull = AnsiTypeCoercion.implicitCast(createNull(from), to)
- assert(DataType.equalsIgnoreCaseAndNullability(
- castNull.map(_.dataType).getOrElse(null), expected),
- s"Failed to cast $from to $to")
- }
-
- private def shouldNotCast(from: DataType, to: AbstractDataType): Unit = {
- // Check default value
- val castDefault = AnsiTypeCoercion.implicitCast(default(from), to)
- assert(castDefault.isEmpty, s"Should not be able to cast $from to $to, but got $castDefault")
-
- // Check null value
- val castNull = AnsiTypeCoercion.implicitCast(createNull(from), to)
- assert(castNull.isEmpty, s"Should not be able to cast $from to $to, but got $castNull")
- }
+ override def dateTimeOperationsRule: TypeCoercionRule = AnsiTypeCoercion.DateTimeOperations
private def shouldCastStringLiteral(to: AbstractDataType, expected: DataType): Unit = {
val input = Literal("123")
@@ -110,35 +82,6 @@ class AnsiTypeCoercionSuite extends AnalysisTest {
assert(castResult.isEmpty, s"Should not be able to cast non-foldable String input to $to")
}
- private def default(dataType: DataType): Expression = dataType match {
- case ArrayType(internalType: DataType, _) =>
- CreateArray(Seq(Literal.default(internalType)))
- case MapType(keyDataType: DataType, valueDataType: DataType, _) =>
- CreateMap(Seq(Literal.default(keyDataType), Literal.default(valueDataType)))
- case _ => Literal.default(dataType)
- }
-
- private def createNull(dataType: DataType): Expression = dataType match {
- case ArrayType(internalType: DataType, _) =>
- CreateArray(Seq(Literal.create(null, internalType)))
- case MapType(keyDataType: DataType, valueDataType: DataType, _) =>
- CreateMap(Seq(Literal.create(null, keyDataType), Literal.create(null, valueDataType)))
- case _ => Literal.create(null, dataType)
- }
-
- // Check whether the type `checkedType` can be cast to all the types in `castableTypes`,
- // but cannot be cast to the other types in `allTypes`.
- private def checkTypeCasting(checkedType: DataType, castableTypes: Seq[DataType]): Unit = {
- val nonCastableTypes = allTypes.filterNot(castableTypes.contains)
-
- castableTypes.foreach { tpe =>
- shouldCast(checkedType, tpe, tpe)
- }
- nonCastableTypes.foreach { tpe =>
- shouldNotCast(checkedType, tpe)
- }
- }
-
private def checkWidenType(
widenFunc: (DataType, DataType) => Option[DataType],
t1: DataType,
@@ -156,81 +99,6 @@ class AnsiTypeCoercionSuite extends AnalysisTest {
}
}
- test("implicit type cast - ByteType") {
- val checkedType = ByteType
- checkTypeCasting(checkedType, castableTypes = numericTypes)
- shouldCast(checkedType, DecimalType, DecimalType.ByteDecimal)
- shouldCast(checkedType, NumericType, checkedType)
- shouldCast(checkedType, IntegralType, checkedType)
- }
-
- test("implicit type cast - ShortType") {
- val checkedType = ShortType
- checkTypeCasting(checkedType, castableTypes = numericTypes.filterNot(_ == ByteType))
- shouldCast(checkedType, DecimalType, DecimalType.ShortDecimal)
- shouldCast(checkedType, NumericType, checkedType)
- shouldCast(checkedType, IntegralType, checkedType)
- }
-
- test("implicit type cast - IntegerType") {
- val checkedType = IntegerType
- checkTypeCasting(checkedType, castableTypes =
- Seq(IntegerType, LongType, FloatType, DoubleType, DecimalType.SYSTEM_DEFAULT))
- shouldCast(IntegerType, DecimalType, DecimalType.IntDecimal)
- shouldCast(checkedType, NumericType, checkedType)
- shouldCast(checkedType, IntegralType, checkedType)
- }
-
- test("implicit type cast - LongType") {
- val checkedType = LongType
- checkTypeCasting(checkedType, castableTypes =
- Seq(LongType, FloatType, DoubleType, DecimalType.SYSTEM_DEFAULT))
- shouldCast(checkedType, DecimalType, DecimalType.LongDecimal)
- shouldCast(checkedType, NumericType, checkedType)
- shouldCast(checkedType, IntegralType, checkedType)
- }
-
- test("implicit type cast - FloatType") {
- val checkedType = FloatType
- checkTypeCasting(checkedType, castableTypes = Seq(FloatType, DoubleType))
- shouldCast(checkedType, DecimalType, DecimalType.FloatDecimal)
- shouldCast(checkedType, NumericType, checkedType)
- shouldNotCast(checkedType, IntegralType)
- }
-
- test("implicit type cast - DoubleType") {
- val checkedType = DoubleType
- checkTypeCasting(checkedType, castableTypes = Seq(DoubleType))
- shouldCast(checkedType, DecimalType, DecimalType.DoubleDecimal)
- shouldCast(checkedType, NumericType, checkedType)
- shouldNotCast(checkedType, IntegralType)
- }
-
- test("implicit type cast - DecimalType(10, 2)") {
- val checkedType = DecimalType(10, 2)
- checkTypeCasting(checkedType, castableTypes = fractionalTypes)
- shouldCast(checkedType, DecimalType, checkedType)
- shouldCast(checkedType, NumericType, checkedType)
- shouldNotCast(checkedType, IntegralType)
- }
-
- test("implicit type cast - BinaryType") {
- val checkedType = BinaryType
- checkTypeCasting(checkedType, castableTypes = Seq(checkedType))
- shouldNotCast(checkedType, DecimalType)
- shouldNotCast(checkedType, NumericType)
- shouldNotCast(checkedType, IntegralType)
- }
-
- test("implicit type cast - BooleanType") {
- val checkedType = BooleanType
- checkTypeCasting(checkedType, castableTypes = Seq(checkedType))
- shouldNotCast(checkedType, DecimalType)
- shouldNotCast(checkedType, NumericType)
- shouldNotCast(checkedType, IntegralType)
- shouldNotCast(checkedType, StringType)
- }
-
test("implicit type cast - unfoldable StringType") {
val nonCastableTypes = allTypes.filterNot(_ == StringType)
nonCastableTypes.foreach { dt =>
@@ -251,23 +119,6 @@ class AnsiTypeCoercionSuite extends AnalysisTest {
shouldCastStringLiteral(NumericType, DoubleType)
}
- test("implicit type cast - DateType") {
- val checkedType = DateType
- checkTypeCasting(checkedType, castableTypes = Seq(checkedType, TimestampType))
- shouldNotCast(checkedType, DecimalType)
- shouldNotCast(checkedType, NumericType)
- shouldNotCast(checkedType, IntegralType)
- shouldNotCast(checkedType, StringType)
- }
-
- test("implicit type cast - TimestampType") {
- val checkedType = TimestampType
- checkTypeCasting(checkedType, castableTypes = Seq(checkedType))
- shouldNotCast(checkedType, DecimalType)
- shouldNotCast(checkedType, NumericType)
- shouldNotCast(checkedType, IntegralType)
- }
-
test("implicit type cast - unfoldable ArrayType(StringType)") {
val input = AttributeReference("a", ArrayType(StringType))()
val nonCastableTypes = allTypes.filterNot(_ == StringType)
@@ -278,55 +129,6 @@ class AnsiTypeCoercionSuite extends AnalysisTest {
assert(AnsiTypeCoercion.implicitCast(input, NumericType).isEmpty)
}
- test("implicit type cast - foldable arrayType(StringType)") {
- val input = Literal(Array("1"))
- assert(AnsiTypeCoercion.implicitCast(input, ArrayType(StringType)) == Some(input))
- (numericTypes ++ datetimeTypes ++ Seq(BinaryType)).foreach { dt =>
- assert(AnsiTypeCoercion.implicitCast(input, ArrayType(dt)) ==
- Some(Cast(input, ArrayType(dt))))
- }
- }
-
- test("implicit type cast between two Map types") {
- val sourceType = MapType(IntegerType, IntegerType, true)
- val castableTypes =
- Seq(IntegerType, LongType, FloatType, DoubleType, DecimalType.SYSTEM_DEFAULT)
- val targetTypes = castableTypes.map { t =>
- MapType(t, sourceType.valueType, valueContainsNull = true)
- }
- val nonCastableTargetTypes = allTypes.filterNot(castableTypes.contains(_)).map {t =>
- MapType(t, sourceType.valueType, valueContainsNull = true)
- }
-
- // Tests that its possible to setup implicit casts between two map types when
- // source map's key type is integer and the target map's key type are either Byte, Short,
- // Long, Double, Float, Decimal(38, 18) or String.
- targetTypes.foreach { targetType =>
- shouldCast(sourceType, targetType, targetType)
- }
-
- // Tests that its not possible to setup implicit casts between two map types when
- // source map's key type is integer and the target map's key type are either Binary,
- // Boolean, Date, Timestamp, Array, Struct, CalendarIntervalType or NullType
- nonCastableTargetTypes.foreach { targetType =>
- shouldNotCast(sourceType, targetType)
- }
-
- // Tests that its not possible to cast from nullable map type to not nullable map type.
- val targetNotNullableTypes = allTypes.filterNot(_ == IntegerType).map { t =>
- MapType(t, sourceType.valueType, valueContainsNull = false)
- }
- val sourceMapExprWithValueNull =
- CreateMap(Seq(Literal.default(sourceType.keyType),
- Literal.create(null, sourceType.valueType)))
- targetNotNullableTypes.foreach { targetType =>
- val castDefault =
- AnsiTypeCoercion.implicitCast(sourceMapExprWithValueNull, targetType)
- assert(castDefault.isEmpty,
- s"Should not be able to cast $sourceType to $targetType, but got $castDefault")
- }
- }
-
test("implicit type cast - StructType().add(\"a1\", StringType)") {
val checkedType = new StructType().add("a1", StringType)
checkTypeCasting(checkedType, castableTypes = Seq(checkedType))
@@ -345,64 +147,12 @@ class AnsiTypeCoercionSuite extends AnalysisTest {
test("implicit type cast - CalendarIntervalType") {
val checkedType = CalendarIntervalType
- checkTypeCasting(checkedType, castableTypes = Seq(checkedType))
+ checkTypeCasting(checkedType, castableTypes = Seq(checkedType, StringType))
shouldNotCast(checkedType, DecimalType)
shouldNotCast(checkedType, NumericType)
shouldNotCast(checkedType, IntegralType)
}
- test("eligible implicit type cast - TypeCollection") {
- shouldCast(StringType, TypeCollection(StringType, BinaryType), StringType)
- shouldCast(BinaryType, TypeCollection(StringType, BinaryType), BinaryType)
- shouldCast(StringType, TypeCollection(BinaryType, StringType), StringType)
-
- shouldCast(IntegerType, TypeCollection(IntegerType, BinaryType), IntegerType)
- shouldCast(IntegerType, TypeCollection(BinaryType, IntegerType), IntegerType)
- shouldCast(BinaryType, TypeCollection(BinaryType, IntegerType), BinaryType)
- shouldCast(BinaryType, TypeCollection(IntegerType, BinaryType), BinaryType)
-
- shouldCast(DecimalType.SYSTEM_DEFAULT,
- TypeCollection(IntegerType, DecimalType), DecimalType.SYSTEM_DEFAULT)
- shouldCast(DecimalType(10, 2), TypeCollection(IntegerType, DecimalType), DecimalType(10, 2))
- shouldCast(DecimalType(10, 2), TypeCollection(DecimalType, IntegerType), DecimalType(10, 2))
-
- shouldCast(
- ArrayType(StringType, false),
- TypeCollection(ArrayType(StringType), StringType),
- ArrayType(StringType, false))
-
- shouldCast(
- ArrayType(StringType, true),
- TypeCollection(ArrayType(StringType), StringType),
- ArrayType(StringType, true))
-
- // When there are multiple convertible types in the `TypeCollection`, use the closest
- // convertible data type among convertible types.
- shouldCast(IntegerType, TypeCollection(BinaryType, FloatType, LongType), LongType)
- shouldCast(ShortType, TypeCollection(BinaryType, LongType, IntegerType), IntegerType)
- shouldCast(ShortType, TypeCollection(DateType, LongType, IntegerType, DoubleType), IntegerType)
- // If the result is Float type and Double type is also among the convertible target types,
- // use Double Type instead of Float type.
- shouldCast(LongType, TypeCollection(FloatType, DoubleType, StringType), DoubleType)
- }
-
- test("ineligible implicit type cast - TypeCollection") {
- shouldNotCast(IntegerType, TypeCollection(StringType, BinaryType))
- shouldNotCast(IntegerType, TypeCollection(BinaryType, StringType))
- shouldNotCast(IntegerType, TypeCollection(DateType, TimestampType))
- shouldNotCast(IntegerType, TypeCollection(DecimalType(10, 2), StringType))
- shouldNotCastStringInput(TypeCollection(NumericType, BinaryType))
- // When there are multiple convertible types in the `TypeCollection` and there is no such
- // a data type that can be implicit cast to all the other convertible types in the collection.
- Seq(TypeCollection(NumericType, BinaryType),
- TypeCollection(NumericType, DecimalType, BinaryType),
- TypeCollection(IntegerType, LongType, BooleanType),
- TypeCollection(DateType, TimestampType, BooleanType)).foreach { typeCollection =>
- shouldNotCastStringLiteral(typeCollection)
- shouldNotCast(NullType, typeCollection)
- }
- }
-
test("tightest common bound for types") {
def widenTest(t1: DataType, t2: DataType, expected: Option[DataType]): Unit =
checkWidenType(AnsiTypeCoercion.findTightestCommonType, t1, t2, expected)
@@ -606,25 +356,6 @@ class AnsiTypeCoercionSuite extends AnalysisTest {
None)
}
- private def ruleTest(rule: Rule[LogicalPlan],
- initial: Expression, transformed: Expression): Unit = {
- ruleTest(Seq(rule), initial, transformed)
- }
-
- private def ruleTest(
- rules: Seq[Rule[LogicalPlan]],
- initial: Expression,
- transformed: Expression): Unit = {
- val testRelation = LocalRelation(AttributeReference("a", IntegerType)())
- val analyzer = new RuleExecutor[LogicalPlan] {
- override val batches = Seq(Batch("Resolution", FixedPoint(3), rules: _*))
- }
-
- comparePlans(
- analyzer.execute(Project(Seq(Alias(initial, "a")()), testRelation)),
- Project(Seq(Alias(transformed, "a")()), testRelation))
- }
-
test("cast NullType for expressions that implement ExpectsInputTypes") {
ruleTest(AnsiTypeCoercion.ImplicitTypeCasts,
AnyTypeUnaryExpression(Literal.create(null, NullType)),
@@ -1000,90 +731,6 @@ class AnsiTypeCoercionSuite extends AnalysisTest {
Literal.create(null, IntegerType), Literal.create(null, StringType))))
}
- test("type coercion for Concat") {
- val rule = AnsiTypeCoercion.ConcatCoercion
-
- ruleTest(rule,
- Concat(Seq(Literal("ab"), Literal("cde"))),
- Concat(Seq(Literal("ab"), Literal("cde"))))
- ruleTest(rule,
- Concat(Seq(Literal(null), Literal("abc"))),
- Concat(Seq(Cast(Literal(null), StringType), Literal("abc"))))
- ruleTest(rule,
- Concat(Seq(Literal(1), Literal("234"))),
- Concat(Seq(Literal(1), Literal("234"))))
- ruleTest(rule,
- Concat(Seq(Literal("1"), Literal("234".getBytes()))),
- Concat(Seq(Literal("1"), Literal("234".getBytes()))))
- ruleTest(rule,
- Concat(Seq(Literal(1L), Literal(2.toByte), Literal(0.1))),
- Concat(Seq(Literal(1L), Literal(2.toByte), Literal(0.1))))
- ruleTest(rule,
- Concat(Seq(Literal(true), Literal(0.1f), Literal(3.toShort))),
- Concat(Seq(Literal(true), Literal(0.1f), Literal(3.toShort))))
- ruleTest(rule,
- Concat(Seq(Literal(1L), Literal(0.1))),
- Concat(Seq(Literal(1L), Literal(0.1))))
- ruleTest(rule,
- Concat(Seq(Literal(Decimal(10)))),
- Concat(Seq(Literal(Decimal(10)))))
- ruleTest(rule,
- Concat(Seq(Literal(BigDecimal.valueOf(10)))),
- Concat(Seq(Literal(BigDecimal.valueOf(10)))))
- ruleTest(rule,
- Concat(Seq(Literal(java.math.BigDecimal.valueOf(10)))),
- Concat(Seq(Literal(java.math.BigDecimal.valueOf(10)))))
- ruleTest(rule,
- Concat(Seq(Literal(new java.sql.Date(0)), Literal(new Timestamp(0)))),
- Concat(Seq(Literal(new java.sql.Date(0)), Literal(new Timestamp(0)))))
-
- ruleTest(rule,
- Concat(Seq(Literal("123".getBytes), Literal("456".getBytes))),
- Concat(Seq(Literal("123".getBytes), Literal("456".getBytes))))
- }
-
- test("type coercion for Elt") {
- val rule = AnsiTypeCoercion.EltCoercion
-
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal("ab"), Literal("cde"))),
- Elt(Seq(Literal(1), Literal("ab"), Literal("cde"))))
- ruleTest(rule,
- Elt(Seq(Literal(1.toShort), Literal("ab"), Literal("cde"))),
- Elt(Seq(Cast(Literal(1.toShort), IntegerType), Literal("ab"), Literal("cde"))))
- ruleTest(rule,
- Elt(Seq(Literal(2), Literal(null), Literal("abc"))),
- Elt(Seq(Literal(2), Cast(Literal(null), StringType), Literal("abc"))))
- ruleTest(rule,
- Elt(Seq(Literal(2), Literal(1), Literal("234"))),
- Elt(Seq(Literal(2), Literal(1), Literal("234"))))
- ruleTest(rule,
- Elt(Seq(Literal(3), Literal(1L), Literal(2.toByte), Literal(0.1))),
- Elt(Seq(Literal(3), Literal(1L), Literal(2.toByte), Literal(0.1))))
- ruleTest(rule,
- Elt(Seq(Literal(2), Literal(true), Literal(0.1f), Literal(3.toShort))),
- Elt(Seq(Literal(2), Literal(true), Literal(0.1f), Literal(3.toShort))))
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal(1L), Literal(0.1))),
- Elt(Seq(Literal(1), Literal(1L), Literal(0.1))))
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal(Decimal(10)))),
- Elt(Seq(Literal(1), Literal(Decimal(10)))))
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal(BigDecimal.valueOf(10)))),
- Elt(Seq(Literal(1), Literal(BigDecimal.valueOf(10)))))
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal(java.math.BigDecimal.valueOf(10)))),
- Elt(Seq(Literal(1), Literal(java.math.BigDecimal.valueOf(10)))))
- ruleTest(rule,
- Elt(Seq(Literal(2), Literal(new java.sql.Date(0)), Literal(new Timestamp(0)))),
- Elt(Seq(Literal(2), Literal(new java.sql.Date(0)), Literal(new Timestamp(0)))))
-
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal("123".getBytes), Literal("456".getBytes))),
- Elt(Seq(Literal(1), Literal("123".getBytes), Literal("456".getBytes))))
- }
-
private def checkOutput(logical: LogicalPlan, expectTypes: Seq[DataType]): Unit = {
logical.output.zip(expectTypes).foreach { case (attr, dt) =>
assert(attr.dataType === dt)
diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala
index 2dc669b..8de84b3 100644
--- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala
+++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala
@@ -18,7 +18,7 @@
package org.apache.spark.sql.catalyst.analysis
import java.sql.Timestamp
-import java.time.{Duration, Period}
+import java.time.{Duration, LocalDateTime, Period}
import org.apache.spark.internal.config.Tests.IS_TESTING
import org.apache.spark.sql.catalyst.analysis.TypeCoercion._
@@ -31,7 +31,7 @@ import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.types._
import org.apache.spark.util.Utils
-class TypeCoercionSuite extends AnalysisTest {
+abstract class TypeCoercionSuiteBase extends AnalysisTest {
import TypeCoercionSuite._
// When Utils.isTesting is true, RuleIdCollection adds individual type coercion rules. Otherwise,
@@ -39,59 +39,35 @@ class TypeCoercionSuite extends AnalysisTest {
// CombinedTypeCoercionRule.
assert(Utils.isTesting, s"${IS_TESTING.key} is not set to true")
- // scalastyle:off line.size.limit
- // The following table shows all implicit data type conversions that are not visible to the user.
- // +----------------------+----------+-----------+-------------+----------+------------+-----------+------------+------------+-------------+------------+----------+---------------+------------+----------+-------------+----------+----------------------+---------------------+-------------+--------------+
- // | Source Type\CAST TO | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | BinaryType | BooleanType | StringType | DateType | TimestampType | ArrayType | MapType | StructType | NullType | CalendarIntervalType | DecimalType | NumericType | IntegralType |
- // +----------------------+----------+-----------+-------------+----------+------------+-----------+------------+------------+-------------+------------+----------+---------------+------------+----------+-------------+----------+----------------------+---------------------+-------------+--------------+
- // | ByteType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(3, 0) | ByteType | ByteType |
- // | ShortType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(5, 0) | ShortType | ShortType |
- // | IntegerType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(10, 0) | IntegerType | IntegerType |
- // | LongType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(20, 0) | LongType | LongType |
- // | DoubleType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(30, 15) | DoubleType | IntegerType |
- // | FloatType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(14, 7) | FloatType | IntegerType |
- // | Dec(10, 2) | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(10, 2) | Dec(10, 2) | IntegerType |
- // | BinaryType | X | X | X | X | X | X | X | BinaryType | X | StringType | X | X | X | X | X | X | X | X | X | X |
- // | BooleanType | X | X | X | X | X | X | X | X | BooleanType | StringType | X | X | X | X | X | X | X | X | X | X |
- // | StringType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | BinaryType | X | StringType | DateType | TimestampType | X | X | X | X | X | DecimalType(38, 18) | DoubleType | X |
- // | DateType | X | X | X | X | X | X | X | X | X | StringType | DateType | TimestampType | X | X | X | X | X | X | X | X |
- // | TimestampType | X | X | X | X | X | X | X | X | X | StringType | DateType | TimestampType | X | X | X | X | X | X | X | X |
- // | ArrayType | X | X | X | X | X | X | X | X | X | X | X | X | ArrayType* | X | X | X | X | X | X | X |
- // | MapType | X | X | X | X | X | X | X | X | X | X | X | X | X | MapType* | X | X | X | X | X | X |
- // | StructType | X | X | X | X | X | X | X | X | X | X | X | X | X | X | StructType* | X | X | X | X | X |
- // | NullType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | BinaryType | BooleanType | StringType | DateType | TimestampType | ArrayType | MapType | StructType | NullType | CalendarIntervalType | DecimalType(38, 18) | DoubleType | IntegerType |
- // | CalendarIntervalType | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | CalendarIntervalType | X | X | X |
- // +----------------------+----------+-----------+-------------+----------+------------+-----------+------------+------------+-------------+------------+----------+---------------+------------+----------+-------------+----------+----------------------+---------------------+-------------+--------------+
- // Note: StructType* is castable when all the internal child types are castable according to the table.
- // Note: ArrayType* is castable when the element type is castable according to the table.
- // Note: MapType* is castable when both the key type and the value type are castable according to the table.
- // scalastyle:on line.size.limit
+ protected def implicitCast(e: Expression, expectedType: AbstractDataType): Option[Expression]
+
+ protected def dateTimeOperationsRule: TypeCoercionRule
- private def shouldCast(from: DataType, to: AbstractDataType, expected: DataType): Unit = {
+ protected def shouldCast(from: DataType, to: AbstractDataType, expected: DataType): Unit = {
// Check default value
- val castDefault = TypeCoercion.implicitCast(default(from), to)
+ val castDefault = implicitCast(default(from), to)
assert(DataType.equalsIgnoreCompatibleNullability(
castDefault.map(_.dataType).getOrElse(null), expected),
s"Failed to cast $from to $to")
// Check null value
- val castNull = TypeCoercion.implicitCast(createNull(from), to)
+ val castNull = implicitCast(createNull(from), to)
assert(DataType.equalsIgnoreCaseAndNullability(
castNull.map(_.dataType).getOrElse(null), expected),
s"Failed to cast $from to $to")
}
- private def shouldNotCast(from: DataType, to: AbstractDataType): Unit = {
+ protected def shouldNotCast(from: DataType, to: AbstractDataType): Unit = {
// Check default value
- val castDefault = TypeCoercion.implicitCast(default(from), to)
+ val castDefault = implicitCast(default(from), to)
assert(castDefault.isEmpty, s"Should not be able to cast $from to $to, but got $castDefault")
// Check null value
- val castNull = TypeCoercion.implicitCast(createNull(from), to)
+ val castNull = implicitCast(createNull(from), to)
assert(castNull.isEmpty, s"Should not be able to cast $from to $to, but got $castNull")
}
- private def default(dataType: DataType): Expression = dataType match {
+ protected def default(dataType: DataType): Expression = dataType match {
case ArrayType(internalType: DataType, _) =>
CreateArray(Seq(Literal.default(internalType)))
case MapType(keyDataType: DataType, valueDataType: DataType, _) =>
@@ -99,7 +75,7 @@ class TypeCoercionSuite extends AnalysisTest {
case _ => Literal.default(dataType)
}
- private def createNull(dataType: DataType): Expression = dataType match {
+ protected def createNull(dataType: DataType): Expression = dataType match {
case ArrayType(internalType: DataType, _) =>
CreateArray(Seq(Literal.create(null, internalType)))
case MapType(keyDataType: DataType, valueDataType: DataType, _) =>
@@ -109,7 +85,7 @@ class TypeCoercionSuite extends AnalysisTest {
// Check whether the type `checkedType` can be cast to all the types in `castableTypes`,
// but cannot be cast to the other types in `allTypes`.
- private def checkTypeCasting(checkedType: DataType, castableTypes: Seq[DataType]): Unit = {
+ protected def checkTypeCasting(checkedType: DataType, castableTypes: Seq[DataType]): Unit = {
val nonCastableTypes = allTypes.filterNot(castableTypes.contains)
castableTypes.foreach { tpe =>
@@ -120,21 +96,23 @@ class TypeCoercionSuite extends AnalysisTest {
}
}
- private def checkWidenType(
- widenFunc: (DataType, DataType) => Option[DataType],
- t1: DataType,
- t2: DataType,
- expected: Option[DataType],
- isSymmetric: Boolean = true): Unit = {
- var found = widenFunc(t1, t2)
- assert(found == expected,
- s"Expected $expected as wider common type for $t1 and $t2, found $found")
- // Test both directions to make sure the widening is symmetric.
- if (isSymmetric) {
- found = widenFunc(t2, t1)
- assert(found == expected,
- s"Expected $expected as wider common type for $t2 and $t1, found $found")
+ protected def ruleTest(rule: Rule[LogicalPlan],
+ initial: Expression, transformed: Expression): Unit = {
+ ruleTest(Seq(rule), initial, transformed)
+ }
+
+ protected def ruleTest(
+ rules: Seq[Rule[LogicalPlan]],
+ initial: Expression,
+ transformed: Expression): Unit = {
+ val testRelation = LocalRelation(AttributeReference("a", IntegerType)())
+ val analyzer = new RuleExecutor[LogicalPlan] {
+ override val batches = Seq(Batch("Resolution", FixedPoint(3), rules: _*))
}
+
+ comparePlans(
+ analyzer.execute(Project(Seq(Alias(initial, "a")()), testRelation)),
+ Project(Seq(Alias(transformed, "a")()), testRelation))
}
test("implicit type cast - ByteType") {
@@ -209,16 +187,6 @@ class TypeCoercionSuite extends AnalysisTest {
shouldNotCast(checkedType, IntegralType)
}
- test("implicit type cast - StringType") {
- val checkedType = StringType
- val nonCastableTypes =
- complexTypes ++ Seq(BooleanType, NullType, CalendarIntervalType)
- checkTypeCasting(checkedType, castableTypes = allTypes.filterNot(nonCastableTypes.contains))
- shouldCast(checkedType, DecimalType, DecimalType.SYSTEM_DEFAULT)
- shouldCast(checkedType, NumericType, NumericType.defaultConcreteType)
- shouldNotCast(checkedType, IntegralType)
- }
-
test("implicit type cast - DateType") {
val checkedType = DateType
checkTypeCasting(checkedType, castableTypes = Seq(checkedType, StringType, TimestampType))
@@ -235,20 +203,6 @@ class TypeCoercionSuite extends AnalysisTest {
shouldNotCast(checkedType, IntegralType)
}
- test("implicit type cast - ArrayType(StringType)") {
- val checkedType = ArrayType(StringType)
- val nonCastableTypes =
- complexTypes ++ Seq(BooleanType, NullType, CalendarIntervalType)
- checkTypeCasting(checkedType,
- castableTypes = allTypes.filterNot(nonCastableTypes.contains).map(ArrayType(_)))
- nonCastableTypes.map(ArrayType(_)).foreach(shouldNotCast(checkedType, _))
- shouldNotCast(ArrayType(DoubleType, containsNull = false),
- ArrayType(LongType, containsNull = false))
- shouldNotCast(checkedType, DecimalType)
- shouldNotCast(checkedType, NumericType)
- shouldNotCast(checkedType, IntegralType)
- }
-
test("implicit type cast between two Map types") {
val sourceType = MapType(IntegerType, IntegerType, true)
val castableTypes = numericTypes ++ Seq(StringType).filter(!Cast.forceNullable(IntegerType, _))
@@ -288,30 +242,6 @@ class TypeCoercionSuite extends AnalysisTest {
}
}
- test("implicit type cast - StructType().add(\"a1\", StringType)") {
- val checkedType = new StructType().add("a1", StringType)
- checkTypeCasting(checkedType, castableTypes = Seq(checkedType))
- shouldNotCast(checkedType, DecimalType)
- shouldNotCast(checkedType, NumericType)
- shouldNotCast(checkedType, IntegralType)
- }
-
- test("implicit type cast - NullType") {
- val checkedType = NullType
- checkTypeCasting(checkedType, castableTypes = allTypes)
- shouldCast(checkedType, DecimalType, DecimalType.SYSTEM_DEFAULT)
- shouldCast(checkedType, NumericType, NumericType.defaultConcreteType)
- shouldCast(checkedType, IntegralType, IntegralType.defaultConcreteType)
- }
-
- test("implicit type cast - CalendarIntervalType") {
- val checkedType = CalendarIntervalType
- checkTypeCasting(checkedType, castableTypes = Seq(checkedType))
- shouldNotCast(checkedType, DecimalType)
- shouldNotCast(checkedType, NumericType)
- shouldNotCast(checkedType, IntegralType)
- }
-
test("eligible implicit type cast - TypeCollection") {
shouldCast(NullType, TypeCollection(StringType, BinaryType), StringType)
@@ -333,8 +263,6 @@ class TypeCoercionSuite extends AnalysisTest {
shouldCast(DecimalType(10, 2), TypeCollection(DecimalType, IntegerType), DecimalType(10, 2))
shouldCast(IntegerType, TypeCollection(DecimalType(10, 2), StringType), DecimalType(10, 2))
- shouldCast(StringType, TypeCollection(NumericType, BinaryType), DoubleType)
-
shouldCast(
ArrayType(StringType, false),
TypeCollection(ArrayType(StringType), StringType),
@@ -350,6 +278,249 @@ class TypeCoercionSuite extends AnalysisTest {
shouldNotCast(IntegerType, TypeCollection(DateType, TimestampType))
}
+ test("type coercion for Concat") {
+ val rule = TypeCoercion.ConcatCoercion
+
+ ruleTest(rule,
+ Concat(Seq(Literal("ab"), Literal("cde"))),
+ Concat(Seq(Literal("ab"), Literal("cde"))))
+ ruleTest(rule,
+ Concat(Seq(Literal(null), Literal("abc"))),
+ Concat(Seq(Cast(Literal(null), StringType), Literal("abc"))))
+ ruleTest(rule,
+ Concat(Seq(Literal(1), Literal("234"))),
+ Concat(Seq(Cast(Literal(1), StringType), Literal("234"))))
+ ruleTest(rule,
+ Concat(Seq(Literal("1"), Literal("234".getBytes()))),
+ Concat(Seq(Literal("1"), Cast(Literal("234".getBytes()), StringType))))
+ ruleTest(rule,
+ Concat(Seq(Literal(1L), Literal(2.toByte), Literal(0.1))),
+ Concat(Seq(Cast(Literal(1L), StringType), Cast(Literal(2.toByte), StringType),
+ Cast(Literal(0.1), StringType))))
+ ruleTest(rule,
+ Concat(Seq(Literal(true), Literal(0.1f), Literal(3.toShort))),
+ Concat(Seq(Cast(Literal(true), StringType), Cast(Literal(0.1f), StringType),
+ Cast(Literal(3.toShort), StringType))))
+ ruleTest(rule,
+ Concat(Seq(Literal(1L), Literal(0.1))),
+ Concat(Seq(Cast(Literal(1L), StringType), Cast(Literal(0.1), StringType))))
+ ruleTest(rule,
+ Concat(Seq(Literal(Decimal(10)))),
+ Concat(Seq(Cast(Literal(Decimal(10)), StringType))))
+ ruleTest(rule,
+ Concat(Seq(Literal(BigDecimal.valueOf(10)))),
+ Concat(Seq(Cast(Literal(BigDecimal.valueOf(10)), StringType))))
+ ruleTest(rule,
+ Concat(Seq(Literal(java.math.BigDecimal.valueOf(10)))),
+ Concat(Seq(Cast(Literal(java.math.BigDecimal.valueOf(10)), StringType))))
+ ruleTest(rule,
+ Concat(Seq(Literal(new java.sql.Date(0)), Literal(new Timestamp(0)))),
+ Concat(Seq(Cast(Literal(new java.sql.Date(0)), StringType),
+ Cast(Literal(new Timestamp(0)), StringType))))
+
+ withSQLConf(SQLConf.CONCAT_BINARY_AS_STRING.key -> "true") {
+ ruleTest(rule,
+ Concat(Seq(Literal("123".getBytes), Literal("456".getBytes))),
+ Concat(Seq(Cast(Literal("123".getBytes), StringType),
+ Cast(Literal("456".getBytes), StringType))))
+ }
+
+ withSQLConf(SQLConf.CONCAT_BINARY_AS_STRING.key -> "false") {
+ ruleTest(rule,
+ Concat(Seq(Literal("123".getBytes), Literal("456".getBytes))),
+ Concat(Seq(Literal("123".getBytes), Literal("456".getBytes))))
+ }
+ }
+
+ test("type coercion for Elt") {
+ val rule = TypeCoercion.EltCoercion
+
+ ruleTest(rule,
+ Elt(Seq(Literal(1), Literal("ab"), Literal("cde"))),
+ Elt(Seq(Literal(1), Literal("ab"), Literal("cde"))))
+ ruleTest(rule,
+ Elt(Seq(Literal(1.toShort), Literal("ab"), Literal("cde"))),
+ Elt(Seq(Cast(Literal(1.toShort), IntegerType), Literal("ab"), Literal("cde"))))
+ ruleTest(rule,
+ Elt(Seq(Literal(2), Literal(null), Literal("abc"))),
+ Elt(Seq(Literal(2), Cast(Literal(null), StringType), Literal("abc"))))
+ ruleTest(rule,
+ Elt(Seq(Literal(2), Literal(1), Literal("234"))),
+ Elt(Seq(Literal(2), Cast(Literal(1), StringType), Literal("234"))))
+ ruleTest(rule,
+ Elt(Seq(Literal(3), Literal(1L), Literal(2.toByte), Literal(0.1))),
+ Elt(Seq(Literal(3), Cast(Literal(1L), StringType), Cast(Literal(2.toByte), StringType),
+ Cast(Literal(0.1), StringType))))
+ ruleTest(rule,
+ Elt(Seq(Literal(2), Literal(true), Literal(0.1f), Literal(3.toShort))),
+ Elt(Seq(Literal(2), Cast(Literal(true), StringType), Cast(Literal(0.1f), StringType),
+ Cast(Literal(3.toShort), StringType))))
+ ruleTest(rule,
+ Elt(Seq(Literal(1), Literal(1L), Literal(0.1))),
+ Elt(Seq(Literal(1), Cast(Literal(1L), StringType), Cast(Literal(0.1), StringType))))
+ ruleTest(rule,
+ Elt(Seq(Literal(1), Literal(Decimal(10)))),
+ Elt(Seq(Literal(1), Cast(Literal(Decimal(10)), StringType))))
+ ruleTest(rule,
+ Elt(Seq(Literal(1), Literal(BigDecimal.valueOf(10)))),
+ Elt(Seq(Literal(1), Cast(Literal(BigDecimal.valueOf(10)), StringType))))
+ ruleTest(rule,
+ Elt(Seq(Literal(1), Literal(java.math.BigDecimal.valueOf(10)))),
+ Elt(Seq(Literal(1), Cast(Literal(java.math.BigDecimal.valueOf(10)), StringType))))
+ ruleTest(rule,
+ Elt(Seq(Literal(2), Literal(new java.sql.Date(0)), Literal(new Timestamp(0)))),
+ Elt(Seq(Literal(2), Cast(Literal(new java.sql.Date(0)), StringType),
+ Cast(Literal(new Timestamp(0)), StringType))))
+
+ withSQLConf(SQLConf.ELT_OUTPUT_AS_STRING.key -> "true") {
+ ruleTest(rule,
+ Elt(Seq(Literal(1), Literal("123".getBytes), Literal("456".getBytes))),
+ Elt(Seq(Literal(1), Cast(Literal("123".getBytes), StringType),
+ Cast(Literal("456".getBytes), StringType))))
+ }
+
+ withSQLConf(SQLConf.ELT_OUTPUT_AS_STRING.key -> "false") {
+ ruleTest(rule,
+ Elt(Seq(Literal(1), Literal("123".getBytes), Literal("456".getBytes))),
+ Elt(Seq(Literal(1), Literal("123".getBytes), Literal("456".getBytes))))
+ }
+ }
+
+ test("Datetime operations") {
+ val rule = dateTimeOperationsRule
+ val dateLiteral = Literal(java.sql.Date.valueOf("2021-01-01"))
+ val timestampLiteral = Literal(Timestamp.valueOf("2021-01-01 00:00:00"))
+ val timestampNTZLiteral = Literal(LocalDateTime.parse("2021-01-01T00:00:00"))
+ val intLiteral = Literal(3)
+ Seq(timestampLiteral, timestampNTZLiteral).foreach { tsLiteral =>
+ ruleTest(rule,
+ DateAdd(tsLiteral, intLiteral),
+ DateAdd(Cast(tsLiteral, DateType), intLiteral))
+ ruleTest(rule,
+ DateSub(tsLiteral, intLiteral),
+ DateSub(Cast(tsLiteral, DateType), intLiteral))
+ ruleTest(rule,
+ SubtractTimestamps(tsLiteral, dateLiteral),
+ SubtractTimestamps(tsLiteral, Cast(dateLiteral, tsLiteral.dataType)))
+ ruleTest(rule,
+ SubtractTimestamps(dateLiteral, tsLiteral),
+ SubtractTimestamps(Cast(dateLiteral, tsLiteral.dataType), tsLiteral))
+ }
+
+ ruleTest(rule,
+ SubtractTimestamps(timestampLiteral, timestampNTZLiteral),
+ SubtractTimestamps(Cast(timestampLiteral, TimestampNTZType), timestampNTZLiteral))
+ ruleTest(rule,
+ SubtractTimestamps(timestampNTZLiteral, timestampLiteral),
+ SubtractTimestamps(timestampNTZLiteral, Cast(timestampLiteral, TimestampNTZType)))
+ }
+
+}
+
+class TypeCoercionSuite extends TypeCoercionSuiteBase {
+ import TypeCoercionSuite._
+
+ // scalastyle:off line.size.limit
+ // The following table shows all implicit data type conversions that are not visible to the user.
+ // +----------------------+----------+-----------+-------------+----------+------------+-----------+------------+------------+-------------+------------+----------+---------------+------------+----------+-------------+----------+----------------------+---------------------+-------------+--------------+
+ // | Source Type\CAST TO | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | BinaryType | BooleanType | StringType | DateType | TimestampType | ArrayType | MapType | StructType | NullType | CalendarIntervalType | DecimalType | NumericType | IntegralType |
+ // +----------------------+----------+-----------+-------------+----------+------------+-----------+------------+------------+-------------+------------+----------+---------------+------------+----------+-------------+----------+----------------------+---------------------+-------------+--------------+
+ // | ByteType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(3, 0) | ByteType | ByteType |
+ // | ShortType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(5, 0) | ShortType | ShortType |
+ // | IntegerType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(10, 0) | IntegerType | IntegerType |
+ // | LongType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(20, 0) | LongType | LongType |
+ // | DoubleType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(30, 15) | DoubleType | IntegerType |
+ // | FloatType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(14, 7) | FloatType | IntegerType |
+ // | Dec(10, 2) | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | X | X | StringType | X | X | X | X | X | X | X | DecimalType(10, 2) | Dec(10, 2) | IntegerType |
+ // | BinaryType | X | X | X | X | X | X | X | BinaryType | X | StringType | X | X | X | X | X | X | X | X | X | X |
+ // | BooleanType | X | X | X | X | X | X | X | X | BooleanType | StringType | X | X | X | X | X | X | X | X | X | X |
+ // | StringType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | BinaryType | X | StringType | DateType | TimestampType | X | X | X | X | X | DecimalType(38, 18) | DoubleType | X |
+ // | DateType | X | X | X | X | X | X | X | X | X | StringType | DateType | TimestampType | X | X | X | X | X | X | X | X |
+ // | TimestampType | X | X | X | X | X | X | X | X | X | StringType | DateType | TimestampType | X | X | X | X | X | X | X | X |
+ // | ArrayType | X | X | X | X | X | X | X | X | X | X | X | X | ArrayType* | X | X | X | X | X | X | X |
+ // | MapType | X | X | X | X | X | X | X | X | X | X | X | X | X | MapType* | X | X | X | X | X | X |
+ // | StructType | X | X | X | X | X | X | X | X | X | X | X | X | X | X | StructType* | X | X | X | X | X |
+ // | NullType | ByteType | ShortType | IntegerType | LongType | DoubleType | FloatType | Dec(10, 2) | BinaryType | BooleanType | StringType | DateType | TimestampType | ArrayType | MapType | StructType | NullType | CalendarIntervalType | DecimalType(38, 18) | DoubleType | IntegerType |
+ // | CalendarIntervalType | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | CalendarIntervalType | X | X | X |
+ // +----------------------+----------+-----------+-------------+----------+------------+-----------+------------+------------+-------------+------------+----------+---------------+------------+----------+-------------+----------+----------------------+---------------------+-------------+--------------+
+ // Note: StructType* is castable when all the internal child types are castable according to the table.
+ // Note: ArrayType* is castable when the element type is castable according to the table.
+ // Note: MapType* is castable when both the key type and the value type are castable according to the table.
+ // scalastyle:on line.size.limit
+ override def implicitCast(e: Expression, expectedType: AbstractDataType): Option[Expression] =
+ TypeCoercion.implicitCast(e, expectedType)
+
+ override def dateTimeOperationsRule: TypeCoercionRule = TypeCoercion.DateTimeOperations
+
+ private def checkWidenType(
+ widenFunc: (DataType, DataType) => Option[DataType],
+ t1: DataType,
+ t2: DataType,
+ expected: Option[DataType],
+ isSymmetric: Boolean = true): Unit = {
+ var found = widenFunc(t1, t2)
+ assert(found == expected,
+ s"Expected $expected as wider common type for $t1 and $t2, found $found")
+ // Test both directions to make sure the widening is symmetric.
+ if (isSymmetric) {
+ found = widenFunc(t2, t1)
+ assert(found == expected,
+ s"Expected $expected as wider common type for $t2 and $t1, found $found")
+ }
+ }
+
+ test("implicit type cast - StringType") {
+ val checkedType = StringType
+ val nonCastableTypes =
+ complexTypes ++ Seq(BooleanType, NullType, CalendarIntervalType)
+ checkTypeCasting(checkedType, castableTypes = allTypes.filterNot(nonCastableTypes.contains))
+ shouldCast(checkedType, DecimalType, DecimalType.SYSTEM_DEFAULT)
+ shouldCast(checkedType, NumericType, NumericType.defaultConcreteType)
+ shouldNotCast(checkedType, IntegralType)
+ }
+
+ test("implicit type cast - ArrayType(StringType)") {
+ val checkedType = ArrayType(StringType)
+ val nonCastableTypes =
+ complexTypes ++ Seq(BooleanType, NullType, CalendarIntervalType)
+ checkTypeCasting(checkedType,
+ castableTypes = allTypes.filterNot(nonCastableTypes.contains).map(ArrayType(_)))
+ nonCastableTypes.map(ArrayType(_)).foreach(shouldNotCast(checkedType, _))
+ shouldNotCast(ArrayType(DoubleType, containsNull = false),
+ ArrayType(LongType, containsNull = false))
+ shouldNotCast(checkedType, DecimalType)
+ shouldNotCast(checkedType, NumericType)
+ shouldNotCast(checkedType, IntegralType)
+ }
+
+ test("implicit type cast - StructType().add(\"a1\", StringType)") {
+ val checkedType = new StructType().add("a1", StringType)
+ checkTypeCasting(checkedType, castableTypes = Seq(checkedType))
+ shouldNotCast(checkedType, DecimalType)
+ shouldNotCast(checkedType, NumericType)
+ shouldNotCast(checkedType, IntegralType)
+ }
+
+ test("implicit type cast - NullType") {
+ val checkedType = NullType
+ checkTypeCasting(checkedType, castableTypes = allTypes)
+ shouldCast(checkedType, DecimalType, DecimalType.SYSTEM_DEFAULT)
+ shouldCast(checkedType, NumericType, NumericType.defaultConcreteType)
+ shouldCast(checkedType, IntegralType, IntegralType.defaultConcreteType)
+ }
+
+ test("implicit type cast - CalendarIntervalType") {
+ val checkedType = CalendarIntervalType
+ checkTypeCasting(checkedType, castableTypes = Seq(checkedType))
+ shouldNotCast(checkedType, DecimalType)
+ shouldNotCast(checkedType, NumericType)
+ shouldNotCast(checkedType, IntegralType)
+ }
+
+ test("eligible implicit type cast - TypeCollection II") {
+ shouldCast(StringType, TypeCollection(NumericType, BinaryType), DoubleType)
+ }
+
test("tightest common bound for types") {
def widenTest(t1: DataType, t2: DataType, expected: Option[DataType]): Unit =
checkWidenType(TypeCoercion.findTightestCommonType, t1, t2, expected)
@@ -717,25 +888,6 @@ class TypeCoercionSuite extends AnalysisTest {
Some(new StructType().add("a", StringType)))
}
- private def ruleTest(rule: Rule[LogicalPlan],
- initial: Expression, transformed: Expression): Unit = {
- ruleTest(Seq(rule), initial, transformed)
- }
-
- private def ruleTest(
- rules: Seq[Rule[LogicalPlan]],
- initial: Expression,
- transformed: Expression): Unit = {
- val testRelation = LocalRelation(AttributeReference("a", IntegerType)())
- val analyzer = new RuleExecutor[LogicalPlan] {
- override val batches = Seq(Batch("Resolution", FixedPoint(3), rules: _*))
- }
-
- comparePlans(
- analyzer.execute(Project(Seq(Alias(initial, "a")()), testRelation)),
- Project(Seq(Alias(transformed, "a")()), testRelation))
- }
-
test("cast NullType for expressions that implement ExpectsInputTypes") {
ruleTest(TypeCoercion.ImplicitTypeCasts,
AnyTypeUnaryExpression(Literal.create(null, NullType)),
@@ -1110,114 +1262,6 @@ class TypeCoercionSuite extends AnalysisTest {
Literal.create(null, IntegerType), Literal.create(null, StringType))))
}
- test("type coercion for Concat") {
- val rule = TypeCoercion.ConcatCoercion
-
- ruleTest(rule,
- Concat(Seq(Literal("ab"), Literal("cde"))),
- Concat(Seq(Literal("ab"), Literal("cde"))))
- ruleTest(rule,
- Concat(Seq(Literal(null), Literal("abc"))),
- Concat(Seq(Cast(Literal(null), StringType), Literal("abc"))))
- ruleTest(rule,
- Concat(Seq(Literal(1), Literal("234"))),
- Concat(Seq(Cast(Literal(1), StringType), Literal("234"))))
- ruleTest(rule,
- Concat(Seq(Literal("1"), Literal("234".getBytes()))),
- Concat(Seq(Literal("1"), Cast(Literal("234".getBytes()), StringType))))
- ruleTest(rule,
- Concat(Seq(Literal(1L), Literal(2.toByte), Literal(0.1))),
- Concat(Seq(Cast(Literal(1L), StringType), Cast(Literal(2.toByte), StringType),
- Cast(Literal(0.1), StringType))))
- ruleTest(rule,
- Concat(Seq(Literal(true), Literal(0.1f), Literal(3.toShort))),
- Concat(Seq(Cast(Literal(true), StringType), Cast(Literal(0.1f), StringType),
- Cast(Literal(3.toShort), StringType))))
- ruleTest(rule,
- Concat(Seq(Literal(1L), Literal(0.1))),
- Concat(Seq(Cast(Literal(1L), StringType), Cast(Literal(0.1), StringType))))
- ruleTest(rule,
- Concat(Seq(Literal(Decimal(10)))),
- Concat(Seq(Cast(Literal(Decimal(10)), StringType))))
- ruleTest(rule,
- Concat(Seq(Literal(BigDecimal.valueOf(10)))),
- Concat(Seq(Cast(Literal(BigDecimal.valueOf(10)), StringType))))
- ruleTest(rule,
- Concat(Seq(Literal(java.math.BigDecimal.valueOf(10)))),
- Concat(Seq(Cast(Literal(java.math.BigDecimal.valueOf(10)), StringType))))
- ruleTest(rule,
- Concat(Seq(Literal(new java.sql.Date(0)), Literal(new Timestamp(0)))),
- Concat(Seq(Cast(Literal(new java.sql.Date(0)), StringType),
- Cast(Literal(new Timestamp(0)), StringType))))
-
- withSQLConf(SQLConf.CONCAT_BINARY_AS_STRING.key -> "true") {
- ruleTest(rule,
- Concat(Seq(Literal("123".getBytes), Literal("456".getBytes))),
- Concat(Seq(Cast(Literal("123".getBytes), StringType),
- Cast(Literal("456".getBytes), StringType))))
- }
-
- withSQLConf(SQLConf.CONCAT_BINARY_AS_STRING.key -> "false") {
- ruleTest(rule,
- Concat(Seq(Literal("123".getBytes), Literal("456".getBytes))),
- Concat(Seq(Literal("123".getBytes), Literal("456".getBytes))))
- }
- }
-
- test("type coercion for Elt") {
- val rule = TypeCoercion.EltCoercion
-
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal("ab"), Literal("cde"))),
- Elt(Seq(Literal(1), Literal("ab"), Literal("cde"))))
- ruleTest(rule,
- Elt(Seq(Literal(1.toShort), Literal("ab"), Literal("cde"))),
- Elt(Seq(Cast(Literal(1.toShort), IntegerType), Literal("ab"), Literal("cde"))))
- ruleTest(rule,
- Elt(Seq(Literal(2), Literal(null), Literal("abc"))),
- Elt(Seq(Literal(2), Cast(Literal(null), StringType), Literal("abc"))))
- ruleTest(rule,
- Elt(Seq(Literal(2), Literal(1), Literal("234"))),
- Elt(Seq(Literal(2), Cast(Literal(1), StringType), Literal("234"))))
- ruleTest(rule,
- Elt(Seq(Literal(3), Literal(1L), Literal(2.toByte), Literal(0.1))),
- Elt(Seq(Literal(3), Cast(Literal(1L), StringType), Cast(Literal(2.toByte), StringType),
- Cast(Literal(0.1), StringType))))
- ruleTest(rule,
- Elt(Seq(Literal(2), Literal(true), Literal(0.1f), Literal(3.toShort))),
- Elt(Seq(Literal(2), Cast(Literal(true), StringType), Cast(Literal(0.1f), StringType),
- Cast(Literal(3.toShort), StringType))))
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal(1L), Literal(0.1))),
- Elt(Seq(Literal(1), Cast(Literal(1L), StringType), Cast(Literal(0.1), StringType))))
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal(Decimal(10)))),
- Elt(Seq(Literal(1), Cast(Literal(Decimal(10)), StringType))))
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal(BigDecimal.valueOf(10)))),
- Elt(Seq(Literal(1), Cast(Literal(BigDecimal.valueOf(10)), StringType))))
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal(java.math.BigDecimal.valueOf(10)))),
- Elt(Seq(Literal(1), Cast(Literal(java.math.BigDecimal.valueOf(10)), StringType))))
- ruleTest(rule,
- Elt(Seq(Literal(2), Literal(new java.sql.Date(0)), Literal(new Timestamp(0)))),
- Elt(Seq(Literal(2), Cast(Literal(new java.sql.Date(0)), StringType),
- Cast(Literal(new Timestamp(0)), StringType))))
-
- withSQLConf(SQLConf.ELT_OUTPUT_AS_STRING.key -> "true") {
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal("123".getBytes), Literal("456".getBytes))),
- Elt(Seq(Literal(1), Cast(Literal("123".getBytes), StringType),
- Cast(Literal("456".getBytes), StringType))))
- }
-
- withSQLConf(SQLConf.ELT_OUTPUT_AS_STRING.key -> "false") {
- ruleTest(rule,
- Elt(Seq(Literal(1), Literal("123".getBytes), Literal("456".getBytes))),
- Elt(Seq(Literal(1), Literal("123".getBytes), Literal("456".getBytes))))
- }
- }
-
test("BooleanEquality type cast") {
val be = TypeCoercion.BooleanEquality
// Use something more than a literal to avoid triggering the simplification rules.
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 ac64bf6..b95c8da 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
@@ -219,10 +219,9 @@ struct<next_day(2015-07-23 12:12:12, Mon):date>
-- !query
select next_day(timestamp_ltz"2015-07-23 12:12:12", "Mon")
-- !query schema
-struct<>
+struct<next_day(TIMESTAMP '2015-07-23 12:12:12', Mon):date>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'next_day(TIMESTAMP '2015-07-23 12:12:12', 'Mon')' due to data type mismatch: argument 1 requires date type, however, 'TIMESTAMP '2015-07-23 12:12:12'' is of timestamp type.; line 1 pos 7
+2015-07-27
-- !query
@@ -354,19 +353,17 @@ NULL
-- !query
select date_add(timestamp_ltz'2011-11-11 12:12:12', 1)
-- !query schema
-struct<>
+struct<date_add(TIMESTAMP '2011-11-11 12:12:12', 1):date>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'date_add(TIMESTAMP '2011-11-11 12:12:12', 1)' due to data type mismatch: argument 1 requires date type, however, 'TIMESTAMP '2011-11-11 12:12:12'' is of timestamp type.; line 1 pos 7
+2011-11-12
-- !query
select date_add(timestamp_ntz'2011-11-11 12:12:12', 1)
-- !query schema
-struct<>
+struct<date_add(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1):date>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'date_add(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1)' due to data type mismatch: argument 1 requires date type, however, 'TIMESTAMP_NTZ '2011-11-11 12:12:12'' is of timestamp_ntz type.; line 1 pos 7
+2011-11-12
-- !query
@@ -464,19 +461,17 @@ NULL
-- !query
select date_sub(timestamp_ltz'2011-11-11 12:12:12', 1)
-- !query schema
-struct<>
+struct<date_sub(TIMESTAMP '2011-11-11 12:12:12', 1):date>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'date_sub(TIMESTAMP '2011-11-11 12:12:12', 1)' due to data type mismatch: argument 1 requires date type, however, 'TIMESTAMP '2011-11-11 12:12:12'' is of timestamp type.; line 1 pos 7
+2011-11-10
-- !query
select date_sub(timestamp_ntz'2011-11-11 12:12:12', 1)
-- !query schema
-struct<>
+struct<date_sub(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1):date>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'date_sub(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1)' due to data type mismatch: argument 1 requires date type, however, 'TIMESTAMP_NTZ '2011-11-11 12:12:12'' is of timestamp_ntz type.; line 1 pos 7
+2011-11-10
-- !query
diff --git a/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out b/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out
index 12c98ff..e9c3232 100644
--- a/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out
@@ -639,8 +639,8 @@ select make_interval(0, 0, 0, 0, 0, 0, 1234567890123456789)
-- !query schema
struct<>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'make_interval(0, 0, 0, 0, 0, 0, 1234567890123456789L)' due to data type mismatch: argument 7 requires decimal(18,6) type, however, '1234567890123456789L' is of bigint type.; line 1 pos 7
+org.apache.spark.SparkArithmeticException
+Decimal(expanded,1234567890123456789,20,0}) cannot be represented as Decimal(18, 6). If necessary set spark.sql.ansi.enabled to false to bypass this error.
-- !query
diff --git a/sql/core/src/test/resources/sql-tests/results/ansi/string-functions.sql.out b/sql/core/src/test/resources/sql-tests/results/ansi/string-functions.sql.out
index 879d89e..45d4038 100644
--- a/sql/core/src/test/resources/sql-tests/results/ansi/string-functions.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/ansi/string-functions.sql.out
@@ -71,10 +71,9 @@ ab abcd ab NULL
-- !query
select left(null, -2)
-- !query schema
-struct<>
+struct<left(NULL, -2):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'substring(NULL, 1, -2)' due to data type mismatch: argument 1 requires (string or binary) type, however, 'NULL' is of void type.; line 1 pos 7
+NULL
-- !query
@@ -89,19 +88,17 @@ invalid input syntax for type numeric: a. To return NULL instead, use 'try_cast'
-- !query
select right("abcd", 2), right("abcd", 5), right("abcd", '2'), right("abcd", null)
-- !query schema
-struct<>
+struct<right(abcd, 2):string,right(abcd, 5):string,right(abcd, 2):string,right(abcd, NULL):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'substring('abcd', (- CAST('2' AS DOUBLE)), 2147483647)' due to data type mismatch: argument 2 requires int type, however, '(- CAST('2' AS DOUBLE))' is of double type.; line 1 pos 43
+cd abcd cd NULL
-- !query
select right(null, -2)
-- !query schema
-struct<>
+struct<right(NULL, -2):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'substring(NULL, (- -2), 2147483647)' due to data type mismatch: argument 1 requires (string or binary) type, however, 'NULL' is of void type.; line 1 pos 7
+NULL
-- !query
@@ -109,8 +106,8 @@ select right("abcd", -2), right("abcd", 0), right("abcd", 'a')
-- !query schema
struct<>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'substring('abcd', (- CAST('a' AS DOUBLE)), 2147483647)' due to data type mismatch: argument 2 requires int type, however, '(- CAST('a' AS DOUBLE))' is of double type.; line 1 pos 44
+java.lang.NumberFormatException
+invalid input syntax for type numeric: a. To return NULL instead, use 'try_cast'. If necessary set spark.sql.ansi.enabled to false to bypass this error.
-- !query
@@ -308,28 +305,25 @@ trim
-- !query
SELECT btrim(encode(" xyz ", 'utf-8'))
-- !query schema
-struct<>
+struct<btrim(encode( xyz , utf-8)):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'trim(encode(' xyz ', 'utf-8'))' due to data type mismatch: argument 1 requires string type, however, 'encode(' xyz ', 'utf-8')' is of binary type.; line 1 pos 7
+xyz
-- !query
SELECT btrim(encode('yxTomxx', 'utf-8'), encode('xyz', 'utf-8'))
-- !query schema
-struct<>
+struct<btrim(encode(yxTomxx, utf-8), encode(xyz, utf-8)):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'TRIM(BOTH encode('xyz', 'utf-8') FROM encode('yxTomxx', 'utf-8'))' due to data type mismatch: argument 1 requires string type, however, 'encode('yxTomxx', 'utf-8')' is of binary type. argument 2 requires string type, however, 'encode('xyz', 'utf-8')' is of binary type.; line 1 pos 7
+Tom
-- !query
SELECT btrim(encode('xxxbarxxx', 'utf-8'), encode('x', 'utf-8'))
-- !query schema
-struct<>
+struct<btrim(encode(xxxbarxxx, utf-8), encode(x, utf-8)):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'TRIM(BOTH encode('x', 'utf-8') FROM encode('xxxbarxxx', 'utf-8'))' due to data type mismatch: argument 1 requires string type, however, 'encode('xxxbarxxx', 'utf-8')' is of binary type. argument 2 requires string type, however, 'encode('x', 'utf-8')' is of binary type.; line 1 pos 7
+bar
-- !query
@@ -545,37 +539,33 @@ AABB
-- !query
SELECT lpad('abc', 5, x'57')
-- !query schema
-struct<>
+struct<lpad(abc, 5, X'57'):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'lpad('abc', 5, X'57')' due to data type mismatch: argument 3 requires string type, however, 'X'57'' is of binary type.; line 1 pos 7
+WWabc
-- !query
SELECT lpad(x'57', 5, 'abc')
-- !query schema
-struct<>
+struct<lpad(X'57', 5, abc):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'lpad(X'57', 5, 'abc')' due to data type mismatch: argument 1 requires string type, however, 'X'57'' is of binary type.; line 1 pos 7
+abcaW
-- !query
SELECT rpad('abc', 5, x'57')
-- !query schema
-struct<>
+struct<rpad(abc, 5, X'57'):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'rpad('abc', 5, X'57')' due to data type mismatch: argument 3 requires string type, however, 'X'57'' is of binary type.; line 1 pos 7
+abcWW
-- !query
SELECT rpad(x'57', 5, 'abc')
-- !query schema
-struct<>
+struct<rpad(X'57', 5, abc):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'rpad(X'57', 5, 'abc')' due to data type mismatch: argument 1 requires string type, however, 'X'57'' is of binary type.; line 1 pos 7
+Wabca
-- !query
diff --git a/sql/core/src/test/resources/sql-tests/results/date.sql.out b/sql/core/src/test/resources/sql-tests/results/date.sql.out
index 2eacb6c..5620289 100644
--- a/sql/core/src/test/resources/sql-tests/results/date.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/date.sql.out
@@ -349,10 +349,9 @@ struct<date_add(TIMESTAMP '2011-11-11 12:12:12', 1):date>
-- !query
select date_add(timestamp_ntz'2011-11-11 12:12:12', 1)
-- !query schema
-struct<>
+struct<date_add(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1):date>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'date_add(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1)' due to data type mismatch: argument 1 requires date type, however, 'TIMESTAMP_NTZ '2011-11-11 12:12:12'' is of timestamp_ntz type.; line 1 pos 7
+2011-11-12
-- !query
@@ -458,10 +457,9 @@ struct<date_sub(TIMESTAMP '2011-11-11 12:12:12', 1):date>
-- !query
select date_sub(timestamp_ntz'2011-11-11 12:12:12', 1)
-- !query schema
-struct<>
+struct<date_sub(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1):date>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'date_sub(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1)' due to data type mismatch: argument 1 requires date type, however, 'TIMESTAMP_NTZ '2011-11-11 12:12:12'' is of timestamp_ntz type.; line 1 pos 7
+2011-11-10
-- !query
diff --git a/sql/core/src/test/resources/sql-tests/results/datetime-legacy.sql.out b/sql/core/src/test/resources/sql-tests/results/datetime-legacy.sql.out
index 573ce3d..74480ab 100644
--- a/sql/core/src/test/resources/sql-tests/results/datetime-legacy.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/datetime-legacy.sql.out
@@ -349,10 +349,9 @@ struct<date_add(TIMESTAMP '2011-11-11 12:12:12', 1):date>
-- !query
select date_add(timestamp_ntz'2011-11-11 12:12:12', 1)
-- !query schema
-struct<>
+struct<date_add(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1):date>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'date_add(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1)' due to data type mismatch: argument 1 requires date type, however, 'TIMESTAMP_NTZ '2011-11-11 12:12:12'' is of timestamp_ntz type.; line 1 pos 7
+2011-11-12
-- !query
@@ -458,10 +457,9 @@ struct<date_sub(TIMESTAMP '2011-11-11 12:12:12', 1):date>
-- !query
select date_sub(timestamp_ntz'2011-11-11 12:12:12', 1)
-- !query schema
-struct<>
+struct<date_sub(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1):date>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'date_sub(TIMESTAMP_NTZ '2011-11-11 12:12:12', 1)' due to data type mismatch: argument 1 requires date type, however, 'TIMESTAMP_NTZ '2011-11-11 12:12:12'' is of timestamp_ntz type.; line 1 pos 7
+2011-11-10
-- !query
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 8a4ee14..bc13bb8 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
@@ -4793,43 +4793,33 @@ Infinity
-- !query
select * from range(cast(0.0 as decimal(38, 18)), cast(4.0 as decimal(38, 18)))
-- !query schema
-struct<>
+struct<id:bigint>
-- !query output
-org.apache.spark.sql.AnalysisException
-Table-valued function range with alternatives:
- range(start: long, end: long, step: long, numSlices: integer)
- range(start: long, end: long, step: long)
- range(start: long, end: long)
- range(end: long)
-cannot be applied to (decimal(38,18), decimal(38,18)): Incompatible input data type. Expected: long; Found: decimal(38,18); line 1 pos 14
+0
+1
+2
+3
-- !query
select * from range(cast(0.1 as decimal(38, 18)), cast(4.0 as decimal(38, 18)), cast(1.3 as decimal(38, 18)))
-- !query schema
-struct<>
+struct<id:bigint>
-- !query output
-org.apache.spark.sql.AnalysisException
-Table-valued function range with alternatives:
- range(start: long, end: long, step: long, numSlices: integer)
- range(start: long, end: long, step: long)
- range(start: long, end: long)
- range(end: long)
-cannot be applied to (decimal(38,18), decimal(38,18), decimal(38,18)): Incompatible input data type. Expected: long; Found: decimal(38,18); line 1 pos 14
+0
+1
+2
+3
-- !query
select * from range(cast(4.0 as decimal(38, 18)), cast(-1.5 as decimal(38, 18)), cast(-2.2 as decimal(38, 18)))
-- !query schema
-struct<>
+struct<id:bigint>
-- !query output
-org.apache.spark.sql.AnalysisException
-Table-valued function range with alternatives:
- range(start: long, end: long, step: long, numSlices: integer)
- range(start: long, end: long, step: long)
- range(start: long, end: long)
- range(end: long)
-cannot be applied to (decimal(38,18), decimal(38,18), decimal(38,18)): Incompatible input data type. Expected: long; Found: decimal(38,18); line 1 pos 14
+0
+2
+4
-- !query
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 253a5e4..2890462 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
@@ -977,37 +977,33 @@ struct<repeat(Pg, -4):string>
-- !query
SELECT trim(binary('\\000') from binary('\\000Tom\\000'))
-- !query schema
-struct<>
+struct<TRIM(BOTH \000 FROM \000Tom\000):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'TRIM(BOTH CAST('\\000' AS BINARY) FROM CAST('\\000Tom\\000' AS BINARY))' due to data type mismatch: argument 1 requires string type, however, 'CAST('\\000Tom\\000' AS BINARY)' is of binary type. argument 2 requires string type, however, 'CAST('\\000' AS BINARY)' is of binary type.; line 1 pos 7
+Tom
-- !query
SELECT btrim(binary('\\000trim\\000'), binary('\\000'))
-- !query schema
-struct<>
+struct<btrim(\000trim\000, \000):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'TRIM(BOTH CAST('\\000' AS BINARY) FROM CAST('\\000trim\\000' AS BINARY))' due to data type mismatch: argument 1 requires string type, however, 'CAST('\\000trim\\000' AS BINARY)' is of binary type. argument 2 requires string type, however, 'CAST('\\000' AS BINARY)' is of binary type.; line 1 pos 7
+trim
-- !query
SELECT btrim(binary(''), binary('\\000'))
-- !query schema
-struct<>
+struct<btrim(, \000):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'TRIM(BOTH CAST('\\000' AS BINARY) FROM CAST('' AS BINARY))' due to data type mismatch: argument 1 requires string type, however, 'CAST('' AS BINARY)' is of binary type. argument 2 requires string type, however, 'CAST('\\000' AS BINARY)' is of binary type.; line 1 pos 7
+
-- !query
SELECT btrim(binary('\\000trim\\000'), binary(''))
-- !query schema
-struct<>
+struct<btrim(\000trim\000, ):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'TRIM(BOTH CAST('' AS BINARY) FROM CAST('\\000trim\\000' AS BINARY))' due to data type mismatch: argument 1 requires string type, however, 'CAST('\\000trim\\000' AS BINARY)' is of binary type. argument 2 requires string type, however, 'CAST('' AS BINARY)' is of binary type.; line 1 pos 7
+\000trim\000
-- !query
diff --git a/sql/core/src/test/resources/sql-tests/results/postgreSQL/text.sql.out b/sql/core/src/test/resources/sql-tests/results/postgreSQL/text.sql.out
index 25bcdb4..75ca4f2 100755
--- a/sql/core/src/test/resources/sql-tests/results/postgreSQL/text.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/postgreSQL/text.sql.out
@@ -54,10 +54,9 @@ struct<two:string,f1:string>
-- !query
select length(42)
-- !query schema
-struct<>
+struct<length(42):int>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'length(42)' due to data type mismatch: argument 1 requires (string or binary) type, however, '42' is of int type.; line 1 pos 7
+2
-- !query
@@ -65,8 +64,8 @@ select string('four: ') || 2+2
-- !query schema
struct<>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'concat(CAST('four: ' AS STRING), 2)' due to data type mismatch: input to function concat should have been string, binary or array, but it's [string, int]; line 1 pos 7
+java.lang.NumberFormatException
+invalid input syntax for type numeric: four: 2. To return NULL instead, use 'try_cast'. If necessary set spark.sql.ansi.enabled to false to bypass this error.
-- !query
@@ -74,17 +73,16 @@ select 'four: ' || 2+2
-- !query schema
struct<>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'concat('four: ', 2)' due to data type mismatch: input to function concat should have been string, binary or array, but it's [string, int]; line 1 pos 7
+java.lang.NumberFormatException
+invalid input syntax for type numeric: four: 2. To return NULL instead, use 'try_cast'. If necessary set spark.sql.ansi.enabled to false to bypass this error.
-- !query
select 3 || 4.0
-- !query schema
-struct<>
+struct<concat(3, 4.0):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'concat(3, 4.0BD)' due to data type mismatch: input to function concat should have been string, binary or array, but it's [int, decimal(2,1)]; line 1 pos 7
+34.0
-- !query
@@ -101,10 +99,9 @@ one
-- !query
select concat(1,2,3,'hello',true, false, to_date('20100309','yyyyMMdd'))
-- !query schema
-struct<>
+struct<concat(1, 2, 3, hello, true, false, to_date(20100309, yyyyMMdd)):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'concat(1, 2, 3, 'hello', true, false, to_date('20100309', 'yyyyMMdd'))' due to data type mismatch: input to function concat should have been string, binary or array, but it's [int, int, int, string, boolean, boolean, date]; line 1 pos 7
+123hellotruefalse2010-03-09
-- !query
@@ -118,37 +115,33 @@ one
-- !query
select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','yyyyMMdd'))
-- !query schema
-struct<>
+struct<concat_ws(#, 1, 2, 3, hello, true, false, to_date(20100309, yyyyMMdd)):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'concat_ws('#', 1, 2, 3, 'hello', true, false, to_date('20100309', 'yyyyMMdd'))' due to data type mismatch: argument 2 requires (array<string> or string) type, however, '1' is of int type. argument 3 requires (array<string> or string) type, however, '2' is of int type. argument 4 requires (array<string> or string) type, however, '3' is of int type. argument 6 requires (array<string> or string) type, however, 'true' is of boolean type. argument 7 requires (array<string> or [...]
+1#x#x#hello#true#false#x-03-09
-- !query
select concat_ws(',',10,20,null,30)
-- !query schema
-struct<>
+struct<concat_ws(,, 10, 20, NULL, 30):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'concat_ws(',', 10, 20, NULL, 30)' due to data type mismatch: argument 2 requires (array<string> or string) type, however, '10' is of int type. argument 3 requires (array<string> or string) type, however, '20' is of int type. argument 4 requires (array<string> or string) type, however, 'NULL' is of void type. argument 5 requires (array<string> or string) type, however, '30' is of int type.; line 1 pos 7
+10,20,30
-- !query
select concat_ws('',10,20,null,30)
-- !query schema
-struct<>
+struct<concat_ws(, 10, 20, NULL, 30):string>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'concat_ws('', 10, 20, NULL, 30)' due to data type mismatch: argument 2 requires (array<string> or string) type, however, '10' is of int type. argument 3 requires (array<string> or string) type, however, '20' is of int type. argument 4 requires (array<string> or string) type, however, 'NULL' is of void type. argument 5 requires (array<string> or string) type, however, '30' is of int type.; line 1 pos 7
+102030
-- !query
select concat_ws(NULL,10,20,null,30) is null
-- !query schema
-struct<>
+struct<(concat_ws(NULL, 10, 20, NULL, 30) IS NULL):boolean>
-- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'concat_ws(CAST(NULL AS STRING), 10, 20, NULL, 30)' due to data type mismatch: argument 2 requires (array<string> or string) type, however, '10' is of int type. argument 3 requires (array<string> or string) type, however, '20' is of int type. argument 4 requires (array<string> or string) type, however, 'NULL' is of void type. argument 5 requires (array<string> or string) type, however, '30' is of int type.; line 1 pos 7
+true
-- !query
@@ -162,10 +155,19 @@ edcba
-- !query
select i, left('ahoj', i), right('ahoj', i) from range(-5, 6) t(i) order by i
-- !query schema
-struct<>
--- !query output
-org.apache.spark.sql.AnalysisException
-cannot resolve 'substring('ahoj', 1, t.i)' due to data type mismatch: argument 3 requires int type, however, 't.i' is of bigint type.; line 1 pos 10
+struct<i:bigint,left(ahoj, i):string,right(ahoj, i):string>
+-- !query output
+-5
+-4
+-3
+-2
+-1
+0
+1 a j
+2 ah oj
+3 aho hoj
+4 ahoj ahoj
+5 ahoj ahoj
-- !query
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org