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

LuciferYang 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 427fb7db38d7 [SPARK-59040][SQL] Assign a name to the error condition 
`_LEGACY_ERROR_TEMP_1166,1167`
427fb7db38d7 is described below

commit 427fb7db38d700434503b6db0679ca4f918a722f
Author: YangJie <[email protected]>
AuthorDate: Tue Sep 1 11:32:11 2026 +0800

    [SPARK-59040][SQL] Assign a name to the error condition 
`_LEGACY_ERROR_TEMP_1166,1167`
    
    ### What changes were proposed in this pull request?
    
    This PR assigns proper names to two placeholder error conditions, both with 
SQLSTATE `42713`:
    
    | legacy condition | new condition |
    |---|---|
    | `_LEGACY_ERROR_TEMP_1166` | `BUCKET_COLUMN_IN_PARTITION_COLUMNS` |
    | `_LEGACY_ERROR_TEMP_1167` | `BUCKET_SORT_COLUMN_IN_PARTITION_COLUMNS` |
    
    The first message is unchanged. The second now says "Bucket sort column" 
where it said "Bucket sorting column", so that the sentence, the condition name 
and the parameter key all use the same term. Two things about how both render 
also change: the column names go through `toSQLId(Seq(...))` instead of the 
legacy single quotes, and the three message parameters are renamed from 
`bucketCol`, `sortCol` and `normalizedPartCols` to `bucketColumn`, `sortColumn` 
and `partitionColumns`. Both b [...]
    
    ### Why are the changes needed?
    
    This continues the work of replacing `_LEGACY_ERROR_TEMP_*` placeholders 
with real condition names. Both of these are reachable from user code: 
`normalizeCatalogTable` runs for `CREATE TABLE`, CTAS and 
`df.write.partitionBy(...).bucketBy(...).saveAsTable(...)`, and either error 
fires when a column is named in both the partition spec and the bucket spec. A 
proper name is the right outcome here, not an internal error.
    
    The names follow how this file already splits the vocabulary: `BUCKET_*` 
when it means the object, `BUCKETING` when it means the feature or the clause. 
Hence `BUCKET_COLUMN` and `BUCKET_SORT_COLUMN`, alongside 
`INVALID_BUCKET_COLUMN_DATA_TYPE` and `CANNOT_ALTER_COLLATION_BUCKET_COLUMN` on 
one side and `SORT_BY_WITHOUT_BUCKETING` and `SPECIFY_BUCKETING_IS_NOT_ALLOWED` 
on the other. `BucketSpec` calls the fields `bucketColumnNames` and 
`sortColumnNames`. Both stay top-level rather than  [...]
    
    `42713` reads "A duplicate object was detected in a list or is the same as 
an existing object", and the second half of that is what happens here: a column 
named in `CLUSTERED BY` is the same as one already named in `PARTITIONED BY`. 
The closest match in the repo is 
`STATIC_PARTITION_COLUMN_IN_INSERT_COLUMN_LIST`, "Static partition column 
`<staticName>` is also specified in the column list", which is both the same 
shape and the precedent for the `X_IN_Y` name. It also leaves a readable [...]
    
    On the quoting, `QueryErrorsBase`'s scaladoc asks for identifiers to be 
wrapped in backticks via `toSQLId()`, which `QueryCompilationErrors` already 
does throughout. The `Seq` overload is the right one here: these names arrive 
already resolved against the table schema, so each is a single part, whereas 
`toSQLId(String)` runs them through `AttributeNameParser.parseAttributeName` 
first and would render a column literally named `x.y` as `` `x`.`y` ``, or fail 
with `INVALID_ATTRIBUTE_NAME [...]
    
    The parameter names are worth settling now for a similar reason of timing: 
they appear in the generated documentation and in `getMessageParameters()`, and 
once the conditions ship the keys cannot move without breaking anything that 
matches on them. `normalizedPartCols` named an internal normalization step that 
means nothing to a reader. The three now read as the singular and plural of one 
noun, which is also how this file spells them: `...Columns` is the prevailing 
name for a list of  [...]
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, in the rendered message. A named condition gains the `[CONDITION] ` 
prefix and the ` SQLSTATE: ...` suffix that 
`SparkThrowableHelper.formatErrorMessage` suppresses for 
`_LEGACY_ERROR_`-prefixed names, and the column names are now backticked:
    
    ```
    -Bucketing column 'j' should not be part of partition columns 'i, j'.
    +[BUCKET_COLUMN_IN_PARTITION_COLUMNS] Bucketing column `j` should not be 
part of partition columns `i`, `j`. SQLSTATE: 42713
    
    -Bucket sorting column 'i' should not be part of partition columns 'i, j'.
    +[BUCKET_SORT_COLUMN_IN_PARTITION_COLUMNS] Bucket sort column `i` should 
not be part of partition columns `i`, `j`. SQLSTATE: 42713
    ```
    
    Code that reads `getMessageParameters()` sees `bucketColumn`, `sortColumn` 
and `partitionColumns` where it saw `bucketCol`, `sortCol` and 
`normalizedPartCols`. The two conditions also start showing up in the generated 
error conditions documentation, which skips `_LEGACY_ERROR_*` entries.
    
    ### How was this patch tested?
    
    `BucketedWriteSuite`'s existing assertions for both conditions now check 
the new names, the backticked parameter values and `sqlState = "42713"`. A new 
test in the same suite writes a table whose columns are named `x.y` and 
``z`w``, and pins them as `` `x.y` `` and `` `z``w` ``; against the 
`toSQLId(String)` overload that test instead fails with 
`INVALID_ATTRIBUTE_NAME_SYNTAX`, which is what a user would have seen. Ran 
`core/testOnly org.apache.spark.SparkThrowableSuite` and `sql/test [...]
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 5)
    
    Closes #58333 from LuciferYang/assign-name-legacy-1166-1167.
    
    Authored-by: YangJie <[email protected]>
    Signed-off-by: yangjie01 <[email protected]>
    (cherry picked from commit c89c0b5974fde219cd85c2887b47912dcfa88f83)
    Signed-off-by: yangjie01 <[email protected]>
---
 .../src/main/resources/error/error-conditions.json | 22 +++++++++++---------
 .../spark/sql/errors/QueryCompilationErrors.scala  | 17 ++++++++-------
 .../spark/sql/sources/BucketedWriteSuite.scala     | 24 ++++++++++++++++++----
 3 files changed, 42 insertions(+), 21 deletions(-)

diff --git a/common/utils/src/main/resources/error/error-conditions.json 
b/common/utils/src/main/resources/error/error-conditions.json
index 0b1dbb183978..62ff7314a8d7 100644
--- a/common/utils/src/main/resources/error/error-conditions.json
+++ b/common/utils/src/main/resources/error/error-conditions.json
@@ -495,6 +495,18 @@
     ],
     "sqlState" : "22001"
   },
+  "BUCKET_COLUMN_IN_PARTITION_COLUMNS" : {
+    "message" : [
+      "Bucketing column <bucketColumn> should not be part of partition columns 
<partitionColumns>."
+    ],
+    "sqlState" : "42713"
+  },
+  "BUCKET_SORT_COLUMN_IN_PARTITION_COLUMNS" : {
+    "message" : [
+      "Bucket sort column <sortColumn> should not be part of partition columns 
<partitionColumns>."
+    ],
+    "sqlState" : "42713"
+  },
   "CALL_ON_STREAMING_DATASET_UNSUPPORTED" : {
     "message" : [
       "The method <methodName> can not be called on streaming 
Dataset/DataFrame."
@@ -10397,16 +10409,6 @@
       "It is not allowed to specify partitioning when the table schema is not 
defined."
     ]
   },
-  "_LEGACY_ERROR_TEMP_1166" : {
-    "message" : [
-      "Bucketing column '<bucketCol>' should not be part of partition columns 
'<normalizedPartCols>'."
-    ]
-  },
-  "_LEGACY_ERROR_TEMP_1167" : {
-    "message" : [
-      "Bucket sorting column '<sortCol>' should not be part of partition 
columns '<normalizedPartCols>'."
-    ]
-  },
   "_LEGACY_ERROR_TEMP_1169" : {
     "message" : [
       "Requested partitioning does not match the table <tableName>:",
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
index a662eee4ea41..3b5d7accd65b 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
@@ -2229,20 +2229,23 @@ private[sql] object QueryCompilationErrors extends 
QueryErrorsBase with Compilat
 
   def bucketingColumnCannotBePartOfPartitionColumnsError(
       bucketCol: String, normalizedPartCols: Seq[String]): Throwable = {
+    // These names are already resolved against the table schema, so each is 
quoted as a single
+    // part. The `String` overload of `toSQLId` would parse them instead.
     new AnalysisException(
-      errorClass = "_LEGACY_ERROR_TEMP_1166",
+      errorClass = "BUCKET_COLUMN_IN_PARTITION_COLUMNS",
       messageParameters = Map(
-        "bucketCol" -> bucketCol,
-        "normalizedPartCols" -> normalizedPartCols.mkString(", ")))
+        "bucketColumn" -> toSQLId(Seq(bucketCol)),
+        "partitionColumns" -> normalizedPartCols.map(c => 
toSQLId(Seq(c))).mkString(", ")))
   }
 
   def bucketSortingColumnCannotBePartOfPartitionColumnsError(
-    sortCol: String, normalizedPartCols: Seq[String]): Throwable = {
+      sortCol: String, normalizedPartCols: Seq[String]): Throwable = {
+    // Quoted as single parts, as in 
bucketingColumnCannotBePartOfPartitionColumnsError above.
     new AnalysisException(
-      errorClass = "_LEGACY_ERROR_TEMP_1167",
+      errorClass = "BUCKET_SORT_COLUMN_IN_PARTITION_COLUMNS",
       messageParameters = Map(
-        "sortCol" -> sortCol,
-        "normalizedPartCols" -> normalizedPartCols.mkString(", ")))
+        "sortColumn" -> toSQLId(Seq(sortCol)),
+        "partitionColumns" -> normalizedPartCols.map(c => 
toSQLId(Seq(c))).mkString(", ")))
   }
 
   def invalidBucketColumnDataTypeError(dataType: DataType): Throwable = {
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/sources/BucketedWriteSuite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/sources/BucketedWriteSuite.scala
index 1423df2f3550..ad485033e672 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/sources/BucketedWriteSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/sources/BucketedWriteSuite.scala
@@ -252,8 +252,9 @@ abstract class BucketedWriteSuite extends QueryTest {
         .bucketBy(8, "j", "k")
         .sortBy("k")
         .saveAsTable("bucketed_table")),
-      condition = "_LEGACY_ERROR_TEMP_1166",
-      parameters = Map("bucketCol" -> "j", "normalizedPartCols" -> "i, j"))
+      condition = "BUCKET_COLUMN_IN_PARTITION_COLUMNS",
+      sqlState = "42713",
+      parameters = Map("bucketColumn" -> "`j`", "partitionColumns" -> "`i`, 
`j`"))
 
     checkError(
       exception = intercept[AnalysisException](df.write
@@ -261,8 +262,23 @@ abstract class BucketedWriteSuite extends QueryTest {
         .bucketBy(8, "k")
         .sortBy("i")
         .saveAsTable("bucketed_table")),
-      condition = "_LEGACY_ERROR_TEMP_1167",
-      parameters = Map("sortCol" -> "i", "normalizedPartCols" -> "i, j"))
+      condition = "BUCKET_SORT_COLUMN_IN_PARTITION_COLUMNS",
+      sqlState = "42713",
+      parameters = Map("sortColumn" -> "`i`", "partitionColumns" -> "`i`, 
`j`"))
+  }
+
+  test("SPARK-59040: overlapping column names are quoted as single 
identifiers") {
+    // A name is already resolved here, so it must not be parsed: "x.y" is one 
column rather than
+    // a qualified reference, and a name holding a backtick has it escaped 
rather than rejected.
+    val df = Seq((1, "a", 2)).toDF("x.y", "z`w", "k")
+    checkError(
+      exception = intercept[AnalysisException](df.write
+        .partitionBy("x.y", "z`w")
+        .bucketBy(8, "x.y")
+        .saveAsTable("bucketed_table")),
+      condition = "BUCKET_COLUMN_IN_PARTITION_COLUMNS",
+      sqlState = "42713",
+      parameters = Map("bucketColumn" -> "`x.y`", "partitionColumns" -> 
"`x.y`, `z``w`"))
   }
 
   test("write bucketed data without partitionBy") {


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

Reply via email to