This is an automated email from the ASF dual-hosted git repository.

starocean999 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 8a12288134e [Enhancement] (nereids) implement ShowCopyCommand in 
nereids (#51040)
8a12288134e is described below

commit 8a12288134e2d8054ac8cdcea038d587e77105c3
Author: yaoxiao <[email protected]>
AuthorDate: Thu May 22 10:19:15 2025 +0800

    [Enhancement] (nereids) implement ShowCopyCommand in nereids (#51040)
---
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |   4 +-
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  37 ++++
 .../trees/plans/commands/ShowCopyCommand.java      | 233 +++++++++++++++++++++
 .../trees/plans/visitor/CommandVisitor.java        |   5 +
 .../trees/plans/commands/ShowCopyCommandTest.java  | 127 +++++++++++
 5 files changed, 404 insertions(+), 2 deletions(-)

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 8764f346bd8..4bfce104713 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
@@ -394,6 +394,8 @@ supportedShowStatement
     | SHOW CATALOG RECYCLE BIN (WHERE expression)?                             
     #showCatalogRecycleBin
     | SHOW TABLET tabletId=INTEGER_VALUE                                       
     #showTabletId
     | SHOW DICTIONARIES wildWhere?                                             
     #showDictionaries
+    | SHOW COPY ((FROM | IN) database=identifier)?
+        whereClause? sortClause? limitClause?                                  
     #showCopy
     | SHOW QUERY STATS ((FOR database=identifier)
             | (FROM tableName=multipartIdentifier (ALL VERBOSE?)?))?           
     #showQueryStats
     ;
@@ -478,8 +480,6 @@ unsupportedShowStatement
     | SHOW BUILD INDEX ((FROM | IN) database=multipartIdentifier)?
         wildWhere? sortClause? limitClause?                                    
     #showBuildIndex
     | SHOW REPLICA STATUS FROM baseTableRef wildWhere?                         
     #showReplicaStatus
-    | SHOW COPY ((FROM | IN) database=multipartIdentifier)?
-        whereClause? sortClause? limitClause?                                  
     #showCopy
     | SHOW WARM UP JOB wildWhere?                                              
     #showWarmUpJob
     ;
 
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 c931653cf99..f3a70391cf2 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
@@ -686,6 +686,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.ShowColumnHistogramStatsCom
 import org.apache.doris.nereids.trees.plans.commands.ShowConfigCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowConstraintsCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowConvertLSCCommand;
+import org.apache.doris.nereids.trees.plans.commands.ShowCopyCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowCreateCatalogCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowCreateDatabaseCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowCreateMTMVCommand;
@@ -7647,4 +7648,40 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
 
         return new RefreshDictionaryCommand(dbName, dictName);
     }
+
+    @Override
+    public LogicalPlan visitShowCopy(DorisParser.ShowCopyContext ctx) {
+        String dbName = null;
+        if (ctx.database != null) {
+            dbName = ctx.database.getText();
+        }
+
+        Expression whereClause = null;
+        if (ctx.whereClause() != null) {
+            whereClause = getExpression(ctx.whereClause().booleanExpression());
+        }
+
+        List<OrderKey> orderKeys = new ArrayList<>();
+        if (ctx.sortClause() != null) {
+            orderKeys = visit(ctx.sortClause().sortItem(), OrderKey.class);
+        }
+
+        long limit = -1L;
+        long offset = 0L;
+        if (ctx.limitClause() != null) {
+            limit = ctx.limitClause().limit != null
+                ? Long.parseLong(ctx.limitClause().limit.getText())
+                : 0;
+            if (limit < 0) {
+                throw new ParseException("Limit requires non-negative number", 
ctx.limitClause());
+            }
+            offset = ctx.limitClause().offset != null
+                ? Long.parseLong(ctx.limitClause().offset.getText())
+                : 0;
+            if (offset < 0) {
+                throw new ParseException("Offset requires non-negative 
number", ctx.limitClause());
+            }
+        }
+        return new ShowCopyCommand(dbName, orderKeys, whereClause, limit, 
offset);
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCopyCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCopyCommand.java
new file mode 100644
index 00000000000..bcaf6aeac77
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCopyCommand.java
@@ -0,0 +1,233 @@
+// 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.RedirectStatus;
+import org.apache.doris.catalog.Column;
+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.proc.LoadProcDir;
+import org.apache.doris.load.LoadJob.JobState;
+import org.apache.doris.nereids.analyzer.UnboundSlot;
+import org.apache.doris.nereids.properties.OrderKey;
+import org.apache.doris.nereids.trees.expressions.And;
+import org.apache.doris.nereids.trees.expressions.CompoundPredicate;
+import org.apache.doris.nereids.trees.expressions.EqualTo;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Like;
+import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.ShowResultSetMetaData;
+
+import com.google.common.base.Strings;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * ShowCopyCommand
+ */
+public class ShowCopyCommand extends ShowLoadCommand {
+
+    private String dbName;
+    private Expression whereClause;
+
+    public ShowCopyCommand(String dbName, List<OrderKey> orderByElements,
+                           Expression where, long limit, long offset) {
+        super(where, orderByElements, limit, offset, dbName, false);
+        this.dbName = dbName;
+        this.whereClause = where;
+    }
+
+    @Override
+    protected boolean validate(ConnectContext ctx) throws AnalysisException {
+        if (Strings.isNullOrEmpty(dbName)) {
+            dbName = ctx.getDatabase();
+            if (Strings.isNullOrEmpty(dbName)) {
+                ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR);
+            }
+        }
+
+        // analyze where clause if not null
+        if (whereClause != null) {
+            if (whereClause instanceof CompoundPredicate) {
+                return analyzeCompoundPredicate(whereClause);
+            } else {
+                return analyzeSubPredicate(whereClause);
+            }
+        }
+        return true;
+    }
+
+    private boolean analyzeSubPredicate(Expression expr) throws 
AnalysisException {
+        if (expr == null) {
+            return true;
+        }
+        return isWhereClauseValid(expr);
+    }
+
+    private boolean isWhereClauseValid(Expression expr) {
+        boolean hasLabel = false;
+        boolean hasState = false;
+        boolean hasCopyId = false;
+        boolean hasTableName = false;
+        boolean hasFile = false;
+
+        if (!((expr instanceof EqualTo) || (expr instanceof Like))) {
+            return false;
+        }
+
+        if (!(expr.child(0) instanceof UnboundSlot)) {
+            return false;
+        }
+
+        String leftKey = ((UnboundSlot) expr.child(0)).getName();
+
+        if (leftKey.equalsIgnoreCase("label")) {
+            hasLabel = true;
+        } else if (leftKey.equalsIgnoreCase("state")) {
+            hasState = true;
+        } else if (leftKey.equalsIgnoreCase("id")) {
+            hasCopyId = true;
+        } else if (leftKey.equalsIgnoreCase("TableName")) {
+            hasTableName = true;
+        } else if (leftKey.equalsIgnoreCase("files")) {
+            hasFile = true;
+        } else {
+            return false;
+        }
+
+        if (hasState && !(expr instanceof EqualTo)) {
+            return false;
+        }
+
+        if (hasLabel && expr instanceof EqualTo) {
+            isAccurateMatch = true;
+        }
+
+        if (hasCopyId && expr instanceof EqualTo) {
+            isCopyIdAccurateMatch = true;
+        }
+
+        if (hasTableName && expr instanceof EqualTo) {
+            isTableNameAccurateMatch = true;
+        }
+
+        if (hasFile && expr instanceof EqualTo) {
+            isFilesAccurateMatch = true;
+        }
+
+        // right child
+        if (!(expr.child(1) instanceof StringLiteral)) {
+            return false;
+        }
+
+        String value = ((StringLiteral) expr.child(1)).getStringValue();
+        if (Strings.isNullOrEmpty(value)) {
+            return false;
+        }
+
+        if (hasLabel && !isAccurateMatch && !value.contains("%")) {
+            value = "%" + value + "%";
+        }
+        if (hasCopyId && !isCopyIdAccurateMatch && !value.contains("%")) {
+            value = "%" + value + "%";
+        }
+        if (hasTableName && !isTableNameAccurateMatch && !value.contains("%")) 
{
+            value = "%" + value + "%";
+        }
+        if (hasFile && !isFilesAccurateMatch && !value.contains("%")) {
+            value = "%" + value + "%";
+        }
+
+        if (hasLabel) {
+            labelValue = value;
+        } else if (hasState) {
+            stateValue = value.toUpperCase();
+
+            try {
+                JobState.valueOf(stateValue);
+            } catch (Exception e) {
+                return false;
+            }
+        } else if (hasCopyId) {
+            copyIdValue = value;
+        } else if (hasTableName) {
+            tableNameValue = value;
+        } else if (hasFile) {
+            fileValue = value;
+        }
+        return true;
+    }
+
+    private boolean analyzeCompoundPredicate(Expression expr) throws 
AnalysisException {
+        List<Expression> children = new ArrayList<>();
+        analyzeCompoundPredicate(expr, children);
+        Set<String> names = new HashSet<>();
+        boolean result = true;
+        for (Expression expression : children) {
+            String name = ((UnboundSlot) expression.child(0)).getName();
+            if (names.contains(name)) {
+                throw new AnalysisException("column names on both sides of 
operator AND should be diffrent");
+            }
+            names.add(name);
+            result = result && analyzeSubPredicate(expression);
+            if (!result) {
+                return result;
+            }
+        }
+        return result;
+    }
+
+    private void analyzeCompoundPredicate(Expression expr, List<Expression> 
exprs) throws AnalysisException {
+        if (expr instanceof CompoundPredicate) {
+            CompoundPredicate cp = (CompoundPredicate) expr;
+            if (!(cp instanceof And)) {
+                throw new AnalysisException("Only allow compound predicate 
with operator AND");
+            }
+            analyzeCompoundPredicate(expr.child(0), exprs);
+            analyzeCompoundPredicate(expr.child(1), exprs);
+        } else {
+            exprs.add(expr);
+        }
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitShowCopyCommand(this, context);
+    }
+
+    @Override
+    public ShowResultSetMetaData getMetaData() {
+        ShowResultSetMetaData.Builder builder = 
ShowResultSetMetaData.builder();
+        for (String title : LoadProcDir.COPY_TITLE_NAMES) {
+            builder.addColumn(new Column(title, ScalarType.createVarchar(30)));
+        }
+        return builder.build();
+    }
+
+    @Override
+    public RedirectStatus toRedirectStatus() {
+        return RedirectStatus.FORWARD_NO_SYNC;
+    }
+}
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 3e63f3328ad..95be19629da 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
@@ -148,6 +148,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.ShowColumnHistogramStatsCom
 import org.apache.doris.nereids.trees.plans.commands.ShowConfigCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowConstraintsCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowConvertLSCCommand;
+import org.apache.doris.nereids.trees.plans.commands.ShowCopyCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowCreateCatalogCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowCreateDatabaseCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowCreateMTMVCommand;
@@ -1166,6 +1167,10 @@ public interface CommandVisitor<R, C> {
         return visitCommand(truncateTableCommand, context);
     }
 
+    default R visitShowCopyCommand(ShowCopyCommand showCopyCommand, C context) 
{
+        return visitCommand(showCopyCommand, context);
+    }
+
     default R visitGrantRoleCommand(GrantRoleCommand grantRoleCommand, C 
context) {
         return visitCommand(grantRoleCommand, context);
     }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowCopyCommandTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowCopyCommandTest.java
new file mode 100644
index 00000000000..77859fa3bb5
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowCopyCommandTest.java
@@ -0,0 +1,127 @@
+// 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.common.AnalysisException;
+import org.apache.doris.common.util.OrderByPair;
+import org.apache.doris.nereids.analyzer.UnboundSlot;
+import org.apache.doris.nereids.properties.OrderKey;
+import org.apache.doris.nereids.trees.expressions.And;
+import org.apache.doris.nereids.trees.expressions.EqualTo;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Like;
+import org.apache.doris.nereids.trees.expressions.Or;
+import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
+import org.apache.doris.utframe.TestWithFeService;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShowCopyCommandTest extends TestWithFeService {
+    public static final ImmutableList<String> LOAD_TITLE_NAMES = new 
ImmutableList.Builder<String>()
+            .add("JobId").add("Label").add("State").add("Progress")
+            
.add("Type").add("EtlInfo").add("TaskInfo").add("ErrorMsg").add("CreateTime")
+            
.add("EtlStartTime").add("EtlFinishTime").add("LoadStartTime").add("LoadFinishTime")
+            
.add("URL").add("JobDetails").add("TransactionId").add("ErrorTablets").add("User").add("Comment")
+            .build();
+
+    @Override
+    protected void runBeforeAll() throws Exception {
+        createDatabase("test");
+    }
+
+    @Test
+    public void testValidate() throws AnalysisException {
+        // test where is null but db is not null
+        ShowCopyCommand command = new ShowCopyCommand("test", null, null, -1, 
-1);
+        Assertions.assertTrue(command.validate(connectContext));
+
+        // test where is not null
+        Expression where1 = new EqualTo(new 
UnboundSlot(Lists.newArrayList("LABEL")),
+                new StringLiteral("xxx"));
+        command = new ShowCopyCommand("test", null, where1, -1, -1);
+        Assertions.assertTrue(command.validate(connectContext));
+        Assertions.assertTrue(command.isAccurateMatch());
+
+        // test where is And, child(0) or child(1) is EqualTo
+        Expression equalTo1 = new EqualTo(new 
UnboundSlot(Lists.newArrayList("STATE")),
+                new StringLiteral("PENDING"));
+        Expression equalTo2 = new EqualTo(new 
UnboundSlot(Lists.newArrayList("LABEL")),
+                new StringLiteral("test_label"));
+        Expression where3 = new And(equalTo1, equalTo2);
+        command = new ShowCopyCommand("test", null, where3, -1, -1);
+        Assertions.assertTrue(command.validate(connectContext));
+
+        // test where is And, child(0) or child(1) is ComparisonPredicate
+        Expression equalTo = new EqualTo(new 
UnboundSlot(Lists.newArrayList("STATE")),
+                new StringLiteral("PENDING"));
+        Expression like = new Like(new 
UnboundSlot(Lists.newArrayList("LABEL")),
+                new StringLiteral("xxx%"));
+        Expression where4 = new And(equalTo, like);
+        command = new ShowCopyCommand("test", null, where4, -1, -1);
+        Assertions.assertTrue(command.validate(connectContext));
+
+        // test where is Or
+        Expression equalTo4 = new EqualTo(new 
UnboundSlot(Lists.newArrayList("STATE")),
+                new StringLiteral("STATE"));
+        Expression equalTo5 = new EqualTo(new 
UnboundSlot(Lists.newArrayList("LABEL")),
+                new StringLiteral("xxx"));
+        Expression where5 = new Or(equalTo4, equalTo5);
+        command = new ShowCopyCommand("test", null, where5, -1, -1);
+        ShowCopyCommand command1 = command;
+        Assertions.assertThrows(AnalysisException.class, () -> 
command1.validate(connectContext));
+    }
+
+    @Test
+    public void testProcessOrderBy() throws AnalysisException {
+        UnboundSlot key = new UnboundSlot(Lists.newArrayList("LABEL"));
+        List<OrderKey> orderKeys = Lists.newArrayList(new OrderKey(key, true, 
true));
+        ShowCopyCommand command = new ShowCopyCommand("test", orderKeys, null, 
-1, -1);
+        ArrayList<OrderByPair> orderByPairs = 
command.getOrderByPairs(orderKeys, LOAD_TITLE_NAMES);
+        OrderByPair op = orderByPairs.get(0);
+        Assertions.assertFalse(op.isDesc());
+        Assertions.assertEquals(1, op.getIndex());
+    }
+
+    @Test
+    public void testApplyLimit() {
+        UnboundSlot key = new UnboundSlot(Lists.newArrayList("LABEL"));
+        List<OrderKey> orderKeys = Lists.newArrayList(new OrderKey(key, true, 
true));
+        long limit = 1;
+        long offset = 1;
+        ShowCopyCommand command = new ShowCopyCommand("test", orderKeys, null, 
limit, offset);
+        List<List<String>> rows = new ArrayList<>();
+        List<String> row1 = new ArrayList<>();
+        List<String> row2 = new ArrayList<>();
+        row1.add("a");
+        row1.add("b");
+        row2.add("x");
+        row2.add("y");
+        rows.add(row1);
+        rows.add(row2);
+        rows = command.applyLimit(limit, offset, rows);
+        Assertions.assertEquals(1, rows.size());
+        Assertions.assertEquals("x", rows.get(0).get(0));
+        Assertions.assertEquals("y", rows.get(0).get(1));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to