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]

Reply via email to