diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 index 64ff9209bfe6f2..bfb6598bcbd913 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 @@ -870,11 +870,11 @@ supportedUnsetStatement supportedUseStatement : SWITCH catalog=identifier #switchCatalog + | USE (catalog=identifier DOT)? database=identifier #useDatabase ; unsupportedUseStatement - : USE (catalog=identifier DOT)? database=identifier #useDatabase - | USE ((catalog=identifier DOT)? database=identifier)? ATSIGN cluster=identifier #useCloudCluster + : USE ((catalog=identifier DOT)? database=identifier)? ATSIGN cluster=identifier #useCloudCluster ; unsupportedDmlStatement diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java index 951c8b342d5d9e..c8be5a2ca890ef 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java @@ -318,6 +318,7 @@ import org.apache.doris.nereids.DorisParser.UpdateAssignmentContext; import org.apache.doris.nereids.DorisParser.UpdateAssignmentSeqContext; import org.apache.doris.nereids.DorisParser.UpdateContext; +import org.apache.doris.nereids.DorisParser.UseDatabaseContext; import org.apache.doris.nereids.DorisParser.UserIdentifyContext; import org.apache.doris.nereids.DorisParser.UserVariableContext; import org.apache.doris.nereids.DorisParser.WhereClauseContext; @@ -671,6 +672,7 @@ import org.apache.doris.nereids.trees.plans.commands.refresh.RefreshCatalogCommand; import org.apache.doris.nereids.trees.plans.commands.refresh.RefreshDatabaseCommand; import org.apache.doris.nereids.trees.plans.commands.use.SwitchCommand; +import org.apache.doris.nereids.trees.plans.commands.use.UseCommand; import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate; import org.apache.doris.nereids.trees.plans.logical.LogicalCTE; import org.apache.doris.nereids.trees.plans.logical.LogicalExcept; @@ -5109,12 +5111,20 @@ public LogicalPlan visitShowQueryProfile(ShowQueryProfileContext ctx) { } @Override - public Object visitSwitchCatalog(SwitchCatalogContext ctx) { - String catalogName = ctx.catalog.getText(); - if (catalogName != null) { - return new SwitchCommand(catalogName); + public LogicalPlan visitSwitchCatalog(SwitchCatalogContext ctx) { + if (ctx.catalog != null) { + return new SwitchCommand(ctx.catalog.getText()); } - throw new AnalysisException("catalog name can not be null"); + throw new ParseException("catalog name can not be null"); + } + + @Override + public LogicalPlan visitUseDatabase(UseDatabaseContext ctx) { + if (ctx.database == null) { + throw new ParseException("database name can not be null"); + } + return ctx.catalog != null ? new UseCommand(ctx.catalog.getText(), ctx.database.getText()) + : new UseCommand(ctx.database.getText()); } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java index 9ed408dfe05b45..ef411b2f871995 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java @@ -260,5 +260,6 @@ public enum PlanType { CREATE_ROUTINE_LOAD_COMMAND, SHOW_TABLE_CREATION_COMMAND, SHOW_QUERY_PROFILE_COMMAND, - SWITCH_COMMAND + SWITCH_COMMAND, + USE_COMMAND } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/use/UseCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/use/UseCommand.java new file mode 100644 index 00000000000000..9223e7d5ad66ed --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/use/UseCommand.java @@ -0,0 +1,115 @@ +// 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.nereids.trees.plans.commands.use; + +import org.apache.doris.analysis.StmtType; +import org.apache.doris.catalog.Env; +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.mysql.privilege.PrivPredicate; +import org.apache.doris.nereids.trees.plans.PlanType; +import org.apache.doris.nereids.trees.plans.commands.Command; +import org.apache.doris.nereids.trees.plans.commands.NoForward; +import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.StmtExecutor; + +import com.google.common.base.Strings; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Representation of a use db statement. + */ +public class UseCommand extends Command implements NoForward { + private static final Logger LOG = LogManager.getLogger(UseCommand.class); + private String catalogName; + private String databaseName; + + public UseCommand(String databaseName) { + super(PlanType.USE_COMMAND); + this.databaseName = databaseName; + } + + public UseCommand(String catalogName, String databaseName) { + super(PlanType.USE_COMMAND); + this.catalogName = catalogName; + this.databaseName = databaseName; + } + + @Override + public void run(ConnectContext ctx, StmtExecutor executor) throws Exception { + validate(ctx); + handleUseStmt(ctx); + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitUseCommand(this, context); + } + + @Override + public StmtType stmtType() { + return StmtType.USE; + } + + private void validate(ConnectContext context) throws AnalysisException { + if (Strings.isNullOrEmpty(databaseName)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR); + } + String currentCatalogName = catalogName == null ? ConnectContext.get().getDefaultCatalog() : catalogName; + + if (!Env.getCurrentEnv().getAccessManager() + .checkDbPriv(ConnectContext.get(), currentCatalogName, databaseName, PrivPredicate.SHOW)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR, context.getQualifiedUser(), + databaseName); + } + } + + /** + * Process use statement. + */ + private void handleUseStmt(ConnectContext context) { + try { + if (catalogName != null) { + context.getEnv().changeCatalog(context, catalogName); + } + context.getEnv().changeDb(context, databaseName); + } catch (DdlException e) { + LOG.warn("The handling of the use command failed.", e); + context.getState().setError(e.getMysqlErrorCode(), e.getMessage()); + return; + } + context.getState().setOk(); + } + + /** + * Generate sql string. + */ + public String toSql() { + StringBuilder sb = new StringBuilder(); + sb.append("USE "); + if (catalogName != null) { + sb.append("`").append(catalogName).append("`."); + } + sb.append("`").append(databaseName).append("`"); + return sb.toString(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java index eb764be57c0097..9954a38f694388 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java @@ -145,6 +145,7 @@ import org.apache.doris.nereids.trees.plans.commands.refresh.RefreshCatalogCommand; import org.apache.doris.nereids.trees.plans.commands.refresh.RefreshDatabaseCommand; import org.apache.doris.nereids.trees.plans.commands.use.SwitchCommand; +import org.apache.doris.nereids.trees.plans.commands.use.UseCommand; /** CommandVisitor. */ public interface CommandVisitor { @@ -667,4 +668,8 @@ default R visitShowQueryProfileCommand(ShowQueryProfileCommand showQueryProfileC default R visitSwitchCommand(SwitchCommand switchCommand, C context) { return visitCommand(switchCommand, context); } + + default R visitUseCommand(UseCommand useCommand, C context) { + return visitCommand(useCommand, context); + } } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java index 9a46b810586eec..3ce7e64560ce1b 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java @@ -448,7 +448,7 @@ public void testParseStmtType() { sql = "use a"; plan = nereidsParser.parseSingle(sql); - Assertions.assertEquals(plan.stmtType(), StmtType.OTHER); + Assertions.assertEquals(plan.stmtType(), StmtType.USE); sql = "CREATE TABLE tbl (`id` INT NOT NULL) DISTRIBUTED BY HASH(`id`) BUCKETS 1"; plan = nereidsParser.parseSingle(sql); @@ -463,10 +463,12 @@ public void testParseStmtType() { public void testParseUse() { NereidsParser nereidsParser = new NereidsParser(); String sql = "use db"; - nereidsParser.parseSingle(sql); + LogicalPlan logicalPlan = nereidsParser.parseSingle(sql); + Assertions.assertEquals(logicalPlan.stmtType(), StmtType.USE); sql = "use catalog.db"; - nereidsParser.parseSingle(sql); + LogicalPlan logicalPlan1 = nereidsParser.parseSingle(sql); + Assertions.assertEquals(logicalPlan1.stmtType(), StmtType.USE); } @Test diff --git a/regression-test/data/nereids_p0/ddl/use/use_command_nereids.out b/regression-test/data/nereids_p0/ddl/use/use_command_nereids.out new file mode 100644 index 00000000000000..17a7eaf6d7e12d --- /dev/null +++ b/regression-test/data/nereids_p0/ddl/use/use_command_nereids.out @@ -0,0 +1,13 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !show_tables_db1 -- +tb1 + +-- !show_tables_db2 -- +tb2 + +-- !show_tables_db1 -- +tb1 + +-- !show_tables_db2 -- +tb2 + diff --git a/regression-test/suites/nereids_p0/ddl/use/use_command_nereids.groovy b/regression-test/suites/nereids_p0/ddl/use/use_command_nereids.groovy new file mode 100644 index 00000000000000..70e0f3403e5855 --- /dev/null +++ b/regression-test/suites/nereids_p0/ddl/use/use_command_nereids.groovy @@ -0,0 +1,79 @@ +// 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. + +suite("use_command_nereids") { + String db1 = "test_use_command_db1" + String db2 = "test_use_command_db2" + String tbl1 = "tb1" + String tbl2 = "tb2" + + sql """drop database if exists ${db1};""" + sql """drop database if exists ${db2};""" + // create database + sql """create database ${db1};""" + sql """create database ${db2};""" + //cloud-mode + if (isCloudMode()) { + return + } + // use command + checkNereidsExecute("use ${db1};") + + """drop table if exists ${tbl1};""" + sql """ create table ${db1}.${tbl1} + ( + c1 bigint, + c2 bigint + ) + ENGINE=OLAP + DUPLICATE KEY(c1, c2) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(c1) BUCKETS 1 + PROPERTIES ( + "replication_num" = "1" + ); + """ + qt_show_tables_db1 """show tables;""" + + checkNereidsExecute("use ${db2};") + """drop table if exists ${tbl2};""" + sql """ create table ${db2}.${tbl2} + ( + c1 bigint, + c2 bigint + ) + ENGINE=OLAP + DUPLICATE KEY(c1, c2) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(c1) BUCKETS 1 + PROPERTIES ( + "replication_num" = "1" + ); + """ + + qt_show_tables_db2 """show tables;""" + + checkNereidsExecute("use internal.${db1};") + qt_show_tables_db1 """show tables;""" + checkNereidsExecute("use internal.${db2};") + qt_show_tables_db2 """show tables;""" + + sql """drop table if exists ${db1}.${tbl1};""" + sql """drop table if exists ${db2}.${tbl2};""" + sql """drop database if exists ${db1};""" + sql """drop database if exists ${db2};""" +} \ No newline at end of file