Skip to content

Commit

Permalink
[SPARK-45060][SQL] Fix an internal error from to_char()on NULL fo…
Browse files Browse the repository at this point in the history
…rmat

### What changes were proposed in this pull request?
In the PR, I propose to check the `format` argument is not a NULL in `ToCharacterBuilder`. And if it is, throw an `AnalysisException` with new error class `INVALID_PARAMETER_VALUE.NULL`.

### Why are the changes needed?
To fix the issue demonstrated by the example:
```sql
$ spark-sql (default)> SELECT to_char(x'537061726b2053514c', CAST(NULL AS STRING));
[INTERNAL_ERROR] The Spark SQL phase analysis failed with an internal error. You hit a bug in Spark or the Spark plugins you use. Please, report this bug to the corresponding communities or vendors, and provide the full stack trace.
```

### Does this PR introduce _any_ user-facing change?
No.

### How was this patch tested?
By running new test:
```
$ build/sbt "test:testOnly *.StringFunctionsSuite"
```

### Was this patch authored or co-authored using generative AI tooling?
No.

Closes apache#42781 from MaxGekk/fix-null-fmt-to_char.

Authored-by: Max Gekk <max.gekk@gmail.com>
Signed-off-by: Max Gekk <max.gekk@gmail.com>
  • Loading branch information
MaxGekk committed Sep 4, 2023
1 parent f2a6c97 commit d03ebce
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 2 deletions.
5 changes: 5 additions & 0 deletions common/utils/src/main/resources/error/error-classes.json
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,11 @@
"expects one of the units without quotes YEAR, QUARTER, MONTH, WEEK, DAY, DAYOFYEAR, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, but got the string literal <invalidValue>."
]
},
"NULL" : {
"message" : [
"expects a non-NULL value."
]
},
"PATTERN" : {
"message" : [
"<value>."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ expects one of binary formats 'base64', 'hex', 'utf-8', but got `<invalidFormat>

expects one of the units without quotes YEAR, QUARTER, MONTH, WEEK, DAY, DAYOFYEAR, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, but got the string literal `<invalidValue>`.

## NULL

expects a non-NULL value.

## PATTERN

`<value>`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,19 @@ case class TryToNumber(left: Expression, right: Expression)
object ToCharacterBuilder extends ExpressionBuilder {
override def build(funcName: String, expressions: Seq[Expression]): Expression = {
val numArgs = expressions.length
if (expressions.length == 2) {
if (numArgs == 2) {
val (inputExpr, format) = (expressions(0), expressions(1))
inputExpr.dataType match {
case _: DatetimeType => DateFormatClass(inputExpr, format)
case _: BinaryType =>
if (!(format.dataType == StringType && format.foldable)) {
throw QueryCompilationErrors.nonFoldableArgumentError(funcName, "format", StringType)
}
format.eval().asInstanceOf[UTF8String].toString.toLowerCase(Locale.ROOT).trim match {
val fmt = format.eval()
if (fmt == null) {
throw QueryCompilationErrors.nullArgumentError(funcName, "format")
}
fmt.asInstanceOf[UTF8String].toString.toLowerCase(Locale.ROOT).trim match {
case "base64" => Base64(inputExpr)
case "hex" => Hex(inputExpr)
case "utf-8" => new Decode(Seq(inputExpr, format))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ private[sql] object QueryCompilationErrors extends QueryErrorsBase with Compilat
"invalidFormat" -> toSQLValue(invalidFormat, StringType)))
}

def nullArgumentError(funcName: String, parameter: String): Throwable = {
new AnalysisException(
errorClass = "INVALID_PARAMETER_VALUE.NULL",
messageParameters = Map(
"parameter" -> toSQLId(parameter),
"functionName" -> toSQLId(funcName)))
}

def unorderablePivotColError(pivotCol: Expression): Throwable = {
new AnalysisException(
errorClass = "INCOMPARABLE_PIVOT_COLUMN",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,20 @@ class StringFunctionsSuite extends QueryTest with SharedSparkSession {
"actualNum" -> "3",
"docroot" -> SPARK_DOC_ROOT),
context = ExpectedContext("", "", 7, 21 + funcName.length, s"$funcName('a', 'b', 'c')"))
checkError(
exception = intercept[AnalysisException] {
sql(s"select $funcName(x'537061726b2053514c', CAST(NULL AS STRING))")
},
errorClass = "INVALID_PARAMETER_VALUE.NULL",
parameters = Map(
"functionName" -> s"`$funcName`",
"parameter" -> "`format`"),
context = ExpectedContext(
"",
"",
7,
51 + funcName.length,
s"$funcName(x'537061726b2053514c', CAST(NULL AS STRING))"))
}
}

Expand Down

0 comments on commit d03ebce

Please sign in to comment.