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");
+ }
}