This is an automated email from the ASF dual-hosted git repository. dataroaring pushed a commit to branch branch-3.0 in repository https://gitbox.apache.org/repos/asf/doris.git
commit fc78d1fd2885d7b68f0c6953a34b7b5261b0f9f9 Author: minghong <engle...@gmail.com> AuthorDate: Mon Jul 1 16:28:44 2024 +0800 [feat](nereids) support explain delete from clause (#36782) ## Proposed changes support explain like: explain delete from T where A=1 Issue Number: close #xxx <!--Describe your changes.--> --- .../doris/nereids/parser/LogicalPlanBuilder.java | 16 ++- .../trees/plans/commands/DeleteFromCommand.java | 92 +++++++++++++++- .../plans/commands/DeleteFromUsingCommand.java | 88 +++------------ .../suites/nereids_p0/explain/explain_dml.groovy | 122 +++++++++++++++++++++ 4 files changed, 236 insertions(+), 82 deletions(-) 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 b6f679da5e4..e53177a0358 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 @@ -941,9 +941,12 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { if (ctx.tableAlias().strictIdentifier() != null) { tableAlias = ctx.tableAlias().getText(); } - if (ctx.USING() == null && ctx.cte() == null && ctx.explain() == null) { + + Command deleteCommand; + if (ctx.USING() == null && ctx.cte() == null) { query = withFilter(query, Optional.ofNullable(ctx.whereClause())); - return new DeleteFromCommand(tableName, tableAlias, partitionSpec.first, partitionSpec.second, query); + deleteCommand = new DeleteFromCommand(tableName, tableAlias, partitionSpec.first, + partitionSpec.second, query); } else { // convert to insert into select query = withRelations(query, ctx.relations().relation()); @@ -952,8 +955,13 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { if (ctx.cte() != null) { cte = Optional.ofNullable(withCte(query, ctx.cte())); } - return withExplain(new DeleteFromUsingCommand(tableName, tableAlias, - partitionSpec.first, partitionSpec.second, query, cte), ctx.explain()); + deleteCommand = new DeleteFromUsingCommand(tableName, tableAlias, + partitionSpec.first, partitionSpec.second, query, cte); + } + if (ctx.explain() != null) { + return withExplain(deleteCommand, ctx.explain()); + } else { + return deleteCommand; } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromCommand.java index 90d159b8274..cbfa94be050 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromCommand.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromCommand.java @@ -28,11 +28,15 @@ import org.apache.doris.catalog.Env; import org.apache.doris.catalog.KeysType; import org.apache.doris.catalog.MaterializedIndexMeta; import org.apache.doris.catalog.OlapTable; +import org.apache.doris.catalog.TableIf; import org.apache.doris.common.Config; import org.apache.doris.common.ErrorCode; import org.apache.doris.mysql.privilege.PrivPredicate; import org.apache.doris.nereids.NereidsPlanner; +import org.apache.doris.nereids.analyzer.UnboundAlias; import org.apache.doris.nereids.analyzer.UnboundRelation; +import org.apache.doris.nereids.analyzer.UnboundSlot; +import org.apache.doris.nereids.analyzer.UnboundTableSinkCreator; import org.apache.doris.nereids.exceptions.AnalysisException; import org.apache.doris.nereids.glue.LogicalPlanAdapter; import org.apache.doris.nereids.rules.RuleType; @@ -41,12 +45,17 @@ import org.apache.doris.nereids.trees.expressions.ComparisonPredicate; import org.apache.doris.nereids.trees.expressions.Expression; import org.apache.doris.nereids.trees.expressions.InPredicate; import org.apache.doris.nereids.trees.expressions.IsNull; +import org.apache.doris.nereids.trees.expressions.NamedExpression; import org.apache.doris.nereids.trees.expressions.Not; import org.apache.doris.nereids.trees.expressions.SlotReference; import org.apache.doris.nereids.trees.expressions.literal.Literal; +import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral; +import org.apache.doris.nereids.trees.plans.Explainable; import org.apache.doris.nereids.trees.plans.Plan; import org.apache.doris.nereids.trees.plans.PlanType; +import org.apache.doris.nereids.trees.plans.commands.info.DMLCommandType; import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; +import org.apache.doris.nereids.trees.plans.logical.LogicalProject; import org.apache.doris.nereids.trees.plans.physical.PhysicalDistribute; import org.apache.doris.nereids.trees.plans.physical.PhysicalEmptyRelation; import org.apache.doris.nereids.trees.plans.physical.PhysicalFilter; @@ -54,6 +63,7 @@ import org.apache.doris.nereids.trees.plans.physical.PhysicalOlapScan; import org.apache.doris.nereids.trees.plans.physical.PhysicalProject; import org.apache.doris.nereids.trees.plans.physical.PhysicalUnary; import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.nereids.util.RelationUtil; import org.apache.doris.nereids.util.Utils; import org.apache.doris.qe.ConnectContext; import org.apache.doris.qe.SessionVariable; @@ -61,6 +71,7 @@ import org.apache.doris.qe.StmtExecutor; import org.apache.doris.qe.VariableMgr; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; @@ -73,13 +84,13 @@ import java.util.stream.Collectors; /** * delete from unique key table. */ -public class DeleteFromCommand extends Command implements ForwardWithSync { +public class DeleteFromCommand extends Command implements ForwardWithSync, Explainable { - private final List<String> nameParts; - private final String tableAlias; - private final boolean isTempPart; - private final List<String> partitions; - private final LogicalPlan logicalQuery; + protected final List<String> nameParts; + protected final String tableAlias; + protected final boolean isTempPart; + protected final List<String> partitions; + protected final LogicalPlan logicalQuery; /** * constructor @@ -354,4 +365,73 @@ public class DeleteFromCommand extends Command implements ForwardWithSync { public <R, C> R accept(PlanVisitor<R, C> visitor, C context) { return visitor.visitDeleteFromCommand(this, context); } + + @Override + public Plan getExplainPlan(ConnectContext ctx) { + if (!ctx.getSessionVariable().isEnableNereidsDML()) { + try { + ctx.getSessionVariable().enableFallbackToOriginalPlannerOnce(); + } catch (Exception e) { + throw new AnalysisException("failed to set fallback to original planner to true", e); + } + throw new AnalysisException("Nereids DML is disabled, will try to fall back to the original planner"); + } + return completeQueryPlan(ctx, logicalQuery); + } + + private OlapTable getTargetTable(ConnectContext ctx) { + List<String> qualifiedTableName = RelationUtil.getQualifierName(ctx, nameParts); + TableIf table = RelationUtil.getTable(qualifiedTableName, ctx.getEnv()); + if (!(table instanceof OlapTable)) { + throw new AnalysisException("table must be olapTable in delete command"); + } + return ((OlapTable) table); + } + + /** + * for explain command + */ + public LogicalPlan completeQueryPlan(ConnectContext ctx, LogicalPlan logicalQuery) { + OlapTable targetTable = getTargetTable(ctx); + checkTargetTable(targetTable); + // add select and insert node. + List<NamedExpression> selectLists = Lists.newArrayList(); + List<String> cols = Lists.newArrayList(); + boolean isMow = targetTable.getEnableUniqueKeyMergeOnWrite(); + String tableName = tableAlias != null ? tableAlias : targetTable.getName(); + for (Column column : targetTable.getFullSchema()) { + if (column.getName().equalsIgnoreCase(Column.DELETE_SIGN)) { + selectLists.add(new UnboundAlias(new TinyIntLiteral(((byte) 1)), Column.DELETE_SIGN)); + } else if (column.getName().equalsIgnoreCase(Column.SEQUENCE_COL) + && targetTable.getSequenceMapCol() != null) { + selectLists.add(new UnboundSlot(tableName, targetTable.getSequenceMapCol())); + } else if (column.isKey()) { + selectLists.add(new UnboundSlot(tableName, column.getName())); + } else if (!isMow && (!column.isVisible() || (!column.isAllowNull() && !column.hasDefaultValue()))) { + selectLists.add(new UnboundSlot(tableName, column.getName())); + } else { + selectLists.add(new UnboundSlot(tableName, column.getName())); + } + cols.add(column.getName()); + } + + logicalQuery = new LogicalProject<>(selectLists, logicalQuery); + + boolean isPartialUpdate = targetTable.getEnableUniqueKeyMergeOnWrite() + && cols.size() < targetTable.getColumns().size(); + logicalQuery = handleCte(logicalQuery); + // make UnboundTableSink + return UnboundTableSinkCreator.createUnboundTableSink(nameParts, cols, ImmutableList.of(), + isTempPart, partitions, isPartialUpdate, DMLCommandType.DELETE, logicalQuery); + } + + protected LogicalPlan handleCte(LogicalPlan logicalPlan) { + return logicalPlan; + } + + protected void checkTargetTable(OlapTable targetTable) { + if (targetTable.getKeysType() != KeysType.UNIQUE_KEYS) { + throw new AnalysisException("delete command on aggregate/duplicate table is not explainable"); + } + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromUsingCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromUsingCommand.java index fff034a1ca5..d212e3d6c28 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromUsingCommand.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromUsingCommand.java @@ -17,56 +17,31 @@ package org.apache.doris.nereids.trees.plans.commands; -import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.KeysType; import org.apache.doris.catalog.OlapTable; -import org.apache.doris.nereids.analyzer.UnboundAlias; -import org.apache.doris.nereids.analyzer.UnboundSlot; -import org.apache.doris.nereids.analyzer.UnboundTableSinkCreator; import org.apache.doris.nereids.exceptions.AnalysisException; -import org.apache.doris.nereids.trees.expressions.NamedExpression; -import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral; -import org.apache.doris.nereids.trees.plans.Explainable; -import org.apache.doris.nereids.trees.plans.Plan; -import org.apache.doris.nereids.trees.plans.PlanType; -import org.apache.doris.nereids.trees.plans.commands.info.DMLCommandType; import org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoTableCommand; import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; -import org.apache.doris.nereids.trees.plans.logical.LogicalProject; import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; -import org.apache.doris.nereids.util.Utils; import org.apache.doris.qe.ConnectContext; import org.apache.doris.qe.StmtExecutor; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; - import java.util.List; import java.util.Optional; /** * delete from unique key table. */ -public class DeleteFromUsingCommand extends Command implements ForwardWithSync, Explainable { - - private final List<String> nameParts; - private final String tableAlias; - private final boolean isTempPart; - private final List<String> partitions; +public class DeleteFromUsingCommand extends DeleteFromCommand { private final Optional<LogicalPlan> cte; - private final LogicalPlan logicalQuery; /** * constructor */ public DeleteFromUsingCommand(List<String> nameParts, String tableAlias, boolean isTempPart, List<String> partitions, LogicalPlan logicalQuery, Optional<LogicalPlan> cte) { - super(PlanType.DELETE_COMMAND); - this.nameParts = Utils.copyRequiredList(nameParts); - this.tableAlias = tableAlias; - this.isTempPart = isTempPart; - this.partitions = Utils.copyRequiredList(partitions); + super(nameParts, tableAlias, isTempPart, partitions, logicalQuery); this.cte = cte; - this.logicalQuery = logicalQuery; } @Override @@ -81,61 +56,30 @@ public class DeleteFromUsingCommand extends Command implements ForwardWithSync, Optional.empty()).run(ctx, executor); } - /** - * public for test - */ - public LogicalPlan completeQueryPlan(ConnectContext ctx, LogicalPlan logicalQuery) { - OlapTable targetTable = CommandUtils.checkAndGetDeleteTargetTable(ctx, nameParts); - // add select and insert node. - List<NamedExpression> selectLists = Lists.newArrayList(); - List<String> cols = Lists.newArrayList(); - boolean isMow = targetTable.getEnableUniqueKeyMergeOnWrite(); - String tableName = tableAlias != null ? tableAlias : targetTable.getName(); - for (Column column : targetTable.getFullSchema()) { - if (column.getName().equalsIgnoreCase(Column.DELETE_SIGN)) { - selectLists.add(new UnboundAlias(new TinyIntLiteral(((byte) 1)), Column.DELETE_SIGN)); - } else if (column.getName().equalsIgnoreCase(Column.SEQUENCE_COL) - && targetTable.getSequenceMapCol() != null) { - selectLists.add(new UnboundSlot(tableName, targetTable.getSequenceMapCol())); - } else if (column.isKey()) { - selectLists.add(new UnboundSlot(tableName, column.getName())); - } else if (!isMow && (!column.isVisible() || (!column.isAllowNull() && !column.hasDefaultValue()))) { - selectLists.add(new UnboundSlot(tableName, column.getName())); - } else { - selectLists.add(new UnboundSlot(tableName, column.getName())); - } - cols.add(column.getName()); - } - - logicalQuery = new LogicalProject<>(selectLists, logicalQuery); + @Override + protected LogicalPlan handleCte(LogicalPlan logicalPlan) { if (cte.isPresent()) { - logicalQuery = ((LogicalPlan) cte.get().withChildren(logicalQuery)); + logicalPlan = ((LogicalPlan) cte.get().withChildren(logicalPlan)); } - - // make UnboundTableSink - return UnboundTableSinkCreator.createUnboundTableSink(nameParts, cols, ImmutableList.of(), - isTempPart, partitions, false, DMLCommandType.DELETE, logicalQuery); + return logicalPlan; } + /** + * for test + */ public LogicalPlan getLogicalQuery() { return logicalQuery; } @Override - public Plan getExplainPlan(ConnectContext ctx) { - if (!ctx.getSessionVariable().isEnableNereidsDML()) { - try { - ctx.getSessionVariable().enableFallbackToOriginalPlannerOnce(); - } catch (Exception e) { - throw new AnalysisException("failed to set fallback to original planner to true", e); - } - throw new AnalysisException("Nereids DML is disabled, will try to fall back to the original planner"); - } - return completeQueryPlan(ctx, logicalQuery); + public <R, C> R accept(PlanVisitor<R, C> visitor, C context) { + return visitor.visitDeleteFromUsingCommand(this, context); } @Override - public <R, C> R accept(PlanVisitor<R, C> visitor, C context) { - return visitor.visitDeleteFromUsingCommand(this, context); + protected void checkTargetTable(OlapTable targetTable) { + if (targetTable.getKeysType() != KeysType.UNIQUE_KEYS) { + throw new AnalysisException("delete command on with using clause only supports unique key model"); + } } } diff --git a/regression-test/suites/nereids_p0/explain/explain_dml.groovy b/regression-test/suites/nereids_p0/explain/explain_dml.groovy new file mode 100644 index 00000000000..a0ff8c3ba7d --- /dev/null +++ b/regression-test/suites/nereids_p0/explain/explain_dml.groovy @@ -0,0 +1,122 @@ +// 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("explain_dml") { + String db = context.config.getDbNameByFile(context.file) + sql "use ${db}" + multi_sql """ + drop table if exists epldel1; + CREATE TABLE epldel1 + (id INT, c1 BIGINT, c2 STRING, c3 DOUBLE, c4 DATE) + UNIQUE KEY (id) + DISTRIBUTED BY HASH (id) + PROPERTIES('replication_num'='1', "function_column.sequence_col" = "c4"); + + drop table if exists epldel2; + CREATE TABLE epldel2 + (id INT, c1 BIGINT, c2 STRING, c3 DOUBLE, c4 DATE) + DISTRIBUTED BY HASH (id) + PROPERTIES('replication_num'='1'); + + drop table if exists epldel3; + CREATE TABLE epldel3 + (id INT) + DISTRIBUTED BY HASH (id) + PROPERTIES('replication_num'='1'); + + INSERT INTO epldel1 VALUES + (1, 1, '1', 1.0, '2000-01-01'), + (2, 2, '2', 2.0, '2000-01-02'), + (3, 3, '3', 3.0, '2000-01-03'); + + INSERT INTO epldel2 VALUES + (1, 10, '10', 10.0, '2000-01-10'), + (2, 20, '20', 20.0, '2000-01-20'), + (3, 30, '30', 30.0, '2000-01-30'), + (4, 4, '4', 4.0, '2000-01-04'), + (5, 5, '5', 5.0, '2000-01-05'); + + INSERT INTO epldel3 VALUES + (1), + (4), + (5); + + drop table if exists aggtbl; + CREATE TABLE `aggtbl` ( + `k1` int(11) NULL COMMENT "", + `v1` int(11) SUM DEFAULT "0", + `v2` int(11) SUM DEFAULT "0" + ) + aggregate key (k1) + DISTRIBUTED BY HASH(`k1`) BUCKETS 10 + PROPERTIES ('replication_num' = '1'); + insert into aggtbl values (1, 1, 1); + + drop table if exists duptbl; + CREATE TABLE `duptbl` ( + `k1` int(11) NULL COMMENT "", + `v1` int(11) SUM DEFAULT "0", + `v2` int(11) SUM DEFAULT "0" + ) + aggregate key (k1) + DISTRIBUTED BY HASH(`k1`) BUCKETS 10 + PROPERTIES ('replication_num' = '1'); + insert into duptbl values (1,1,1); + """ + + explain { + sql "delete from epldel1 where id=0;" + contains "PLAN FRAGMENT 0" + } + + explain { + sql """ + DELETE FROM epldel1 + USING epldel2 INNER JOIN epldel3 ON epldel2.id = epldel3.id + WHERE epldel1.id = epldel2.id; + """ + contains "PLAN FRAGMENT 0" + } + + test { + sql "explain delete from aggtbl where v1=6;" + exception "delete command on aggregate/duplicate table is not explainable" + } + + test { + sql """ + explain DELETE FROM aggtbl + USING epldel2 INNER JOIN epldel3 ON epldel2.id = epldel3.id + WHERE aggtbl.k1 = epldel2.id;""" + exception "delete command on with using clause only supports unique key model" + } + + test { + sql "delete from aggtbl where v1=6;" + exception "delete predicate on value column only supports Unique table with merge-on-write enabled and Duplicate table, but Table[aggtbl] is an Aggregate table." + } + + test { + sql "update aggtbl set v1=1 where k1=1;" + exception "Only unique table could be updated." + } + + test { + sql "update duptbl set v1=1 where k1=1;" + exception "Only unique table could be updated." + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org