From 1113f951c34d734ed64de5f398bef485ebdf850d Mon Sep 17 00:00:00 2001 From: xy720 <22125576+xy720@users.noreply.github.com> Date: Fri, 27 Dec 2019 14:02:56 +0800 Subject: [PATCH] Alter view stmt (#2522) This commit adds a new statement named alter view, like ALTER VIEW view_name ( col_1, col_2, col_3, ) AS SELECT k1, k2, SUM(v1) FROM exampleDb.testTbl GROUP BY k1,k2 --- .../Data Definition/ALTER VIEW.md | 45 +++++ .../Data Definition/ALTER VIEW_EN.md | 44 +++++ fe/src/main/cup/sql_parser.cup | 5 + .../java/org/apache/doris/alter/Alter.java | 78 ++++++++ .../apache/doris/analysis/AlterViewStmt.java | 90 +++++++++ .../apache/doris/analysis/BaseViewStmt.java | 143 ++++++++++++++ .../apache/doris/analysis/CreateViewStmt.java | 105 +--------- .../org/apache/doris/catalog/Catalog.java | 17 +- .../java/org/apache/doris/catalog/View.java | 10 +- .../apache/doris/journal/JournalEntity.java | 6 + .../apache/doris/persist/AlterViewInfo.java | 76 ++++++++ .../org/apache/doris/persist/EditLog.java | 9 + .../apache/doris/persist/OperationType.java | 1 + .../java/org/apache/doris/qe/DdlExecutor.java | 3 + .../doris/analysis/AlterViewStmtTest.java | 181 ++++++++++++++++++ 15 files changed, 702 insertions(+), 111 deletions(-) create mode 100644 docs/documentation/cn/sql-reference/sql-statements/Data Definition/ALTER VIEW.md create mode 100644 docs/documentation/en/sql-reference/sql-statements/Data Definition/ALTER VIEW_EN.md create mode 100644 fe/src/main/java/org/apache/doris/analysis/AlterViewStmt.java create mode 100644 fe/src/main/java/org/apache/doris/analysis/BaseViewStmt.java create mode 100644 fe/src/main/java/org/apache/doris/persist/AlterViewInfo.java create mode 100644 fe/src/test/java/org/apache/doris/analysis/AlterViewStmtTest.java diff --git a/docs/documentation/cn/sql-reference/sql-statements/Data Definition/ALTER VIEW.md b/docs/documentation/cn/sql-reference/sql-statements/Data Definition/ALTER VIEW.md new file mode 100644 index 0000000000..2baff68999 --- /dev/null +++ b/docs/documentation/cn/sql-reference/sql-statements/Data Definition/ALTER VIEW.md @@ -0,0 +1,45 @@ + + +# ALTER VIEW +## description + 该语句用于修改一个view的定义 + 语法: + ALTER VIEW + [db_name.]view_name + (column1[ COMMENT "col comment"][, column2, ...]) + AS query_stmt + + 说明: + 1. 视图都是逻辑上的,其中的数据不会存储在物理介质上,在查询时视图将作为语句中的子查询,因此,修改视图的定义等价于修改query_stmt。 + 2. query_stmt 为任意支持的 SQL + +## example + 1、修改example_db上的视图example_view + + ALTER VIEW example_db.example_view + ( + c1 COMMENT "column 1", + c2 COMMENT "column 2", + c3 COMMENT "column 3" + ) + AS SELECT k1, k2, SUM(v1) FROM example_table + GROUP BY k1, k2 + + \ No newline at end of file diff --git a/docs/documentation/en/sql-reference/sql-statements/Data Definition/ALTER VIEW_EN.md b/docs/documentation/en/sql-reference/sql-statements/Data Definition/ALTER VIEW_EN.md new file mode 100644 index 0000000000..03b849ec25 --- /dev/null +++ b/docs/documentation/en/sql-reference/sql-statements/Data Definition/ALTER VIEW_EN.md @@ -0,0 +1,44 @@ + + +# ALTER VIEW +## description + This statement is used to modify the definition of a view + Syntax: + ALTER VIEW + [db_name.]view_name + (column1[ COMMENT "col comment"][, column2, ...]) + AS query_stmt + + Explain: + 1. View is logical, it isn't stored in the physical medium. When we querying, view will be embed as subqueries in query statement. Therefore, modifying the definition of views is equivalent to modifying query_stmt which is defined in view. + 2. query_stmt is arbitrarily supported SQL. + +## example + + 1. Modify example_view on the example_db + + ALTER VIEW example_db.example_view + ( + c1 COMMENT "column 1", + c2 COMMENT "column 2", + c3 COMMENT "column 3" + ) + AS SELECT k1, k2, SUM(v1) FROM example_table + GROUP BY k1, k2 \ No newline at end of file diff --git a/fe/src/main/cup/sql_parser.cup b/fe/src/main/cup/sql_parser.cup index 961386bca9..f330cb7598 100644 --- a/fe/src/main/cup/sql_parser.cup +++ b/fe/src/main/cup/sql_parser.cup @@ -615,6 +615,11 @@ alter_stmt ::= {: RESULT = new AlterTableStmt(tbl, clauses); :} + | KW_ALTER KW_VIEW table_name:tbl + opt_col_with_comment_list:columns KW_AS query_stmt:view_def + {: + RESULT = new AlterViewStmt(tbl, columns, view_def); + :} | KW_ALTER KW_SYSTEM alter_system_clause:clause {: RESULT = new AlterSystemStmt(clause); diff --git a/fe/src/main/java/org/apache/doris/alter/Alter.java b/fe/src/main/java/org/apache/doris/alter/Alter.java index fd1e88b942..ef95a8b2c2 100644 --- a/fe/src/main/java/org/apache/doris/alter/Alter.java +++ b/fe/src/main/java/org/apache/doris/alter/Alter.java @@ -24,6 +24,7 @@ import org.apache.doris.analysis.AddRollupClause; import org.apache.doris.analysis.AlterClause; import org.apache.doris.analysis.AlterSystemStmt; import org.apache.doris.analysis.AlterTableStmt; +import org.apache.doris.analysis.AlterViewStmt; import org.apache.doris.analysis.ColumnRenameClause; import org.apache.doris.analysis.CreateMaterializedViewStmt; import org.apache.doris.analysis.DropColumnClause; @@ -38,11 +39,13 @@ import org.apache.doris.analysis.RollupRenameClause; import org.apache.doris.analysis.TableName; import org.apache.doris.analysis.TableRenameClause; import org.apache.doris.catalog.Catalog; +import org.apache.doris.catalog.Column; import org.apache.doris.catalog.Database; import org.apache.doris.catalog.OlapTable; import org.apache.doris.catalog.OlapTable.OlapTableState; import org.apache.doris.catalog.Table; import org.apache.doris.catalog.Table.TableType; +import org.apache.doris.catalog.View; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.DdlException; import org.apache.doris.common.ErrorCode; @@ -51,6 +54,8 @@ import org.apache.doris.common.UserException; import com.google.common.base.Preconditions; +import org.apache.doris.persist.AlterViewInfo; +import org.apache.doris.qe.ConnectContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -266,6 +271,79 @@ public class Alter { } } + public void processAlterView(AlterViewStmt stmt, ConnectContext ctx) throws UserException { + TableName dbTableName = stmt.getTbl(); + String dbName = dbTableName.getDb(); + + Database db = Catalog.getInstance().getDb(dbName); + if (db == null) { + ErrorReport.reportDdlException(ErrorCode.ERR_BAD_DB_ERROR, dbName); + } + + String tableName = dbTableName.getTbl(); + db.writeLock(); + try { + Table table = db.getTable(tableName); + if (table == null) { + ErrorReport.reportDdlException(ErrorCode.ERR_BAD_TABLE_ERROR, tableName); + } + + if (table.getType() != TableType.VIEW) { + throw new DdlException("The specified table [" + tableName + "] is not a view"); + } + + View view = (View) table; + modifyViewDef(db, view, stmt.getInlineViewDef(), ctx.getSessionVariable().getSqlMode(), stmt.getColumns()); + } finally { + db.writeUnlock(); + } + } + + private void modifyViewDef(Database db, View view, String inlineViewDef, long sqlMode, List newFullSchema) throws DdlException { + String viewName = view.getName(); + + view.setInlineViewDefWithSqlMode(inlineViewDef, sqlMode); + try { + view.init(); + } catch (UserException e) { + throw new DdlException("failed to init view stmt", e); + } + view.setNewFullSchema(newFullSchema); + + db.dropTable(viewName); + db.createTable(view); + + AlterViewInfo alterViewInfo = new AlterViewInfo(db.getId(), view.getId(), inlineViewDef, sqlMode); + Catalog.getInstance().getEditLog().logModifyViewDef(alterViewInfo); + LOG.info("modify view[{}] definition to {}", viewName, inlineViewDef); + } + + public void replayModifyViewDef(AlterViewInfo alterViewInfo) throws DdlException { + long dbId = alterViewInfo.getDbId(); + long tableId = alterViewInfo.getTableId(); + String inlineViewDef = alterViewInfo.getInlineViewDef(); + + Database db = Catalog.getInstance().getDb(dbId); + db.writeLock(); + try { + View view = (View) db.getTable(tableId); + String viewName = view.getName(); + view.setInlineViewDefWithSqlMode(inlineViewDef, alterViewInfo.getSqlMode()); + try { + view.init(); + } catch (UserException e) { + throw new DdlException("failed to init view stmt", e); + } + + db.dropTable(viewName); + db.createTable(view); + + LOG.info("replay modify view[{}] definition to {}", viewName, inlineViewDef); + } finally { + db.writeUnlock(); + } + } + public void processAlterCluster(AlterSystemStmt stmt) throws UserException { clusterHandler.process(Arrays.asList(stmt.getAlterClause()), stmt.getClusterName(), null, null); } diff --git a/fe/src/main/java/org/apache/doris/analysis/AlterViewStmt.java b/fe/src/main/java/org/apache/doris/analysis/AlterViewStmt.java new file mode 100644 index 0000000000..2f3861936d --- /dev/null +++ b/fe/src/main/java/org/apache/doris/analysis/AlterViewStmt.java @@ -0,0 +1,90 @@ +// 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.ErrorCode; +import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.UserException; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.qe.ConnectContext; + +import java.util.List; + +// Alter view statement +public class AlterViewStmt extends BaseViewStmt { + + public AlterViewStmt(TableName tbl, List cols, QueryStmt queryStmt) { + super(tbl, cols, queryStmt); + } + + public TableName getTbl() { + return tableName; + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, UserException { + super.analyze(analyzer); + if (tableName == null) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_TABLES_USED); + } + tableName.analyze(analyzer); + + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tableName.getDb(), tableName.getTbl(), + PrivPredicate.ALTER)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "ALTER VIEW", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + tableName.getTbl()); + } + + if (cols != null) { + cloneStmt = viewDefStmt.clone(); + } + viewDefStmt.setNeedToSql(true); + Analyzer viewAnalyzer = new Analyzer(analyzer); + + viewDefStmt.analyze(viewAnalyzer); + createColumnAndViewDefs(analyzer); + } + + @Override + public String toSql() { + StringBuilder sb = new StringBuilder(); + sb.append("ALTER VIEW ").append(tableName.toSql()).append("\n"); + if (cols != null) { + sb.append("(\n"); + for (int i = 0 ; i < cols.size(); i++) { + if (i != 0) { + sb.append(",\n"); + } + sb.append(" ").append(cols.get(i).getColName()); + } + sb.append("\n)"); + } + sb.append("\n"); + sb.append("AS ").append(viewDefStmt.toSql()).append("\n"); + return sb.toString(); + } + + @Override + public String toString() { + return toSql(); + } +} diff --git a/fe/src/main/java/org/apache/doris/analysis/BaseViewStmt.java b/fe/src/main/java/org/apache/doris/analysis/BaseViewStmt.java new file mode 100644 index 0000000000..5cc71402e6 --- /dev/null +++ b/fe/src/main/java/org/apache/doris/analysis/BaseViewStmt.java @@ -0,0 +1,143 @@ +// 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 com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.PrimitiveType; +import org.apache.doris.catalog.ScalarType; +import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.UserException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class BaseViewStmt extends DdlStmt { + private static final Logger LOG = LogManager.getLogger(BaseViewStmt.class); + + protected final TableName tableName; + protected final List cols; + protected final QueryStmt viewDefStmt; + + // Set during analyze + protected final List finalCols; + + protected String originalViewDef; + protected String inlineViewDef; + protected QueryStmt cloneStmt; + + public BaseViewStmt(TableName tableName, List cols, QueryStmt queryStmt) { + this.tableName = tableName; + this.cols = cols; + this.viewDefStmt = queryStmt; + finalCols = Lists.newArrayList(); + } + + public String getDbName() { + return tableName.getDb(); + } + + public String getTable() { + return tableName.getTbl(); + } + + + public List getColumns() { + return finalCols; + } + + public String getInlineViewDef() { + return inlineViewDef; + } + + /** + * Sets the originalViewDef and the expanded inlineViewDef based on viewDefStmt. + * If columnNames were given, checks that they do not contain duplicate column names + * and throws an exception if they do. + */ + protected void createColumnAndViewDefs(Analyzer analyzer) throws AnalysisException, UserException { + if (cols != null) { + if (cols.size() != viewDefStmt.getColLabels().size()) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_VIEW_WRONG_LIST); + } + // TODO(zc): type + for (int i = 0; i < cols.size(); ++i) { + PrimitiveType type = viewDefStmt.getBaseTblResultExprs().get(i).getType().getPrimitiveType(); + Column col = new Column(cols.get(i).getColName(), ScalarType.createType(type)); + col.setComment(cols.get(i).getComment()); + finalCols.add(col); + } + } else { + // TODO(zc): type + for (int i = 0; i < viewDefStmt.getBaseTblResultExprs().size(); ++i) { + PrimitiveType type = viewDefStmt.getBaseTblResultExprs().get(i).getType().getPrimitiveType(); + finalCols.add(new Column( + viewDefStmt.getColLabels().get(i), + ScalarType.createType(type))); + } + } + // Set for duplicate columns + Set colSets = Sets.newTreeSet(String.CASE_INSENSITIVE_ORDER); + for (Column col : finalCols) { + if (!colSets.add(col.getName())) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DUP_FIELDNAME, col.getName()); + } + } + + // format view def string + originalViewDef = viewDefStmt.toSql(); + + if (cols == null) { + inlineViewDef = originalViewDef; + return; + } + + Analyzer tmpAnalyzer = new Analyzer(analyzer); + List colNames = cols.stream().map(c -> c.getColName()).collect(Collectors.toList()); + cloneStmt.substituteSelectList(tmpAnalyzer, colNames); + inlineViewDef = cloneStmt.toSql(); + + // StringBuilder sb = new StringBuilder(); + // sb.append("SELECT "); + // for (int i = 0; i < columnNames.size(); ++i) { + // if (i != 0) { + // sb.append(", "); + // } + // String colRef = viewDefStmt.getColLabels().get(i); + // if (!colRef.startsWith("`")) { + // colRef = "`" + colRef + "`"; + // } + // String colAlias = finalCols.get(i).getName(); + + // sb.append(String.format("`%s`.%s AS `%s`", tableName.getTbl(), colRef, colAlias)); + // } + // sb.append(String.format(" FROM (%s) %s", originalViewDef, tableName.getTbl())); + // inlineViewDef = sb.toString(); + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, UserException { + super.analyze(analyzer); + } +} diff --git a/fe/src/main/java/org/apache/doris/analysis/CreateViewStmt.java b/fe/src/main/java/org/apache/doris/analysis/CreateViewStmt.java index adb008db3f..5328065ddf 100644 --- a/fe/src/main/java/org/apache/doris/analysis/CreateViewStmt.java +++ b/fe/src/main/java/org/apache/doris/analysis/CreateViewStmt.java @@ -18,9 +18,6 @@ package org.apache.doris.analysis; import org.apache.doris.catalog.Catalog; -import org.apache.doris.catalog.Column; -import org.apache.doris.catalog.PrimitiveType; -import org.apache.doris.catalog.ScalarType; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; @@ -29,131 +26,33 @@ import org.apache.doris.mysql.privilege.PrivPredicate; import org.apache.doris.qe.ConnectContext; import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -public class CreateViewStmt extends DdlStmt { +public class CreateViewStmt extends BaseViewStmt { private static final Logger LOG = LogManager.getLogger(CreateViewStmt.class); private final boolean ifNotExists; - private final TableName tableName; - private final List cols; private final String comment; - private final QueryStmt viewDefStmt; - - // Set during analyze - private final List finalCols; - - private String originalViewDef; - private String inlineViewDef; - private QueryStmt cloneStmt; public CreateViewStmt(boolean ifNotExists, TableName tableName, List cols, String comment, QueryStmt queryStmt) { + super(tableName, cols, queryStmt); this.ifNotExists = ifNotExists; - this.tableName = tableName; - this.cols = cols; this.comment = Strings.nullToEmpty(comment); - this.viewDefStmt = queryStmt; - finalCols = Lists.newArrayList(); - } - - public String getDbName() { - return tableName.getDb(); - } - - public String getTable() { - return tableName.getTbl(); } public boolean isSetIfNotExists() { return ifNotExists; } - public List getColumns() { - return finalCols; - } - - public String getInlineViewDef() { - return inlineViewDef; - } - public String getComment() { return comment; } - /** - * Sets the originalViewDef and the expanded inlineViewDef based on viewDefStmt. - * If columnNames were given, checks that they do not contain duplicate column names - * and throws an exception if they do. - */ - private void createColumnAndViewDefs(Analyzer analyzer) throws AnalysisException, UserException { - if (cols != null) { - if (cols.size() != viewDefStmt.getColLabels().size()) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_VIEW_WRONG_LIST); - } - // TODO(zc): type - for (int i = 0; i < cols.size(); ++i) { - PrimitiveType type = viewDefStmt.getBaseTblResultExprs().get(i).getType().getPrimitiveType(); - Column col = new Column(cols.get(i).getColName(), ScalarType.createType(type)); - col.setComment(cols.get(i).getComment()); - finalCols.add(col); - } - } else { - // TODO(zc): type - for (int i = 0; i < viewDefStmt.getBaseTblResultExprs().size(); ++i) { - PrimitiveType type = viewDefStmt.getBaseTblResultExprs().get(i).getType().getPrimitiveType(); - finalCols.add(new Column( - viewDefStmt.getColLabels().get(i), - ScalarType.createType(type))); - } - } - // Set for duplicate columns - Set colSets = Sets.newTreeSet(String.CASE_INSENSITIVE_ORDER); - for (Column col : finalCols) { - if (!colSets.add(col.getName())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DUP_FIELDNAME, col.getName()); - } - } - - // format view def string - originalViewDef = viewDefStmt.toSql(); - - if (cols == null) { - inlineViewDef = originalViewDef; - return; - } - - Analyzer tmpAnalyzer = new Analyzer(analyzer); - List colNames = cols.stream().map(c -> c.getColName()).collect(Collectors.toList()); - cloneStmt.substituteSelectList(tmpAnalyzer, colNames); - inlineViewDef = cloneStmt.toSql(); - - // StringBuilder sb = new StringBuilder(); - // sb.append("SELECT "); - // for (int i = 0; i < columnNames.size(); ++i) { - // if (i != 0) { - // sb.append(", "); - // } - // String colRef = viewDefStmt.getColLabels().get(i); - // if (!colRef.startsWith("`")) { - // colRef = "`" + colRef + "`"; - // } - // String colAlias = finalCols.get(i).getName(); - - // sb.append(String.format("`%s`.%s AS `%s`", tableName.getTbl(), colRef, colAlias)); - // } - // sb.append(String.format(" FROM (%s) %s", originalViewDef, tableName.getTbl())); - // inlineViewDef = sb.toString(); - } - @Override public void analyze(Analyzer analyzer) throws AnalysisException, UserException { if (cols != null) { diff --git a/fe/src/main/java/org/apache/doris/catalog/Catalog.java b/fe/src/main/java/org/apache/doris/catalog/Catalog.java index 7a502368e4..12b4abc62a 100644 --- a/fe/src/main/java/org/apache/doris/catalog/Catalog.java +++ b/fe/src/main/java/org/apache/doris/catalog/Catalog.java @@ -51,11 +51,13 @@ import org.apache.doris.analysis.DropDbStmt; import org.apache.doris.analysis.DropFunctionStmt; import org.apache.doris.analysis.DropPartitionClause; import org.apache.doris.analysis.DropTableStmt; +import org.apache.doris.analysis.TruncateTableStmt; import org.apache.doris.analysis.FunctionName; import org.apache.doris.analysis.HashDistributionDesc; import org.apache.doris.analysis.KeysDesc; import org.apache.doris.analysis.LinkDbStmt; import org.apache.doris.analysis.MigrateDbStmt; +import org.apache.doris.analysis.AlterViewStmt; import org.apache.doris.analysis.ModifyPartitionClause; import org.apache.doris.analysis.PartitionDesc; import org.apache.doris.analysis.PartitionRenameClause; @@ -67,10 +69,9 @@ import org.apache.doris.analysis.RestoreStmt; import org.apache.doris.analysis.RollupRenameClause; import org.apache.doris.analysis.ShowAlterStmt.AlterType; import org.apache.doris.analysis.SingleRangePartitionDesc; -import org.apache.doris.analysis.TableName; -import org.apache.doris.analysis.TableRef; import org.apache.doris.analysis.TableRenameClause; -import org.apache.doris.analysis.TruncateTableStmt; +import org.apache.doris.analysis.TableRef; +import org.apache.doris.analysis.TableName; import org.apache.doris.analysis.UserDesc; import org.apache.doris.analysis.UserIdentity; import org.apache.doris.backup.BackupHandler; @@ -4734,6 +4735,13 @@ public class Catalog { this.alter.processAlterTable(stmt); } + /** + * used for handling AlterViewStmt (the ALTER VIEW command). + */ + public void alterView(AlterViewStmt stmt) throws DdlException, UserException { + this.alter.processAlterView(stmt, ConnectContext.get()); + } + public void createMaterializedView(CreateMaterializedViewStmt stmt) throws AnalysisException, DdlException { // TODO(ml): remove it throw new AnalysisException("The materialized view is coming soon"); @@ -5134,7 +5142,8 @@ public class Catalog { long tableId = Catalog.getInstance().getNextId(); View newView = new View(tableId, tableName, columns); newView.setComment(stmt.getComment()); - newView.setInlineViewDef(stmt.getInlineViewDef()); + newView.setInlineViewDefWithSqlMode(stmt.getInlineViewDef(), + ConnectContext.get().getSessionVariable().getSqlMode()); // init here in case the stmt string from view.toSql() has some syntax error. try { newView.init(); diff --git a/fe/src/main/java/org/apache/doris/catalog/View.java b/fe/src/main/java/org/apache/doris/catalog/View.java index 7ac04bec8a..f2226c4d01 100644 --- a/fe/src/main/java/org/apache/doris/catalog/View.java +++ b/fe/src/main/java/org/apache/doris/catalog/View.java @@ -27,7 +27,6 @@ import org.apache.doris.common.io.Text; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; -import org.apache.doris.qe.ConnectContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -68,6 +67,9 @@ public class View extends Table { // Hive would produce in view creation. private String inlineViewDef; + // for persist + private long sqlMode = 0L; + // View definition created by parsing inlineViewDef_ into a QueryStmt. // 'queryStmt' is a strong reference, which is used when this view is created directly from a QueryStmt // 'queryStmtRef' is a soft reference, it is created from parsing query stmt, and it will be cleared if @@ -128,8 +130,9 @@ public class View extends Table { return retStmt; } - public void setInlineViewDef(String inlineViewDef) { + public void setInlineViewDefWithSqlMode(String inlineViewDef, long sqlMode) { this.inlineViewDef = inlineViewDef; + this.sqlMode = sqlMode; } public String getInlineViewDef() { @@ -146,8 +149,7 @@ public class View extends Table { Preconditions.checkNotNull(inlineViewDef); // Parse the expanded view definition SQL-string into a QueryStmt and // populate a view definition. - SqlScanner input = new SqlScanner(new StringReader(inlineViewDef), - ConnectContext.get().getSessionVariable().getSqlMode()); + SqlScanner input = new SqlScanner(new StringReader(inlineViewDef), sqlMode); SqlParser parser = new SqlParser(input); ParseNode node; try { diff --git a/fe/src/main/java/org/apache/doris/journal/JournalEntity.java b/fe/src/main/java/org/apache/doris/journal/JournalEntity.java index e3538c01f2..4282468715 100644 --- a/fe/src/main/java/org/apache/doris/journal/JournalEntity.java +++ b/fe/src/main/java/org/apache/doris/journal/JournalEntity.java @@ -43,6 +43,7 @@ import org.apache.doris.load.loadv2.LoadJobFinalOperation; import org.apache.doris.load.routineload.RoutineLoadJob; import org.apache.doris.master.Checkpoint; import org.apache.doris.mysql.privilege.UserPropertyInfo; +import org.apache.doris.persist.AlterViewInfo; import org.apache.doris.persist.BackendIdsUpdateInfo; import org.apache.doris.persist.BackendTabletsInfo; import org.apache.doris.persist.ClusterInfo; @@ -223,6 +224,11 @@ public class JournalEntity implements Writable { isRead = true; break; } + case OperationType.OP_MODIFY_VIEW_DEF: { + data = AlterViewInfo.read(in); + isRead = true; + break; + } case OperationType.OP_BACKUP_JOB: { data = BackupJob.read(in); isRead = true; diff --git a/fe/src/main/java/org/apache/doris/persist/AlterViewInfo.java b/fe/src/main/java/org/apache/doris/persist/AlterViewInfo.java new file mode 100644 index 0000000000..fbbccb5bc1 --- /dev/null +++ b/fe/src/main/java/org/apache/doris/persist/AlterViewInfo.java @@ -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. + +package org.apache.doris.persist; + +import com.google.gson.annotations.SerializedName; +import org.apache.doris.common.io.Text; +import org.apache.doris.common.io.Writable; +import org.apache.doris.persist.gson.GsonUtils; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class AlterViewInfo implements Writable { + @SerializedName(value = "dbId") + private long dbId; + @SerializedName(value = "tableId") + private long tableId; + @SerializedName(value = "inlineViewDef") + private String inlineViewDef; + @SerializedName(value = "sqlMode") + private long sqlMode; + + public AlterViewInfo() { + // for persist + } + + public AlterViewInfo(long dbId, long tableId, String inlineViewDef, long sqlMode) { + this.dbId = dbId; + this.tableId = tableId; + this.inlineViewDef = inlineViewDef; + this.sqlMode = sqlMode; + } + + public long getDbId() { + return dbId; + } + + public long getTableId() { + return tableId; + } + + public String getInlineViewDef() { + return inlineViewDef; + } + + public long getSqlMode() { + return sqlMode; + } + + @Override + public void write(DataOutput out) throws IOException { + String json = GsonUtils.GSON.toJson(this); + Text.writeString(out, json); + } + + public static AlterViewInfo read(DataInput in) throws IOException { + String json = Text.readString(in); + return GsonUtils.GSON.fromJson(json, AlterViewInfo.class); + } +} diff --git a/fe/src/main/java/org/apache/doris/persist/EditLog.java b/fe/src/main/java/org/apache/doris/persist/EditLog.java index 942e4f8e05..3d6c016852 100644 --- a/fe/src/main/java/org/apache/doris/persist/EditLog.java +++ b/fe/src/main/java/org/apache/doris/persist/EditLog.java @@ -237,6 +237,11 @@ public class EditLog { catalog.replayRenameTable(info); break; } + case OperationType.OP_MODIFY_VIEW_DEF: { + AlterViewInfo info = (AlterViewInfo) journal.getData(); + catalog.getAlterInstance().replayModifyViewDef(info); + break; + } case OperationType.OP_RENAME_PARTITION: { TableInfo info = (TableInfo) journal.getData(); catalog.replayRenamePartition(info); @@ -1016,6 +1021,10 @@ public class EditLog { logEdit(OperationType.OP_RENAME_TABLE, tableInfo); } + public void logModifyViewDef(AlterViewInfo alterViewInfo) { + logEdit(OperationType.OP_MODIFY_VIEW_DEF, alterViewInfo); + } + public void logRollupRename(TableInfo tableInfo) { logEdit(OperationType.OP_RENAME_ROLLUP, tableInfo); } diff --git a/fe/src/main/java/org/apache/doris/persist/OperationType.java b/fe/src/main/java/org/apache/doris/persist/OperationType.java index c972e04430..f6a8352786 100644 --- a/fe/src/main/java/org/apache/doris/persist/OperationType.java +++ b/fe/src/main/java/org/apache/doris/persist/OperationType.java @@ -42,6 +42,7 @@ public class OperationType { public static final short OP_BACKUP_JOB = 116; public static final short OP_RESTORE_JOB = 117; public static final short OP_TRUNCATE_TABLE = 118; + public static final short OP_MODIFY_VIEW_DEF = 119; // 20~29 120~129 220~229 ... public static final short OP_START_ROLLUP = 20; diff --git a/fe/src/main/java/org/apache/doris/qe/DdlExecutor.java b/fe/src/main/java/org/apache/doris/qe/DdlExecutor.java index 1e3ce11d4c..e8964f7811 100644 --- a/fe/src/main/java/org/apache/doris/qe/DdlExecutor.java +++ b/fe/src/main/java/org/apache/doris/qe/DdlExecutor.java @@ -66,6 +66,7 @@ import org.apache.doris.analysis.SetUserPropertyStmt; import org.apache.doris.analysis.StopRoutineLoadStmt; import org.apache.doris.analysis.SyncStmt; import org.apache.doris.analysis.TruncateTableStmt; +import org.apache.doris.analysis.AlterViewStmt; import org.apache.doris.catalog.Catalog; import org.apache.doris.common.Config; import org.apache.doris.common.DdlException; @@ -101,6 +102,8 @@ public class DdlExecutor { catalog.createMaterializedView((CreateMaterializedViewStmt) ddlStmt); } else if (ddlStmt instanceof AlterTableStmt) { catalog.alterTable((AlterTableStmt) ddlStmt); + } else if (ddlStmt instanceof AlterViewStmt) { + catalog.alterView((AlterViewStmt) ddlStmt); } else if (ddlStmt instanceof CancelAlterTableStmt) { catalog.cancelAlter((CancelAlterTableStmt) ddlStmt); } else if (ddlStmt instanceof LoadStmt) { diff --git a/fe/src/test/java/org/apache/doris/analysis/AlterViewStmtTest.java b/fe/src/test/java/org/apache/doris/analysis/AlterViewStmtTest.java new file mode 100644 index 0000000000..04e04f2b40 --- /dev/null +++ b/fe/src/test/java/org/apache/doris/analysis/AlterViewStmtTest.java @@ -0,0 +1,181 @@ +// 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 com.google.common.collect.Lists; +import mockit.Expectations; +import mockit.Mock; +import mockit.MockUp; +import mockit.Mocked; +import org.apache.doris.catalog.Catalog; +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.Database; +import org.apache.doris.catalog.KeysType; +import org.apache.doris.catalog.OlapTable; +import org.apache.doris.catalog.PrimitiveType; +import org.apache.doris.catalog.SinglePartitionInfo; +import org.apache.doris.catalog.View; +import org.apache.doris.common.UserException; +import org.apache.doris.common.jmockit.Deencapsulation; +import org.apache.doris.mysql.privilege.PaloAuth; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.persist.AlterViewInfo; +import org.apache.doris.persist.CreateTableInfo; +import org.apache.doris.persist.EditLog; +import org.apache.doris.qe.ConnectContext; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.StringReader; +import java.util.LinkedList; +import java.util.List; + +public class AlterViewStmtTest { + private Analyzer analyzer; + + private Catalog catalog; + + @Mocked + EditLog editLog; + + @Mocked + private ConnectContext connectContext; + + @Mocked + private PaloAuth auth; + + @Before + public void setUp() { + catalog = Deencapsulation.newInstance(Catalog.class); + analyzer = new Analyzer(catalog, connectContext); + + + Database db = new Database(50000L, "testCluster:testDb"); + + Column column1 = new Column("col1", PrimitiveType.BIGINT); + Column column2 = new Column("col2", PrimitiveType.DOUBLE); + + List baseSchema = new LinkedList(); + baseSchema.add(column1); + baseSchema.add(column2); + + OlapTable table = new OlapTable(30000, "testTbl", + baseSchema, KeysType.AGG_KEYS, new SinglePartitionInfo(), null); + db.createTable(table); + + + new Expectations(auth) { + { + auth.checkGlobalPriv((ConnectContext) any, (PrivPredicate) any); + minTimes = 0; + result = true; + + auth.checkDbPriv((ConnectContext) any, anyString, (PrivPredicate) any); + minTimes = 0; + result = true; + + auth.checkTblPriv((ConnectContext) any, anyString, anyString, (PrivPredicate) any); + minTimes = 0; + result = true; + } + }; + + new Expectations(editLog) { + { + editLog.logCreateTable((CreateTableInfo) any); + minTimes = 0; + + editLog.logModifyViewDef((AlterViewInfo) any); + minTimes = 0; + } + }; + + Deencapsulation.setField(catalog, "editLog", editLog); + + new MockUp() { + @Mock + Catalog getInstance() { + return catalog; + } + @Mock + PaloAuth getAuth() { + return auth; + } + @Mock + Database getDb(long dbId) { + return db; + } + @Mock + Database getDb(String dbName) { + return db; + } + }; + + new MockUp() { + @Mock + String getClusterName() { + return "testCluster"; + } + }; + } + + @Test + public void testNormal() { + String originStmt = "select col1 as c1, sum(col2) as c2 from testTbl group by col1"; + View view = new View(30000L, "testView", null); + view.setInlineViewDefWithSqlMode("select col1 as c1, sum(col2) as c2 from testTbl group by col1", 0L); + try { + view.init(); + } catch (UserException e) { + Assert.fail(); + } + + Database db = analyzer.getCatalog().getDb("testDb"); + db.createTable(view); + + Assert.assertEquals(originStmt, view.getInlineViewDef()); + + String alterStmt = "select col1 as k1, col2 as k2 from testDb.testTbl where col1 > 10"; + SqlParser parser = new SqlParser(new SqlScanner(new StringReader(alterStmt))); + QueryStmt alterQueryStmt = null; + try { + alterQueryStmt = (QueryStmt) parser.parse().value; + } catch (Error e) { + Assert.fail(e.getMessage()); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + + ColWithComment col1 = new ColWithComment("h1", null); + ColWithComment col2 = new ColWithComment("h2", null); + + AlterViewStmt alterViewStmt = new AlterViewStmt(new TableName("testDb", "testView"), Lists.newArrayList(col1, col2), alterQueryStmt); + try { + alterViewStmt.analyze(analyzer); + analyzer.getCatalog().alterView(alterViewStmt); + } catch (UserException e) { + Assert.fail(); + } + + View newView = (View) db.getTable("testView"); + + Assert.assertEquals("SELECT `col1` AS `h1`, `col2` AS `h2` FROM `testCluster:testDb`.`testTbl` WHERE `col1` > 10", + newView.getInlineViewDef()); + } +}