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 6365ffeba9 [core] Only require the <db>.db warehouse layout for
catalog-storage Iceberg metadata (#8911)
6365ffeba9 is described below
commit 6365ffeba91caf1fafcfe8d77db72f943c1ea036
Author: Tejansh <[email protected]>
AuthorDate: Thu Jul 30 05:00:16 2026 +0100
[core] Only require the <db>.db warehouse layout for catalog-storage
Iceberg metadata (#8911)
---
.../paimon/iceberg/IcebergCommitCallback.java | 22 ++++++++++-----
.../paimon/iceberg/IcebergCommitCallbackTest.java | 33 ++++++++++++++++++++++
2 files changed, 48 insertions(+), 7 deletions(-)
diff --git
a/paimon-core/src/main/java/org/apache/paimon/iceberg/IcebergCommitCallback.java
b/paimon-core/src/main/java/org/apache/paimon/iceberg/IcebergCommitCallback.java
index d8fa8457db..336b0fe865 100644
---
a/paimon-core/src/main/java/org/apache/paimon/iceberg/IcebergCommitCallback.java
+++
b/paimon-core/src/main/java/org/apache/paimon/iceberg/IcebergCommitCallback.java
@@ -198,13 +198,6 @@ public class IcebergCommitCallback implements
CommitCallback, TagCallback {
IcebergOptions.StorageType storageType =
table.coreOptions().toConfiguration().get(IcebergOptions.METADATA_ICEBERG_STORAGE);
- if (!dbPath.getName().endsWith(dbSuffix)) {
- throw new UnsupportedOperationException(
- String.format(
- "Storage type %s can only be used on Paimon tables
in a Paimon warehouse.",
- storageType.name()));
- }
-
IcebergOptions.StorageLocation storageLocation =
table.coreOptions()
.toConfiguration()
@@ -213,8 +206,23 @@ public class IcebergCommitCallback implements
CommitCallback, TagCallback {
switch (storageLocation) {
case TABLE_LOCATION:
+ // Iceberg metadata is written beside the table, under the
database's own location,
+ // so no warehouse (<db>.db) layout is required. This lets the
table register in any
+ // catalog, including a database whose location is not a
Paimon warehouse path (e.g.
+ // an externally-provisioned / cross-account catalog database).
return dbPath;
case CATALOG_STORAGE:
+ // Catalog-storage derives a warehouse-relative iceberg/<db>/
path by stripping the
+ // ".db" suffix, so it only applies under the Paimon <db>.db
warehouse layout.
+ if (!dbPath.getName().endsWith(dbSuffix)) {
+ throw new UnsupportedOperationException(
+ String.format(
+ "Storage type %s with catalog-location
Iceberg metadata requires a "
+ + "Paimon warehouse database (a
<db>.db location); set "
+ +
"metadata.iceberg.storage-location=table-location for a "
+ + "database with a non-warehouse
location.",
+ storageType.name()));
+ }
String dbName =
dbPath.getName()
.substring(0, dbPath.getName().length() -
dbSuffix.length());
diff --git
a/paimon-core/src/test/java/org/apache/paimon/iceberg/IcebergCommitCallbackTest.java
b/paimon-core/src/test/java/org/apache/paimon/iceberg/IcebergCommitCallbackTest.java
index cb8a83c2a2..e1ecb772db 100644
---
a/paimon-core/src/test/java/org/apache/paimon/iceberg/IcebergCommitCallbackTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/iceberg/IcebergCommitCallbackTest.java
@@ -38,6 +38,7 @@ import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -164,6 +165,38 @@ public class IcebergCommitCallbackTest {
assertThat(result.toString()).isEqualTo(expectedPath);
}
+ @Test
+ void testCatalogDatabasePathTableLocationAllowsNonWarehouseDatabase() {
+ // A database whose location is not a Paimon <db>.db warehouse path
(e.g. an externally
+ // provisioned / cross-account catalog database).
+ when(mockTable.location()).thenReturn(new
Path("s3://bucket/ingest/mydb/mytable"));
+ when(mockConfig.get(IcebergOptions.METADATA_ICEBERG_STORAGE))
+ .thenReturn(IcebergOptions.StorageType.REST_CATALOG);
+
when(mockConfig.getOptional(IcebergOptions.METADATA_ICEBERG_STORAGE_LOCATION))
+
.thenReturn(Optional.of(IcebergOptions.StorageLocation.TABLE_LOCATION));
+
+ // table-location writes Iceberg metadata beside the table, so the
database path is used
+ // as-is with no <db>.db requirement.
+
assertThat(IcebergCommitCallback.catalogDatabasePath(mockTable).toString())
+ .isEqualTo("s3://bucket/ingest/mydb");
+ }
+
+ @Test
+ void testCatalogDatabasePathCatalogStorageRejectsNonWarehouseDatabase() {
+ when(mockTable.location()).thenReturn(new
Path("s3://bucket/ingest/mydb/mytable"));
+ when(mockConfig.get(IcebergOptions.METADATA_ICEBERG_STORAGE))
+ .thenReturn(IcebergOptions.StorageType.REST_CATALOG);
+
when(mockConfig.getOptional(IcebergOptions.METADATA_ICEBERG_STORAGE_LOCATION))
+
.thenReturn(Optional.of(IcebergOptions.StorageLocation.CATALOG_STORAGE));
+
+ // catalog-storage derives a warehouse-relative iceberg/<db>/ path by
stripping ".db", so a
+ // non-".db" database is still rejected (with a message pointing at
the table-location fix).
+ assertThatThrownBy(() ->
IcebergCommitCallback.catalogDatabasePath(mockTable))
+ .isInstanceOf(UnsupportedOperationException.class)
+ .hasMessageContaining("requires a Paimon warehouse database")
+ .hasMessageContaining("table-location");
+ }
+
private static Stream<Arguments> provideMetadataPathsWithStorageType() {
return Stream.of(
Arguments.of(