This is an automated email from the ASF dual-hosted git repository.
cloud-fan 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 45900f6126eb [SPARK-56862][SQL] Preserve SQL UDF call-site origin in
input Cast for runtime error context
45900f6126eb is described below
commit 45900f6126eb1c1e338900c13c8259131d728e8f
Author: Mikhail NIkoliukin <[email protected]>
AuthorDate: Tue May 19 21:10:14 2026 +0800
[SPARK-56862][SQL] Preserve SQL UDF call-site origin in input Cast for
runtime error context
### What changes were proposed in this pull request?
Wrap `resolve(f)` in `Analyzer.ResolveSQLFunctions.rewriteSQLFunctions`
with `CurrentOrigin.withOrigin(f.origin) { ... }` so that the input-binding
`Cast`s constructed inside `SessionCatalog.makeSQLFunctionPlan` capture the SQL
UDF call-site position in their `queryContext` snapshot.
### Why are the changes needed?
Without this change, runtime errors raised inside a SQL UDF input-binding
`Cast` (`CAST_INVALID_INPUT`, arithmetic overflow, number-format errors, ...)
lose their query context. Users see an empty `fragment` instead of the call
site that triggered the error, defeating the purpose of `queryContext` for SQL
UDF debugging.
### Does this PR introduce _any_ user-facing change?
Yes. correctly filled queryContext for SQL UDFs
### How was this patch tested?
Regenerated sql/core/src/test/resources/sql-tests/results/sql-udf.sql.out.
The diff shows 8 previously empty queryContext entries gaining
startIndex, stopIndex, and a populated fragment; nothing else
changed.
### Was this patch authored or co-authored using generative AI tooling?
Claude Code (Opus 4.7)
Closes #55980 from mikhailnik-db/spark-56862-sql-udf-origin.
Authored-by: Mikhail NIkoliukin <[email protected]>
Signed-off-by: Wenchen Fan <[email protected]>
(cherry picked from commit dc27d73618631ee951d17a7f339429c89d1c76ef)
Signed-off-by: Wenchen Fan <[email protected]>
---
.../spark/sql/catalyst/analysis/Analyzer.scala | 29 ++++++++++++--------
.../resources/sql-tests/results/sql-udf.sql.out | 32 ++++++++++++++++------
2 files changed, 41 insertions(+), 20 deletions(-)
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala
index 441a156672c8..09c6a0984258 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala
@@ -2649,18 +2649,23 @@ class Analyzer(
// unresolved.
!f.inputs.exists(_.containsPattern(LATERAL_COLUMN_ALIAS_REFERENCE))
=>
withPosition(f) {
- val plan = resolve(f)
- // Extract the function input project list from the SQL function
plan and
- // inline the SQL function expression.
- plan match {
- case Project(body :: Nil, Project(aliases, _: OneRowRelation)) =>
- val inputs = aliases.map(stripOuterReference)
- projectList ++= inputs
- SQLScalarFunction(f.function, inputs.map(_.toAttribute), body)
- case o =>
- throw new AnalysisException(
- errorClass = "INVALID_SQL_FUNCTION_PLAN_STRUCTURE",
- messageParameters = Map("plan" -> o.toString))
+ // Set CurrentOrigin to the SQL function call site so that
input-binding
+ // Casts constructed inside makeSQLFunctionPlan capture the
call-site
+ // position in their queryContext snapshot (see
Cast.initQueryContext).
+ withOrigin(f.origin) {
+ val plan = resolve(f)
+ // Extract the function input project list from the SQL function
plan and
+ // inline the SQL function expression.
+ plan match {
+ case Project(body :: Nil, Project(aliases, _: OneRowRelation))
=>
+ val inputs = aliases.map(stripOuterReference)
+ projectList ++= inputs
+ SQLScalarFunction(f.function, inputs.map(_.toAttribute),
body)
+ case o =>
+ throw new AnalysisException(
+ errorClass = "INVALID_SQL_FUNCTION_PLAN_STRUCTURE",
+ messageParameters = Map("plan" -> o.toString))
+ }
}
}
case o => o.mapChildren(rewriteSQLFunctions(_, projectList))
diff --git a/sql/core/src/test/resources/sql-tests/results/sql-udf.sql.out
b/sql/core/src/test/resources/sql-tests/results/sql-udf.sql.out
index a5e7965c10f4..b227fd3c9475 100644
--- a/sql/core/src/test/resources/sql-tests/results/sql-udf.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/sql-udf.sql.out
@@ -823,7 +823,9 @@ org.apache.spark.SparkException
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
- "fragment" : ""
+ "startIndex" : 8,
+ "stopIndex" : 14,
+ "fragment" : "foo51()"
} ]
}
@@ -956,7 +958,9 @@ org.apache.spark.SparkRuntimeException
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
- "fragment" : ""
+ "startIndex" : 8,
+ "stopIndex" : 24,
+ "fragment" : "foo9a('Nonsense')"
} ]
}
@@ -1209,7 +1213,9 @@ org.apache.spark.SparkArithmeticException
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
- "fragment" : ""
+ "startIndex" : 8,
+ "stopIndex" : 17,
+ "fragment" : "foo9f(999)"
} ]
}
@@ -1232,7 +1238,9 @@ org.apache.spark.SparkArithmeticException
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
- "fragment" : ""
+ "startIndex" : 8,
+ "stopIndex" : 21,
+ "fragment" : "foo9f(999 + 1)"
} ]
}
@@ -1271,7 +1279,9 @@ org.apache.spark.SparkNumberFormatException
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
- "fragment" : ""
+ "startIndex" : 8,
+ "stopIndex" : 26,
+ "fragment" : "foo9g('hello', '7')"
} ]
}
@@ -1294,7 +1304,9 @@ org.apache.spark.SparkNumberFormatException
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
- "fragment" : ""
+ "startIndex" : 8,
+ "stopIndex" : 25,
+ "fragment" : "foo9g(123.23, 'q')"
} ]
}
@@ -1333,7 +1345,9 @@ org.apache.spark.SparkNumberFormatException
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
- "fragment" : ""
+ "startIndex" : 8,
+ "stopIndex" : 26,
+ "fragment" : "foo9h('hello', '7')"
} ]
}
@@ -1356,7 +1370,9 @@ org.apache.spark.SparkNumberFormatException
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
- "fragment" : ""
+ "startIndex" : 8,
+ "stopIndex" : 25,
+ "fragment" : "foo9h(123.23, 'q')"
} ]
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]