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 df3dd39c9c [iceberg] Pass table location when creating tables via the 
REST committer (#8290)
df3dd39c9c is described below

commit df3dd39c9c6c50c468dd225f90b1c058221786ee
Author: Tejansh <[email protected]>
AuthorDate: Sat Jun 20 08:41:35 2026 +0100

    [iceberg] Pass table location when creating tables via the REST committer 
(#8290)
    
    `IcebergRestMetadataCommitter#createTable` creates tables without a
    location. Catalogs that auto-assign one tolerated this but AWS Glue's
    Iceberg REST endpoint does not, so creation failed. This PR passes the
    location Paimon already writes its metadata to normalised to s3:// (Glue
    rejects the s3a:// scheme Paimon's warehouse uses).
---
 .../iceberg/IcebergRestMetadataCommitter.java      | 46 ++++++++++++++++++++--
 .../iceberg/IcebergRestMetadataCommitterTest.java  | 16 ++++++++
 2 files changed, 58 insertions(+), 4 deletions(-)

diff --git 
a/paimon-iceberg/src/main/java/org/apache/paimon/iceberg/IcebergRestMetadataCommitter.java
 
b/paimon-iceberg/src/main/java/org/apache/paimon/iceberg/IcebergRestMetadataCommitter.java
index 584966382e..f144ea96de 100644
--- 
a/paimon-iceberg/src/main/java/org/apache/paimon/iceberg/IcebergRestMetadataCommitter.java
+++ 
b/paimon-iceberg/src/main/java/org/apache/paimon/iceberg/IcebergRestMetadataCommitter.java
@@ -295,8 +295,7 @@ public class IcebergRestMetadataCommitter implements 
IcebergMetadataCommitter {
                 LOG.info(
                         "Partition fieldId = 0. The Iceberg REST committer 
will use partition evolution to support Iceberg compatibility with the Paimon 
schema. If you want to avoid this, use a non-zero fieldId partition field");
             }
-            Schema emptySchema = new Schema();
-            return restCatalog.createTable(icebergTableIdentifier, 
emptySchema);
+            return createTable(new Schema(), null, newMetadata);
         } else {
             LOG.info(
                     "Partition fieldId > 0. In order to avoid partition 
evlolution, dummy schema will be created first");
@@ -316,11 +315,50 @@ public class IcebergRestMetadataCommitter implements 
IcebergMetadataCommitter {
                 columns[f.sourceId() - 1] = 
newMetadata.schema().findField(f.sourceId());
             }
 
-            Schema dummySchema = new Schema(columns);
-            return restCatalog.createTable(icebergTableIdentifier, 
dummySchema, spec);
+            return createTable(new Schema(columns), spec, newMetadata);
         }
     }
 
+    private Table createTable(
+            Schema schema, @Nullable PartitionSpec spec, TableMetadata 
newMetadata) {
+        try {
+            // Path-based catalogs (e.g. Hadoop) derive and assign the table 
location themselves
+            // and reject a custom one, so first try letting the catalog 
assign it.
+            return newTableBuilder(schema, spec).create();
+        } catch (RuntimeException e) {
+            // Some Iceberg REST catalogs (notably AWS Glue) do not 
auto-assign a table location
+            // and reject creation without one. Retry with the location Paimon 
writes its metadata
+            // to, normalised to the s3:// scheme such catalogs require.
+            try {
+                return newTableBuilder(schema, spec)
+                        .withLocation(toRestLocation(newMetadata.location()))
+                        .create();
+            } catch (RuntimeException retryError) {
+                e.addSuppressed(retryError);
+                throw e;
+            }
+        }
+    }
+
+    private Catalog.TableBuilder newTableBuilder(Schema schema, @Nullable 
PartitionSpec spec) {
+        Catalog.TableBuilder builder = 
restCatalog.buildTable(icebergTableIdentifier, schema);
+        return spec == null ? builder : builder.withPartitionSpec(spec);
+    }
+
+    /** Normalises a table location's URI scheme for the Iceberg REST catalog. 
*/
+    static String toRestLocation(String location) {
+        if (location == null) {
+            return null;
+        }
+        if (location.startsWith("s3a://")) {
+            return "s3://" + location.substring("s3a://".length());
+        }
+        if (location.startsWith("s3n://")) {
+            return "s3://" + location.substring("s3n://".length());
+        }
+        return location;
+    }
+
     private Table getTable() {
         return restCatalog.loadTable(icebergTableIdentifier);
     }
diff --git 
a/paimon-iceberg/src/test/java/org/apache/paimon/iceberg/IcebergRestMetadataCommitterTest.java
 
b/paimon-iceberg/src/test/java/org/apache/paimon/iceberg/IcebergRestMetadataCommitterTest.java
index 7111726f36..de39ea6f6d 100644
--- 
a/paimon-iceberg/src/test/java/org/apache/paimon/iceberg/IcebergRestMetadataCommitterTest.java
+++ 
b/paimon-iceberg/src/test/java/org/apache/paimon/iceberg/IcebergRestMetadataCommitterTest.java
@@ -769,6 +769,22 @@ public class IcebergRestMetadataCommitterTest {
         commit.close();
     }
 
+    @Test
+    public void testToRestLocationNormalisesScheme() {
+        // s3a:// and legacy s3n:// are rewritten to s3:// (Glue's REST 
endpoint only accepts
+        // s3://).
+        
assertThat(IcebergRestMetadataCommitter.toRestLocation("s3a://bucket/db/t"))
+                .isEqualTo("s3://bucket/db/t");
+        
assertThat(IcebergRestMetadataCommitter.toRestLocation("s3n://bucket/db/t"))
+                .isEqualTo("s3://bucket/db/t");
+        // s3:// and other schemes pass through unchanged; null is preserved.
+        
assertThat(IcebergRestMetadataCommitter.toRestLocation("s3://bucket/db/t"))
+                .isEqualTo("s3://bucket/db/t");
+        
assertThat(IcebergRestMetadataCommitter.toRestLocation("file:///tmp/db/t"))
+                .isEqualTo("file:///tmp/db/t");
+        assertThat(IcebergRestMetadataCommitter.toRestLocation(null)).isNull();
+    }
+
     private static class TestRecord {
         private final BinaryRow partition;
         private final GenericRow record;

Reply via email to