From 4e0bd5ec1d123406a6db7b8d0ff8387c4baac773 Mon Sep 17 00:00:00 2001 From: Sridhar R M Date: Sat, 2 Nov 2024 15:00:13 +0000 Subject: [PATCH] [Enhancement] (nereids)implement showCreateViewCommand in nereids --- .../org/apache/doris/nereids/DorisParser.g4 | 2 +- .../nereids/parser/LogicalPlanBuilder.java | 7 + .../doris/nereids/trees/plans/PlanType.java | 1 + .../plans/commands/ShowCreateViewCommand.java | 134 ++++++++++++++++++ .../trees/plans/visitor/CommandVisitor.java | 5 + .../nereids_p0/show/test_show_create_view.out | 7 + .../show/test_show_create_view.groovy | 76 ++++++++++ 7 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCreateViewCommand.java create mode 100644 regression-test/data/nereids_p0/show/test_show_create_view.out create mode 100644 regression-test/suites/nereids_p0/show/test_show_create_view.groovy 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 b7fa04c2ef796fb..b319a74a5d0ecfe 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 @@ -195,6 +195,7 @@ supportedShowStatement | SHOW VIEW (FROM |IN) tableName=multipartIdentifier ((FROM | IN) database=identifier)? #showView + | SHOW CREATE VIEW name=multipartIdentifier #showCreateView ; unsupportedOtherStatement @@ -245,7 +246,6 @@ unsupportedShowStatement | SHOW STORAGE? ENGINES #showStorageEngines | SHOW AUTHORS #showAuthors | SHOW BRIEF? CREATE TABLE name=multipartIdentifier #showCreateTable - | SHOW CREATE VIEW name=multipartIdentifier #showCreateView | SHOW CREATE MATERIALIZED VIEW name=multipartIdentifier #showMaterializedView | SHOW CREATE (DATABASE | SCHEMA) name=multipartIdentifier #showCreateDatabase | SHOW CREATE CATALOG name=identifier #showCreateCatalog 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 8362d002d2b8135..820de509007ab3a 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 @@ -195,6 +195,7 @@ import org.apache.doris.nereids.DorisParser.ShowConstraintContext; import org.apache.doris.nereids.DorisParser.ShowCreateMTMVContext; import org.apache.doris.nereids.DorisParser.ShowCreateProcedureContext; +import org.apache.doris.nereids.DorisParser.ShowCreateViewContext; import org.apache.doris.nereids.DorisParser.ShowProcedureStatusContext; import org.apache.doris.nereids.DorisParser.ShowVariablesContext; import org.apache.doris.nereids.DorisParser.ShowViewContext; @@ -426,6 +427,7 @@ import org.apache.doris.nereids.trees.plans.commands.ShowConstraintsCommand; import org.apache.doris.nereids.trees.plans.commands.ShowCreateMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.ShowCreateProcedureCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowCreateViewCommand; import org.apache.doris.nereids.trees.plans.commands.ShowProcedureStatusCommand; import org.apache.doris.nereids.trees.plans.commands.ShowVariablesCommand; import org.apache.doris.nereids.trees.plans.commands.ShowViewCommand; @@ -4046,4 +4048,9 @@ public ShowViewCommand visitShowView(ShowViewContext ctx) { } return new ShowViewCommand(databaseName, new TableNameInfo(tableNameParts)); } + + public LogicalPlan visitShowCreateView(ShowCreateViewContext ctx) { + List nameParts = visitMultipartIdentifier(ctx.name); + return new ShowCreateViewCommand(new TableNameInfo(nameParts)); + } } 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 b9fb9e96f879272..23e06dd48991c3d 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 @@ -176,6 +176,7 @@ public enum PlanType { PREPARED_COMMAND, EXECUTE_COMMAND, SHOW_CONFIG_COMMAND, + SHOW_CREATE_VIEW_COMMAND, SHOW_VARIABLES_COMMAND, SHOW_VIEW_COMMAND, REPLAY_COMMAND diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCreateViewCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCreateViewCommand.java new file mode 100644 index 000000000000000..517129ddcd197a0 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCreateViewCommand.java @@ -0,0 +1,134 @@ +// 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; + +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.DatabaseIf; +import org.apache.doris.catalog.Env; +import org.apache.doris.catalog.MTMV; +import org.apache.doris.catalog.ScalarType; +import org.apache.doris.catalog.TableIf; +import org.apache.doris.catalog.View; +import org.apache.doris.common.AnalysisException; +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.info.TableNameInfo; +import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.ShowResultSet; +import org.apache.doris.qe.ShowResultSetMetaData; +import org.apache.doris.qe.StmtExecutor; + +import com.google.common.collect.Lists; + +import java.util.List; + +/** + * Represents the command for SHOW CREATE VIEW. + */ +public class ShowCreateViewCommand extends ShowCommand { + private static final ShowResultSetMetaData VIEW_META_DATA = + ShowResultSetMetaData.builder() + .addColumn(new Column("View", ScalarType.createVarchar(20))) + .addColumn(new Column("Create View", ScalarType.createVarchar(30))) + .addColumn(new Column("character_set_client", ScalarType.createVarchar(30))) + .addColumn(new Column("collation_connection", ScalarType.createVarchar(30))) + .build(); + + private final TableNameInfo tblNameInfo; + + public ShowCreateViewCommand(TableNameInfo tableNameInfo) { + super(PlanType.SHOW_CREATE_VIEW_COMMAND); + this.tblNameInfo = tableNameInfo; + } + + private void validate(ConnectContext ctx) throws AnalysisException { + tblNameInfo.analyze(ctx); + + TableIf tableIf = Env.getCurrentEnv().getCatalogMgr() + .getCatalogOrAnalysisException(tblNameInfo.getCtl()) + .getDbOrAnalysisException(tblNameInfo.getDb()).getTableOrAnalysisException(tblNameInfo.getTbl()); + + if (tableIf instanceof MTMV) { + ErrorReport.reportAnalysisException("not support async materialized view, " + + "please use `show create materialized view`"); + } + + PrivPredicate wanted; + if (tableIf instanceof View) { + wanted = PrivPredicate.SHOW_VIEW; + } else { + wanted = PrivPredicate.SHOW; + } + + if (!Env.getCurrentEnv().getAccessManager().checkTblPriv(ConnectContext.get(), + tblNameInfo.getCtl(), tblNameInfo.getDb(), tblNameInfo.getTbl(), wanted)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "SHOW CREATE TABLE", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + tblNameInfo.getDb() + ": " + tblNameInfo.getTbl()); + } + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitShowCreateViewCommand(this, context); + } + + @Override + public ShowResultSet doRun(ConnectContext ctx, StmtExecutor executor) throws Exception { + validate(ctx); + // If dbName is not provided, use the default database from the context + String effectiveDbName = (tblNameInfo.getDb() == null || tblNameInfo.getDb().isEmpty()) + ? ctx.getDatabase() : tblNameInfo.getDb(); + + // If catalog is not provided, use the default catalog (e.g., internal) + String effectiveCatalog = (tblNameInfo.getCtl() == null || tblNameInfo.getCtl().isEmpty()) + ? "internal" : tblNameInfo.getCtl(); + + // Fetch the catalog, database, and view metadata + Env env = Env.getCurrentEnv(); + DatabaseIf db = env.getCatalogMgr().getCatalogOrAnalysisException(effectiveCatalog) + .getDbOrMetaException(effectiveDbName); + TableIf view = db.getTableOrMetaException(tblNameInfo.getTbl()); + + if (!(view instanceof View)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_OBJECT, effectiveDbName, tblNameInfo.getTbl(), + "VIEW", "Use 'SHOW CREATE TABLE '" + tblNameInfo.getTbl()); + } + + List> rows = Lists.newArrayList(); + // Lock the view to ensure consistent metadata access + view.readLock(); + try { + List createViewStmt = Lists.newArrayList(); + env.getDdlStmt(null, null, view, createViewStmt, null, null, false, true, false, -1L, false, false); + + if (!createViewStmt.isEmpty()) { + rows.add(Lists.newArrayList(view.getName(), createViewStmt.get(0), "utf8mb4", "utf8mb4_0900_bin")); + } + } finally { + view.readUnlock(); + } + + // Set the result set and send it using the executor + return new ShowResultSet(VIEW_META_DATA, rows); + } +} 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 e89c9a3a5c5f1c6..daaeca4153f313d 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 @@ -51,6 +51,7 @@ import org.apache.doris.nereids.trees.plans.commands.ShowConstraintsCommand; import org.apache.doris.nereids.trees.plans.commands.ShowCreateMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.ShowCreateProcedureCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowCreateViewCommand; import org.apache.doris.nereids.trees.plans.commands.ShowProcedureStatusCommand; import org.apache.doris.nereids.trees.plans.commands.ShowVariablesCommand; import org.apache.doris.nereids.trees.plans.commands.ShowViewCommand; @@ -242,4 +243,8 @@ default R visitShowVariablesCommand(ShowVariablesCommand showVariablesCommand, C default R visitShowViewCommand(ShowViewCommand showViewCommand, C context) { return visitCommand(showViewCommand, context); } + + default R visitShowCreateViewCommand(ShowCreateViewCommand showCreateViewCommand, C context) { + return visitCommand(showCreateViewCommand, context); + } } diff --git a/regression-test/data/nereids_p0/show/test_show_create_view.out b/regression-test/data/nereids_p0/show/test_show_create_view.out new file mode 100644 index 000000000000000..8ac669337b482c7 --- /dev/null +++ b/regression-test/data/nereids_p0/show/test_show_create_view.out @@ -0,0 +1,7 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !cmd -- +view_show_create_view CREATE VIEW `view_show_create_view` AS SELECT `internal`.`regression_test_nereids_p0_show`.`table_for_view_test`.`id`, `internal`.`regression_test_nereids_p0_show`.`table_for_view_test`.`name` FROM `internal`.`regression_test_nereids_p0_show`.`table_for_view_test`; utf8mb4 utf8mb4_0900_bin + +-- !cmd -- +view_show_create_view_2 CREATE VIEW `view_show_create_view_2` AS SELECT `internal`.`regression_test_nereids_p0_show`.`table_for_view_test_2`.`key_field`, `internal`.`regression_test_nereids_p0_show`.`table_for_view_test_2`.`value` FROM `internal`.`regression_test_nereids_p0_show`.`table_for_view_test_2`; utf8mb4 utf8mb4_0900_bin + diff --git a/regression-test/suites/nereids_p0/show/test_show_create_view.groovy b/regression-test/suites/nereids_p0/show/test_show_create_view.groovy new file mode 100644 index 000000000000000..65ed4758a9ae66d --- /dev/null +++ b/regression-test/suites/nereids_p0/show/test_show_create_view.groovy @@ -0,0 +1,76 @@ +// 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("test_show_create_view", "query,arrow_flight_sql") { + String view_name = "view_show_create_view"; + String table_name = "table_for_view_test"; + try { + // Create a table for testing + sql """ + CREATE TABLE IF NOT EXISTS ${table_name} ( + id INT COMMENT "Primary key", + name STRING COMMENT "Name field" + ) + DISTRIBUTED BY HASH(id) BUCKETS 5 + PROPERTIES ("replication_num" = "1"); + """ + + // Create a view based on the created table + sql """ + CREATE VIEW IF NOT EXISTS ${view_name} AS + SELECT id, name FROM ${table_name} + """ + + // Execute the SHOW CREATE VIEW command + checkNereidsExecute("""show create view `${view_name}`;""") + qt_cmd("""show create view `${view_name}`;""") + } finally { + // Drop the view and table after testing + try_sql("DROP VIEW IF EXISTS `${view_name}`") + try_sql("DROP TABLE IF EXISTS `${table_name}`") + } + + // Additional case: Create another view based on a different table + String view_name_2 = "view_show_create_view_2"; + String table_name_2 = "table_for_view_test_2"; + try { + // Create another table for testing + sql """ + CREATE TABLE IF NOT EXISTS ${table_name_2} ( + `key_field` INT COMMENT "Key field", + `value` STRING COMMENT "Value field" + ) + DISTRIBUTED BY HASH(key_field) BUCKETS 3 + PROPERTIES ("replication_num" = "1"); + """ + + // Create a view based on the new table + sql """ + CREATE VIEW IF NOT EXISTS ${view_name_2} AS + SELECT key_field, value FROM ${table_name_2} + """ + + // Execute the SHOW CREATE VIEW command for the new view + checkNereidsExecute("""show create view `${view_name_2}`;""") + qt_cmd("""show create view `${view_name_2}`;""") + + } finally { + // Drop the view and table after testing + try_sql("DROP VIEW IF EXISTS `${view_name_2}`") + try_sql("DROP TABLE IF EXISTS `${table_name_2}`") + } +}