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 014aaf8648ef [SPARK-58103][SQL] Assign a name to the error condition
_LEGACY_ERROR_TEMP_2228
014aaf8648ef is described below
commit 014aaf8648ef45a027bcfd554268fd9a64f7c10c
Author: Wenchen Fan <[email protected]>
AuthorDate: Wed Jul 15 09:30:02 2026 +0800
[SPARK-58103][SQL] Assign a name to the error condition
_LEGACY_ERROR_TEMP_2228
### What changes were proposed in this pull request?
Replace the legacy error condition `_LEGACY_ERROR_TEMP_2228` with a
descriptive condition `UDT_CLASS_NOT_FOUND` (SQLSTATE `38000`), part of the
error-class migration (umbrella
[SPARK-37935](https://issues.apache.org/jira/browse/SPARK-37935)).
- Add `UDT_CLASS_NOT_FOUND` to `error-conditions.json` with two subclasses
and remove `_LEGACY_ERROR_TEMP_2228`:
- `FOR_USER_CLASS` — the UDT class registered for a user class via
`UDTRegistration` cannot be loaded (replaces `_LEGACY_ERROR_TEMP_2228`).
- `WITHOUT_USER_CLASS` — the UDT class named directly in a schema string
cannot be loaded.
- `DataTypeErrors.cannotLoadUserDefinedTypeError` now raises
`UDT_CLASS_NOT_FOUND.FOR_USER_CLASS`.
- `DataType.fromJson`'s Scala/Java UDT branch now catches
`ClassNotFoundException` from `SparkClassUtils.classForName` and rewraps it as
`UDT_CLASS_NOT_FOUND.WITHOUT_USER_CLASS`. Previously a raw
`java.lang.ClassNotFoundException` (no error class, no SQLSTATE) propagated
when a schema string (for example the schema stored in Parquet/ORC file
metadata, or a Delta table's stored schema) referenced a UDT class that was not
on the classpath.
- Add tests in `DataTypeSuite` (`WITHOUT_USER_CLASS`) and
`UDTRegistrationSuite` (`FOR_USER_CLASS`).
### Why are the changes needed?
Part of the error-class migration
([SPARK-37935](https://issues.apache.org/jira/browse/SPARK-37935)). The legacy
condition used free-form `name`/`userClass` parameters and, for the
`DataType.fromJson` path, a missing UDT class was not wrapped at all: a raw
`ClassNotFoundException` propagated without any error class or SQLSTATE, so it
could not be identified or handled programmatically. Assigning a proper name
and SQLSTATE, and wrapping the `fromJson` path, makes both failures diagnosable.
### Does this PR introduce _any_ user-facing change?
Yes, within the unreleased `master` branch only. The error condition name
and SQLSTATE change (`_LEGACY_ERROR_TEMP_2228` ->
`UDT_CLASS_NOT_FOUND.FOR_USER_CLASS`), and loading a non-existent UDT class
named in a schema string now raises `UDT_CLASS_NOT_FOUND.WITHOUT_USER_CLASS`
(SQLSTATE `38000`) instead of a raw `ClassNotFoundException`.
### How was this patch tested?
New unit tests:
- `DataTypeSuite`: "UDT_CLASS_NOT_FOUND.WITHOUT_USER_CLASS when UDT class
is not on classpath".
- `UDTRegistrationSuite`: "UDT_CLASS_NOT_FOUND.FOR_USER_CLASS when UDT
class is not loadable".
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Opus 4.8
Closes #57228 from cloud-fan/SPARK-58103.
Authored-by: Wenchen Fan <[email protected]>
Signed-off-by: Wenchen Fan <[email protected]>
---
.../src/main/resources/error/error-conditions.json | 24 +++++++++++++++++-----
.../apache/spark/sql/errors/DataTypeErrors.scala | 4 ++--
.../org/apache/spark/sql/types/DataType.scala | 13 ++++++++++--
.../org/apache/spark/sql/types/DataTypeSuite.scala | 16 ++++++++++++++-
.../apache/spark/sql/UDTRegistrationSuite.scala | 13 ++++++++++++
5 files changed, 60 insertions(+), 10 deletions(-)
diff --git a/common/utils/src/main/resources/error/error-conditions.json
b/common/utils/src/main/resources/error/error-conditions.json
index c3b065358373..c374b6b08e2c 100644
--- a/common/utils/src/main/resources/error/error-conditions.json
+++ b/common/utils/src/main/resources/error/error-conditions.json
@@ -7712,6 +7712,25 @@
],
"sqlState" : "2203G"
},
+ "UDT_CLASS_NOT_FOUND" : {
+ "message" : [
+ "Could not load UserDefinedType class <udtClass>.",
+ "Ensure the JAR containing this class is on the classpath."
+ ],
+ "subClass" : {
+ "FOR_USER_CLASS" : {
+ "message" : [
+ "The class was registered as the UserDefinedType for user class
<userClass>."
+ ]
+ },
+ "WITHOUT_USER_CLASS" : {
+ "message" : [
+ ""
+ ]
+ }
+ },
+ "sqlState" : "38000"
+ },
"UDT_CLASS_NOT_USER_DEFINED_TYPE" : {
"message" : [
"The class <udtClass> cannot be loaded as a user-defined type because it
is not a subtype of UserDefinedType."
@@ -10877,11 +10896,6 @@
"<name> is not an UserDefinedType. Please make sure registering an
UserDefinedType for <userClass>."
]
},
- "_LEGACY_ERROR_TEMP_2228" : {
- "message" : [
- "Can not load in UserDefinedType <name> for user class <userClass>."
- ]
- },
"_LEGACY_ERROR_TEMP_2229" : {
"message" : [
"<name> is not a public class. Only public classes are supported."
diff --git
a/sql/api/src/main/scala/org/apache/spark/sql/errors/DataTypeErrors.scala
b/sql/api/src/main/scala/org/apache/spark/sql/errors/DataTypeErrors.scala
index e7c5d329e404..8d65217fb4e4 100644
--- a/sql/api/src/main/scala/org/apache/spark/sql/errors/DataTypeErrors.scala
+++ b/sql/api/src/main/scala/org/apache/spark/sql/errors/DataTypeErrors.scala
@@ -79,8 +79,8 @@ private[sql] object DataTypeErrors extends DataTypeErrorsBase
{
def cannotLoadUserDefinedTypeError(name: String, userClass: String):
Throwable = {
new SparkException(
- errorClass = "_LEGACY_ERROR_TEMP_2228",
- messageParameters = Map("name" -> name, "userClass" -> userClass),
+ errorClass = "UDT_CLASS_NOT_FOUND.FOR_USER_CLASS",
+ messageParameters = Map("udtClass" -> name, "userClass" -> userClass),
cause = null)
}
diff --git a/sql/api/src/main/scala/org/apache/spark/sql/types/DataType.scala
b/sql/api/src/main/scala/org/apache/spark/sql/types/DataType.scala
index 8bce7b50f5d8..c8621ca4da17 100644
--- a/sql/api/src/main/scala/org/apache/spark/sql/types/DataType.scala
+++ b/sql/api/src/main/scala/org/apache/spark/sql/types/DataType.scala
@@ -27,7 +27,7 @@ import org.json4s.JsonAST.JValue
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._
-import org.apache.spark.{SparkIllegalArgumentException, SparkThrowable}
+import org.apache.spark.{SparkClassNotFoundException,
SparkIllegalArgumentException, SparkThrowable}
import org.apache.spark.annotation.Stable
import org.apache.spark.sql.catalyst.analysis.SqlApiAnalysis
import org.apache.spark.sql.catalyst.parser.DataTypeParser
@@ -345,7 +345,16 @@ object DataType {
}
// Defense in depth: resolve the class without initializing it and
verify that it really is a
// UserDefinedType subclass before constructing it.
- val clazz = SparkClassUtils.classForName[UserDefinedType[_]](udtClass,
initialize = false)
+ val clazz =
+ try {
+ SparkClassUtils.classForName[UserDefinedType[_]](udtClass,
initialize = false)
+ } catch {
+ case e: ClassNotFoundException =>
+ throw new SparkClassNotFoundException(
+ errorClass = "UDT_CLASS_NOT_FOUND.WITHOUT_USER_CLASS",
+ messageParameters = Map("udtClass" -> udtClass),
+ cause = e)
+ }
if (!classOf[UserDefinedType[_]].isAssignableFrom(clazz)) {
throw DataTypeErrors.udtClassNotUserDefinedTypeError(udtClass)
}
diff --git
a/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeSuite.scala
b/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeSuite.scala
index 6282832420b7..285d840eed6c 100644
--- a/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeSuite.scala
+++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeSuite.scala
@@ -22,7 +22,8 @@ import java.util.Locale
import com.fasterxml.jackson.core.JsonParseException
import org.json4s.jackson.JsonMethods
-import org.apache.spark.{SparkException, SparkFunSuite,
SparkIllegalArgumentException}
+import org.apache.spark.{SparkClassNotFoundException, SparkException,
SparkFunSuite}
+import org.apache.spark.SparkIllegalArgumentException
import org.apache.spark.sql.catalyst.analysis.{caseInsensitiveResolution,
caseSensitiveResolution}
import org.apache.spark.sql.catalyst.parser.{CatalystSqlParser, ParseException}
import org.apache.spark.sql.catalyst.plans.SQLHelper
@@ -255,6 +256,19 @@ class DataTypeSuite extends SparkFunSuite with SQLHelper {
}
}
+ test("UDT_CLASS_NOT_FOUND.WITHOUT_USER_CLASS when UDT class is not on
classpath") {
+ val fakeUdtClass = "org.apache.spark.sql.types.NonExistentUDT"
+ val udtJson =
+
s"""{"class":"$fakeUdtClass","pyClass":"","sqlType":"integer","type":"udt"}"""
+ checkError(
+ exception = intercept[SparkClassNotFoundException] {
+ DataType.fromJson(udtJson)
+ },
+ condition = "UDT_CLASS_NOT_FOUND.WITHOUT_USER_CLASS",
+ sqlState = Some("38000"),
+ parameters = Map("udtClass" -> fakeUdtClass))
+ }
+
def checkDataTypeFromJson(dataType: DataType): Unit = {
test(s"from Json - $dataType") {
assert(DataType.fromJson(dataType.json) === dataType)
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/UDTRegistrationSuite.scala
b/sql/core/src/test/scala/org/apache/spark/sql/UDTRegistrationSuite.scala
index 5c4de5d4d963..503a6106e7a1 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/UDTRegistrationSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/UDTRegistrationSuite.scala
@@ -86,4 +86,17 @@ class UDTRegistrationSuite extends SparkFunSuite {
assert(!UDTRegistration.exists(classOf[TestUserClass3].getName))
assert(UDTRegistration.getUDTFor(classOf[TestUserClass3].getName).isEmpty)
}
+
+ test("UDT_CLASS_NOT_FOUND.FOR_USER_CLASS when UDT class is not loadable") {
+ val userClass = classOf[TestUserClass3].getName
+ val fakeUdtClass = "org.apache.spark.sql.NonExistentUDT"
+ UDTRegistration.register(userClass, fakeUdtClass)
+ checkError(
+ exception = intercept[SparkException] {
+ UDTRegistration.getUDTFor(userClass)
+ },
+ condition = "UDT_CLASS_NOT_FOUND.FOR_USER_CLASS",
+ sqlState = Some("38000"),
+ parameters = Map("udtClass" -> fakeUdtClass, "userClass" -> userClass))
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]