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 d9d67511b6e [Feat](nereids) support ShowQueryStatsCommand (#50998)
d9d67511b6e is described below

commit d9d67511b6e8574a05c611beb252066748283f99
Author: Jensen <czjour...@163.com>
AuthorDate: Tue May 20 14:32:49 2025 +0800

    [Feat](nereids) support ShowQueryStatsCommand (#50998)
---
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |   4 +-
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  17 ++
 .../apache/doris/nereids/trees/plans/PlanType.java |   1 +
 .../plans/commands/ShowQueryStatsCommand.java      | 207 +++++++++++++++++++++
 .../trees/plans/visitor/CommandVisitor.java        |   6 +
 .../plans/commands/ShowQueryStatsCommandTest.java  | 116 ++++++++++++
 6 files changed, 349 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 cb4889547e9..4c1c7f97f18 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
@@ -384,6 +384,8 @@ supportedShowStatement
         wildWhere? sortClause? limitClause?                                    
     #showTabletsFromTable
     | SHOW TABLET tabletId=INTEGER_VALUE                                       
     #showTabletId
     | SHOW DICTIONARIES wildWhere?                                             
     #showDictionaries
+    | SHOW QUERY STATS ((FOR database=identifier)
+            | (FROM tableName=multipartIdentifier (ALL VERBOSE?)?))?           
     #showQueryStats
     ;
 
 supportedLoadStatement
@@ -465,8 +467,6 @@ unsupportedShowStatement
     | SHOW TRANSACTION ((FROM | IN) database=multipartIdentifier)? wildWhere?  
     #showTransaction
     | SHOW CACHE HOTSPOT tablePath=STRING_LITERAL                              
     #showCacheHotSpot
     | SHOW CATALOG RECYCLE BIN wildWhere?                                      
     #showCatalogRecycleBin
-    | SHOW QUERY STATS ((FOR database=identifier)
-            | (FROM tableName=multipartIdentifier (ALL VERBOSE?)?))?           
     #showQueryStats
     | SHOW BUILD INDEX ((FROM | IN) database=multipartIdentifier)?
         wildWhere? sortClause? limitClause?                                    
     #showBuildIndex
     | SHOW REPLICA STATUS FROM baseTableRef wildWhere?                         
     #showReplicaStatus
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 b65bfac2728..c3ba347cca9 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
@@ -359,6 +359,7 @@ import org.apache.doris.nereids.DorisParser.ShowProcContext;
 import org.apache.doris.nereids.DorisParser.ShowProcedureStatusContext;
 import org.apache.doris.nereids.DorisParser.ShowProcessListContext;
 import org.apache.doris.nereids.DorisParser.ShowQueryProfileContext;
+import org.apache.doris.nereids.DorisParser.ShowQueryStatsContext;
 import org.apache.doris.nereids.DorisParser.ShowQueuedAnalyzeJobsContext;
 import org.apache.doris.nereids.DorisParser.ShowReplicaDistributionContext;
 import org.apache.doris.nereids.DorisParser.ShowRepositoriesContext;
@@ -704,6 +705,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.ShowProcCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.ShowProcedureStatusCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowProcessListCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowQueryProfileCommand;
+import org.apache.doris.nereids.trees.plans.commands.ShowQueryStatsCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.ShowQueuedAnalyzeJobsCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.ShowReplicaDistributionCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowRepositoriesCommand;
@@ -6171,6 +6173,21 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return new ShowQueryProfileCommand(queryIdPath, limit);
     }
 
+    @Override
+    public LogicalPlan visitShowQueryStats(ShowQueryStatsContext ctx) {
+        String dbName = null;
+        if (ctx.database != null) {
+            dbName = ctx.database.getText();
+        }
+        TableNameInfo tableNameInfo = null;
+        if (ctx.tableName != null) {
+            tableNameInfo = new 
TableNameInfo(visitMultipartIdentifier(ctx.tableName));
+        }
+        boolean isAll = ctx.ALL() != null;
+        boolean isVerbose = ctx.VERBOSE() != null;
+        return new ShowQueryStatsCommand(dbName, tableNameInfo, isAll, 
isVerbose);
+    }
+
     @Override
     public LogicalPlan visitSwitchCatalog(SwitchCatalogContext ctx) {
         if (ctx.catalog != null) {
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 d469575c4c5..5b0c16cb4c2 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
@@ -318,6 +318,7 @@ public enum PlanType {
     CREATE_ROUTINE_LOAD_COMMAND,
     SHOW_TABLE_CREATION_COMMAND,
     SHOW_QUERY_PROFILE_COMMAND,
+    SHOW_QUERY_STATS_COMMAND,
     SWITCH_COMMAND,
     HELP_COMMAND,
     USE_COMMAND,
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowQueryStatsCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowQueryStatsCommand.java
new file mode 100644
index 00000000000..b3b10c56ddb
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowQueryStatsCommand.java
@@ -0,0 +1,207 @@
+// 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.Column;
+import org.apache.doris.catalog.DatabaseIf;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.ScalarType;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+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.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.qe.ConnectContext;
+import org.apache.doris.qe.ShowResultSet;
+import org.apache.doris.qe.ShowResultSetMetaData;
+import org.apache.doris.qe.StmtExecutor;
+import org.apache.doris.statistics.query.QueryStatsUtil;
+
+import com.google.common.base.Preconditions;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The command for show query stats
+ */
+public class ShowQueryStatsCommand extends ShowCommand {
+    private static final ShowResultSetMetaData 
SHOW_QUERY_STATS_CATALOG_META_DATA = ShowResultSetMetaData.builder()
+            .addColumn(new Column("Database", ScalarType.createVarchar(20)))
+            .addColumn(new Column("QueryCount", 
ScalarType.createVarchar(30))).build();
+    private static final ShowResultSetMetaData 
SHOW_QUERY_STATS_DATABASE_META_DATA = ShowResultSetMetaData.builder()
+            .addColumn(new Column("TableName", ScalarType.createVarchar(20)))
+            .addColumn(new Column("QueryCount", 
ScalarType.createVarchar(30))).build();
+    private static final ShowResultSetMetaData 
SHOW_QUERY_STATS_TABLE_META_DATA = ShowResultSetMetaData.builder()
+            .addColumn(new Column("Field", ScalarType.createVarchar(20)))
+            .addColumn(new Column("QueryCount", ScalarType.createVarchar(30)))
+            .addColumn(new Column("FilterCount", 
ScalarType.createVarchar(30))).build();
+    private static final ShowResultSetMetaData 
SHOW_QUERY_STATS_TABLE_ALL_META_DATA = ShowResultSetMetaData.builder()
+            .addColumn(new Column("IndexName", ScalarType.createVarchar(20)))
+            .addColumn(new Column("QueryCount", 
ScalarType.createVarchar(30))).build();
+    private static final ShowResultSetMetaData 
SHOW_QUERY_STATS_TABLE_ALL_VERBOSE_META_DATA
+            = ShowResultSetMetaData.builder().addColumn(new 
Column("IndexName", ScalarType.createVarchar(20)))
+            .addColumn(new Column("Field", ScalarType.createVarchar(20)))
+            .addColumn(new Column("QueryCount", ScalarType.createVarchar(30)))
+            .addColumn(new Column("FilterCount", 
ScalarType.createVarchar(30))).build();
+
+    TableNameInfo tableNameInfo;
+    String dbName;
+    boolean all;
+    boolean verbose;
+    List<List<String>> totalRows = new ArrayList<>();
+    ShowQueryStatsType type;
+
+    public ShowQueryStatsCommand(String dbName, TableNameInfo tableNameInfo, 
boolean all, boolean verbose) {
+        super(PlanType.SHOW_QUERY_STATS_COMMAND);
+        this.dbName = dbName;
+        this.tableNameInfo = tableNameInfo;
+        this.all = all;
+        this.verbose = verbose;
+    }
+
+    /**
+     * validate
+     */
+    public void validate(ConnectContext ctx) throws UserException {
+        if (StringUtils.isEmpty(dbName)) {
+            dbName = ctx.getDatabase();
+            type = ShowQueryStatsType.DATABASE;
+        }
+        String catalog = Env.getCurrentEnv().getCurrentCatalog().getName();
+        if (tableNameInfo == null && StringUtils.isEmpty(dbName)) {
+            if 
(!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), 
PrivPredicate.ADMIN)) {
+                
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, 
"SHOW QUERY STATS");
+            }
+            Map<String, Long> result = 
QueryStatsUtil.getMergedCatalogStats(catalog);
+            result.forEach((dbName, queryHit) -> {
+                totalRows.add(Arrays.asList(dbName, String.valueOf(queryHit)));
+            });
+            type = ShowQueryStatsType.CATALOG;
+            return;
+        }
+        if (tableNameInfo != null) {
+            tableNameInfo.analyze(ctx);
+            dbName = tableNameInfo.getDb();
+        }
+        DatabaseIf db = 
Env.getCurrentEnv().getCurrentCatalog().getDbOrDdlException(dbName);
+        String ctlName = db.getCatalog().getName();
+        if (tableNameInfo != null) {
+            db.getTableOrDdlException(tableNameInfo.getTbl());
+        }
+        if (tableNameInfo == null) {
+            Map<String, Long> stats = 
QueryStatsUtil.getMergedDatabaseStats(catalog, dbName);
+            stats.forEach((tableName, queryHit) -> {
+                if (Env.getCurrentEnv().getAccessManager()
+                        .checkTblPriv(ConnectContext.get(), ctlName, dbName, 
tableName, PrivPredicate.SHOW)) {
+                    if (Util.isTempTable(tableName)) {
+                        if (Util.isTempTableInCurrentSession(tableName)) {
+                            
totalRows.add(Arrays.asList(Util.getTempTableDisplayName(tableName),
+                                    String.valueOf(queryHit)));
+                        }
+                    } else {
+                        totalRows.add(Arrays.asList(tableName, 
String.valueOf(queryHit)));
+                    }
+                }
+            });
+        } else {
+            if (!Env.getCurrentEnv().getAccessManager()
+                    .checkTblPriv(ConnectContext.get(), tableNameInfo, 
PrivPredicate.SHOW)) {
+                
ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, 
"SHOW QUERY STATS",
+                        ConnectContext.get().getQualifiedUser(), 
ConnectContext.get().getRemoteIP(),
+                        dbName + ": " + tableNameInfo);
+            }
+            if (all && verbose) {
+                type = ShowQueryStatsType.TABLE_ALL_VERBOSE;
+                QueryStatsUtil.getMergedTableAllVerboseStats(catalog, dbName, 
tableNameInfo.getTbl())
+                        .forEach((indexName, stat) -> {
+                            final boolean[] firstRow = new boolean[] {true};
+                            stat.forEach((col, statCount) -> {
+                                totalRows.add(Arrays.asList(firstRow[0] ? 
indexName : "", col,
+                                        String.valueOf(statCount.first), 
String.valueOf(statCount.second)));
+                                firstRow[0] = false;
+                            });
+                        });
+            } else if (all) {
+                type = ShowQueryStatsType.TABLE_ALL;
+
+                Map<String, Long> stats = 
QueryStatsUtil.getMergedTableAllStats(catalog,
+                        dbName, tableNameInfo.getTbl());
+                stats.forEach((indexName, queryHit) -> {
+                    totalRows.add(Arrays.asList(indexName, 
String.valueOf(queryHit)));
+                });
+            } else if (verbose) {
+                Preconditions.checkState(false, "verbose is not supported if 
all is not set");
+            } else {
+                type = ShowQueryStatsType.TABLE;
+                QueryStatsUtil.getMergedTableStats(catalog, dbName, 
tableNameInfo.getTbl())
+                        .forEach((col, statCount) -> {
+                            totalRows.add(
+                                    Arrays.asList(col, 
String.valueOf(statCount.first),
+                                            String.valueOf(statCount.second)));
+                        });
+            }
+        }
+    }
+
+    /**
+     * MetaData
+     */
+    @Override
+    public ShowResultSetMetaData getMetaData() {
+        switch (type) {
+            case CATALOG:
+                return SHOW_QUERY_STATS_CATALOG_META_DATA;
+            case DATABASE:
+                return SHOW_QUERY_STATS_DATABASE_META_DATA;
+            case TABLE:
+                return SHOW_QUERY_STATS_TABLE_META_DATA;
+            case TABLE_ALL:
+                return SHOW_QUERY_STATS_TABLE_ALL_META_DATA;
+            case TABLE_ALL_VERBOSE:
+                return SHOW_QUERY_STATS_TABLE_ALL_VERBOSE_META_DATA;
+            default:
+                Preconditions.checkState(false);
+                return null;
+        }
+    }
+
+    /**
+     * Show query statistics type
+     */
+    public enum ShowQueryStatsType {
+        CATALOG, DATABASE, TABLE, TABLE_ALL, TABLE_ALL_VERBOSE
+    }
+
+    @Override
+    public ShowResultSet doRun(ConnectContext ctx, StmtExecutor executor) 
throws Exception {
+        validate(ctx);
+        return new ShowResultSet(getMetaData(), totalRows);
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitShowQueryStatsCommand(this, context);
+    }
+}
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 9171bab18e3..066366c70be 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
@@ -173,6 +173,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.ShowProcCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.ShowProcedureStatusCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowProcessListCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowQueryProfileCommand;
+import org.apache.doris.nereids.trees.plans.commands.ShowQueryStatsCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.ShowQueuedAnalyzeJobsCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.ShowReplicaDistributionCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowRepositoriesCommand;
@@ -896,6 +897,11 @@ public interface CommandVisitor<R, C> {
         return visitCommand(showQueryProfileCommand, context);
     }
 
+    default R visitShowQueryStatsCommand(ShowQueryStatsCommand 
showQueryStatsCommand,
+            C context) {
+        return visitCommand(showQueryStatsCommand, context);
+    }
+
     default R visitShowConvertLscCommand(ShowConvertLSCCommand 
showConvertLSCCommand, C context) {
         return visitCommand(showConvertLSCCommand, context);
     }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowQueryStatsCommandTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowQueryStatsCommandTest.java
new file mode 100644
index 00000000000..4226ab6a960
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowQueryStatsCommandTest.java
@@ -0,0 +1,116 @@
+// 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.backup.CatalogMocker;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+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 mockit.Expectations;
+import mockit.Mocked;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ShowQueryStatsCommandTest {
+    private static final String internalCtl = 
InternalCatalog.INTERNAL_CATALOG_NAME;
+    @Mocked
+    private Env env;
+    @Mocked
+    private AccessControllerManager accessControllerManager;
+    @Mocked
+    private ConnectContext connectContext;
+
+    private void runBefore() throws Exception {
+        TableNameInfo tableNameInfo = new TableNameInfo(internalCtl, 
CatalogMocker.TEST_DB_NAME,
+                CatalogMocker.TEST_TBL_NAME);
+        new Expectations() {
+            {
+                Env.getCurrentEnv();
+                minTimes = 0;
+                result = env;
+
+                env.getAccessManager();
+                minTimes = 0;
+                result = accessControllerManager;
+
+                ConnectContext.get();
+                minTimes = 0;
+                result = connectContext;
+
+                connectContext.isSkipAuth();
+                minTimes = 0;
+                result = true;
+
+                accessControllerManager.checkGlobalPriv(connectContext, 
PrivPredicate.ADMIN);
+                minTimes = 0;
+                result = true;
+
+                accessControllerManager.checkTblPriv(connectContext, 
tableNameInfo, PrivPredicate.SHOW);
+                minTimes = 0;
+                result = true;
+            }
+        };
+    }
+
+    @Test
+    public void testValidateWithPrivilege() throws Exception {
+        runBefore();
+        TableNameInfo tableNameInfo =
+                new TableNameInfo(CatalogMocker.TEST_DB_NAME, 
CatalogMocker.TEST_TBL_NAME);
+
+        // normal
+        ShowQueryStatsCommand command = new 
ShowQueryStatsCommand(tableNameInfo.getDb(),
+                tableNameInfo, false, false);
+        Assertions.assertDoesNotThrow(() -> command.validate(connectContext));
+    }
+
+    @Test
+    void testValidateNoPrivilege() {
+        new Expectations() {
+            {
+                Env.getCurrentEnv();
+                minTimes = 0;
+                result = env;
+
+                env.getAccessManager();
+                minTimes = 0;
+                result = accessControllerManager;
+
+                accessControllerManager.checkGlobalPriv(connectContext, 
PrivPredicate.ADMIN);
+                minTimes = 0;
+                result = false;
+
+                accessControllerManager.checkTblPriv(connectContext, 
internalCtl, CatalogMocker.TEST_DB_NAME,
+                        CatalogMocker.TEST_TBL2_NAME, PrivPredicate.SHOW);
+                minTimes = 0;
+                result = false;
+            }
+        };
+
+        TableNameInfo tableNameInfo =
+                new TableNameInfo(CatalogMocker.TEST_DB_NAME, 
CatalogMocker.TEST_TBL_NAME);
+        ShowQueryStatsCommand command = new 
ShowQueryStatsCommand(tableNameInfo.getDb(),
+                tableNameInfo, false, false);
+        Assertions.assertThrows(AnalysisException.class, () -> 
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