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

wenchen 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 37f8df483aa1 [SPARK-53507][CORE] Don't use case class for 
BreakingChangeInfo
37f8df483aa1 is described below

commit 37f8df483aa1801f34cbaa6efec455b114336f22
Author: imarkowitz <[email protected]>
AuthorDate: Thu Oct 9 18:36:38 2025 +0800

    [SPARK-53507][CORE] Don't use case class for BreakingChangeInfo
    
    ### What changes were proposed in this pull request?
    
    Don't use a Scala case class for BreakingChangeInfo and MitigationConfig
    
    ### Why are the changes needed?
    
    Per comment: 
https://github.com/apache/spark/pull/52256#discussion_r2378561779
    
    > [cloud-fan](https://github.com/cloud-fan) [5 days 
ago](https://github.com/apache/spark/pull/52256#discussion_r2378561779)
    > I just realize that we expose it as a public API via 
SparkThrowable.getBreakingChangeInfo. We shouldn't expose a case class as 
public API as it has a wide API surface, including the companion object.
    > We should follow SparkThrowable and define it in Java.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No -- interface is almost the same
    
    ### How was this patch tested?
    
    Updated unit tests
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Used Github co-pilot, mainly for the `equals` and `hashCode` functions
    
    Closes #52484 from imarkowitz/ian/breaking-changes-case-class.
    
    Lead-authored-by: imarkowitz <[email protected]>
    Co-authored-by: Wenchen Fan <[email protected]>
    Signed-off-by: Wenchen Fan <[email protected]>
---
 .../org/apache/spark/ErrorClassesJSONReader.scala  | 42 ++++++++++++++++++----
 .../org/apache/spark/SparkThrowableSuite.scala     | 10 +++---
 .../service/FetchErrorDetailsHandlerSuite.scala    |  6 ++--
 3 files changed, 44 insertions(+), 14 deletions(-)

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 8a9adf7f87ad..b2415920c9bf 100644
--- a/common/utils/src/main/scala/org/apache/spark/ErrorClassesJSONReader.scala
+++ b/common/utils/src/main/scala/org/apache/spark/ErrorClassesJSONReader.scala
@@ -203,18 +203,48 @@ private case class ErrorSubInfo(
  *                       If false, the spark job should be retried by setting 
the
  *                       mitigationConfig.
  */
-case class BreakingChangeInfo(
-    migrationMessage: Seq[String],
-    mitigationConfig: Option[MitigationConfig] = None,
-    needsAudit: Boolean = true
-)
+class BreakingChangeInfo(
+    val migrationMessage: Seq[String],
+    val mitigationConfig: Option[MitigationConfig] = None,
+    val needsAudit: Boolean = true) {
+  override def equals(other: Any): Boolean = other match {
+    case that: BreakingChangeInfo =>
+      migrationMessage == that.migrationMessage &&
+        mitigationConfig == that.mitigationConfig &&
+        needsAudit == that.needsAudit
+    case _ => false
+  }
+
+  override def hashCode(): Int = {
+    val prime = 31
+    var result = 1
+    result = prime * result + migrationMessage.hashCode()
+    result = prime * result + mitigationConfig.hashCode()
+    result = prime * result + needsAudit.hashCode()
+    result
+  }
+}
 
 /**
  * A spark config flag that can be used to mitigate a breaking change.
  * @param key The spark config key.
  * @param value The spark config value that mitigates the breaking change.
  */
-case class MitigationConfig(key: String, value: String)
+class MitigationConfig(val key: String, val value: String) {
+  override def equals(other: Any): Boolean = other match {
+    case that: MitigationConfig =>
+      key == that.key && value == that.value
+    case _ => false
+  }
+
+  override def hashCode(): Int = {
+    val prime = 31
+    var result = 1
+    result = prime * result + key.hashCode()
+    result = prime * result + value.hashCode()
+    result
+  }
+}
 
 /**
  * Information associated with an error state / SQLSTATE.
diff --git a/core/src/test/scala/org/apache/spark/SparkThrowableSuite.scala 
b/core/src/test/scala/org/apache/spark/SparkThrowableSuite.scala
index 61ffe9b4bd93..897ff4f891e9 100644
--- a/core/src/test/scala/org/apache/spark/SparkThrowableSuite.scala
+++ b/core/src/test/scala/org/apache/spark/SparkThrowableSuite.scala
@@ -570,10 +570,10 @@ class SparkThrowableSuite extends SparkFunSuite {
       val breakingChangeInfo = reader.getBreakingChangeInfo("TEST_ERROR")
       assert(
         breakingChangeInfo.contains(
-          BreakingChangeInfo(
+          new BreakingChangeInfo(
             Seq("Migration message with <param2>."),
-            Some(MitigationConfig("config.key1", "config.value1")),
-            needsAudit = false)))
+            Some(new MitigationConfig("config.key1", "config.value1")),
+            false)))
       val errorMessage2 =
         reader.getErrorMessage("TEST_ERROR_WITH_SUBCLASS.SUBCLASS", 
error2Params)
       assert(
@@ -582,9 +582,9 @@ class SparkThrowableSuite extends SparkFunSuite {
       val breakingChangeInfo2 = 
reader.getBreakingChangeInfo("TEST_ERROR_WITH_SUBCLASS.SUBCLASS")
       assert(
         breakingChangeInfo2.contains(
-          BreakingChangeInfo(
+          new BreakingChangeInfo(
             Seq("Subclass migration message with <param3>."),
-            Some(MitigationConfig("config.key2", "config.value2")))))
+            Some(new MitigationConfig("config.key2", "config.value2")))))
     }
   }
 
diff --git 
a/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/service/FetchErrorDetailsHandlerSuite.scala
 
b/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/service/FetchErrorDetailsHandlerSuite.scala
index f389b753ad01..569ee9737c69 100644
--- 
a/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/service/FetchErrorDetailsHandlerSuite.scala
+++ 
b/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/service/FetchErrorDetailsHandlerSuite.scala
@@ -291,9 +291,9 @@ class FetchErrorDetailsHandlerSuite extends 
SharedSparkSession with ResourceHelp
     val migrationMessages =
       Seq("Please update your code to use new API", "See documentation for 
details")
     val mitigationConfig =
-      Some(MitigationConfig("spark.sql.legacy.behavior.enabled", "true"))
+      Some(new MitigationConfig("spark.sql.legacy.behavior.enabled", "true"))
     val breakingChangeInfo =
-      BreakingChangeInfo(migrationMessages, mitigationConfig, needsAudit = 
false)
+      new BreakingChangeInfo(migrationMessages, mitigationConfig, false)
 
     val testError = new TestSparkThrowableWithBreakingChange(
       "TEST_BREAKING_CHANGE_ERROR",
@@ -342,7 +342,7 @@ class FetchErrorDetailsHandlerSuite extends 
SharedSparkSession with ResourceHelp
   test(
     "throwableToFetchErrorDetailsResponse with breaking change info without 
mitigation config") {
     val migrationMessages = Seq("Migration message only")
-    val breakingChangeInfo = BreakingChangeInfo(migrationMessages, None, 
needsAudit = true)
+    val breakingChangeInfo = new BreakingChangeInfo(migrationMessages, None, 
true)
 
     val testError = new TestSparkThrowableWithBreakingChange(
       "TEST_BREAKING_CHANGE_NO_MITIGATION",


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

Reply via email to