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 7487064878..b84f2186b9 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 @@ -63,6 +63,8 @@ statementBase | CREATE VIEW (IF NOT EXISTS)? name=multipartIdentifier (LEFT_PAREN cols=simpleColumnDefs RIGHT_PAREN)? (COMMENT STRING_LITERAL)? AS query #createView + | ALTER VIEW name=multipartIdentifier (LEFT_PAREN cols=simpleColumnDefs RIGHT_PAREN)? + AS query #alterView | explain? INSERT (INTO | OVERWRITE TABLE) (tableName=multipartIdentifier | DORIS_INTERNAL_TABLE_ID LEFT_PAREN tableId=INTEGER_VALUE RIGHT_PAREN) partitionSpec? // partition define diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterViewStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterViewStmt.java index 355c9723c8..39ea2ff129 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterViewStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterViewStmt.java @@ -17,6 +17,7 @@ package org.apache.doris.analysis; +import org.apache.doris.catalog.Column; import org.apache.doris.catalog.DatabaseIf; import org.apache.doris.catalog.Env; import org.apache.doris.catalog.TableIf; @@ -77,6 +78,14 @@ public class AlterViewStmt extends BaseViewStmt { createColumnAndViewDefs(analyzer); } + public void setInlineViewDef(String querySql) { + inlineViewDef = querySql; + } + + public void setFinalColumns(List columns) { + finalCols.addAll(columns); + } + @Override public String toSql() { StringBuilder sb = new StringBuilder(); 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 4739e74b26..239d898f02 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 @@ -48,6 +48,7 @@ import org.apache.doris.nereids.DorisParser.AggStateDataTypeContext; import org.apache.doris.nereids.DorisParser.AliasQueryContext; import org.apache.doris.nereids.DorisParser.AliasedQueryContext; import org.apache.doris.nereids.DorisParser.AlterMTMVContext; +import org.apache.doris.nereids.DorisParser.AlterViewContext; import org.apache.doris.nereids.DorisParser.ArithmeticBinaryContext; import org.apache.doris.nereids.DorisParser.ArithmeticUnaryContext; import org.apache.doris.nereids.DorisParser.ArrayLiteralContext; @@ -353,6 +354,7 @@ import org.apache.doris.nereids.trees.plans.algebra.Aggregate; import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier; import org.apache.doris.nereids.trees.plans.commands.AddConstraintCommand; import org.apache.doris.nereids.trees.plans.commands.AlterMTMVCommand; +import org.apache.doris.nereids.trees.plans.commands.AlterViewCommand; import org.apache.doris.nereids.trees.plans.commands.CallCommand; import org.apache.doris.nereids.trees.plans.commands.CancelMTMVTaskCommand; import org.apache.doris.nereids.trees.plans.commands.Command; @@ -382,6 +384,7 @@ import org.apache.doris.nereids.trees.plans.commands.info.AlterMTMVInfo; import org.apache.doris.nereids.trees.plans.commands.info.AlterMTMVPropertyInfo; import org.apache.doris.nereids.trees.plans.commands.info.AlterMTMVRefreshInfo; import org.apache.doris.nereids.trees.plans.commands.info.AlterMTMVRenameInfo; +import org.apache.doris.nereids.trees.plans.commands.info.AlterViewInfo; import org.apache.doris.nereids.trees.plans.commands.info.BulkLoadDataDesc; import org.apache.doris.nereids.trees.plans.commands.info.BulkStorageDesc; import org.apache.doris.nereids.trees.plans.commands.info.CancelMTMVTaskInfo; @@ -804,6 +807,16 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor { return new AlterMTMVCommand(alterMTMVInfo); } + @Override + public LogicalPlan visitAlterView(AlterViewContext ctx) { + List nameParts = visitMultipartIdentifier(ctx.name); + LogicalPlan logicalPlan = visitQuery(ctx.query()); + String querySql = getOriginSql(ctx.query()); + AlterViewInfo info = new AlterViewInfo(new TableNameInfo(nameParts), logicalPlan, querySql, + ctx.cols == null ? Lists.newArrayList() : visitSimpleColumnDefs(ctx.cols)); + return new AlterViewCommand(info); + } + @Override public LogicalPlan visitShowConstraint(ShowConstraintContext ctx) { List parts = visitMultipartIdentifier(ctx.table); 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 165dcd5e3c..bf4741cff4 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 @@ -151,5 +151,6 @@ public enum PlanType { DROP_PROCEDURE_COMMAND, SHOW_PROCEDURE_COMMAND, SHOW_CREATE_PROCEDURE_COMMAND, - CREATE_VIEW_COMMAND + CREATE_VIEW_COMMAND, + ALTER_VIEW_COMMAND } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterViewCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterViewCommand.java new file mode 100644 index 0000000000..ebcbe28053 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterViewCommand.java @@ -0,0 +1,49 @@ +// 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.analysis.AlterViewStmt; +import org.apache.doris.catalog.Env; +import org.apache.doris.nereids.trees.plans.PlanType; +import org.apache.doris.nereids.trees.plans.commands.info.AlterViewInfo; +import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.StmtExecutor; + +/**AlterViewCommand*/ +public class AlterViewCommand extends Command implements ForwardWithSync { + private final AlterViewInfo alterViewInfo; + + public AlterViewCommand(AlterViewInfo alterViewInfo) { + super(PlanType.ALTER_VIEW_COMMAND); + this.alterViewInfo = alterViewInfo; + } + + @Override + public void run(ConnectContext ctx, StmtExecutor executor) throws Exception { + alterViewInfo.init(ctx); + alterViewInfo.validate(ctx); + AlterViewStmt alterViewStmt = alterViewInfo.translateToLegacyStmt(ctx); + Env.getCurrentEnv().alterView(alterViewStmt); + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitAlterViewCommand(this, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/AlterViewInfo.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/AlterViewInfo.java new file mode 100644 index 0000000000..5579837714 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/AlterViewInfo.java @@ -0,0 +1,121 @@ +// 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.info; + +import org.apache.doris.analysis.AlterViewStmt; +import org.apache.doris.analysis.ColWithComment; +import org.apache.doris.analysis.TableName; +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.DatabaseIf; +import org.apache.doris.catalog.Env; +import org.apache.doris.catalog.TableIf; +import org.apache.doris.catalog.View; +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.mysql.privilege.PrivPredicate; +import org.apache.doris.nereids.NereidsPlanner; +import org.apache.doris.nereids.analyzer.UnboundResultSink; +import org.apache.doris.nereids.exceptions.AnalysisException; +import org.apache.doris.nereids.properties.PhysicalProperties; +import org.apache.doris.nereids.trees.expressions.Slot; +import org.apache.doris.nereids.trees.plans.commands.ExplainCommand.ExplainLevel; +import org.apache.doris.nereids.trees.plans.logical.LogicalFileSink; +import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; +import org.apache.doris.qe.ConnectContext; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import java.util.List; +import java.util.Set; + +/** AlterViewInfo */ +public class AlterViewInfo extends BaseViewInfo { + /** constructor*/ + public AlterViewInfo(TableNameInfo viewName, LogicalPlan logicalQuery, + String querySql, List simpleColumnDefinitions) { + super(viewName, logicalQuery, querySql, simpleColumnDefinitions); + if (logicalQuery instanceof LogicalFileSink) { + throw new AnalysisException("Not support OUTFILE clause in ALTER VIEW statement"); + } + } + + /** init */ + public void init(ConnectContext ctx) throws UserException { + if (viewName == null) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_TABLES_USED); + } + viewName.analyze(ctx); + FeNameFormat.checkTableName(viewName.getTbl()); + // disallow external catalog + Util.prohibitExternalCatalog(viewName.getCtl(), "AlterViewStmt"); + + DatabaseIf db = Env.getCurrentInternalCatalog().getDbOrAnalysisException(viewName.getDb()); + TableIf table = db.getTableOrAnalysisException(viewName.getTbl()); + if (!(table instanceof View)) { + throw new org.apache.doris.common.AnalysisException( + String.format("ALTER VIEW not allowed on a table:%s.%s", viewName.getDb(), viewName.getTbl())); + } + + // check privilege + if (!Env.getCurrentEnv().getAccessManager().checkTblPriv(ctx, new TableName(viewName.getCtl(), viewName.getDb(), + viewName.getTbl()), PrivPredicate.ALTER)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLE_ACCESS_DENIED_ERROR, + PrivPredicate.ALTER.getPrivs().toString(), viewName.getTbl()); + } + analyzeAndFillRewriteSqlMap(querySql, ctx); + OutermostPlanFinderContext outermostPlanFinderContext = new OutermostPlanFinderContext(); + analyzedPlan.accept(OutermostPlanFinder.INSTANCE, outermostPlanFinderContext); + List outputs = outermostPlanFinderContext.outermostPlan.getOutput(); + createFinalCols(outputs); + } + + /**validate*/ + public void validate(ConnectContext ctx) throws UserException { + NereidsPlanner planner = new NereidsPlanner(ctx.getStatementContext()); + planner.plan(new UnboundResultSink<>(logicalQuery), PhysicalProperties.ANY, ExplainLevel.NONE); + 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()); + } + } + } + + /**translateToLegacyStmt*/ + public AlterViewStmt translateToLegacyStmt(ConnectContext ctx) { + List cols = Lists.newArrayList(); + for (SimpleColumnDefinition def : simpleColumnDefinitions) { + cols.add(def.translateToColWithComment()); + } + AlterViewStmt alterViewStmt = new AlterViewStmt(viewName.transferToTableName(), cols, + null); + // expand star(*) in project list and replace table name with qualifier + String rewrittenSql = rewriteSql(ctx.getStatementContext().getIndexInSqlToString()); + + // rewrite project alias + rewrittenSql = rewriteProjectsToUserDefineAlias(rewrittenSql); + + alterViewStmt.setInlineViewDef(rewrittenSql); + alterViewStmt.setFinalColumns(finalCols); + return alterViewStmt; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/BaseViewInfo.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/BaseViewInfo.java new file mode 100644 index 0000000000..e0b00edf7f --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/BaseViewInfo.java @@ -0,0 +1,261 @@ +// 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.info; + +import org.apache.doris.catalog.Column; +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.Pair; +import org.apache.doris.nereids.CascadesContext; +import org.apache.doris.nereids.DorisParser; +import org.apache.doris.nereids.DorisParser.NamedExpressionContext; +import org.apache.doris.nereids.DorisParser.NamedExpressionSeqContext; +import org.apache.doris.nereids.DorisParserBaseVisitor; +import org.apache.doris.nereids.StatementContext; +import org.apache.doris.nereids.analyzer.UnboundResultSink; +import org.apache.doris.nereids.jobs.executor.AbstractBatchJobExecutor; +import org.apache.doris.nereids.jobs.rewrite.RewriteJob; +import org.apache.doris.nereids.parser.NereidsParser; +import org.apache.doris.nereids.properties.PhysicalProperties; +import org.apache.doris.nereids.rules.analysis.AnalyzeCTE; +import org.apache.doris.nereids.rules.analysis.BindExpression; +import org.apache.doris.nereids.rules.analysis.BindRelation; +import org.apache.doris.nereids.rules.analysis.CheckPolicy; +import org.apache.doris.nereids.rules.analysis.EliminateLogicalSelectHint; +import org.apache.doris.nereids.trees.expressions.Slot; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.logical.LogicalCTEAnchor; +import org.apache.doris.nereids.trees.plans.logical.LogicalCTEProducer; +import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; +import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanVisitor; +import org.apache.doris.qe.ConnectContext; + +import com.google.common.collect.Lists; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.RuleNode; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; + +/** BaseViewInfo */ +public class BaseViewInfo { + protected final TableNameInfo viewName; + protected final LogicalPlan logicalQuery; + protected final String querySql; + protected final List simpleColumnDefinitions; + protected final List finalCols = Lists.newArrayList(); + protected Plan analyzedPlan; + + public BaseViewInfo(TableNameInfo viewName, LogicalPlan logicalQuery, + String querySql, List simpleColumnDefinitions) { + this.viewName = viewName; + this.logicalQuery = logicalQuery; + this.querySql = querySql; + this.simpleColumnDefinitions = simpleColumnDefinitions; + } + + protected void analyzeAndFillRewriteSqlMap(String sql, ConnectContext ctx) { + StatementContext stmtCtx = ctx.getStatementContext(); + LogicalPlan parsedViewPlan = new NereidsParser().parseForCreateView(sql); + if (parsedViewPlan instanceof UnboundResultSink) { + parsedViewPlan = (LogicalPlan) ((UnboundResultSink) parsedViewPlan).child(); + } + CascadesContext viewContextForStar = CascadesContext.initContext( + stmtCtx, parsedViewPlan, PhysicalProperties.ANY); + AnalyzerForCreateView analyzerForStar = new AnalyzerForCreateView(viewContextForStar); + analyzerForStar.analyze(); + analyzedPlan = viewContextForStar.getRewritePlan(); + } + + protected String rewriteSql(Map, String> indexStringSqlMap) { + StringBuilder builder = new StringBuilder(); + int beg = 0; + for (Map.Entry, String> entry : indexStringSqlMap.entrySet()) { + Pair index = entry.getKey(); + builder.append(querySql, beg, index.first); + builder.append(entry.getValue()); + beg = index.second + 1; + } + builder.append(querySql, beg, querySql.length()); + return builder.toString(); + } + + protected String rewriteProjectsToUserDefineAlias(String resSql) { + IndexFinder finder = new IndexFinder(); + ParserRuleContext tree = NereidsParser.toAst(resSql, DorisParser::singleStatement); + finder.visit(tree); + if (simpleColumnDefinitions.isEmpty()) { + return resSql; + } + List namedExpressionContexts = finder.getNamedExpressionContexts(); + StringBuilder replaceWithColsBuilder = new StringBuilder(); + for (int i = 0; i < namedExpressionContexts.size(); ++i) { + NamedExpressionContext namedExpressionContext = namedExpressionContexts.get(i); + int start = namedExpressionContext.expression().start.getStartIndex(); + int stop = namedExpressionContext.expression().stop.getStopIndex(); + replaceWithColsBuilder.append(resSql, start, stop + 1); + replaceWithColsBuilder.append(" AS `"); + String escapeBacktick = finalCols.get(i).getName().replace("`", "``"); + replaceWithColsBuilder.append(escapeBacktick); + replaceWithColsBuilder.append('`'); + if (i != namedExpressionContexts.size() - 1) { + replaceWithColsBuilder.append(", "); + } + } + String replaceWithCols = replaceWithColsBuilder.toString(); + return StringUtils.overlay(resSql, replaceWithCols, finder.getIndex().first, + finder.getIndex().second + 1); + } + + protected void createFinalCols(List outputs) throws org.apache.doris.common.AnalysisException { + if (simpleColumnDefinitions.isEmpty()) { + for (Slot output : outputs) { + Column column = new Column(output.getName(), output.getDataType().toCatalogDataType()); + finalCols.add(column); + } + } else { + if (outputs.size() != simpleColumnDefinitions.size()) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_VIEW_WRONG_LIST); + } + for (int i = 0; i < simpleColumnDefinitions.size(); ++i) { + Column column = new Column(simpleColumnDefinitions.get(i).getName(), + outputs.get(i).getDataType().toCatalogDataType()); + column.setComment(simpleColumnDefinitions.get(i).getComment()); + finalCols.add(column); + } + } + } + + /**OutermostPlanFinderContext*/ + protected static class OutermostPlanFinderContext { + public Plan outermostPlan = null; + public boolean found = false; + } + + /**OutermostPlanFinder*/ + protected static class OutermostPlanFinder extends + DefaultPlanVisitor { + public static final OutermostPlanFinder INSTANCE = new OutermostPlanFinder(); + + @Override + public Void visit(Plan plan, OutermostPlanFinderContext ctx) { + if (ctx.found) { + return null; + } + ctx.outermostPlan = plan; + ctx.found = true; + return null; + } + + @Override + public Void visitLogicalCTEAnchor(LogicalCTEAnchor cteAnchor, + OutermostPlanFinderContext ctx) { + if (ctx.found) { + return null; + } + return super.visit(cteAnchor, ctx); + } + + @Override + public Void visitLogicalCTEProducer(LogicalCTEProducer cteProducer, + OutermostPlanFinderContext ctx) { + return null; + } + } + + /** traverse ast to find the outermost project list location information in sql*/ + protected static class IndexFinder extends DorisParserBaseVisitor { + private boolean found = false; + private int startIndex; + private int stopIndex; + private List namedExpressionContexts = Lists.newArrayList(); + + @Override + public Void visitChildren(RuleNode node) { + if (found) { + return null; + } + int n = node.getChildCount(); + for (int i = 0; i < n; ++i) { + ParseTree c = node.getChild(i); + c.accept(this); + } + return null; + } + + @Override + public Void visitCte(DorisParser.CteContext ctx) { + return null; + } + + @Override + public Void visitSelectColumnClause(DorisParser.SelectColumnClauseContext ctx) { + if (found) { + return null; + } + startIndex = ctx.getStart().getStartIndex(); + stopIndex = ctx.getStop().getStopIndex(); + found = true; + + NamedExpressionSeqContext namedExpressionSeqContext = ctx.namedExpressionSeq(); + namedExpressionContexts = namedExpressionSeqContext.namedExpression(); + return null; + } + + public Pair getIndex() { + return Pair.of(startIndex, stopIndex); + } + + public List getNamedExpressionContexts() { + return namedExpressionContexts; + } + } + + /**AnalyzerForCreateView*/ + protected static class AnalyzerForCreateView extends AbstractBatchJobExecutor { + private final List jobs; + + public AnalyzerForCreateView(CascadesContext cascadesContext) { + super(cascadesContext); + jobs = buildAnalyzeViewJobsForStar(); + } + + public void analyze() { + execute(); + } + + @Override + public List getJobs() { + return jobs; + } + + private static List buildAnalyzeViewJobsForStar() { + return jobs( + topDown(new EliminateLogicalSelectHint()), + topDown(new AnalyzeCTE()), + bottomUp( + new BindRelation(), + new CheckPolicy(), + new BindExpression() + ) + ); + } + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateViewInfo.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateViewInfo.java index 084fe3af3f..e52f69954b 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateViewInfo.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateViewInfo.java @@ -25,74 +25,41 @@ import org.apache.doris.catalog.Env; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.common.FeNameFormat; -import org.apache.doris.common.Pair; import org.apache.doris.common.UserException; import org.apache.doris.common.util.Util; import org.apache.doris.mysql.privilege.PrivPredicate; -import org.apache.doris.nereids.CascadesContext; -import org.apache.doris.nereids.DorisParser; -import org.apache.doris.nereids.DorisParser.NamedExpressionContext; -import org.apache.doris.nereids.DorisParser.NamedExpressionSeqContext; -import org.apache.doris.nereids.DorisParserBaseVisitor; import org.apache.doris.nereids.NereidsPlanner; -import org.apache.doris.nereids.StatementContext; import org.apache.doris.nereids.analyzer.UnboundResultSink; import org.apache.doris.nereids.exceptions.AnalysisException; -import org.apache.doris.nereids.jobs.executor.AbstractBatchJobExecutor; -import org.apache.doris.nereids.jobs.rewrite.RewriteJob; -import org.apache.doris.nereids.parser.NereidsParser; import org.apache.doris.nereids.properties.PhysicalProperties; -import org.apache.doris.nereids.rules.analysis.AnalyzeCTE; -import org.apache.doris.nereids.rules.analysis.BindExpression; -import org.apache.doris.nereids.rules.analysis.BindRelation; -import org.apache.doris.nereids.rules.analysis.CheckPolicy; -import org.apache.doris.nereids.rules.analysis.EliminateLogicalSelectHint; import org.apache.doris.nereids.trees.expressions.Slot; -import org.apache.doris.nereids.trees.plans.Plan; import org.apache.doris.nereids.trees.plans.commands.ExplainCommand.ExplainLevel; -import org.apache.doris.nereids.trees.plans.logical.LogicalCTEAnchor; -import org.apache.doris.nereids.trees.plans.logical.LogicalCTEProducer; import org.apache.doris.nereids.trees.plans.logical.LogicalFileSink; import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; -import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanVisitor; import org.apache.doris.qe.ConnectContext; import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.RuleNode; -import org.apache.commons.lang3.StringUtils; import java.util.List; -import java.util.Map; import java.util.Set; /** * CreateViewInfo */ -public class CreateViewInfo { +public class CreateViewInfo extends BaseViewInfo { private final boolean ifNotExists; - private final TableNameInfo viewName; private final String comment; - private final LogicalPlan logicalQuery; - private final String querySql; - private final List simpleColumnDefinitions; - private final List finalCols = Lists.newArrayList(); - private Plan analyzedPlan; /** constructor*/ public CreateViewInfo(boolean ifNotExists, TableNameInfo viewName, String comment, LogicalPlan logicalQuery, String querySql, List simpleColumnDefinitions) { + super(viewName, logicalQuery, querySql, simpleColumnDefinitions); this.ifNotExists = ifNotExists; - this.viewName = viewName; this.comment = comment; if (logicalQuery instanceof LogicalFileSink) { throw new AnalysisException("Not support OUTFILE clause in CREATE VIEW statement"); } - this.logicalQuery = logicalQuery; - this.querySql = querySql; - this.simpleColumnDefinitions = simpleColumnDefinitions; } /** init */ @@ -144,188 +111,4 @@ public class CreateViewInfo { createViewStmt.setFinalColumns(finalCols); return createViewStmt; } - - private void analyzeAndFillRewriteSqlMap(String sql, ConnectContext ctx) { - StatementContext stmtCtx = ctx.getStatementContext(); - LogicalPlan parsedViewPlan = new NereidsParser().parseForCreateView(sql); - if (parsedViewPlan instanceof UnboundResultSink) { - parsedViewPlan = (LogicalPlan) ((UnboundResultSink) parsedViewPlan).child(); - } - CascadesContext viewContextForStar = CascadesContext.initContext( - stmtCtx, parsedViewPlan, PhysicalProperties.ANY); - AnalyzerForCreateView analyzerForStar = new AnalyzerForCreateView(viewContextForStar); - analyzerForStar.analyze(); - analyzedPlan = viewContextForStar.getRewritePlan(); - } - - private String rewriteSql(Map, String> indexStringSqlMap) { - StringBuilder builder = new StringBuilder(); - int beg = 0; - for (Map.Entry, String> entry : indexStringSqlMap.entrySet()) { - Pair index = entry.getKey(); - builder.append(querySql, beg, index.first); - builder.append(entry.getValue()); - beg = index.second + 1; - } - builder.append(querySql, beg, querySql.length()); - return builder.toString(); - } - - private String rewriteProjectsToUserDefineAlias(String resSql) { - IndexFinder finder = new IndexFinder(); - ParserRuleContext tree = NereidsParser.toAst(resSql, DorisParser::singleStatement); - finder.visit(tree); - if (simpleColumnDefinitions.isEmpty()) { - return resSql; - } - List namedExpressionContexts = finder.getNamedExpressionContexts(); - StringBuilder replaceWithColsBuilder = new StringBuilder(); - for (int i = 0; i < namedExpressionContexts.size(); ++i) { - NamedExpressionContext namedExpressionContext = namedExpressionContexts.get(i); - int start = namedExpressionContext.expression().start.getStartIndex(); - int stop = namedExpressionContext.expression().stop.getStopIndex(); - replaceWithColsBuilder.append(resSql, start, stop + 1); - replaceWithColsBuilder.append(" AS `"); - String escapeBacktick = finalCols.get(i).getName().replace("`", "``"); - replaceWithColsBuilder.append(escapeBacktick); - replaceWithColsBuilder.append('`'); - if (i != namedExpressionContexts.size() - 1) { - replaceWithColsBuilder.append(", "); - } - } - String replaceWithCols = replaceWithColsBuilder.toString(); - return StringUtils.overlay(resSql, replaceWithCols, finder.getIndex().first, - finder.getIndex().second + 1); - } - - private void createFinalCols(List outputs) throws org.apache.doris.common.AnalysisException { - if (simpleColumnDefinitions.isEmpty()) { - for (Slot output : outputs) { - Column column = new Column(output.getName(), output.getDataType().toCatalogDataType()); - finalCols.add(column); - } - } else { - if (outputs.size() != simpleColumnDefinitions.size()) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_VIEW_WRONG_LIST); - } - for (int i = 0; i < simpleColumnDefinitions.size(); ++i) { - Column column = new Column(simpleColumnDefinitions.get(i).getName(), - outputs.get(i).getDataType().toCatalogDataType()); - column.setComment(simpleColumnDefinitions.get(i).getComment()); - finalCols.add(column); - } - } - } - - private static class OutermostPlanFinderContext { - public Plan outermostPlan = null; - public boolean found = false; - } - - private static class OutermostPlanFinder extends DefaultPlanVisitor { - public static final OutermostPlanFinder INSTANCE = new OutermostPlanFinder(); - - @Override - public Void visit(Plan plan, OutermostPlanFinderContext ctx) { - if (ctx.found) { - return null; - } - ctx.outermostPlan = plan; - ctx.found = true; - return null; - } - - @Override - public Void visitLogicalCTEAnchor(LogicalCTEAnchor cteAnchor, - OutermostPlanFinderContext ctx) { - if (ctx.found) { - return null; - } - return super.visit(cteAnchor, ctx); - } - - @Override - public Void visitLogicalCTEProducer(LogicalCTEProducer cteProducer, - OutermostPlanFinderContext ctx) { - return null; - } - } - - /** traverse ast to find the outermost project list location information in sql*/ - private static class IndexFinder extends DorisParserBaseVisitor { - private boolean found = false; - private int startIndex; - private int stopIndex; - private List namedExpressionContexts = Lists.newArrayList(); - - @Override - public Void visitChildren(RuleNode node) { - if (found) { - return null; - } - int n = node.getChildCount(); - for (int i = 0; i < n; ++i) { - ParseTree c = node.getChild(i); - c.accept(this); - } - return null; - } - - @Override - public Void visitCte(DorisParser.CteContext ctx) { - return null; - } - - @Override - public Void visitSelectColumnClause(DorisParser.SelectColumnClauseContext ctx) { - if (found) { - return null; - } - startIndex = ctx.getStart().getStartIndex(); - stopIndex = ctx.getStop().getStopIndex(); - found = true; - - NamedExpressionSeqContext namedExpressionSeqContext = ctx.namedExpressionSeq(); - namedExpressionContexts = namedExpressionSeqContext.namedExpression(); - return null; - } - - public Pair getIndex() { - return Pair.of(startIndex, stopIndex); - } - - public List getNamedExpressionContexts() { - return namedExpressionContexts; - } - } - - private static class AnalyzerForCreateView extends AbstractBatchJobExecutor { - private final List jobs; - - public AnalyzerForCreateView(CascadesContext cascadesContext) { - super(cascadesContext); - jobs = buildAnalyzeViewJobsForStar(); - } - - public void analyze() { - execute(); - } - - @Override - public List getJobs() { - return jobs; - } - - private static List buildAnalyzeViewJobsForStar() { - return jobs( - topDown(new EliminateLogicalSelectHint()), - topDown(new AnalyzeCTE()), - bottomUp( - new BindRelation(), - new CheckPolicy(), - new BindExpression() - ) - ); - } - } } 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 527ccb1ffe..d23cb855af 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 @@ -19,6 +19,7 @@ package org.apache.doris.nereids.trees.plans.visitor; import org.apache.doris.nereids.trees.plans.commands.AddConstraintCommand; import org.apache.doris.nereids.trees.plans.commands.AlterMTMVCommand; +import org.apache.doris.nereids.trees.plans.commands.AlterViewCommand; import org.apache.doris.nereids.trees.plans.commands.CallCommand; import org.apache.doris.nereids.trees.plans.commands.CancelMTMVTaskCommand; import org.apache.doris.nereids.trees.plans.commands.Command; @@ -161,4 +162,8 @@ public interface CommandVisitor { default R visitCreateViewCommand(CreateViewCommand createViewCommand, C context) { return visitCommand(createViewCommand, context); } + + default R visitAlterViewCommand(AlterViewCommand alterViewCommand, C context) { + return visitCommand(alterViewCommand, context); + } } diff --git a/regression-test/data/ddl_p0/test_alter_view_nereids.out b/regression-test/data/ddl_p0/test_alter_view_nereids.out new file mode 100644 index 0000000000..ef50a9c880 --- /dev/null +++ b/regression-test/data/ddl_p0/test_alter_view_nereids.out @@ -0,0 +1,17 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !select -- +1 1 +1 5 +2 1 +2 10 + +-- !select -- +1 60 +2 70 + +-- !sql -- +test_alter_view_view_nereids CREATE VIEW `test_alter_view_view_nereids` COMMENT 'test_view' AS SELECT c1 AS `k1`, sum(c3) AS `k2` FROM `internal`.`regression_test_ddl_p0`.`test_alter_view_table_nereids` GROUP BY c1; utf8mb4 utf8mb4_0900_bin + +-- !sql -- +test_alter_view_view_nereids CREATE VIEW `test_alter_view_view_nereids` COMMENT 'test_view' AS (select `internal`.`regression_test_ddl_p0`.`test_alter_view_table_nereids`.`c1`, `internal`.`regression_test_ddl_p0`.`test_alter_view_table_nereids`.`c2`, `internal`.`regression_test_ddl_p0`.`test_alter_view_table_nereids`.`c3` from `internal`.`regression_test_ddl_p0`.`test_alter_view_table_nereids`); utf8mb4 utf8mb4_0900_bin + diff --git a/regression-test/suites/ddl_p0/test_alter_view_nereids.groovy b/regression-test/suites/ddl_p0/test_alter_view_nereids.groovy new file mode 100644 index 0000000000..6b26d093e5 --- /dev/null +++ b/regression-test/suites/ddl_p0/test_alter_view_nereids.groovy @@ -0,0 +1,69 @@ +// 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_alter_view_nereids") { + sql "SET enable_nereids_planner=true;" + sql "SET enable_fallback_to_original_planner=false;" + String tableName = "test_alter_view_table_nereids"; + String viewName = "test_alter_view_view_nereids"; + sql " DROP TABLE IF EXISTS ${tableName}" + sql """ + CREATE TABLE IF NOT EXISTS ${tableName} + ( + c1 BIGINT NOT NULL, + c2 BIGINT NOT NULL, + c3 BIGINT NOT NULL + ) + UNIQUE KEY (`c1`, `c2`) + DISTRIBUTED BY HASH(`c1`) BUCKETS 1 + PROPERTIES + ( + "replication_num" = "1" + ) + """ + sql """ + CREATE VIEW IF NOT EXISTS ${viewName} (k1, k2) + COMMENT "test_view" + AS + SELECT c1 as k1, c2 as k2 FROM ${tableName} + """ + + sql """ + INSERT INTO ${tableName} VALUES + (1, 1, 10), + (1, 5, 50), + (2, 1, 20), + (2, 10, 50) + """ + + qt_select " SELECT * FROM ${viewName} order by k1, k2 " + + sql """ + ALTER VIEW ${viewName} (k1, k2) + AS + SELECT c1 as k1, sum(c3) as k2 FROM ${tableName} GROUP BY c1 + """ + + qt_select " SELECT * FROM ${viewName} order by k1, k2 " + qt_sql "show create view ${viewName}" + + sql "alter view ${viewName} as (select * from ${tableName})" + qt_sql "show create view ${viewName}" + + sql "DROP VIEW ${viewName}" + sql "DROP TABLE ${tableName}" +}