This is an automated email from the ASF dual-hosted git repository.

MaxGekk pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new 7c1c749f472b [SPARK-57517][SQL] Fix schema_of_json to return proper 
error on non-string literal input
7c1c749f472b is described below

commit 7c1c749f472b41df404cf8fec92c265ccced3cab
Author: Jubin Soni <[email protected]>
AuthorDate: Fri Jun 19 07:57:01 2026 +0200

    [SPARK-57517][SQL] Fix schema_of_json to return proper error on non-string 
literal input
    
    ## What is the purpose of the change
    
    Fixes [SPARK-57517](https://issues.apache.org/jira/browse/SPARK-57517) — 
`schema_of_json` throws a `ClassCastException` during analysis when called with 
a non-string literal (e.g., `SELECT schema_of_json(42)`), instead of surfacing 
a clean `DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE` error.
    
    The root cause is in `SchemaOfJson.checkInputDataTypes()`: it references a 
`lazy val json = child.eval().asInstanceOf[UTF8String]` before verifying that 
the child's type is `StringType`. For an integer literal, the 
`asInstanceOf[UTF8String]` cast throws `ClassCastException` at analysis time 
rather than producing a user-facing error.
    
    The companion functions `schema_of_csv` and `schema_of_xml` were fixed for 
the same issue in SPARK-52234, but `schema_of_json` was missed. This PR applies 
the same fix: restructuring `checkInputDataTypes` to check `!foldable` → 
`eval() == null` → `dataType != StringType` in safe order, and removing the 
unsafe lazy val entirely.
    
    ## Brief change log
    
    - `SchemaOfJson.checkInputDataTypes()`: removed the `lazy val json` that 
performed an unsafe `asInstanceOf[UTF8String]` cast; restructured the condition 
chain to check for non-foldable input, null input, and wrong type (adding a new 
`UNEXPECTED_INPUT_TYPE` branch) before delegating to 
`super.checkInputDataTypes()`
    - Added `select schema_of_json(42)` to `json-functions.sql` input
    - Added corresponding `DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE` expected 
entries to `analyzer-results/json-functions.sql.out` and 
`results/json-functions.sql.out`
    
    ## Verifying this change
    
    This change is covered by golden file SQL query tests in 
`SQLQueryTestSuite`:
    
    - `select schema_of_json(42)` — verifies that a non-string integer literal 
produces `DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE` at analysis time (previously 
threw `ClassCastException`)
    - Existing tests for `schema_of_json(null)` and 
`schema_of_json(nonFoldableColumn)` continue to pass, confirming the null and 
non-foldable branches are unaffected
    
    ## Does this pull request potentially affect one of the following parts
    
    - **Dependencies** (does it add or upgrade a dependency): no
    - **The public API, i.e., is any changed class annotated with 
`Public(Evolving)`**: no — `SchemaOfJson` is an internal catalyst expression
    - **The serializers**: no
    - **The runtime per-record code paths (performance sensitive)**: no — only 
affects the analysis-time type check path
    - **Anything that affects deployment or recovery**: no
    - **The S3 file system connector**: no
    
    ## Documentation
    
    Does this pull request introduce a new feature? no
    
    If yes, how is the feature documented? not applicable
    
    ## Was generative AI tooling used to co-author this PR?
    
    - [x] Yes — Claude Code was used as a pair-programming assistant. All code 
was written, understood, and verified by the author.
    
    Generated-by: Claude Opus 4.8
    
    Closes #56582 from jubins/j-SPARK-57517-fix-class-cast-exception.
    
    Authored-by: Jubin Soni <[email protected]>
    Signed-off-by: Max Gekk <[email protected]>
    (cherry picked from commit 7ce5ae79327a24135006b2663a96725e6cbdb8ae)
    Signed-off-by: Max Gekk <[email protected]>
---
 .../sql/catalyst/expressions/jsonExpressions.scala | 19 ++++++++++------
 .../analyzer-results/json-functions.sql.out        | 24 ++++++++++++++++++++
 .../resources/sql-tests/inputs/json-functions.sql  |  1 +
 .../sql-tests/results/json-functions.sql.out       | 26 ++++++++++++++++++++++
 4 files changed, 63 insertions(+), 7 deletions(-)

diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/jsonExpressions.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/jsonExpressions.scala
index 3ec17c6ca584..ed1b7191963c 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/jsonExpressions.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/jsonExpressions.scala
@@ -454,23 +454,28 @@ case class SchemaOfJson(
 
   override def nullable: Boolean = false
 
-  @transient
-  private lazy val json = child.eval().asInstanceOf[UTF8String]
-
   override def checkInputDataTypes(): TypeCheckResult = {
-    if (child.foldable && json != null) {
-      super.checkInputDataTypes()
-    } else if (!child.foldable) {
+    if (!child.foldable) {
       DataTypeMismatch(
         errorSubClass = "NON_FOLDABLE_INPUT",
         messageParameters = Map(
           "inputName" -> toSQLId("json"),
           "inputType" -> toSQLType(child.dataType),
           "inputExpr" -> toSQLExpr(child)))
-    } else {
+    } else if (child.eval() == null) {
       DataTypeMismatch(
         errorSubClass = "UNEXPECTED_NULL",
         messageParameters = Map("exprName" -> "json"))
+    } else if (child.dataType != StringType) {
+      DataTypeMismatch(
+        errorSubClass = "UNEXPECTED_INPUT_TYPE",
+        messageParameters = Map(
+          "paramIndex" -> ordinalNumber(0),
+          "inputSql" -> toSQLExpr(child),
+          "inputType" -> toSQLType(child.dataType),
+          "requiredType" -> toSQLType(StringType)))
+    } else {
+      super.checkInputDataTypes()
     }
   }
 
diff --git 
a/sql/core/src/test/resources/sql-tests/analyzer-results/json-functions.sql.out 
b/sql/core/src/test/resources/sql-tests/analyzer-results/json-functions.sql.out
index 9afb3e1749ed..4fb1f0f04231 100644
--- 
a/sql/core/src/test/resources/sql-tests/analyzer-results/json-functions.sql.out
+++ 
b/sql/core/src/test/resources/sql-tests/analyzer-results/json-functions.sql.out
@@ -446,6 +446,30 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
 }
 
 
+-- !query
+select schema_of_json(42)
+-- !query analysis
+org.apache.spark.sql.catalyst.ExtendedAnalysisException
+{
+  "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
+  "sqlState" : "42K09",
+  "messageParameters" : {
+    "inputSql" : "\"42\"",
+    "inputType" : "\"INT\"",
+    "paramIndex" : "first",
+    "requiredType" : "\"STRING\"",
+    "sqlExpr" : "\"schema_of_json(42)\""
+  },
+  "queryContext" : [ {
+    "objectType" : "",
+    "objectName" : "",
+    "startIndex" : 8,
+    "stopIndex" : 25,
+    "fragment" : "schema_of_json(42)"
+  } ]
+}
+
+
 -- !query
 CREATE TEMPORARY VIEW jsonTable(jsonField, a) AS SELECT * FROM VALUES ('{"a": 
1, "b": 2}', 'a')
 -- !query analysis
diff --git a/sql/core/src/test/resources/sql-tests/inputs/json-functions.sql 
b/sql/core/src/test/resources/sql-tests/inputs/json-functions.sql
index 71706d3c2e23..66134545107a 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/json-functions.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/json-functions.sql
@@ -75,6 +75,7 @@ select to_json(array(array(1, 2, 3), array(4)));
 select schema_of_json('{"c1":1}', map('primitivesAsString', 'true'));
 select schema_of_json('{"c1":01, "c2":0.1}', map('allowNumericLeadingZeros', 
'true', 'prefersDecimal', 'true'));
 select schema_of_json(null);
+select schema_of_json(42);
 CREATE TEMPORARY VIEW jsonTable(jsonField, a) AS SELECT * FROM VALUES ('{"a": 
1, "b": 2}', 'a');
 SELECT schema_of_json(jsonField) FROM jsonTable;
 
diff --git 
a/sql/core/src/test/resources/sql-tests/results/json-functions.sql.out 
b/sql/core/src/test/resources/sql-tests/results/json-functions.sql.out
index 53ade56684ad..96c6dab19dc7 100644
--- a/sql/core/src/test/resources/sql-tests/results/json-functions.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/json-functions.sql.out
@@ -496,6 +496,32 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
 }
 
 
+-- !query
+select schema_of_json(42)
+-- !query schema
+struct<>
+-- !query output
+org.apache.spark.sql.catalyst.ExtendedAnalysisException
+{
+  "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
+  "sqlState" : "42K09",
+  "messageParameters" : {
+    "inputSql" : "\"42\"",
+    "inputType" : "\"INT\"",
+    "paramIndex" : "first",
+    "requiredType" : "\"STRING\"",
+    "sqlExpr" : "\"schema_of_json(42)\""
+  },
+  "queryContext" : [ {
+    "objectType" : "",
+    "objectName" : "",
+    "startIndex" : 8,
+    "stopIndex" : 25,
+    "fragment" : "schema_of_json(42)"
+  } ]
+}
+
+
 -- !query
 CREATE TEMPORARY VIEW jsonTable(jsonField, a) AS SELECT * FROM VALUES ('{"a": 
1, "b": 2}', 'a')
 -- !query schema


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to