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

pan3793 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 7979093dc745 [SPARK-58352][SQL] Assign a name to the error condition 
_LEGACY_ERROR_TEMP_2070
7979093dc745 is described below

commit 7979093dc74509db48bf637c8da57c58b0afff94
Author: Xiao Li <[email protected]>
AuthorDate: Thu Aug 13 17:13:45 2026 +0800

    [SPARK-58352][SQL] Assign a name to the error condition 
_LEGACY_ERROR_TEMP_2070
    
    ### What changes were proposed in this pull request?
    
    Assign the name `WRITING_JOB_FAILED` to the legacy error condition
    `_LEGACY_ERROR_TEMP_2070`. This error is a `SparkException` raised by
    `QueryExecutionErrors.writingJobFailedError`, thrown by 
`WriteToDataSourceV2Exec`
    when a DataSource V2 batch write fails and the subsequent `abort()` also 
fails (the
    original cause is chained onto the exception). The new condition is given 
SQLSTATE
    `58030` (matching the sibling `TASK_WRITE_FAILED`); it stays parameterless 
and
    preserves the chained `cause`.
    
    ### Why are the changes needed?
    
    The error conditions 
[README](https://github.com/apache/spark/blob/master/common/utils/src/main/resources/error/README.md)
    disallows new `_LEGACY_ERROR_TEMP_*` entries and asks existing ones to be 
assigned
    proper names. This resolves one of them, under the umbrella
    [SPARK-37935](https://issues.apache.org/jira/browse/SPARK-37935).
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. The `_LEGACY_ERROR_TEMP_*` names are not part of the public API. The 
error
    condition name changes to `WRITING_JOB_FAILED` and now reports SQLSTATE 
`58030`; the
    message text ("Writing job failed.") is unchanged.
    
    ### How was this patch tested?
    
    Added a `checkError` test in `DataSourceV2Suite` with a new
    `CommitAndAbortFailingDataSource` whose batch write fails to commit and 
then fails to
    abort, driving the exact `WriteToDataSourceV2Exec` branch that raises
    `WRITING_JOB_FAILED`. `SparkThrowableSuite` validates SQLSTATE presence and
    error-file formatting/sorting for the new entry.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Yes
    
    This patch had conflicts when merged, resolved by
    Committer: Cheng Pan <[email protected]>
    
    Closes #57540 from gatorsmile/SPARK-58352-writing-job-failed.
    
    Authored-by: Xiao Li <[email protected]>
    Signed-off-by: Cheng Pan <[email protected]>
    (cherry picked from commit ed16a7eef93798cbbdd77eb0e0d74c5840577fa7)
    Signed-off-by: Cheng Pan <[email protected]>
---
 .../src/main/resources/error/error-conditions.json | 11 ++--
 .../spark/sql/errors/QueryExecutionErrors.scala    |  2 +-
 .../spark/sql/connector/DataSourceV2Suite.scala    | 64 +++++++++++++++++++++-
 3 files changed, 70 insertions(+), 7 deletions(-)

diff --git a/common/utils/src/main/resources/error/error-conditions.json 
b/common/utils/src/main/resources/error/error-conditions.json
index c96bfa437dc4..bff7eadda787 100644
--- a/common/utils/src/main/resources/error/error-conditions.json
+++ b/common/utils/src/main/resources/error/error-conditions.json
@@ -9745,6 +9745,12 @@
     ],
     "sqlState" : "42601"
   },
+  "WRITING_JOB_FAILED" : {
+    "message" : [
+      "Writing job failed."
+    ],
+    "sqlState" : "58030"
+  },
   "WRONG_COMMAND_FOR_OBJECT_TYPE" : {
     "message" : [
       "The operation <operation> requires a <requiredType>. But <objectName> 
is a <foundType>. Use <alternative> instead."
@@ -10831,11 +10837,6 @@
       "Missing database location."
     ]
   },
-  "_LEGACY_ERROR_TEMP_2070" : {
-    "message" : [
-      "Writing job failed."
-    ]
-  },
   "_LEGACY_ERROR_TEMP_2071" : {
     "message" : [
       "Commit denied for partition <partId> (task <taskId>, attempt 
<attemptId>, stage <stageId>.<stageAttempt>)."
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryExecutionErrors.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryExecutionErrors.scala
index 541f47e34b32..08eaf35b4f92 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryExecutionErrors.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryExecutionErrors.scala
@@ -974,7 +974,7 @@ private[sql] object QueryExecutionErrors extends 
QueryErrorsBase with ExecutionE
 
   def writingJobFailedError(cause: Throwable): Throwable = {
     new SparkException(
-      errorClass = "_LEGACY_ERROR_TEMP_2070",
+      errorClass = "WRITING_JOB_FAILED",
       messageParameters = Map.empty,
       cause = cause)
   }
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2Suite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2Suite.scala
index 08afcf79f3e7..eb4e6b53dd23 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2Suite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2Suite.scala
@@ -34,7 +34,7 @@ import org.apache.spark.sql.catalyst.expressions.{
   LessThan => CatalystLessThan, Literal => CatalystLiteral, ScalarSubquery}
 import org.apache.spark.sql.catalyst.plans.logical.{Aggregate, Filter => 
LogicalFilter, Project}
 import 
org.apache.spark.sql.catalyst.plans.logical.statsEstimation.EstimationUtils
-import org.apache.spark.sql.connector.catalog.{PartitionInternalRow, 
SupportsRead, Table, TableCapability, TableProvider}
+import org.apache.spark.sql.connector.catalog.{PartitionInternalRow, 
SupportsRead, SupportsWrite, Table, TableCapability, TableProvider}
 import org.apache.spark.sql.connector.catalog.TableCapability._
 import org.apache.spark.sql.connector.expressions.{Expression, FieldReference, 
Literal, NamedReference, NullOrdering, SortDirection, SortOrder, Transform}
 import org.apache.spark.sql.connector.expressions.filter.Predicate
@@ -42,6 +42,7 @@ import org.apache.spark.sql.connector.read._
 import org.apache.spark.sql.connector.read.Scan.ColumnarSupportMode
 import org.apache.spark.sql.connector.read.colstats.ColumnStatistics
 import 
org.apache.spark.sql.connector.read.partitioning.{KeyGroupedPartitioning, 
Partitioning, UnknownPartitioning}
+import org.apache.spark.sql.connector.write.{BatchWrite, DataWriter, 
DataWriterFactory, LogicalWriteInfo, PhysicalWriteInfo, Write, WriteBuilder, 
WriterCommitMessage}
 import org.apache.spark.sql.execution.SortExec
 import org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanHelper
 import org.apache.spark.sql.execution.datasources.v2.{
@@ -504,6 +505,17 @@ class DataSourceV2Suite extends SharedSparkSession with 
AdaptiveSparkPlanHelper
     }
   }
 
+  test("SPARK-58352: WRITING_JOB_FAILED when batch write commit and abort both 
fail") {
+    val cls = classOf[CommitAndAbortFailingDataSource]
+    checkError(
+      exception = intercept[SparkException] {
+        spark.range(1).select($"id" as Symbol("i"), -$"id" as Symbol("j"))
+          .write.format(cls.getName).mode("append").save()
+      },
+      condition = "WRITING_JOB_FAILED",
+      parameters = Map.empty[String, String])
+  }
+
   test("simple counter in writer with onDataWriterCommit") {
     Seq(classOf[SimpleWritableDataSource]).foreach { cls =>
       withTempPath { file =>
@@ -2506,6 +2518,56 @@ object SpecificReaderFactory extends 
PartitionReaderFactory {
 
 class SchemaReadAttemptException(m: String) extends RuntimeException(m)
 
+/**
+ * A writable data source whose batch write both fails to commit and then 
fails to abort. This
+ * drives the exact branch in `WriteToDataSourceV2Exec` that raises 
`WRITING_JOB_FAILED`: the
+ * write's `commit` throws, and the follow-up `abort` also throws, so the 
original failure is
+ * wrapped rather than re-thrown. The per-task `DataWriter` succeeds so the 
failure is driver-side
+ * and deterministic (no dependence on task scheduling).
+ */
+class CommitAndAbortFailingDataSource extends TestingV2Source {
+
+  override def getTable(options: CaseInsensitiveStringMap): Table = new 
SimpleBatchTable
+    with SupportsWrite {
+
+    override def capabilities(): java.util.Set[TableCapability] =
+      java.util.EnumSet.of(TableCapability.BATCH_READ, 
TableCapability.BATCH_WRITE,
+        TableCapability.TRUNCATE)
+
+    override def newScanBuilder(options: CaseInsensitiveStringMap): 
ScanBuilder =
+      new SimpleScanBuilder {
+        override def planInputPartitions(): Array[InputPartition] = Array.empty
+      }
+
+    override def newWriteBuilder(info: LogicalWriteInfo): WriteBuilder = new 
WriteBuilder {
+      override def build(): Write = new Write {
+        override def toBatch: BatchWrite = new BatchWrite {
+          override def createBatchWriterFactory(info: PhysicalWriteInfo): 
DataWriterFactory =
+            CommitAndAbortFailingDataSource.WriterFactory
+
+          override def commit(messages: Array[WriterCommitMessage]): Unit =
+            throw new RuntimeException("commit failed")
+
+          override def abort(messages: Array[WriterCommitMessage]): Unit =
+            throw new RuntimeException("abort failed")
+        }
+      }
+    }
+  }
+}
+
+object CommitAndAbortFailingDataSource {
+  object WriterFactory extends DataWriterFactory {
+    override def createWriter(partitionId: Int, taskId: Long): 
DataWriter[InternalRow] =
+      new DataWriter[InternalRow] {
+        override def write(record: InternalRow): Unit = {}
+        override def commit(): WriterCommitMessage = null
+        override def abort(): Unit = {}
+        override def close(): Unit = {}
+      }
+  }
+}
+
 class SimpleWriteOnlyDataSource extends SimpleWritableDataSource {
 
   override def getTable(options: CaseInsensitiveStringMap): Table = {


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

Reply via email to