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 9ce55d1a0de [Enhancement] (nereids) implement cleanQueryStatsCommand 
in nereids (#49120)
9ce55d1a0de is described below

commit 9ce55d1a0de0ad5900120ce2fbcd8fa5366c6f87
Author: yaoxiao <yx136264...@163.com>
AuthorDate: Wed Apr 30 11:23:58 2025 +0800

    [Enhancement] (nereids) implement cleanQueryStatsCommand in nereids (#49120)
    
    Issue Number: #42571, #42572
---
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |  10 +-
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  16 ++
 .../apache/doris/nereids/trees/plans/PlanType.java |   1 +
 .../plans/commands/CleanQueryStatsCommand.java     | 176 +++++++++++++++++++++
 .../trees/plans/commands/info/TableNameInfo.java   |  13 ++
 .../trees/plans/visitor/CommandVisitor.java        |   5 +
 .../plans/commands/CleanQueryStatsCommandTest.java | 108 +++++++++++++
 7 files changed, 322 insertions(+), 7 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 7679e7354d9..8b3bd5b2d3a 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
@@ -81,7 +81,6 @@ unsupportedStatement
     | unsupportedGrantRevokeStatement
     | unsupportedAdminStatement
     | unsupportedCancelStatement
-    | unsupportedCleanStatement
     | unsupportedRefreshStatement
     | unsupportedLoadStatement
     | unsupportedShowStatement
@@ -530,18 +529,15 @@ supportedRefreshStatement
 supportedCleanStatement
     : CLEAN ALL PROFILE                                                        
     #cleanAllProfile
     | CLEAN LABEL label=identifier? (FROM | IN) database=identifier            
     #cleanLabel
+    | CLEAN QUERY STATS ((FOR database=identifier)
+        | ((FROM | IN) table=multipartIdentifier))                             
     #cleanQueryStats
+    | CLEAN ALL QUERY STATS                                                    
     #cleanAllQueryStats
     ;
 
 unsupportedRefreshStatement
     : REFRESH LDAP (ALL | (FOR user=identifierOrText))                         
     #refreshLdap
     ;
 
-unsupportedCleanStatement
-    : CLEAN QUERY STATS ((FOR database=identifier)
-        | ((FROM | IN) table=multipartIdentifier))                             
     #cleanQueryStats
-    | CLEAN ALL QUERY STATS                                                    
     #cleanAllQueryStats
-    ;
-
 supportedCancelStatement
     : CANCEL LOAD ((FROM | IN) database=identifier)? wildWhere?                
     #cancelLoad
     | CANCEL EXPORT ((FROM | IN) database=identifier)? wildWhere?              
     #cancelExport
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 ab97bcdc989..853826595f9 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
@@ -562,6 +562,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.CancelLoadCommand;
 import org.apache.doris.nereids.trees.plans.commands.CancelMTMVTaskCommand;
 import org.apache.doris.nereids.trees.plans.commands.CancelWarmUpJobCommand;
 import org.apache.doris.nereids.trees.plans.commands.CleanAllProfileCommand;
+import org.apache.doris.nereids.trees.plans.commands.CleanQueryStatsCommand;
 import org.apache.doris.nereids.trees.plans.commands.Command;
 import org.apache.doris.nereids.trees.plans.commands.Constraint;
 import org.apache.doris.nereids.trees.plans.commands.CopyIntoCommand;
@@ -6647,6 +6648,21 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
             properties);
     }
 
+    @Override
+    public LogicalPlan 
visitCleanAllQueryStats(DorisParser.CleanAllQueryStatsContext ctx) {
+        return new CleanQueryStatsCommand();
+    }
+
+    @Override
+    public LogicalPlan visitCleanQueryStats(DorisParser.CleanQueryStatsContext 
ctx) {
+        if (ctx.database != null) {
+            return new CleanQueryStatsCommand(ctx.identifier().getText());
+        } else {
+            TableNameInfo tableNameInfo = new 
TableNameInfo(visitMultipartIdentifier(ctx.table));
+            return new CleanQueryStatsCommand(tableNameInfo);
+        }
+    }
+
     @Override
     public LogicalPlan visitStopDataSyncJob(DorisParser.StopDataSyncJobContext 
ctx) {
         List<String> nameParts = visitMultipartIdentifier(ctx.name);
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 70ef5d782be..08bfb08b035 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
@@ -339,6 +339,7 @@ public enum PlanType {
     DROP_EXPIRED_STATS_COMMAND,
     ALTER_TABLE_STATS_COMMAND,
     ALTER_COLUMN_STATS_COMMAND,
+    CLEAN_QUERY_STATS_COMMAND,
     PAUSE_DATA_SYNC_JOB_COMMAND,
     RESUME_DATA_SYNC_JOB_COMMAND,
     STOP_DATA_SYNC_JOB_COMMAND,
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CleanQueryStatsCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CleanQueryStatsCommand.java
new file mode 100644
index 00000000000..d8f9d8a182b
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CleanQueryStatsCommand.java
@@ -0,0 +1,176 @@
+// 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.CleanQueryStatsStmt;
+import org.apache.doris.analysis.StmtType;
+import org.apache.doris.catalog.DatabaseIf;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.cluster.ClusterNamespace;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.commands.info.TableNameInfo;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.persist.CleanQueryStatsInfo;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.StmtExecutor;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * CLEAN ALL QUERY STATS;
+ * CLEAN DATABASE QUERY STATS FROM db;
+ * CLEAN TABLE QUERY STATS FROM db.table;
+ */
+public class CleanQueryStatsCommand extends Command implements ForwardWithSync 
{
+    private String dbName;
+    private TableNameInfo tableNameInfo;
+    private Scope scope;
+
+    /**
+     * CLEAN ALL QUERY STATS
+     */
+    public CleanQueryStatsCommand() {
+        super(PlanType.CLEAN_QUERY_STATS_COMMAND);
+        this.scope = Scope.ALL;
+        this.tableNameInfo = null;
+        this.dbName = null;
+    }
+
+    /**
+     * CLEAN DATABASE QUERY STATS FROM db;
+     */
+    public CleanQueryStatsCommand(String dbName) {
+        super(PlanType.CLEAN_QUERY_STATS_COMMAND);
+        this.dbName = dbName;
+        this.tableNameInfo = null;
+        this.scope = Scope.DB;
+    }
+
+    /**
+     * CLEAN TABLE QUERY STATS FROM db.table;
+     */
+    public CleanQueryStatsCommand(TableNameInfo tableNameInfo) {
+        super(PlanType.CLEAN_QUERY_STATS_COMMAND);
+        this.dbName = null;
+        this.tableNameInfo = tableNameInfo;
+        this.scope = Scope.TABLE;
+    }
+
+    @Override
+    public void run(ConnectContext ctx, StmtExecutor executor) throws 
Exception {
+        validate(ctx);
+        handleCleanQueryStatsCommand(ctx);
+    }
+
+    /**
+     * validate
+     */
+    public void validate(ConnectContext ctx) throws AnalysisException {
+        switch (scope) {
+            case ALL:
+                if (!Env.getCurrentEnv().getAccessManager()
+                        .checkGlobalPriv(ConnectContext.get(), 
PrivPredicate.ADMIN)) {
+                    
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
+                            "CLEAN ALL QUERY STATS");
+                }
+                break;
+            case DB:
+                if (StringUtils.isEmpty(dbName)) {
+                    dbName = ctx.getDatabase();
+                }
+                if (StringUtils.isEmpty(dbName)) {
+                    
ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR);
+                }
+
+                
Env.getCurrentEnv().getCurrentCatalog().getDbOrAnalysisException(dbName);
+                if (!Env.getCurrentEnv().getAccessManager()
+                        .checkDbPriv(ConnectContext.get(), 
ctx.getCurrentCatalog().getName(), dbName,
+                            PrivPredicate.ALTER)) {
+                    
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
+                            "CLEAN DATABASE QUERY STATS FOR " + 
ClusterNamespace.getNameFromFullName(dbName));
+                }
+                break;
+            case TABLE:
+                tableNameInfo.analyze(ctx);
+                dbName = tableNameInfo.getDb();
+                if (StringUtils.isEmpty(dbName)) {
+                    
ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR);
+                }
+                DatabaseIf db = 
Env.getCurrentEnv().getCurrentCatalog().getDbOrAnalysisException(dbName);
+                db.getTableOrAnalysisException(tableNameInfo.getTbl());
+                if (!Env.getCurrentEnv().getAccessManager()
+                        .checkTblPriv(ConnectContext.get(), 
tableNameInfo.getCtl(), dbName, tableNameInfo.getTbl(),
+                            PrivPredicate.ALTER)) {
+                    
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
+                            "CLEAN TABLE QUERY STATS FROM " + tableNameInfo);
+                }
+                break;
+            default:
+                throw new IllegalStateException("Unexpected value: " + scope);
+        }
+    }
+
+    private void handleCleanQueryStatsCommand(ConnectContext ctx) throws 
DdlException {
+        CleanQueryStatsInfo cleanQueryStatsInfo;
+        Env env = ctx.getEnv();
+        switch (scope) {
+            case ALL:
+                cleanQueryStatsInfo = new CleanQueryStatsInfo(
+                    translateToLegacyScope(Scope.ALL), 
env.getCurrentCatalog().getName(), null, null);
+                break;
+            case DB:
+                cleanQueryStatsInfo = new CleanQueryStatsInfo(
+                    translateToLegacyScope(Scope.DB), 
env.getCurrentCatalog().getName(), tableNameInfo.getDb(), null);
+                break;
+            case TABLE:
+                cleanQueryStatsInfo = new CleanQueryStatsInfo(
+                    translateToLegacyScope(Scope.TABLE), 
env.getCurrentCatalog().getName(), tableNameInfo.getDb(),
+                        tableNameInfo.getTbl());
+                break;
+            default:
+                throw new DdlException("Unknown scope: " + scope);
+        }
+        env.cleanQueryStats(cleanQueryStatsInfo);
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitCleanQueryStatsCommand(this, context);
+    }
+
+    /**
+     * Scope of clean query stats
+     */
+    public enum Scope {
+        ALL, DB, TABLE
+    }
+
+    private CleanQueryStatsStmt.Scope translateToLegacyScope(Scope scope) {
+        return CleanQueryStatsStmt.Scope.valueOf(scope.name());
+    }
+
+    @Override
+    public StmtType stmtType() {
+        return StmtType.CLEAN;
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/TableNameInfo.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/TableNameInfo.java
index d184c330116..bc951cb7fc4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/TableNameInfo.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/TableNameInfo.java
@@ -196,6 +196,19 @@ public class TableNameInfo implements Writable {
         tbl = fromJson.tbl;
     }
 
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder();
+        if (ctl != null && !ctl.equals(InternalCatalog.INTERNAL_CATALOG_NAME)) 
{
+            stringBuilder.append(ctl).append(".");
+        }
+        if (db != null) {
+            stringBuilder.append(db).append(".");
+        }
+        stringBuilder.append(tbl);
+        return stringBuilder.toString();
+    }
+
     /**
      * equals
      */
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 ff96b0ae75e..92dae110281 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
@@ -47,6 +47,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.CancelLoadCommand;
 import org.apache.doris.nereids.trees.plans.commands.CancelMTMVTaskCommand;
 import org.apache.doris.nereids.trees.plans.commands.CancelWarmUpJobCommand;
 import org.apache.doris.nereids.trees.plans.commands.CleanAllProfileCommand;
+import org.apache.doris.nereids.trees.plans.commands.CleanQueryStatsCommand;
 import org.apache.doris.nereids.trees.plans.commands.Command;
 import org.apache.doris.nereids.trees.plans.commands.CopyIntoCommand;
 import org.apache.doris.nereids.trees.plans.commands.CreateCatalogCommand;
@@ -968,6 +969,10 @@ public interface CommandVisitor<R, C> {
         return visitCommand(alterColumnStatsCommand, context);
     }
 
+    default R visitCleanQueryStatsCommand(CleanQueryStatsCommand 
cleanQueryStatsCommand, C context) {
+        return visitCommand(cleanQueryStatsCommand, context);
+    }
+
     default R visitPauseDataSyncJobCommand(PauseDataSyncJobCommand 
pauseDataSyncJobCommand, C context) {
         return visitCommand(pauseDataSyncJobCommand, context);
     }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/CleanQueryStatsCommandTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/CleanQueryStatsCommandTest.java
new file mode 100644
index 00000000000..eae35f9f2c5
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/CleanQueryStatsCommandTest.java
@@ -0,0 +1,108 @@
+// 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.catalog.Env;
+import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.mysql.privilege.AccessControllerManager;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.trees.plans.commands.info.TableNameInfo;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.utframe.TestWithFeService;
+
+import mockit.Expectations;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+public class CleanQueryStatsCommandTest extends TestWithFeService {
+    private static final String internalCtl = 
InternalCatalog.INTERNAL_CATALOG_NAME;
+    private ConnectContext connectContext;
+    private Env env;
+    private AccessControllerManager accessControllerManager;
+
+    public void runBefore() throws IOException {
+        connectContext = createDefaultCtx();
+        env = Env.getCurrentEnv();
+        accessControllerManager = env.getAccessManager();
+    }
+
+    @Test
+    public void testAllNormal() throws IOException {
+        runBefore();
+        new Expectations() {
+            {
+                connectContext.isSkipAuth();
+                minTimes = 0;
+                result = true;
+
+                accessControllerManager.checkGlobalPriv(connectContext, 
PrivPredicate.ADMIN);
+                minTimes = 0;
+                result = true;
+            }
+        };
+        CleanQueryStatsCommand command = new CleanQueryStatsCommand();
+        Assertions.assertDoesNotThrow(() -> command.validate(connectContext));
+    }
+
+    @Test
+    public void testDB() throws Exception {
+        runBefore();
+        connectContext.setDatabase("test_db");
+        //normal
+        new Expectations() {
+            {
+                connectContext.isSkipAuth();
+                minTimes = 0;
+                result = true;
+
+                accessControllerManager.checkDbPriv(connectContext, 
internalCtl, "test_db", PrivPredicate.ALTER);
+                minTimes = 0;
+                result = true;
+            }
+        };
+        CleanQueryStatsCommand command = new CleanQueryStatsCommand("test_db");
+        Assertions.assertDoesNotThrow(() -> command.validate(connectContext));
+    }
+
+    @Test
+    public void testTbl() throws Exception {
+        runBefore();
+        createDatabase("test_db");
+        createTable("create table test_db.test_tbl\n" + "(k1 int, k2 int)\n"
+                + "duplicate key(k1)\n" + "partition by range(k2)\n" + 
"(partition p1 values less than(\"10\"))\n"
+                + "distributed by hash(k2) buckets 1\n" + 
"properties('replication_num' = '1'); ");
+        TableNameInfo tableNameInfo = new TableNameInfo("test_db", "test_tbl");
+        connectContext.setDatabase("test_db");
+        //normal
+        new Expectations() {
+            {
+                connectContext.isSkipAuth();
+                minTimes = 0;
+                result = true;
+
+                accessControllerManager.checkTblPriv(connectContext, 
tableNameInfo, PrivPredicate.ALTER);
+                minTimes = 0;
+                result = true;
+            }
+        };
+        CleanQueryStatsCommand command = new 
CleanQueryStatsCommand(tableNameInfo);
+        Assertions.assertDoesNotThrow(() -> command.validate(connectContext));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org

Reply via email to