This is an automated email from the ASF dual-hosted git repository.
Gabriel39 pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new 2c3ae38c344 branch-4.1: [fix](catalog) Support database properties for
Paimon and Iceberg (#67163)
2c3ae38c344 is described below
commit 2c3ae38c3448aabb2a2fcd7af2e14340a779c375
Author: Socrates <[email protected]>
AuthorDate: Mon Aug 31 10:05:59 2026 +0800
branch-4.1: [fix](catalog) Support database properties for Paimon and
Iceberg (#67163)
### What problem does this PR solve?
Problem Summary:
Doris rejected database properties for every Paimon catalog except HMS
and for every Iceberg catalog except HMS, even when the upstream catalog
implementation supports namespace/database properties. In particular,
this prevented creating databases with properties in Paimon JDBC
catalogs.
This change aligns Doris with the pinned upstream catalog capabilities:
- Paimon: allow ordinary database properties for HMS, JDBC, REST, and
DLF catalogs; keep rejecting them for filesystem catalogs.
- Iceberg: allow ordinary namespace properties for HMS, JDBC, and Glue
catalogs; keep rejecting them for Hadoop, REST, DLF, and S3 Tables
catalogs. Iceberg REST remains unsupported because the protocol allows
servers to ignore namespace properties.
- Treat the operational `location` property separately. Allow it only
where it controls default table placement: Paimon HMS/DLF and Iceberg
HMS/Glue. Reject it for Paimon JDBC/REST and Iceberg JDBC, where
accepting it would persist metadata without relocating subsequently
created tables.
- Forward supported property maps unchanged to the upstream catalog
implementation.
- Validate Paimon `ALTER TABLE SET` keys with the Paimon SDK and reject
`path` before issuing the remote alter, because changing the option does
not relocate an existing table.
- Add unit coverage for the capability matrix and pre-alter validation,
and extend the JDBC and Paimon alter regression cases.
### Release note
Support creating databases with properties in supported Paimon and
Iceberg catalogs, while rejecting location properties that do not
control table placement.
### Check List (For Author)
- Test
- [x] Regression test
- [x] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason
Unit tests:
```shell
DORIS_GCC_HOME=/usr mvn test -pl fe-common,fe-core -am -Dskip.clean=true
-Dcheckstyle.skip=true -DfailIfNoTests=false
-Dtest=org.apache.doris.datasource.paimon.PaimonMetadataOpsTest,org.apache.doris.datasource.iceberg.IcebergMetadataOpTest
```
Result: 26 tests passed (18 Paimon and 8 Iceberg). The external
regression cases were updated but were not executed locally because they
require the full external service environment.
- Behavior changed:
- [ ] No.
- [x] Yes. Database properties are accepted and forwarded according to
catalog capability; operational location changes are rejected when the
upstream implementation cannot honor their placement semantics.
- Does this need documentation?
- [x] No.
- [ ] Yes.
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
---
.../datasource/iceberg/IcebergMetadataOps.java | 28 ++++-
.../doris/datasource/paimon/PaimonMetadataOps.java | 36 ++++++-
.../datasource/iceberg/IcebergMetadataOpTest.java | 106 ++++++++++++++++++
.../datasource/paimon/PaimonMetadataOpsTest.java | 118 +++++++++++++++++++++
.../iceberg/test_iceberg_jdbc_catalog.groovy | 10 +-
.../paimon/test_paimon_alter_properties.groovy | 9 ++
.../paimon/test_paimon_jdbc_catalog.groovy | 9 +-
7 files changed, 305 insertions(+), 11 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
index 8ee05db379f..74a3b366f87 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
@@ -100,6 +100,7 @@ import java.util.stream.Stream;
public class IcebergMetadataOps implements ExternalMetadataOps {
private static final Logger LOG =
LogManager.getLogger(IcebergMetadataOps.class);
+ private static final String PROP_LOCATION = "location";
protected Catalog catalog;
protected ExternalCatalog dorisCatalog;
protected SupportsNamespaces nsCatalog;
@@ -250,15 +251,34 @@ public class IcebergMetadataOps implements
ExternalMetadataOps {
}
if (!properties.isEmpty() && dorisCatalog instanceof
IcebergExternalCatalog) {
String icebergCatalogType = ((IcebergExternalCatalog)
dorisCatalog).getIcebergCatalogType();
- if
(!IcebergExternalCatalog.ICEBERG_HMS.equals(icebergCatalogType)) {
- throw new DdlException(
- "Not supported: create database with properties for
iceberg catalog type: " + icebergCatalogType);
- }
+ validateDatabaseProperties(icebergCatalogType, properties);
}
nsCatalog.createNamespace(getNamespace(dbName), properties);
return false;
}
+ private boolean supportsDatabaseProperties(String catalogType) {
+ return IcebergExternalCatalog.ICEBERG_HMS.equals(catalogType)
+ || IcebergExternalCatalog.ICEBERG_JDBC.equals(catalogType)
+ || IcebergExternalCatalog.ICEBERG_GLUE.equals(catalogType);
+ }
+
+ private boolean supportsDatabaseLocation(String catalogType) {
+ return IcebergExternalCatalog.ICEBERG_HMS.equals(catalogType)
+ || IcebergExternalCatalog.ICEBERG_GLUE.equals(catalogType);
+ }
+
+ private void validateDatabaseProperties(String catalogType, Map<String,
String> properties) throws DdlException {
+ if (!supportsDatabaseProperties(catalogType)) {
+ throw new DdlException(
+ "Not supported: create database with properties for
iceberg catalog type: " + catalogType);
+ }
+ if (properties.containsKey(PROP_LOCATION) &&
!supportsDatabaseLocation(catalogType)) {
+ throw new DdlException("Not supported: database property
'location' for iceberg catalog type: "
+ + catalogType + " because it does not determine the
default table location");
+ }
+ }
+
@Override
public void dropDbImpl(String dbName, boolean ifExists, boolean force)
throws DdlException {
try {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonMetadataOps.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonMetadataOps.java
index ace93f14655..5f3c949b820 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonMetadataOps.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonMetadataOps.java
@@ -52,6 +52,7 @@ import
org.apache.paimon.catalog.Catalog.TableNotExistException;
import org.apache.paimon.catalog.Identifier;
import org.apache.paimon.schema.Schema;
import org.apache.paimon.schema.SchemaChange;
+import org.apache.paimon.schema.SchemaManager;
import org.apache.paimon.types.DataField;
import org.apache.paimon.types.DataType;
@@ -109,16 +110,36 @@ public class PaimonMetadataOps implements
ExternalMetadataOps {
if (!properties.isEmpty() && dorisCatalog instanceof
PaimonExternalCatalog) {
String catalogType = ((PaimonExternalCatalog)
dorisCatalog).getCatalogType();
- if (!PaimonExternalCatalog.PAIMON_HMS.equals(catalogType)) {
- throw new DdlException(
- "Not supported: create database with properties for paimon
catalog type: " + catalogType);
- }
+ validateDatabaseProperties(catalogType, properties);
}
catalog.createDatabase(dbName, ifNotExists, properties);
return false;
}
+ private boolean supportsDatabaseProperties(String catalogType) {
+ return PaimonExternalCatalog.PAIMON_HMS.equals(catalogType)
+ || PaimonExternalCatalog.PAIMON_JDBC.equals(catalogType)
+ || PaimonExternalCatalog.PAIMON_REST.equals(catalogType)
+ || PaimonExternalCatalog.PAIMON_DLF.equals(catalogType);
+ }
+
+ private boolean supportsDatabaseLocation(String catalogType) {
+ return PaimonExternalCatalog.PAIMON_HMS.equals(catalogType)
+ || PaimonExternalCatalog.PAIMON_DLF.equals(catalogType);
+ }
+
+ private void validateDatabaseProperties(String catalogType, Map<String,
String> properties) throws DdlException {
+ if (!supportsDatabaseProperties(catalogType)) {
+ throw new DdlException(
+ "Not supported: create database with properties for paimon
catalog type: " + catalogType);
+ }
+ if (properties.containsKey(PROP_LOCATION) &&
!supportsDatabaseLocation(catalogType)) {
+ throw new DdlException("Not supported: database property
'location' for paimon catalog type: "
+ + catalogType + " because it does not determine the
default table location");
+ }
+ }
+
@Override
public void afterCreateDb() {
dorisCatalog.resetMetaCacheNames();
@@ -533,6 +554,13 @@ public class PaimonMetadataOps implements
ExternalMetadataOps {
@Override
public void updateTableProperties(ExternalTable dorisTable, Map<String,
String> properties, long updateTime)
throws UserException {
+ try {
+ properties.keySet().forEach(SchemaManager::checkAlterTablePath);
+ } catch (UnsupportedOperationException e) {
+ throw new UserException("Failed to set properties for Paimon table
" + dorisTable.getName()
+ + ": " + ExceptionUtils.getRootCauseMessage(e), e);
+ }
+
List<SchemaChange> changes = new ArrayList<>(properties.size());
properties.forEach((key, value) ->
changes.add(SchemaChange.setOption(key, value)));
alterTable(dorisTable, changes, "set properties", updateTime);
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java
b/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java
index 55a3a63a5fa..ccb43dc8740 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java
@@ -19,6 +19,7 @@ package org.apache.doris.datasource.iceberg;
import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.Type;
+import org.apache.doris.common.DdlException;
import org.apache.doris.common.security.authentication.ExecutionAuthenticator;
import org.apache.doris.datasource.CatalogProperty;
import org.apache.doris.datasource.ExternalDatabase;
@@ -160,6 +161,111 @@ public class IcebergMetadataOpTest {
propsCaptor.getValue(), catalogProps));
}
+ @Test
+ public void testCreateDatabaseWithPropertiesForSupportedCatalogs() throws
Exception {
+ List<String> supportedCatalogTypes = Arrays.asList(
+ IcebergExternalCatalog.ICEBERG_HMS,
+ IcebergExternalCatalog.ICEBERG_JDBC,
+ IcebergExternalCatalog.ICEBERG_GLUE);
+ for (String catalogType : supportedCatalogTypes) {
+ String dbName = catalogType + "_db";
+ Catalog icebergCatalog = Mockito.mock(Catalog.class,
+
Mockito.withSettings().extraInterfaces(SupportsNamespaces.class));
+ SupportsNamespaces namespaceCatalog = (SupportsNamespaces)
icebergCatalog;
+ IcebergExternalCatalog dorisCatalog =
Mockito.mock(IcebergExternalCatalog.class);
+
Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new
ExecutionAuthenticator() {});
+
Mockito.when(dorisCatalog.getProperties()).thenReturn(Collections.emptyMap());
+
Mockito.when(dorisCatalog.getIcebergCatalogType()).thenReturn(catalogType);
+
Mockito.when(namespaceCatalog.namespaceExists(Namespace.of(dbName))).thenReturn(false);
+ IcebergMetadataOps ops = new IcebergMetadataOps(dorisCatalog,
icebergCatalog);
+ Map<String, String> properties = Collections.singletonMap("owner",
"doris");
+
+ Assert.assertFalse(ops.createDbImpl(dbName, false, properties));
+
+
Mockito.verify(namespaceCatalog).createNamespace(Namespace.of(dbName),
properties);
+ }
+ }
+
+ @Test
+ public void testCreateDatabaseWithLocationForSupportedCatalogs() throws
Exception {
+ List<String> supportedCatalogTypes = Arrays.asList(
+ IcebergExternalCatalog.ICEBERG_HMS,
+ IcebergExternalCatalog.ICEBERG_GLUE);
+ for (String catalogType : supportedCatalogTypes) {
+ String dbName = catalogType + "_location_db";
+ Catalog icebergCatalog = Mockito.mock(Catalog.class,
+
Mockito.withSettings().extraInterfaces(SupportsNamespaces.class));
+ SupportsNamespaces namespaceCatalog = (SupportsNamespaces)
icebergCatalog;
+ IcebergExternalCatalog dorisCatalog =
Mockito.mock(IcebergExternalCatalog.class);
+
Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new
ExecutionAuthenticator() {});
+
Mockito.when(dorisCatalog.getProperties()).thenReturn(Collections.emptyMap());
+
Mockito.when(dorisCatalog.getIcebergCatalogType()).thenReturn(catalogType);
+
Mockito.when(namespaceCatalog.namespaceExists(Namespace.of(dbName))).thenReturn(false);
+ IcebergMetadataOps ops = new IcebergMetadataOps(dorisCatalog,
icebergCatalog);
+ Map<String, String> properties = Collections.singletonMap(
+ "location", "s3://warehouse/" + dbName);
+
+ Assert.assertFalse(ops.createDbImpl(dbName, false, properties));
+
+
Mockito.verify(namespaceCatalog).createNamespace(Namespace.of(dbName),
properties);
+ }
+ }
+
+ @Test
+ public void testCreateDatabaseWithLocationForJdbcCatalogIsRejected() {
+ String dbName = "jdbc_location_db";
+ Catalog icebergCatalog = Mockito.mock(Catalog.class,
+
Mockito.withSettings().extraInterfaces(SupportsNamespaces.class));
+ SupportsNamespaces namespaceCatalog = (SupportsNamespaces)
icebergCatalog;
+ IcebergExternalCatalog dorisCatalog =
Mockito.mock(IcebergExternalCatalog.class);
+ Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new
ExecutionAuthenticator() {});
+
Mockito.when(dorisCatalog.getProperties()).thenReturn(Collections.emptyMap());
+
Mockito.when(dorisCatalog.getIcebergCatalogType()).thenReturn(IcebergExternalCatalog.ICEBERG_JDBC);
+
Mockito.when(namespaceCatalog.namespaceExists(Namespace.of(dbName))).thenReturn(false);
+ IcebergMetadataOps ops = new IcebergMetadataOps(dorisCatalog,
icebergCatalog);
+ Map<String, String> properties = Collections.singletonMap(
+ "location", "s3://warehouse/" + dbName);
+
+ DdlException exception = Assert.assertThrows(
+ DdlException.class,
+ () -> ops.createDbImpl(dbName, false, properties));
+
+ Assert.assertTrue(exception.getMessage().contains(
+ "database property 'location' for iceberg catalog type:
jdbc"));
+ Mockito.verify(namespaceCatalog, Mockito.never())
+ .createNamespace(Mockito.any(Namespace.class),
Mockito.anyMap());
+ }
+
+ @Test
+ public void testCreateDatabaseWithPropertiesForUnsupportedCatalogs() {
+ List<String> unsupportedCatalogTypes = Arrays.asList(
+ IcebergExternalCatalog.ICEBERG_HADOOP,
+ IcebergExternalCatalog.ICEBERG_REST,
+ IcebergExternalCatalog.ICEBERG_DLF,
+ IcebergExternalCatalog.ICEBERG_S3_TABLES);
+ for (String catalogType : unsupportedCatalogTypes) {
+ String dbName = catalogType + "_db";
+ Catalog icebergCatalog = Mockito.mock(Catalog.class,
+
Mockito.withSettings().extraInterfaces(SupportsNamespaces.class));
+ SupportsNamespaces namespaceCatalog = (SupportsNamespaces)
icebergCatalog;
+ IcebergExternalCatalog dorisCatalog =
Mockito.mock(IcebergExternalCatalog.class);
+
Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new
ExecutionAuthenticator() {});
+
Mockito.when(dorisCatalog.getProperties()).thenReturn(Collections.emptyMap());
+
Mockito.when(dorisCatalog.getIcebergCatalogType()).thenReturn(catalogType);
+
Mockito.when(namespaceCatalog.namespaceExists(Namespace.of(dbName))).thenReturn(false);
+ IcebergMetadataOps ops = new IcebergMetadataOps(dorisCatalog,
icebergCatalog);
+ Map<String, String> properties = Collections.singletonMap("owner",
"doris");
+
+ DdlException exception = Assert.assertThrows(
+ DdlException.class,
+ () -> ops.createDbImpl(dbName, false, properties));
+
+ Assert.assertTrue(exception.getMessage().contains("iceberg catalog
type: " + catalogType));
+ Mockito.verify(namespaceCatalog, Mockito.never())
+ .createNamespace(Mockito.any(Namespace.class),
Mockito.anyMap());
+ }
+ }
+
private IcebergExternalCatalog mockHmsCatalog(Map<String, String>
catalogProperties) {
IcebergExternalCatalog dorisCatalog =
Mockito.mock(IcebergExternalCatalog.class);
Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new
ExecutionAuthenticator() {
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/datasource/paimon/PaimonMetadataOpsTest.java
b/fe/fe-core/src/test/java/org/apache/doris/datasource/paimon/PaimonMetadataOpsTest.java
index 79b7d48e5c2..dc218ddc9f8 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/datasource/paimon/PaimonMetadataOpsTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/datasource/paimon/PaimonMetadataOpsTest.java
@@ -60,6 +60,7 @@ import org.mockito.Mockito;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -186,6 +187,25 @@ public class PaimonMetadataOpsTest {
changesCaptor.getValue());
}
+ @Test
+ public void testUpdateTablePropertiesRejectsPathBeforeRemoteAlter() throws
Exception {
+ String tableName = getTableName();
+ Catalog remoteCatalog = Mockito.mock(Catalog.class);
+ ExternalCatalog dorisCatalog = Mockito.mock(ExternalCatalog.class);
+ PaimonMetadataOps propertyOps = newMetadataOps(dorisCatalog,
remoteCatalog);
+ Map<String, String> properties = new LinkedHashMap<>();
+ properties.put("snapshot.num-retained.max", "10");
+ properties.put("PATH", "s3://warehouse/relocated_table");
+
+ UserException exception = Assert.assertThrows(UserException.class,
+ () ->
propertyOps.updateTableProperties(mockExternalTable(tableName), properties,
123L));
+
+ Assert.assertTrue(exception.getMessage().contains("Change path is not
supported yet"));
+ Mockito.verify(remoteCatalog, Mockito.never())
+ .alterTable(Mockito.any(Identifier.class), Mockito.anyList(),
Mockito.anyBoolean());
+ Mockito.verify(dorisCatalog,
Mockito.never()).getDbForReplay(Mockito.anyString());
+ }
+
@Test
public void testUpdateTablePropertiesRejectsInvalidBatchAtomically()
throws Exception {
String tableName = getTableName();
@@ -449,4 +469,102 @@ public class PaimonMetadataOpsTest {
Assert.assertTrue(t.getMessage().contains("database doesn't
exist"));
}
}
+
+ @Test
+ public void testCreateDatabaseWithPropertiesForSupportedCatalogs() throws
Exception {
+ List<String> supportedCatalogTypes = Arrays.asList(
+ PaimonExternalCatalog.PAIMON_HMS,
+ PaimonExternalCatalog.PAIMON_JDBC,
+ PaimonExternalCatalog.PAIMON_REST,
+ PaimonExternalCatalog.PAIMON_DLF);
+ for (String catalogType : supportedCatalogTypes) {
+ String remoteDbName = catalogType + "_db";
+ Catalog remoteCatalog = Mockito.mock(Catalog.class);
+ PaimonExternalCatalog dorisCatalog =
Mockito.mock(PaimonExternalCatalog.class);
+
Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new
ExecutionAuthenticator() {});
+
Mockito.when(dorisCatalog.getCatalogType()).thenReturn(catalogType);
+ Mockito.doThrow(new
Catalog.DatabaseNotExistException(remoteDbName))
+ .when(remoteCatalog).getDatabase(remoteDbName);
+ PaimonMetadataOps catalogOps = new PaimonMetadataOps(dorisCatalog,
remoteCatalog);
+ HashMap<String, String> properties = Maps.newHashMap();
+ properties.put("owner", "doris");
+
+ Assert.assertFalse(catalogOps.createDbImpl(remoteDbName, false,
properties));
+
+ Mockito.verify(remoteCatalog).createDatabase(remoteDbName, false,
properties);
+ }
+ }
+
+ @Test
+ public void testCreateDatabaseWithLocationForSupportedCatalogs() throws
Exception {
+ List<String> supportedCatalogTypes = Arrays.asList(
+ PaimonExternalCatalog.PAIMON_HMS,
+ PaimonExternalCatalog.PAIMON_DLF);
+ for (String catalogType : supportedCatalogTypes) {
+ String remoteDbName = catalogType + "_location_db";
+ Catalog remoteCatalog = Mockito.mock(Catalog.class);
+ PaimonExternalCatalog dorisCatalog =
Mockito.mock(PaimonExternalCatalog.class);
+
Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new
ExecutionAuthenticator() {});
+
Mockito.when(dorisCatalog.getCatalogType()).thenReturn(catalogType);
+ Mockito.doThrow(new
Catalog.DatabaseNotExistException(remoteDbName))
+ .when(remoteCatalog).getDatabase(remoteDbName);
+ PaimonMetadataOps catalogOps = new PaimonMetadataOps(dorisCatalog,
remoteCatalog);
+ HashMap<String, String> properties = Maps.newHashMap();
+ properties.put("location", "s3://warehouse/" + remoteDbName);
+
+ Assert.assertFalse(catalogOps.createDbImpl(remoteDbName, false,
properties));
+
+ Mockito.verify(remoteCatalog).createDatabase(remoteDbName, false,
properties);
+ }
+ }
+
+ @Test
+ public void
testCreateDatabaseWithLocationForCatalogsThatIgnoreItIsRejected() throws
Exception {
+ List<String> unsupportedCatalogTypes = Arrays.asList(
+ PaimonExternalCatalog.PAIMON_JDBC,
+ PaimonExternalCatalog.PAIMON_REST);
+ for (String catalogType : unsupportedCatalogTypes) {
+ String remoteDbName = catalogType + "_location_db";
+ Catalog remoteCatalog = Mockito.mock(Catalog.class);
+ PaimonExternalCatalog dorisCatalog =
Mockito.mock(PaimonExternalCatalog.class);
+
Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new
ExecutionAuthenticator() {});
+
Mockito.when(dorisCatalog.getCatalogType()).thenReturn(catalogType);
+ Mockito.doThrow(new
Catalog.DatabaseNotExistException(remoteDbName))
+ .when(remoteCatalog).getDatabase(remoteDbName);
+ PaimonMetadataOps catalogOps = new PaimonMetadataOps(dorisCatalog,
remoteCatalog);
+ HashMap<String, String> properties = Maps.newHashMap();
+ properties.put("location", "s3://warehouse/" + remoteDbName);
+
+ DdlException exception = Assert.assertThrows(
+ DdlException.class,
+ () -> catalogOps.createDbImpl(remoteDbName, false,
properties));
+
+ Assert.assertTrue(exception.getMessage().contains(
+ "database property 'location' for paimon catalog type: " +
catalogType));
+ Mockito.verify(remoteCatalog, Mockito.never())
+ .createDatabase(Mockito.anyString(), Mockito.anyBoolean(),
Mockito.anyMap());
+ }
+ }
+
+ @Test
+ public void
testCreateDatabaseWithPropertiesForFilesystemCatalogIsRejected() throws
Exception {
+ String filesystemDbName = "filesystem_db";
+ Catalog remoteCatalog = Mockito.mock(Catalog.class);
+ PaimonExternalCatalog dorisCatalog =
Mockito.mock(PaimonExternalCatalog.class);
+ Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new
ExecutionAuthenticator() {});
+
Mockito.when(dorisCatalog.getCatalogType()).thenReturn(PaimonExternalCatalog.PAIMON_FILESYSTEM);
+ Mockito.doThrow(new
Catalog.DatabaseNotExistException(filesystemDbName))
+ .when(remoteCatalog).getDatabase(filesystemDbName);
+ PaimonMetadataOps filesystemOps = new PaimonMetadataOps(dorisCatalog,
remoteCatalog);
+ HashMap<String, String> properties = Maps.newHashMap();
+ properties.put("owner", "doris");
+
+ DdlException exception = Assert.assertThrows(
+ DdlException.class,
+ () -> filesystemOps.createDbImpl(filesystemDbName, false,
properties));
+
+ Assert.assertTrue(exception.getMessage().contains("paimon catalog
type: filesystem"));
+ Mockito.verify(remoteCatalog, Mockito.never())
+ .createDatabase(Mockito.anyString(), Mockito.anyBoolean(),
Mockito.anyMap());
+ }
}
diff --git
a/regression-test/suites/external_table_p0/iceberg/test_iceberg_jdbc_catalog.groovy
b/regression-test/suites/external_table_p0/iceberg/test_iceberg_jdbc_catalog.groovy
index 3ad24e515c6..2ed8af6c586 100644
---
a/regression-test/suites/external_table_p0/iceberg/test_iceberg_jdbc_catalog.groovy
+++
b/regression-test/suites/external_table_p0/iceberg/test_iceberg_jdbc_catalog.groovy
@@ -153,7 +153,13 @@ suite("test_iceberg_jdbc_catalog",
"p0,external,iceberg,external_docker,external
// Test: Create database
sql """DROP DATABASE IF EXISTS ${db_name} FORCE"""
- sql """CREATE DATABASE ${db_name}"""
+ test {
+ sql """CREATE DATABASE ${db_name} PROPERTIES (
+ 'location' = 's3://warehouse/rejected_database_location/'
+ )"""
+ exception "database property 'location' for iceberg catalog type:
jdbc"
+ }
+ sql """CREATE DATABASE ${db_name} PROPERTIES ('owner' = 'doris')"""
def databases = sql """SHOW DATABASES"""
assertTrue(databases.toString().contains(db_name))
@@ -311,7 +317,7 @@ suite("test_iceberg_jdbc_catalog",
"p0,external,iceberg,external_docker,external
String mysql_db_name = "mysql_test_db"
sql """DROP DATABASE IF EXISTS ${mysql_db_name} FORCE"""
- sql """CREATE DATABASE ${mysql_db_name}"""
+ sql """CREATE DATABASE ${mysql_db_name} PROPERTIES ('owner' =
'doris')"""
sql """USE ${mysql_db_name}"""
sql """DROP TABLE IF EXISTS test_mysql_catalog"""
diff --git
a/regression-test/suites/external_table_p0/paimon/test_paimon_alter_properties.groovy
b/regression-test/suites/external_table_p0/paimon/test_paimon_alter_properties.groovy
index 69694b6f16d..47a093392e9 100644
---
a/regression-test/suites/external_table_p0/paimon/test_paimon_alter_properties.groovy
+++
b/regression-test/suites/external_table_p0/paimon/test_paimon_alter_properties.groovy
@@ -131,4 +131,13 @@ suite("test_paimon_alter_properties",
"p0,external,paimon") {
assertEquals(beforeSchemaId, schemaId())
assertEquals("8", optionValue("snapshot.num-retained.max"))
assertTrue(optionRows("fields.missing.sequence-group").isEmpty())
+
+ // Paimon exposes path as an internal option, but changing it does not
+ // relocate an existing table. Reject it before creating a schema version.
+ beforeSchemaId = schemaId()
+ test {
+ sql """ALTER TABLE `${tableName}` SET ('PATH' =
's3://warehouse/relocated_table')"""
+ exception "Change path is not supported yet"
+ }
+ assertEquals(beforeSchemaId, schemaId())
}
diff --git
a/regression-test/suites/external_table_p0/paimon/test_paimon_jdbc_catalog.groovy
b/regression-test/suites/external_table_p0/paimon/test_paimon_jdbc_catalog.groovy
index b57bd35617b..c9a27b344d6 100644
---
a/regression-test/suites/external_table_p0/paimon/test_paimon_jdbc_catalog.groovy
+++
b/regression-test/suites/external_table_p0/paimon/test_paimon_jdbc_catalog.groovy
@@ -265,7 +265,14 @@ suite("test_paimon_jdbc_catalog", "p0,external") {
assertTrue(catalogs.toString().contains(catalogName))
sql """DROP DATABASE IF EXISTS ${dbName} FORCE"""
- sql """CREATE DATABASE ${dbName}"""
+ test {
+ sql """CREATE DATABASE ${dbName} PROPERTIES (
+ 'location' =
's3://${warehouseBucket}/rejected_database_location/'
+ )"""
+ exception "database property 'location' for paimon catalog type:
jdbc"
+ }
+ // Paimon JDBC persists database properties in
paimon_database_properties.
+ sql """CREATE DATABASE ${dbName} PROPERTIES ('owner' = 'doris')"""
def databases = sql """SHOW DATABASES"""
assertTrue(databases.toString().contains(dbName))
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]