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

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 0e8ce76645 [spark] Reject ALTER TABLE REPLACE COLUMNS to avoid silent 
data corruption (#8246)
0e8ce76645 is described below

commit 0e8ce76645e4a660a7ed19850707b521fb567dd1
Author: huangxiaoping <[email protected]>
AuthorDate: Tue Jun 23 14:11:41 2026 +0800

    [spark] Reject ALTER TABLE REPLACE COLUMNS to avoid silent data corruption 
(#8246)
    
    Spark translates `ALTER TABLE ... REPLACE COLUMNS` into a batch that
    drops every existing column and re-adds the new set (a combination of
    `DeleteColumn` + `AddColumn`). For Paimon this is unsafe: re-adding
    columns assigns brand-new field ids while existing data files keep the
    old ids, so same-named columns are treated as new columns and read back
    as `null` — a silent data corruption.
    
    This PR detects that change pattern in `SparkCatalog.alterTable` and
    throws an `UnsupportedOperationException` with a clear message pointing
    users to `RENAME COLUMN` / `ALTER COLUMN TYPE` / `DROP COLUMN` / `ADD
    COLUMN` instead.
    
    The detection matches exclusively on `DeleteColumn` + `AddColumn` so a
    legitimate mixed batch (e.g. a programmatic DROP + RENAME) is not
    mistaken for a replace.
---
 .../paimon/spark/catalyst/analysis/PaimonAnalysis.scala | 10 ++++++++++
 .../apache/paimon/spark/SparkSchemaEvolutionITCase.java | 17 +++++++++++++++++
 2 files changed, 27 insertions(+)

diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/PaimonAnalysis.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/PaimonAnalysis.scala
index c26eeedd3f..87bdfeffba 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/PaimonAnalysis.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/PaimonAnalysis.scala
@@ -81,6 +81,16 @@ class PaimonAnalysis(session: SparkSession) extends 
Rule[LogicalPlan] {
         if d.resolved =>
       PaimonDropPartitions.validate(table, parts.asResolvedPartitionSpecs)
       d
+
+    case r: ReplaceColumns if r.resolved && isPaimonTable(r.table) =>
+      // Spark rewrites REPLACE COLUMNS into a batch that drops every existing 
column and re-adds
+      // the new set. Re-adding columns assigns brand-new field ids while 
existing data files keep
+      // the old ids, so same-named columns are read back as null, silently 
corrupting data. Reject
+      // it here, before the change batch reaches the catalog where it is 
indistinguishable from an
+      // ordinary drop+add.
+      throw new UnsupportedOperationException(
+        "ALTER TABLE ... REPLACE COLUMNS is not supported for Paimon tables. " 
+
+          "Please use RENAME COLUMN, ALTER COLUMN TYPE, DROP COLUMN, and ADD 
COLUMN instead.")
   }
 
   private def writeOptions(v2WriteCommand: V2WriteCommand): Map[String, 
String] = {
diff --git 
a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkSchemaEvolutionITCase.java
 
b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkSchemaEvolutionITCase.java
index 7afe3c76cf..4e15b0880b 100644
--- 
a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkSchemaEvolutionITCase.java
+++ 
b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkSchemaEvolutionITCase.java
@@ -248,6 +248,23 @@ public class SparkSchemaEvolutionITCase extends 
SparkReadTestBase {
                 .contains(showCreateString("testDropColumns", "a INT NOT 
NULL"));
     }
 
+    @Test
+    public void testReplaceColumnsUnsupported() {
+        createTable("testReplaceColumnsUnsupported");
+
+        assertThatThrownBy(
+                        () ->
+                                spark.sql(
+                                        "ALTER TABLE 
testReplaceColumnsUnsupported REPLACE COLUMNS "
+                                                + "(a BIGINT, bb STRING, c 
STRING)"))
+                .satisfies(
+                        anyCauseMatches(
+                                UnsupportedOperationException.class,
+                                "ALTER TABLE ... REPLACE COLUMNS is not 
supported for Paimon tables. "
+                                        + "Please use RENAME COLUMN, ALTER 
COLUMN TYPE, DROP COLUMN, "
+                                        + "and ADD COLUMN instead."));
+    }
+
     @Test
     public void testDropPartitionKey() {
         spark.sql(

Reply via email to