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