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

cloud-fan 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 dc27d7361863 [SPARK-56862][SQL] Preserve SQL UDF call-site origin in 
input Cast for runtime error context
dc27d7361863 is described below

commit dc27d73618631ee951d17a7f339429c89d1c76ef
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]>
---
 .../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 f31354179674..b1eaec8bb521 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
@@ -2650,18 +2650,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]

Reply via email to