From 3d6dd5cd5d0432b2efdd7d659bf9252a59c2c8a8 Mon Sep 17 00:00:00 2001 From: yangjie01 Date: Thu, 18 Jun 2026 10:59:31 +0800 Subject: [PATCH] [SPARK-57519][CORE] Fix NoSuchElementException in ErrorClassesJsonReader.isValidErrorClass for error classes without sub-classes `isValidErrorClass` called `info.subClass.get` on the sub-class `Option`, which threw `NoSuchElementException` when a main error class exists but defines no sub-classes and is queried with a two-part name (`MAIN.SUB`). This is reachable from user SQL: SQL scripting handler declarations validate two-part condition names via `SparkThrowableHelper.isValidErrorClass`, so the internal exception leaked instead of the intended `conditionNotFound` error. Replace `info.subClass.get.contains(subClass)` with `info.subClass.exists(_.contains(subClass))`, which returns `false` when no sub-classes are defined. Add a regression test in `SparkThrowableSuite`. --- .../apache/spark/ErrorClassesJSONReader.scala | 2 +- .../apache/spark/SparkThrowableSuite.scala | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/common/utils/src/main/scala/org/apache/spark/ErrorClassesJSONReader.scala b/common/utils/src/main/scala/org/apache/spark/ErrorClassesJSONReader.scala index 0d958e3f71604..18a47e7ee37a7 100644 --- a/common/utils/src/main/scala/org/apache/spark/ErrorClassesJSONReader.scala +++ b/common/utils/src/main/scala/org/apache/spark/ErrorClassesJSONReader.scala @@ -136,7 +136,7 @@ class ErrorClassesJsonReader(jsonFileURLs: Seq[URL]) { errorClasses match { case Array(mainClass) => errorInfoMap.contains(mainClass) case Array(mainClass, subClass) => errorInfoMap.get(mainClass).exists { info => - info.subClass.get.contains(subClass) + info.subClass.exists(_.contains(subClass)) } case _ => false } diff --git a/core/src/test/scala/org/apache/spark/SparkThrowableSuite.scala b/core/src/test/scala/org/apache/spark/SparkThrowableSuite.scala index b04e735d6bfbc..5fb6924383f0e 100644 --- a/core/src/test/scala/org/apache/spark/SparkThrowableSuite.scala +++ b/core/src/test/scala/org/apache/spark/SparkThrowableSuite.scala @@ -503,6 +503,50 @@ class SparkThrowableSuite extends SparkFunSuite { } } + test("isValidErrorClass with a main class that has no sub-classes") { + withTempDir { dir => + val json = new File(dir, "errors.json") + Files.writeString(json.toPath(), + """ + |{ + | "MAIN_NO_SUBCLASS" : { + | "message" : [ + | "abc" + | ] + | }, + | "MAIN_WITH_SUBCLASS" : { + | "message" : [ + | "abc" + | ], + | "subClass" : { + | "VALID_SUB" : { + | "message" : [ + | "def" + | ] + | } + | } + | } + |} + |""".stripMargin, StandardCharsets.UTF_8) + val reader = new ErrorClassesJsonReader(Seq(errorJsonFilePath.toUri.toURL, json.toURI.toURL)) + // A main class with no sub-classes is valid on its own, but querying it with a sub-class + // must return false rather than throw NoSuchElementException (reachable from user SQL). + assert(reader.isValidErrorClass("MAIN_NO_SUBCLASS")) + assert(!reader.isValidErrorClass("MAIN_NO_SUBCLASS.NON_EXISTENT")) + // A main class that does define sub-classes: main-only and a valid sub are valid; an + // unknown sub is not. + assert(reader.isValidErrorClass("MAIN_WITH_SUBCLASS")) + assert(reader.isValidErrorClass("MAIN_WITH_SUBCLASS.VALID_SUB")) + assert(!reader.isValidErrorClass("MAIN_WITH_SUBCLASS.NON_EXISTENT_SUB")) + // Unknown main class: well-formed but not registered. + assert(!reader.isValidErrorClass("NON_EXISTENT")) + assert(!reader.isValidErrorClass("NON_EXISTENT.SUB")) + // Malformed: empty string or more than two parts. + assert(!reader.isValidErrorClass("")) + assert(!reader.isValidErrorClass("MAIN_NO_SUBCLASS.X.Y")) + } + } + test("breaking changes info") { assert(SparkThrowableHelper.getBreakingChangeInfo(null).isEmpty)