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

Reply via email to