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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]