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 5a15b5dc5fe [Enhancement] (nereids) support show table status command 
(#48715)
5a15b5dc5fe is described below

commit 5a15b5dc5fee554f6a7fa546f3ad612a27471c9c
Author: Jensen <czjour...@163.com>
AuthorDate: Mon Mar 10 10:16:17 2025 +0800

    [Enhancement] (nereids) support show table status command (#48715)
---
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |   2 +-
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  29 +++
 .../apache/doris/nereids/trees/plans/PlanType.java |   1 +
 .../plans/commands/ShowTableStatusCommand.java     | 243 +++++++++++++++++++++
 .../trees/plans/visitor/CommandVisitor.java        |   5 +
 .../suites/auth_call/test_dml_analyze_auth.groovy  |   9 +-
 6 files changed, 284 insertions(+), 5 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 9a819baaf0a..5f1728d0aee 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
@@ -337,6 +337,7 @@ supportedShowStatement
     | SHOW QUERY PROFILE queryIdPath=STRING_LITERAL? limitClause?              
      #showQueryProfile
     | SHOW CONVERT_LSC ((FROM | IN) database=multipartIdentifier)?             
     #showConvertLsc
     | SHOW FULL? TABLES ((FROM | IN) database=multipartIdentifier)? wildWhere? 
     #showTables
+    | SHOW TABLE STATUS ((FROM | IN) database=multipartIdentifier)? wildWhere? 
     #showTableStatus
     ;
 
 supportedLoadStatement
@@ -378,7 +379,6 @@ unsupportedShowStatement
     : SHOW ROW POLICY (FOR (userIdentify | (ROLE role=identifier)))?           
     #showRowPolicy
     | SHOW STORAGE (VAULT | VAULTS)                                            
     #showStorageVault
     | SHOW OPEN TABLES ((FROM | IN) database=multipartIdentifier)? wildWhere?  
     #showOpenTables
-    | SHOW TABLE STATUS ((FROM | IN) database=multipartIdentifier)? wildWhere? 
     #showTableStatus
     | SHOW FULL? VIEWS ((FROM | IN) database=multipartIdentifier)? wildWhere?  
     #showViews
     | SHOW CREATE MATERIALIZED VIEW name=multipartIdentifier                   
     #showMaterializedView
     | SHOW CREATE statementScope? FUNCTION functionIdentifier
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 5129c0991a5..bade48c43a6 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
@@ -650,6 +650,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.ShowSyncJobCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowTableCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowTableCreationCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowTableIdCommand;
+import org.apache.doris.nereids.trees.plans.commands.ShowTableStatusCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.ShowTabletStorageFormatCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowTabletsBelongCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowTrashCommand;
@@ -5927,6 +5928,34 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return new ShowQueuedAnalyzeJobsCommand(tableName, stateKey, 
stateValue);
     }
 
+    @Override
+    public LogicalPlan visitShowTableStatus(DorisParser.ShowTableStatusContext 
ctx) {
+        String ctlName = null;
+        String dbName = null;
+        if (ctx.database != null) {
+            List<String> nameParts = visitMultipartIdentifier(ctx.database);
+            if (nameParts.size() == 1) {
+                dbName = nameParts.get(0);
+            } else if (nameParts.size() == 2) {
+                ctlName = nameParts.get(0);
+                dbName = nameParts.get(1);
+            } else {
+                throw new AnalysisException("nameParts in analyze database 
should be [ctl.]db");
+            }
+        }
+
+        if (ctx.wildWhere() != null) {
+            if (ctx.wildWhere().LIKE() != null) {
+                return new ShowTableStatusCommand(dbName, ctlName,
+                    stripQuotes(ctx.wildWhere().STRING_LITERAL().getText()), 
null);
+            } else {
+                Expression expr = (Expression) 
ctx.wildWhere().expression().accept(this);
+                return new ShowTableStatusCommand(dbName, ctlName, null, expr);
+            }
+        }
+        return new ShowTableStatusCommand(dbName, ctlName);
+    }
+
     @Override
     public LogicalPlan visitShowTables(DorisParser.ShowTablesContext ctx) {
         String ctlName = 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 f6a3997dd0f..b319195bbaf 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
@@ -264,6 +264,7 @@ public enum PlanType {
     SHOW_SYNC_JOB_COMMAND,
     SHOW_TABLE_ID_COMMAND,
     SHOW_TABLES,
+    SHOW_TABLES_STATUS,
     SHOW_TRASH_COMMAND,
     SHOW_TABLET_STORAGE_FORMAT_COMMAND,
     SHOW_TRIGGERS_COMMAND,
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowTableStatusCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowTableStatusCommand.java
new file mode 100644
index 00000000000..dbc7e38ce2c
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowTableStatusCommand.java
@@ -0,0 +1,243 @@
+// 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.Env;
+import org.apache.doris.catalog.InfoSchemaDb;
+import org.apache.doris.catalog.PrimitiveType;
+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.Pair;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.analyzer.UnboundSlot;
+import org.apache.doris.nereids.glue.LogicalPlanAdapter;
+import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import 
org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+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.ResultRow;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * ShowTableStautsCommand
+ */
+public class ShowTableStatusCommand extends ShowCommand {
+    private static final ShowResultSetMetaData META_DATA = 
ShowResultSetMetaData.builder()
+            .addColumn(new Column("Name", ScalarType.createVarchar(64)))
+            .addColumn(new Column("Engine", ScalarType.createVarchar(10)))
+            .addColumn(new Column("Version", 
ScalarType.createType(PrimitiveType.BIGINT)))
+            .addColumn(new Column("Row_format", ScalarType.createVarchar(64)))
+            .addColumn(new Column("Rows", 
ScalarType.createType(PrimitiveType.BIGINT)))
+            .addColumn(new Column("Avg_row_length", 
ScalarType.createType(PrimitiveType.BIGINT)))
+            .addColumn(new Column("Data_length", 
ScalarType.createType(PrimitiveType.BIGINT)))
+            .addColumn(new Column("Max_data_length", 
ScalarType.createType(PrimitiveType.BIGINT)))
+            .addColumn(new Column("Index_length", 
ScalarType.createType(PrimitiveType.BIGINT)))
+            .addColumn(new Column("Data_free", 
ScalarType.createType(PrimitiveType.BIGINT)))
+            .addColumn(new Column("Auto_increment", 
ScalarType.createType(PrimitiveType.BIGINT)))
+            .addColumn(new Column("Create_time", 
ScalarType.createType(PrimitiveType.DATETIME)))
+            .addColumn(new Column("Update_time", 
ScalarType.createType(PrimitiveType.DATETIME)))
+            .addColumn(new Column("Check_time", 
ScalarType.createType(PrimitiveType.DATETIME)))
+            .addColumn(new Column("Collation", ScalarType.createVarchar(64)))
+            .addColumn(new Column("Checksum", 
ScalarType.createType(PrimitiveType.BIGINT)))
+            .addColumn(new Column("Create_options", 
ScalarType.createVarchar(64)))
+            .addColumn(new Column("Comment", ScalarType.createVarchar(64)))
+            .build();
+
+    private static Map<String, String> ALIAS_COLUMN_MAP = 
ImmutableMap.<String, String>builder()
+            .put("name", "TABLE_NAME")
+            .put("engine", "ENGINE")
+            .put("version", "VERSION")
+            .put("row_format", "ROW_FORMAT")
+            .put("rows", "TABLE_ROWS")
+            .put("avg_row_length", "AVG_ROW_LENGTH")
+            .put("data_length", "DATA_LENGTH")
+            .put("max_data_length", "MAX_DATA_LENGTH")
+            .put("index_length", "INDEX_LENGTH")
+            .put("data_free", "DATA_FREE")
+            .put("auto_increment", "AUTO_INCREMENT")
+            .put("create_time", "CREATE_TIME")
+            .put("update_time", "UPDATE_TIME")
+            .put("check_time", "CHECK_TIME")
+            .put("collation", "TABLE_COLLATION")
+            .put("checksum", "CHECKSUM")
+            .put("create_options", "CREATE_OPTIONS")
+            .put("comment", "TABLE_COMMENT")
+            .build();
+
+    private String catalog;
+    private String db;
+    private final String likePattern;
+    private final Expression whereClause;
+
+    public ShowTableStatusCommand(String db, String catalog) {
+        this(db, catalog, null, null);
+    }
+
+    /**
+     * ShowTableStautsCommand
+     */
+    public ShowTableStatusCommand(String db, String catalog,
+                                  String likePattern, Expression whereClause) {
+        super(PlanType.SHOW_TABLES_STATUS);
+        this.catalog = catalog;
+        this.db = db;
+        this.likePattern = likePattern;
+        this.whereClause = whereClause;
+    }
+
+    /**
+     * validate
+     */
+    public void validate(ConnectContext ctx) throws AnalysisException {
+        if (Strings.isNullOrEmpty(db)) {
+            db = ctx.getDatabase();
+            if (Strings.isNullOrEmpty(db)) {
+                ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR);
+            }
+        }
+        if (Strings.isNullOrEmpty(catalog)) {
+            catalog = ctx.getDefaultCatalog();
+            if (Strings.isNullOrEmpty(catalog)) {
+                
ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_NAME_FOR_CATALOG);
+            }
+        }
+
+        if 
(!Env.getCurrentEnv().getAccessManager().checkDbPriv(ConnectContext.get(),
+                catalog, db, PrivPredicate.SHOW)) {
+            
ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR, 
ctx.getQualifiedUser(), db);
+        }
+    }
+
+    /**
+     * sql to logical plan
+     * @param sql sql
+     */
+    private LogicalPlan toLogicalPlan(String sql) {
+        return new NereidsParser().parseSingle(sql);
+    }
+
+    /**
+     * Construct a basic SQL without query conditions
+     * @param columns (original name, alias)
+     * @param tableName ctl.db.tbl
+     */
+    private String toBaseSql(List<Pair<String, String>> columns, String 
tableName) throws AnalysisException {
+        if (columns == null || columns.isEmpty()) {
+            throw new AnalysisException("columns cannot be empty");
+        }
+        if (tableName == null || tableName.isEmpty()) {
+            throw new AnalysisException("tableName cannot be empty");
+        }
+
+        StringBuilder sb = new StringBuilder("SELECT ");
+        columns.forEach(column -> {
+            sb.append(column.first);
+            if (column.second != null && !column.second.isEmpty()) {
+                sb.append(" AS ").append(column.second);
+            }
+            sb.append(", ");
+        });
+        sb.setLength(sb.length() - 2);
+        sb.append(" FROM ").append(tableName);
+        return sb.toString();
+    }
+
+    /**
+     * replaceColumnNameVisitor
+     * replace column name to real column name
+     */
+    private static class ReplaceColumnNameVisitor extends 
DefaultExpressionRewriter<Void> {
+        @Override
+        public Expression visitUnboundSlot(UnboundSlot slot, Void context) {
+            String columnName = 
ALIAS_COLUMN_MAP.get(slot.getName().toLowerCase(Locale.ROOT));
+            if (columnName != null) {
+                return UnboundSlot.quoted(columnName);
+            }
+            return slot;
+        }
+    }
+
+    /**
+     * execute sql and return result
+     */
+    private ShowResultSet execute(ConnectContext ctx, StmtExecutor executor, 
String whereClause)
+            throws AnalysisException {
+        List<Pair<String, String>> columns = new ArrayList<>();
+        ALIAS_COLUMN_MAP.forEach((key, value) -> {
+            columns.add(Pair.of("`" + value + "`", "'" + key + "'"));
+        });
+
+        String fullTblName = String.format("`%s`.`%s`.`%s`",
+                catalog,
+                InfoSchemaDb.DATABASE_NAME,
+                "tables");
+
+        // We need to use TABLE_SCHEMA as a condition to query When querying 
external catalogs.
+        // This also applies to the internal catalog.
+        LogicalPlan plan = toLogicalPlan(toBaseSql(columns, fullTblName) + 
whereClause);
+        LogicalPlanAdapter adapter = new LogicalPlanAdapter(plan, 
ctx.getStatementContext());
+        executor.setParsedStmt(adapter);
+        List<ResultRow> resultRows = executor.executeInternalQuery();
+        List<List<String>> rows = resultRows.stream()
+                .map(ResultRow::getValues).collect(Collectors.toList());
+        return new ShowResultSet(getMetaData(), rows);
+    }
+
+    @Override
+    public ShowResultSet doRun(ConnectContext ctx, StmtExecutor executor) 
throws Exception {
+        validate(ctx);
+        if (whereClause != null) {
+            Expression rewrited = whereClause.accept(new 
ReplaceColumnNameVisitor(), null);
+            String whereCondition = " WHERE `TABLE_SCHEMA` = '" + db + "' AND 
" + rewrited.toSql();
+            return execute(ctx, executor, whereCondition);
+        } else if (likePattern != null) {
+            return execute(ctx, executor, " WHERE TABLE_NAME LIKE '"
+                    + likePattern + "' and `TABLE_SCHEMA` = '" + db + "'");
+        }
+        return execute(ctx, executor, "WHERE `TABLE_SCHEMA` = '" + db + "'");
+    }
+
+    /**
+     * getMetaData
+     */
+    public ShowResultSetMetaData getMetaData() {
+        return META_DATA;
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitShowTableStatusCommand(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 c1c805c4d24..2de8a1fada2 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
@@ -150,6 +150,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.ShowSyncJobCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowTableCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowTableCreationCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowTableIdCommand;
+import org.apache.doris.nereids.trees.plans.commands.ShowTableStatusCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.ShowTabletStorageFormatCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowTabletsBelongCommand;
 import org.apache.doris.nereids.trees.plans.commands.ShowTrashCommand;
@@ -831,6 +832,10 @@ public interface CommandVisitor<R, C> {
         return visitCommand(describeCommand, context);
     }
 
+    default R visitShowTableStatusCommand(ShowTableStatusCommand 
showTableStatusCommand, C context) {
+        return visitCommand(showTableStatusCommand, context);
+    }
+
     default R visitShowTableCommand(ShowTableCommand showTableCommand, C 
context) {
         return visitCommand(showTableCommand, context);
     }
diff --git a/regression-test/suites/auth_call/test_dml_analyze_auth.groovy 
b/regression-test/suites/auth_call/test_dml_analyze_auth.groovy
index 8bc6a070d61..22b99b851b3 100644
--- a/regression-test/suites/auth_call/test_dml_analyze_auth.groovy
+++ b/regression-test/suites/auth_call/test_dml_analyze_auth.groovy
@@ -25,6 +25,11 @@ suite("test_dml_analyze_auth","p0,auth_call") {
     String dbName = 'test_dml_analyze_auth_db'
     String tableName = 'test_dml_analyze_auth_tb'
 
+    try_sql("DROP USER ${user}")
+    try_sql """drop database if exists ${dbName}"""
+    sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'"""
+    sql """grant select_priv on regression_test to ${user}"""
+
     //cloud-mode
     if (isCloudMode()) {
         def clusters = sql " SHOW CLUSTERS; "
@@ -33,10 +38,6 @@ suite("test_dml_analyze_auth","p0,auth_call") {
         sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}""";
     }
 
-    try_sql("DROP USER ${user}")
-    try_sql """drop database if exists ${dbName}"""
-    sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'"""
-    sql """grant select_priv on regression_test to ${user}"""
     sql """create database ${dbName}"""
 
     sql """create table ${dbName}.${tableName} (


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

Reply via email to