You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by mo...@apache.org on 2022/06/24 02:42:19 UTC

[doris] branch master updated: [feature-wip](multi-catalog) support to switch catalog (#10381)

This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 516f5b1789 [feature-wip](multi-catalog) support to switch catalog (#10381)
516f5b1789 is described below

commit 516f5b17894233771e86e17e6a950296b1ac596e
Author: Ashin Gau <As...@users.noreply.github.com>
AuthorDate: Fri Jun 24 10:42:13 2022 +0800

    [feature-wip](multi-catalog) support to switch catalog (#10381)
    
    Add `switch catalog` stmt with privilege check
---
 fe/fe-core/src/main/cup/sql_parser.cup             |  15 +-
 .../doris/analysis/AlterCatalogNameStmt.java       |  20 +--
 .../doris/analysis/AlterCatalogPropertyStmt.java   |  20 +--
 .../apache/doris/analysis/CreateCatalogStmt.java   |  13 +-
 .../org/apache/doris/analysis/DropCatalogStmt.java |  19 +--
 .../java/org/apache/doris/analysis/GrantStmt.java  |  10 +-
 .../{DropCatalogStmt.java => SwitchStmt.java}      |  55 +++----
 .../java/org/apache/doris/catalog/Catalog.java     |   9 ++
 .../java/org/apache/doris/common/ErrorCode.java    |   5 +-
 .../java/org/apache/doris/common/util/Util.java    |  28 ++++
 .../org/apache/doris/datasource/DataSourceMgr.java |  27 +++-
 .../apache/doris/mysql/privilege/DbPrivTable.java  |  18 +++
 .../org/apache/doris/mysql/privilege/PaloAuth.java |  52 ++++++-
 .../doris/mysql/privilege/TablePrivTable.java      |  17 +++
 .../java/org/apache/doris/qe/StmtExecutor.java     |  15 ++
 fe/fe-core/src/main/jflex/sql_scanner.flex         |   1 +
 .../doris/analysis/AlterCatalogNameStmtTest.java   |   2 +-
 .../doris/analysis/AlterCatalogPropsStmtTest.java  |   2 +-
 .../doris/analysis/CreateCatalogStmtTest.java      |   2 +-
 .../apache/doris/analysis/DropCatalogStmtTest.java |   2 +-
 .../org/apache/doris/analysis/SwitchStmtTest.java  | 161 +++++++++++++++++++++
 .../org/apache/doris/utframe/UtFrameUtils.java     |  14 +-
 22 files changed, 406 insertions(+), 101 deletions(-)

diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup
index 0a1b969218..3b1404fc2e 100644
--- a/fe/fe-core/src/main/cup/sql_parser.cup
+++ b/fe/fe-core/src/main/cup/sql_parser.cup
@@ -278,7 +278,8 @@ terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALIAS, KW_ALL, KW_A
     KW_WARNINGS, KW_WEEK, KW_WHEN, KW_WHITELIST, KW_WHERE, KW_WITH, KW_WORK, KW_WRITE,
     KW_YEAR,
     KW_NOT_NULL,
-    KW_CATALOG, KW_CATALOGS;
+    KW_CATALOG, KW_CATALOGS,
+    KW_SWITCH;
 
 terminal COMMA, COLON, DOT, DOTDOTDOT, AT, STAR, LPAREN, RPAREN, SEMICOLON, LBRACKET, RBRACKET, DIVIDE, MOD, ADD, SUBTRACT;
 terminal BITAND, BITOR, BITXOR, BITNOT;
@@ -301,7 +302,7 @@ nonterminal StatementBase stmt, show_stmt, show_param, help_stmt, load_stmt,
     show_routine_load_stmt, show_routine_load_task_stmt, show_create_routine_load_stmt,
     describe_stmt, alter_stmt,
     use_stmt, kill_stmt, drop_stmt, recover_stmt, grant_stmt, revoke_stmt, create_stmt, set_stmt, sync_stmt, cancel_stmt, cancel_param, delete_stmt,
-    link_stmt, migrate_stmt, enter_stmt, transaction_stmt, unsupported_stmt, export_stmt, admin_stmt, truncate_stmt,
+    link_stmt, migrate_stmt, switch_stmt, enter_stmt, transaction_stmt, unsupported_stmt, export_stmt, admin_stmt, truncate_stmt,
     import_columns_stmt, import_delete_on_stmt, import_sequence_stmt, import_where_stmt, install_plugin_stmt, uninstall_plugin_stmt,
     import_preceding_filter_stmt, unlock_tables_stmt, lock_tables_stmt, refresh_stmt;
 
@@ -670,6 +671,8 @@ stmt ::=
     {: RESULT = query; :}
     | migrate_stmt:query
     {: RESULT = query; :}
+    | switch_stmt:stmt
+    {: RESULT = stmt; :}
     | enter_stmt:enter
     {: RESULT = enter; :}
     | query_stmt:query
@@ -3422,6 +3425,14 @@ opt_set_qualifier ::=
   {: RESULT = Qualifier.ALL; :}
   ;
 
+// Change catalog
+switch_stmt ::=
+    KW_SWITCH ident:catalog
+    {:
+        RESULT = new SwitchStmt(catalog);
+    :}
+    ;
+
 // Change cluster
 enter_stmt ::=
     KW_ENTER ident:cluster
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterCatalogNameStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterCatalogNameStmt.java
index e8eb8bb473..67776bf510 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterCatalogNameStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterCatalogNameStmt.java
@@ -17,18 +17,14 @@
 
 package org.apache.doris.analysis;
 
-
-import org.apache.doris.analysis.CompoundPredicate.Operator;
 import org.apache.doris.catalog.Catalog;
 import org.apache.doris.common.AnalysisException;
-import org.apache.doris.common.Config;
 import org.apache.doris.common.ErrorCode;
 import org.apache.doris.common.ErrorReport;
 import org.apache.doris.common.FeNameFormat;
 import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.Util;
 import org.apache.doris.datasource.InternalDataSource;
-import org.apache.doris.mysql.privilege.PaloPrivilege;
-import org.apache.doris.mysql.privilege.PrivBitSet;
 import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.qe.ConnectContext;
 
@@ -57,21 +53,15 @@ public class AlterCatalogNameStmt extends DdlStmt {
     @Override
     public void analyze(Analyzer analyzer) throws UserException {
         super.analyze(analyzer);
-        if (!Config.enable_multi_catalog) {
-            throw new AnalysisException("The multi-catalog feature is still in experiment, and you can enable it "
-                    + "manually by set fe configuration named `enable_multi_catalog` to be ture.");
-        }
-        if (Strings.isNullOrEmpty(catalogName)) {
-            throw new AnalysisException("Datasource name is not set");
-        }
+        Util.checkCatalogAllRules(catalogName);
 
         if (catalogName.equals(InternalDataSource.INTERNAL_DS_NAME)) {
             throw new AnalysisException("Internal catalog can't be alter.");
         }
 
-        if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(),
-                PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, PaloPrivilege.ALTER_PRIV), Operator.OR))) {
-            ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR,
+        if (!Catalog.getCurrentCatalog().getAuth().checkCtlPriv(
+                ConnectContext.get(), catalogName, PrivPredicate.ALTER)) {
+            ErrorReport.reportAnalysisException(ErrorCode.ERR_CATALOG_ACCESS_DENIED,
                     analyzer.getQualifiedUser(), catalogName);
         }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterCatalogPropertyStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterCatalogPropertyStmt.java
index 7f39780eca..a19bf67fa0 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterCatalogPropertyStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterCatalogPropertyStmt.java
@@ -17,14 +17,17 @@
 
 package org.apache.doris.analysis;
 
+import org.apache.doris.catalog.Catalog;
 import org.apache.doris.common.AnalysisException;
-import org.apache.doris.common.Config;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
 import org.apache.doris.common.FeNameFormat;
 import org.apache.doris.common.UserException;
 import org.apache.doris.common.util.PrintableMap;
+import org.apache.doris.common.util.Util;
 import org.apache.doris.datasource.InternalDataSource;
-
-import com.google.common.base.Strings;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.qe.ConnectContext;
 
 import java.util.Map;
 
@@ -51,12 +54,11 @@ public class AlterCatalogPropertyStmt extends DdlStmt {
     @Override
     public void analyze(Analyzer analyzer) throws UserException {
         super.analyze(analyzer);
-        if (!Config.enable_multi_catalog) {
-            throw new AnalysisException("The multi-catalog feature is still in experiment, and you can enable it "
-                    + "manually by set fe configuration named `enable_multi_catalog` to be ture.");
-        }
-        if (Strings.isNullOrEmpty(catalogName)) {
-            throw new AnalysisException("Datasource name is not set");
+        Util.checkCatalogAllRules(catalogName);
+        if (!Catalog.getCurrentCatalog().getAuth().checkCtlPriv(
+                ConnectContext.get(), catalogName, PrivPredicate.ALTER)) {
+            ErrorReport.reportAnalysisException(ErrorCode.ERR_CATALOG_ACCESS_DENIED,
+                    analyzer.getQualifiedUser(), catalogName);
         }
 
         if (catalogName.equals(InternalDataSource.INTERNAL_DS_NAME)) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateCatalogStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateCatalogStmt.java
index ee89675d6c..d7ddd3320c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateCatalogStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateCatalogStmt.java
@@ -19,12 +19,12 @@ package org.apache.doris.analysis;
 
 import org.apache.doris.catalog.Catalog;
 import org.apache.doris.common.AnalysisException;
-import org.apache.doris.common.Config;
 import org.apache.doris.common.ErrorCode;
 import org.apache.doris.common.ErrorReport;
 import org.apache.doris.common.FeNameFormat;
 import org.apache.doris.common.UserException;
 import org.apache.doris.common.util.PrintableMap;
+import org.apache.doris.common.util.Util;
 import org.apache.doris.datasource.InternalDataSource;
 import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.qe.ConnectContext;
@@ -64,17 +64,14 @@ public class CreateCatalogStmt extends DdlStmt {
     @Override
     public void analyze(Analyzer analyzer) throws UserException {
         super.analyze(analyzer);
-        if (!Config.enable_multi_catalog) {
-            throw new AnalysisException("The multi-catalog feature is still in experiment, and you can enable it "
-                    + "manually by set fe configuration named `enable_multi_catalog` to be ture.");
-        }
+        Util.checkCatalogAllRules(catalogName);
         if (catalogName.equals(InternalDataSource.INTERNAL_DS_NAME)) {
             throw new AnalysisException("Internal catalog name can't be create.");
         }
-        FeNameFormat.checkCommonName("catalog", catalogName);
 
-        if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.CREATE)) {
-            ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR,
+        if (!Catalog.getCurrentCatalog().getAuth().checkCtlPriv(
+                ConnectContext.get(), catalogName, PrivPredicate.CREATE)) {
+            ErrorReport.reportAnalysisException(ErrorCode.ERR_CATALOG_ACCESS_DENIED,
                     analyzer.getQualifiedUser(), catalogName);
         }
         FeNameFormat.checkCatalogProperties(properties);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/DropCatalogStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/DropCatalogStmt.java
index aa620b17a4..6e5233916f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/DropCatalogStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/DropCatalogStmt.java
@@ -19,16 +19,14 @@ package org.apache.doris.analysis;
 
 import org.apache.doris.catalog.Catalog;
 import org.apache.doris.common.AnalysisException;
-import org.apache.doris.common.Config;
 import org.apache.doris.common.ErrorCode;
 import org.apache.doris.common.ErrorReport;
 import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.Util;
 import org.apache.doris.datasource.InternalDataSource;
 import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.qe.ConnectContext;
 
-import com.google.common.base.Strings;
-
 /**
  * Statement for drop a catalog.
  */
@@ -52,21 +50,16 @@ public class DropCatalogStmt extends DdlStmt {
     @Override
     public void analyze(Analyzer analyzer) throws UserException {
         super.analyze(analyzer);
-        if (!Config.enable_multi_catalog) {
-            throw new AnalysisException("The multi-catalog feature is still in experiment, and you can enable it "
-                    + "manually by set fe configuration named `enable_multi_catalog` to be ture.");
-        }
-        if (Strings.isNullOrEmpty(catalogName)) {
-            throw new AnalysisException("Datasource name is not set");
-        }
+        Util.checkCatalogAllRules(catalogName);
 
         if (catalogName.equals(InternalDataSource.INTERNAL_DS_NAME)) {
             throw new AnalysisException("Internal catalog can't be drop.");
         }
 
-        if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.DROP)) {
-            ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR,
-                    ConnectContext.get().getQualifiedUser(), catalogName);
+        if (!Catalog.getCurrentCatalog().getAuth().checkCtlPriv(
+                ConnectContext.get(), catalogName, PrivPredicate.DROP)) {
+            ErrorReport.reportAnalysisException(ErrorCode.ERR_CATALOG_ACCESS_DENIED,
+                    analyzer.getQualifiedUser(), catalogName);
         }
     }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
index 4ad8fe8073..9d5cf88967 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
@@ -168,15 +168,21 @@ public class GrantStmt extends DdlStmt {
                 if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
                     ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
                 }
+            } else if (tblPattern.getPrivLevel() == PrivLevel.CATALOG) {
+                if (!Catalog.getCurrentCatalog().getAuth().checkCtlPriv(ConnectContext.get(),
+                        tblPattern.getQualifiedCtl(), PrivPredicate.GRANT)) {
+                    ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
+                }
             } else if (tblPattern.getPrivLevel() == PrivLevel.DATABASE) {
                 if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(),
-                        tblPattern.getQualifiedDb(), PrivPredicate.GRANT)) {
+                        tblPattern.getQualifiedCtl(), tblPattern.getQualifiedDb(), PrivPredicate.GRANT)) {
                     ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
                 }
             } else {
                 // table level
                 if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(),
-                        tblPattern.getQualifiedDb(), tblPattern.getTbl(), PrivPredicate.GRANT)) {
+                        tblPattern.getQualifiedCtl(), tblPattern.getQualifiedDb(),
+                        tblPattern.getTbl(), PrivPredicate.GRANT)) {
                     ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
                 }
             }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/DropCatalogStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/SwitchStmt.java
similarity index 52%
copy from fe/fe-core/src/main/java/org/apache/doris/analysis/DropCatalogStmt.java
copy to fe/fe-core/src/main/java/org/apache/doris/analysis/SwitchStmt.java
index aa620b17a4..cf47511863 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/DropCatalogStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SwitchStmt.java
@@ -18,65 +18,48 @@
 package org.apache.doris.analysis;
 
 import org.apache.doris.catalog.Catalog;
-import org.apache.doris.common.AnalysisException;
-import org.apache.doris.common.Config;
 import org.apache.doris.common.ErrorCode;
 import org.apache.doris.common.ErrorReport;
 import org.apache.doris.common.UserException;
-import org.apache.doris.datasource.InternalDataSource;
+import org.apache.doris.common.util.Util;
 import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.qe.ConnectContext;
 
-import com.google.common.base.Strings;
-
-/**
- * Statement for drop a catalog.
- */
-public class DropCatalogStmt extends DdlStmt {
-    private final boolean ifExists;
+public class SwitchStmt extends StatementBase {
     private final String catalogName;
 
-    public DropCatalogStmt(boolean ifExists, String catalogName) {
-        this.ifExists = ifExists;
+    public SwitchStmt(String catalogName) {
         this.catalogName = catalogName;
     }
 
-    public boolean isSetIfExists() {
-        return ifExists;
+    public String getCatalogName() {
+        return catalogName;
     }
 
-    public String getCatalogName() {
-        return this.catalogName;
+    @Override
+    public String toSql() {
+        return "SWITCH `" + catalogName + "`";
     }
 
     @Override
+    public String toString() {
+        return toSql();
+    }
+
     public void analyze(Analyzer analyzer) throws UserException {
         super.analyze(analyzer);
-        if (!Config.enable_multi_catalog) {
-            throw new AnalysisException("The multi-catalog feature is still in experiment, and you can enable it "
-                    + "manually by set fe configuration named `enable_multi_catalog` to be ture.");
-        }
-        if (Strings.isNullOrEmpty(catalogName)) {
-            throw new AnalysisException("Datasource name is not set");
-        }
 
-        if (catalogName.equals(InternalDataSource.INTERNAL_DS_NAME)) {
-            throw new AnalysisException("Internal catalog can't be drop.");
-        }
+        Util.checkCatalogAllRules(catalogName);
 
-        if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.DROP)) {
-            ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR,
-                    ConnectContext.get().getQualifiedUser(), catalogName);
+        if (!Catalog.getCurrentCatalog().getAuth().checkCtlPriv(
+                ConnectContext.get(), catalogName, PrivPredicate.SHOW)) {
+            ErrorReport.reportAnalysisException(
+                    ErrorCode.ERR_CATALOG_ACCESS_DENIED, analyzer.getQualifiedUser(), catalogName);
         }
     }
 
     @Override
-    public String toSql() {
-        return "DROP CATALOG " + "`" + catalogName + "`";
-    }
-
-    @Override
-    public String toString() {
-        return toSql();
+    public RedirectStatus getRedirectStatus() {
+        return RedirectStatus.NO_FORWARD;
     }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Catalog.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/Catalog.java
index 4abd66d200..4b8c6a14da 100755
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Catalog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Catalog.java
@@ -4218,6 +4218,15 @@ public class Catalog {
         this.alter.getClusterHandler().cancel(stmt);
     }
 
+    // Switch catalog of this sesseion.
+    public void changeCatalog(ConnectContext ctx, String catalogName) throws DdlException {
+        if (dataSourceMgr.getCatalogNullable(catalogName) == null) {
+            throw new DdlException(ErrorCode.ERR_UNKNOWN_CATALOG.formatErrorMsg(
+                    catalogName), ErrorCode.ERR_UNKNOWN_CATALOG);
+        }
+        ctx.changeDefaultCatalog(catalogName);
+    }
+
     // Change current database of this session.
     public void changeDb(ConnectContext ctx, String qualifiedDb) throws DdlException {
         if (!auth.checkDbPriv(ctx, qualifiedDb, PrivPredicate.SHOW)) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java b/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java
index a8f73b9773..4fdd640b4e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java
@@ -1686,7 +1686,10 @@ public enum ErrorCode {
                     + "Use `SHOW PARTITIONS FROM %s` to see the currently partitions of this table. "),
     ERROR_SQL_AND_LIMITATIONS_SET_IN_ONE_RULE(5084, new byte[]{'4', '2', '0', '0', '0'},
             "sql/sqlHash and partition_num/tablet_num/cardinality cannot be set in one rule."),
-    ERR_WRONG_CATALOG_NAME(5085, new byte[]{'4', '2', '0', '0', '0'}, "Incorrect catalog name '%s'");
+    ERR_WRONG_CATALOG_NAME(5085, new byte[]{'4', '2', '0', '0', '0'}, "Incorrect catalog name '%s'"),
+    ERR_UNKNOWN_CATALOG(5086, new byte[]{'4', '2', '0', '0', '0'}, "Unknown catalog '%s'"),
+    ERR_CATALOG_ACCESS_DENIED(5087, new byte[]{'4', '2', '0', '0', '0'},
+            "Access denied for user '%s' to catalog '%s'");
 
     // This is error code
     private final int code;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/util/Util.java b/fe/fe-core/src/main/java/org/apache/doris/common/util/Util.java
index a63c40223e..b2857d26f3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/util/Util.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/Util.java
@@ -20,6 +20,9 @@ package org.apache.doris.common.util;
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.Config;
+import org.apache.doris.common.FeNameFormat;
+import org.apache.doris.datasource.InternalDataSource;
 import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.base.Preconditions;
@@ -457,4 +460,29 @@ public class Util {
         }
         return s;
     }
+
+    /**
+     * Multi-catalog feature is in experiment, and should be enabled by user manually.
+     */
+    public static void checkCatalogEnabled() throws AnalysisException {
+        if (!Config.enable_multi_catalog) {
+            throw new AnalysisException("The multi-catalog feature is still in experiment, and you can enable it "
+                    + "manually by set fe configuration named `enable_multi_catalog` to be ture.");
+        }
+    }
+
+    /**
+     * Check all rules of catalog.
+     */
+    public static void checkCatalogAllRules(String catalog) throws AnalysisException {
+        checkCatalogEnabled();
+
+        if (Strings.isNullOrEmpty(catalog)) {
+            throw new AnalysisException("Catalog name is empty.");
+        }
+
+        if (!catalog.equals(InternalDataSource.INTERNAL_DS_NAME)) {
+            FeNameFormat.checkCommonName("catalog", catalog);
+        }
+    }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/DataSourceMgr.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/DataSourceMgr.java
index 1192eb2b22..d77cc9a18d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/DataSourceMgr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/DataSourceMgr.java
@@ -25,11 +25,15 @@ import org.apache.doris.analysis.ShowCatalogStmt;
 import org.apache.doris.catalog.Catalog;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
 import org.apache.doris.common.UserException;
 import org.apache.doris.common.io.Text;
 import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.persist.OperationType;
 import org.apache.doris.persist.gson.GsonUtils;
+import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.qe.ShowResultSet;
 
 import com.google.common.collect.Lists;
@@ -150,6 +154,13 @@ public class DataSourceMgr implements Writable {
         Catalog.getCurrentCatalog().getEditLog().logDatasourceLog(OperationType.OP_ALTER_DS_PROPS, log);
     }
 
+    /**
+     * Get catalog, or null if not exists.
+     */
+    public DataSourceIf getCatalogNullable(String catalogName) {
+        return nameToCatalogs.get(catalogName);
+    }
+
     /**
      * List all catalog or get the special catalog with a name.
      */
@@ -159,16 +170,24 @@ public class DataSourceMgr implements Writable {
         try {
             if (showStmt.getCatalogName() == null) {
                 for (DataSourceIf ds : nameToCatalogs.values()) {
-                    List<String> row = Lists.newArrayList();
-                    row.add(ds.getName());
-                    row.add(ds.getType());
-                    rows.add(row);
+                    if (Catalog.getCurrentCatalog().getAuth().checkCtlPriv(
+                            ConnectContext.get(), ds.getName(), PrivPredicate.SHOW)) {
+                        List<String> row = Lists.newArrayList();
+                        row.add(ds.getName());
+                        row.add(ds.getType());
+                        rows.add(row);
+                    }
                 }
             } else {
                 if (!nameToCatalogs.containsKey(showStmt.getCatalogName())) {
                     throw new AnalysisException("No catalog found with name: " + showStmt.getCatalogName());
                 }
                 DataSourceIf ds = nameToCatalogs.get(showStmt.getCatalogName());
+                if (!Catalog.getCurrentCatalog().getAuth().checkCtlPriv(
+                        ConnectContext.get(), ds.getName(), PrivPredicate.SHOW)) {
+                    ErrorReport.reportAnalysisException(ErrorCode.ERR_CATALOG_ACCESS_DENIED,
+                            ConnectContext.get().getQualifiedUser(), ds.getName());
+                }
                 for (Map.Entry<String, String>  elem : ds.getProperties().entrySet()) {
                     List<String> row = Lists.newArrayList();
                     row.add(elem.getKey());
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java
index 87ef9ad50e..961cd1cb8c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivTable.java
@@ -21,6 +21,7 @@ import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.common.io.Text;
 import org.apache.doris.qe.ConnectContext;
 
+import com.google.common.base.Preconditions;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -66,6 +67,23 @@ public class DbPrivTable extends PrivTable {
         savedPrivs.or(matchedEntry.getPrivSet());
     }
 
+    public boolean hasPrivsOfCatalog(UserIdentity currentUser, String ctl) {
+        for (PrivEntry entry : entries) {
+            DbPrivEntry dbPrivEntry = (DbPrivEntry) entry;
+
+            if (!dbPrivEntry.match(currentUser, true)) {
+                continue;
+            }
+
+            // check catalog
+            Preconditions.checkState(!dbPrivEntry.isAnyCtl());
+            if (dbPrivEntry.getCtlPattern().match(ctl)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public boolean hasClusterPriv(ConnectContext ctx, String clusterName) {
         for (PrivEntry entry : entries) {
             DbPrivEntry dbPrivEntry = (DbPrivEntry) entry;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java
index 9459f14ed9..e54232e024 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java
@@ -353,6 +353,35 @@ public class PaloAuth implements Writable {
         return false;
     }
 
+    public boolean checkCtlPriv(ConnectContext ctx, String ctl, PrivPredicate wanted) {
+        return checkCtlPriv(ctx.getCurrentUserIdentity(), ctl, wanted);
+    }
+
+    public boolean checkCtlPriv(UserIdentity currentUser, String ctl, PrivPredicate wanted) {
+        if (!Config.enable_auth_check) {
+            return true;
+        }
+        if (wanted.getPrivs().containsNodePriv()) {
+            LOG.debug("should not check NODE priv in catalog level. user: {}, catalog: {}",
+                    currentUser, ctl);
+            return false;
+        }
+
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        if (checkGlobalInternal(currentUser, wanted, savedPrivs)
+                || checkCatalogInternal(currentUser, ctl, wanted, savedPrivs)) {
+            return true;
+        }
+
+        // if user has any privs of databases or tables in this catalog, and the wanted priv is SHOW, return true
+        if (ctl != null && wanted == PrivPredicate.SHOW && checkAnyPrivWithinCatalog(currentUser, ctl)) {
+            return true;
+        }
+
+        LOG.debug("failed to get wanted privs: {}, granted: {}", wanted, savedPrivs);
+        return false;
+    }
+
     public boolean checkDbPriv(ConnectContext ctx, String qualifiedDb, PrivPredicate wanted) {
         return checkDbPriv(ctx.getCurrentUserIdentity(), qualifiedDb, wanted);
     }
@@ -361,6 +390,10 @@ public class PaloAuth implements Writable {
         return checkDbPriv(currentUser, DEFAULT_CATALOG, db, wanted);
     }
 
+    public boolean checkDbPriv(ConnectContext ctx, String ctl, String db, PrivPredicate wanted) {
+        return checkDbPriv(ctx.getCurrentUserIdentity(), ctl, db, wanted);
+    }
+
     /*
      * Check if 'user'@'host' on 'db' has 'wanted' priv.
      * If the given db is null, which means it will no check if database name is matched.
@@ -383,7 +416,7 @@ public class PaloAuth implements Writable {
         }
 
         // if user has any privs of table in this db, and the wanted priv is SHOW, return true
-        if (ctl != null && db != null && wanted == PrivPredicate.SHOW && checkTblWithDb(currentUser, ctl, db)) {
+        if (ctl != null && db != null && wanted == PrivPredicate.SHOW && checkAnyPrivWithinDb(currentUser, ctl, db)) {
             return true;
         }
 
@@ -391,12 +424,27 @@ public class PaloAuth implements Writable {
         return false;
     }
 
+    /*
+     * User may not have privs on a catalog, but have privs of databases or tables in this catalog.
+     * So we have to check if user has any privs of databases or tables in this catalog.
+     * if so, the catalog should be visible to this user.
+     */
+    private boolean checkAnyPrivWithinCatalog(UserIdentity currentUser, String ctl) {
+        readLock();
+        try {
+            return dbPrivTable.hasPrivsOfCatalog(currentUser, ctl)
+                    || tablePrivTable.hasPrivsOfCatalog(currentUser, ctl);
+        } finally {
+            readUnlock();
+        }
+    }
+
     /*
      * User may not have privs on a database, but have privs of tables in this database.
      * So we have to check if user has any privs of tables in this database.
      * if so, the database should be visible to this user.
      */
-    private boolean checkTblWithDb(UserIdentity currentUser, String ctl, String db) {
+    private boolean checkAnyPrivWithinDb(UserIdentity currentUser, String ctl, String db) {
         readLock();
         try {
             return (isLdapAuthEnabled() && LdapPrivsChecker.hasPrivsOfDb(currentUser, db))
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java
index 475452ac29..c4ded036f1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivTable.java
@@ -69,6 +69,23 @@ public class TablePrivTable extends PrivTable {
         savedPrivs.or(matchedEntry.getPrivSet());
     }
 
+    public boolean hasPrivsOfCatalog(UserIdentity currentUser, String ctl) {
+        for (PrivEntry entry : entries) {
+            TablePrivEntry tblPrivEntry = (TablePrivEntry) entry;
+
+            if (!tblPrivEntry.match(currentUser, true)) {
+                continue;
+            }
+
+            // check catalog
+            Preconditions.checkState(!tblPrivEntry.isAnyCtl());
+            if (tblPrivEntry.getCtlPattern().match(ctl)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public boolean hasPrivsOfDb(UserIdentity currentUser, String ctl, String db) {
         for (PrivEntry entry : entries) {
             TablePrivEntry tblPrivEntry = (TablePrivEntry) entry;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
index e9e6d4e215..ba4ab0d200 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
@@ -44,6 +44,7 @@ import org.apache.doris.analysis.SqlScanner;
 import org.apache.doris.analysis.StatementBase;
 import org.apache.doris.analysis.StmtRewriter;
 import org.apache.doris.analysis.StringLiteral;
+import org.apache.doris.analysis.SwitchStmt;
 import org.apache.doris.analysis.TransactionBeginStmt;
 import org.apache.doris.analysis.TransactionCommitStmt;
 import org.apache.doris.analysis.TransactionRollbackStmt;
@@ -414,6 +415,8 @@ public class StmtExecutor implements ProfileWriter {
                 handleSetStmt();
             } else if (parsedStmt instanceof EnterStmt) {
                 handleEnterStmt();
+            } else if (parsedStmt instanceof SwitchStmt) {
+                handleSwitchStmt();
             } else if (parsedStmt instanceof UseStmt) {
                 handleUseStmt();
             } else if (parsedStmt instanceof TransactionStmt) {
@@ -1410,6 +1413,18 @@ public class StmtExecutor implements ProfileWriter {
         context.getState().setOk();
     }
 
+    // Process switch catalog
+    private void handleSwitchStmt() throws AnalysisException {
+        SwitchStmt switchStmt = (SwitchStmt) parsedStmt;
+        try {
+            context.getCatalog().changeCatalog(context, switchStmt.getCatalogName());
+        } catch (DdlException e) {
+            context.getState().setError(e.getMysqlErrorCode(), e.getMessage());
+            return;
+        }
+        context.getState().setOk();
+    }
+
     // Process use statement.
     private void handleUseStmt() throws AnalysisException {
         UseStmt useStmt = (UseStmt) parsedStmt;
diff --git a/fe/fe-core/src/main/jflex/sql_scanner.flex b/fe/fe-core/src/main/jflex/sql_scanner.flex
index f7d3638c2a..ecc4a59b65 100644
--- a/fe/fe-core/src/main/jflex/sql_scanner.flex
+++ b/fe/fe-core/src/main/jflex/sql_scanner.flex
@@ -435,6 +435,7 @@ import org.apache.doris.qe.SqlModeHelper;
         keywordMap.put("not_null", new Integer(SqlParserSymbols.KW_NOT_NULL));
         keywordMap.put("catalog", new Integer(SqlParserSymbols.KW_CATALOG));
         keywordMap.put("catalogs", new Integer(SqlParserSymbols.KW_CATALOGS));
+        keywordMap.put("switch", new Integer(SqlParserSymbols.KW_SWITCH));
    }
     
   // map from token id to token description
diff --git a/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterCatalogNameStmtTest.java b/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterCatalogNameStmtTest.java
index c7f3e393d0..25c05d05dd 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterCatalogNameStmtTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterCatalogNameStmtTest.java
@@ -43,7 +43,7 @@ public class AlterCatalogNameStmtTest {
         Config.enable_multi_catalog = true;
         analyzer = AccessTestUtil.fetchAdminAnalyzer(false);
         MockedAuth.mockedAuth(auth);
-        MockedAuth.mockedConnectContext(ctx, "root", "127.0.0.1");
+        MockedAuth.mockedConnectContext(ctx, "root", "%");
     }
 
     @Test
diff --git a/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterCatalogPropsStmtTest.java b/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterCatalogPropsStmtTest.java
index 821d65498a..d1b8f09b55 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterCatalogPropsStmtTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterCatalogPropsStmtTest.java
@@ -46,7 +46,7 @@ public class AlterCatalogPropsStmtTest {
         Config.enable_multi_catalog = true;
         analyzer = AccessTestUtil.fetchAdminAnalyzer(false);
         MockedAuth.mockedAuth(auth);
-        MockedAuth.mockedConnectContext(ctx, "root", "127.0.0.1");
+        MockedAuth.mockedConnectContext(ctx, "root", "%");
     }
 
     @Test
diff --git a/fe/fe-core/src/test/java/org/apache/doris/analysis/CreateCatalogStmtTest.java b/fe/fe-core/src/test/java/org/apache/doris/analysis/CreateCatalogStmtTest.java
index 4d61e6c219..1313991c03 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/analysis/CreateCatalogStmtTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/CreateCatalogStmtTest.java
@@ -47,7 +47,7 @@ public class CreateCatalogStmtTest {
         Config.enable_multi_catalog = true;
         analyzer = AccessTestUtil.fetchAdminAnalyzer(true);
         MockedAuth.mockedAuth(auth);
-        MockedAuth.mockedConnectContext(ctx, "root", "127.0.0.1");
+        MockedAuth.mockedConnectContext(ctx, "root", "%");
     }
 
     @Test
diff --git a/fe/fe-core/src/test/java/org/apache/doris/analysis/DropCatalogStmtTest.java b/fe/fe-core/src/test/java/org/apache/doris/analysis/DropCatalogStmtTest.java
index 36357bfc7e..5ff0ce6056 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/analysis/DropCatalogStmtTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/DropCatalogStmtTest.java
@@ -44,7 +44,7 @@ public class DropCatalogStmtTest {
         Config.enable_multi_catalog = true;
         analyzer = AccessTestUtil.fetchAdminAnalyzer(true);
         MockedAuth.mockedAuth(auth);
-        MockedAuth.mockedConnectContext(ctx, "root", "127.0.0.1");
+        MockedAuth.mockedConnectContext(ctx, "root", "%");
     }
 
     @Test
diff --git a/fe/fe-core/src/test/java/org/apache/doris/analysis/SwitchStmtTest.java b/fe/fe-core/src/test/java/org/apache/doris/analysis/SwitchStmtTest.java
new file mode 100644
index 0000000000..d90a8a68d2
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/SwitchStmtTest.java
@@ -0,0 +1,161 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.analysis;
+
+import org.apache.doris.catalog.Catalog;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.Config;
+import org.apache.doris.datasource.InternalDataSource;
+import org.apache.doris.mysql.privilege.PaloAuth;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.utframe.DorisAssert;
+import org.apache.doris.utframe.UtFrameUtils;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.UUID;
+
+public class SwitchStmtTest {
+    private static String runningDir = "fe/mocked/DemoTest/" + UUID.randomUUID().toString() + "/";
+    private static DorisAssert dorisAssert;
+    private static String clusterName = "default_cluster";
+
+    private static PaloAuth auth;
+    private static Catalog catalog;
+    private static UserIdentity user1;
+    private static UserIdentity user2;
+
+    @AfterClass
+    public static void tearDown() throws Exception {
+        UtFrameUtils.cleanDorisFeDir(runningDir);
+    }
+
+    @BeforeClass
+    public static void setUp() throws Exception {
+        Config.enable_multi_catalog = true;
+        UtFrameUtils.createDorisCluster(runningDir);
+
+        // use root to initialize.
+        ConnectContext rootCtx = UtFrameUtils.createDefaultCtx();
+        catalog = Catalog.getCurrentCatalog();
+        auth = catalog.getAuth();
+
+        // grant with no catalog is switched, internal catalog works.
+        CreateRoleStmt createRole1 = (CreateRoleStmt) UtFrameUtils.parseAndAnalyzeStmt("create role role1;", rootCtx);
+        auth.createRole(createRole1);
+        GrantStmt grantRole1 = (GrantStmt) UtFrameUtils.parseAndAnalyzeStmt(
+                "grant grant_priv on tpch.* to role 'role1';", rootCtx);
+        auth.grant(grantRole1);
+        // user1 can't switch to hive
+        auth.createUser((CreateUserStmt) UtFrameUtils.parseAndAnalyzeStmt(
+                "create user 'user1'@'%' identified by 'pwd1' default role 'role1';", rootCtx));
+        user1 = new UserIdentity("user1", "%");
+        user1.analyze(clusterName);
+
+        // create catalog
+        CreateCatalogStmt hiveCatalog = (CreateCatalogStmt) UtFrameUtils.parseAndAnalyzeStmt(
+                "create catalog hive properties('type' = 'hms', 'hive.metastore.uris' = 'thrift://192.168.0.1:9083');",
+                rootCtx);
+        catalog.getDataSourceMgr().createCatalog(hiveCatalog);
+        CreateCatalogStmt iceBergCatalog = (CreateCatalogStmt) UtFrameUtils.parseAndAnalyzeStmt(
+                "create catalog iceberg properties('type' = 'hms', 'iceberg.hive.metastore.uris' = 'thrift://192.168.0.1:9083');",
+                rootCtx);
+        catalog.getDataSourceMgr().createCatalog(iceBergCatalog);
+
+        // switch to hive.
+        SwitchStmt switchHive = (SwitchStmt) UtFrameUtils.parseAndAnalyzeStmt("switch hive;", rootCtx);
+        catalog.changeCatalog(rootCtx, switchHive.getCatalogName());
+        CreateRoleStmt createRole2 = (CreateRoleStmt) UtFrameUtils.parseAndAnalyzeStmt("create role role2;", rootCtx);
+        auth.createRole(createRole2);
+        GrantStmt grantRole2 = (GrantStmt) UtFrameUtils.parseAndAnalyzeStmt(
+                "grant grant_priv on tpch.customer to role 'role2';", rootCtx);
+        auth.grant(grantRole2);
+        auth.createUser((CreateUserStmt) UtFrameUtils.parseAndAnalyzeStmt(
+                "create user 'user2'@'%' identified by 'pwd2' default role 'role2';", rootCtx));
+        user2 = new UserIdentity("user2", "%");
+        user2.analyze(clusterName);
+    }
+
+    @Test
+    public void testSwitchCommand() throws Exception {
+        // mock the login of user1
+        ConnectContext user1Ctx = UtFrameUtils.createDefaultCtx(user1, "127.0.0.1");
+        // user1 can switch to internal catalog
+        UtFrameUtils.parseAndAnalyzeStmt("switch " + InternalDataSource.INTERNAL_DS_NAME + ";", user1Ctx);
+        Assert.assertEquals(InternalDataSource.INTERNAL_DS_NAME, user1Ctx.getDefaultCatalog());
+        // user1 can't switch to hive
+        try {
+            UtFrameUtils.parseAndAnalyzeStmt("switch hive;", user1Ctx);
+            Assert.fail("user1 switch to hive with no privilege.");
+        } catch (AnalysisException e) {
+            Assert.assertEquals(e.getMessage(),
+                    "errCode = 2, detailMessage = Access denied for user 'default_cluster:user1' to catalog 'hive'");
+        }
+        Assert.assertEquals(InternalDataSource.INTERNAL_DS_NAME, user1Ctx.getDefaultCatalog());
+
+        // mock the login of user2
+        ConnectContext user2Ctx = UtFrameUtils.createDefaultCtx(user2, "127.0.0.1");
+        // user2 can switch to internal catalog
+        UtFrameUtils.parseAndAnalyzeStmt("switch " + InternalDataSource.INTERNAL_DS_NAME + ";", user2Ctx);
+        Assert.assertEquals(InternalDataSource.INTERNAL_DS_NAME, user2Ctx.getDefaultCatalog());
+        // user2 can switch to hive
+        SwitchStmt switchHive = (SwitchStmt) UtFrameUtils.parseAndAnalyzeStmt("switch hive;", user2Ctx);
+        catalog.changeCatalog(user2Ctx, switchHive.getCatalogName());
+        Assert.assertEquals(user2Ctx.getDefaultCatalog(), "hive");
+        // user2 can grant select_priv to tpch.customer
+        GrantStmt user2GrantHiveTable = (GrantStmt) UtFrameUtils.parseAndAnalyzeStmt(
+                "grant select_priv on tpch.customer to 'user2'@'%';", user2Ctx);
+        auth.grant(user2GrantHiveTable);
+    }
+
+    @Test
+    public void testShowCatalogStmtWithPrivileges() throws Exception {
+        // mock the login of user1
+        ConnectContext user1Ctx = UtFrameUtils.createDefaultCtx(user1, "127.0.0.1");
+        ShowCatalogStmt user1Show = (ShowCatalogStmt) UtFrameUtils.parseAndAnalyzeStmt("show catalogs;", user1Ctx);
+        List<List<String>> user1ShowResult = catalog.getDataSourceMgr().showCatalogs(user1Show).getResultRows();
+        Assert.assertEquals(user1ShowResult.size(), 1);
+        Assert.assertEquals(user1ShowResult.get(0).get(0), InternalDataSource.INTERNAL_DS_NAME);
+
+        // mock the login of user1
+        ConnectContext user2Ctx = UtFrameUtils.createDefaultCtx(user2, "127.0.0.1");
+        ShowCatalogStmt user2Show = (ShowCatalogStmt) UtFrameUtils.parseAndAnalyzeStmt("show catalogs;", user2Ctx);
+        List<List<String>> user2ShowResult = catalog.getDataSourceMgr().showCatalogs(user2Show).getResultRows();
+        Assert.assertEquals(user2ShowResult.size(), 2);
+        Assert.assertTrue(user2ShowResult.stream().map(l -> l.get(0)).anyMatch(c -> c.equals("hive")));
+
+        // access denied
+        ShowCatalogStmt user2ShowHive = (ShowCatalogStmt) UtFrameUtils.parseAndAnalyzeStmt("show catalog hive;",
+                user2Ctx);
+        List<List<String>> user2ShowHiveResult = catalog.getDataSourceMgr().showCatalogs(user2ShowHive).getResultRows();
+        Assert.assertTrue(
+                user2ShowHiveResult.stream().map(l -> l.get(0)).anyMatch(c -> c.equals("hive.metastore.uris")));
+        try {
+            catalog.getDataSourceMgr().showCatalogs(
+                    (ShowCatalogStmt) UtFrameUtils.parseAndAnalyzeStmt("show catalog iceberg;", user2Ctx));
+            Assert.fail("");
+        } catch (AnalysisException e) {
+            Assert.assertEquals(e.getMessage(),
+                    "errCode = 2, detailMessage = Access denied for user 'default_cluster:user2' to catalog 'iceberg'");
+        }
+    }
+}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java b/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java
index 9bbf6278b7..e822667383 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java
@@ -31,7 +31,6 @@ import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.FeConstants;
 import org.apache.doris.common.util.SqlParserUtils;
-import org.apache.doris.mysql.privilege.PaloAuth;
 import org.apache.doris.planner.Planner;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.qe.OriginStatement;
@@ -74,18 +73,23 @@ import java.util.UUID;
 public class UtFrameUtils {
 
     // Help to create a mocked ConnectContext.
-    public static ConnectContext createDefaultCtx() throws IOException {
+    public static ConnectContext createDefaultCtx(UserIdentity userIdentity, String remoteIp) throws IOException {
         SocketChannel channel = SocketChannel.open();
         ConnectContext ctx = new ConnectContext(channel);
         ctx.setCluster(SystemInfoService.DEFAULT_CLUSTER);
-        ctx.setCurrentUserIdentity(UserIdentity.ROOT);
-        ctx.setQualifiedUser(PaloAuth.ROOT_USER);
-        ctx.setRemoteIP("127.0.0.1");
+        ctx.setCurrentUserIdentity(userIdentity);
+        ctx.setQualifiedUser(userIdentity.getQualifiedUser());
+        ctx.setRemoteIP(remoteIp);
         ctx.setCatalog(Catalog.getCurrentCatalog());
         ctx.setThreadLocalInfo();
         return ctx;
     }
 
+    // Help to create a mocked ConnectContext for root.
+    public static ConnectContext createDefaultCtx() throws IOException {
+        return createDefaultCtx(UserIdentity.ROOT, "127.0.0.1");
+    }
+
     // Parse an origin stmt and analyze it. Return a StatementBase instance.
     public static StatementBase parseAndAnalyzeStmt(String originStmt, ConnectContext ctx)
             throws Exception {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org