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

roryqi pushed a commit to branch branch-1.1
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.1 by this push:
     new e84287ea7b [#9666]fix(iceberg): Fix Iceberg migrate procedure by 
preserving stageCreate flag (#9669)
e84287ea7b is described below

commit e84287ea7b6cb139d04ab6c14075c12eaf894547
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sat Jan 10 18:27:42 2026 +0800

    [#9666]fix(iceberg): Fix Iceberg migrate procedure by preserving 
stageCreate flag (#9669)
    
    ### What changes were proposed in this pull request?
    
    This PR fixes the Iceberg system.migrate() procedure for JDBC-backed
    catalogs by preserving stageCreate flag in IcebergTableOperationExecutor
    
    ### Why are the changes needed?
    
    The system.migrate() procedure fails when migrating external Iceberg
    tables to JDBC-backed catalogs (MySQL, PostgreSQL) with the error:
    ```
    org.apache.iceberg.exceptions.AlreadyExistsException: Table already exists 
in database
    ```
    
    Root Cause:
    The staging protocol requires 3 distinct phases to safely migrate tables
    with existing data:
    
    Stage: Create table metadata without registering in the catalog
    Write: Client writes/copies data files to the staged location
    Commit: Atomically register the table with assert-create to prevent race
    conditions
    JDBC catalogs skip phase 1 and immediately commit the table to the
    database, causing the migration to fail because the table already exists
    when phase 3 attempts the final commit.
    
    This is a critical bug that makes it impossible to migrate existing
    Iceberg tables to Gravitino JDBC catalogs.
    
    Fix: #9666
    
    ### Does this PR introduce _any_ user-facing change?
    
    No user-facing API changes. This fix enables the system.migrate()
    procedure to work correctly with JDBC catalogs, which was previously
    broken.
    
    ### How was this patch tested?
    - Added unit test
    
    Manual Testing:
    
    Configured Gravitino with JDBC catalog (MySQL backend)
    Created external Iceberg table in Hive catalog
    Successfully migrated table using CALL
    system.migrate('catalog.database.table')
    Verified table metadata and data files are correctly staged and
    committed
    Confirmed migration fails without the fix (table already exists error)
    
    Co-authored-by: Bharath Krishna <[email protected]>
---
 .../dispatcher/IcebergTableOperationExecutor.java  | 12 ++++--
 .../TestIcebergTableOperationExecutor.java         | 44 ++++++++++++++++++++++
 2 files changed, 53 insertions(+), 3 deletions(-)

diff --git 
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableOperationExecutor.java
 
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableOperationExecutor.java
index 6d23c89360..18177c0922 100644
--- 
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableOperationExecutor.java
+++ 
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableOperationExecutor.java
@@ -72,15 +72,21 @@ public class IcebergTableOperationExecutor implements 
IcebergTableOperationDispa
             authenticatedUser);
 
         // CreateTableRequest is immutable, so we need to rebuild it with 
modified properties
-        createTableRequest =
+        CreateTableRequest.Builder builder =
             CreateTableRequest.builder()
                 .withName(createTableRequest.name())
                 .withSchema(createTableRequest.schema())
                 .withPartitionSpec(createTableRequest.spec())
                 .withWriteOrder(createTableRequest.writeOrder())
                 .withLocation(createTableRequest.location())
-                .setProperties(properties)
-                .build();
+                .setProperties(properties);
+
+        // Preserve the stageCreate flag when rebuilding the request
+        if (createTableRequest.stageCreate()) {
+          builder.stageCreate();
+        }
+
+        createTableRequest = builder.build();
       }
     }
 
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableOperationExecutor.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableOperationExecutor.java
index 47c2ffb4d2..65e271731d 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableOperationExecutor.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableOperationExecutor.java
@@ -144,4 +144,48 @@ public class TestIcebergTableOperationExecutor {
     String actualOwner = 
requestCaptor.getValue().properties().get(IcebergConstants.OWNER);
     Assertions.assertEquals(clientProvidedOwner, actualOwner);
   }
+
+  @Test
+  public void testCreateTablePreservesStageCreateTrue() {
+    when(mockContext.userName()).thenReturn("[email protected]");
+    LoadTableResponse mockResponse = mock(LoadTableResponse.class);
+    when(mockCatalogWrapper.createTable(any(), any(), 
anyBoolean())).thenReturn(mockResponse);
+
+    CreateTableRequest stagedRequest =
+        CreateTableRequest.builder()
+            .withName("test_table")
+            .withSchema(TABLE_SCHEMA)
+            .stageCreate()
+            .build();
+
+    executor.createTable(mockContext, Namespace.of("test_namespace"), 
stagedRequest);
+
+    ArgumentCaptor<CreateTableRequest> requestCaptor =
+        ArgumentCaptor.forClass(CreateTableRequest.class);
+    verify(mockCatalogWrapper).createTable(any(), requestCaptor.capture(), 
anyBoolean());
+
+    Assertions.assertTrue(
+        requestCaptor.getValue().stageCreate(),
+        "stageCreate=true must be preserved when rebuilding request");
+  }
+
+  @Test
+  public void testCreateTablePreservesStageCreateFalse() {
+    when(mockContext.userName()).thenReturn("[email protected]");
+    LoadTableResponse mockResponse = mock(LoadTableResponse.class);
+    when(mockCatalogWrapper.createTable(any(), any(), 
anyBoolean())).thenReturn(mockResponse);
+
+    CreateTableRequest normalRequest =
+        
CreateTableRequest.builder().withName("test_table").withSchema(TABLE_SCHEMA).build();
+
+    executor.createTable(mockContext, Namespace.of("test_namespace"), 
normalRequest);
+
+    ArgumentCaptor<CreateTableRequest> requestCaptor =
+        ArgumentCaptor.forClass(CreateTableRequest.class);
+    verify(mockCatalogWrapper).createTable(any(), requestCaptor.capture(), 
anyBoolean());
+
+    Assertions.assertFalse(
+        requestCaptor.getValue().stageCreate(),
+        "stageCreate=false must remain false when rebuilding request");
+  }
 }

Reply via email to