Skip to content

Commit 5a15b5d

Browse files
authored
[Enhancement] (nereids) support show table status command (apache#48715)
1 parent 865b45c commit 5a15b5d

File tree

6 files changed

+284
-5
lines changed

6 files changed

+284
-5
lines changed

fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4

+1-1
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ supportedShowStatement
337337
| SHOW QUERY PROFILE queryIdPath=STRING_LITERAL? limitClause? #showQueryProfile
338338
| SHOW CONVERT_LSC ((FROM | IN) database=multipartIdentifier)? #showConvertLsc
339339
| SHOW FULL? TABLES ((FROM | IN) database=multipartIdentifier)? wildWhere? #showTables
340+
| SHOW TABLE STATUS ((FROM | IN) database=multipartIdentifier)? wildWhere? #showTableStatus
340341
;
341342

342343
supportedLoadStatement
@@ -378,7 +379,6 @@ unsupportedShowStatement
378379
: SHOW ROW POLICY (FOR (userIdentify | (ROLE role=identifier)))? #showRowPolicy
379380
| SHOW STORAGE (VAULT | VAULTS) #showStorageVault
380381
| SHOW OPEN TABLES ((FROM | IN) database=multipartIdentifier)? wildWhere? #showOpenTables
381-
| SHOW TABLE STATUS ((FROM | IN) database=multipartIdentifier)? wildWhere? #showTableStatus
382382
| SHOW FULL? VIEWS ((FROM | IN) database=multipartIdentifier)? wildWhere? #showViews
383383
| SHOW CREATE MATERIALIZED VIEW name=multipartIdentifier #showMaterializedView
384384
| SHOW CREATE statementScope? FUNCTION functionIdentifier

fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java

+29
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@
650650
import org.apache.doris.nereids.trees.plans.commands.ShowTableCommand;
651651
import org.apache.doris.nereids.trees.plans.commands.ShowTableCreationCommand;
652652
import org.apache.doris.nereids.trees.plans.commands.ShowTableIdCommand;
653+
import org.apache.doris.nereids.trees.plans.commands.ShowTableStatusCommand;
653654
import org.apache.doris.nereids.trees.plans.commands.ShowTabletStorageFormatCommand;
654655
import org.apache.doris.nereids.trees.plans.commands.ShowTabletsBelongCommand;
655656
import org.apache.doris.nereids.trees.plans.commands.ShowTrashCommand;
@@ -5927,6 +5928,34 @@ public LogicalPlan visitShowQueuedAnalyzeJobs(ShowQueuedAnalyzeJobsContext ctx)
59275928
return new ShowQueuedAnalyzeJobsCommand(tableName, stateKey, stateValue);
59285929
}
59295930

5931+
@Override
5932+
public LogicalPlan visitShowTableStatus(DorisParser.ShowTableStatusContext ctx) {
5933+
String ctlName = null;
5934+
String dbName = null;
5935+
if (ctx.database != null) {
5936+
List<String> nameParts = visitMultipartIdentifier(ctx.database);
5937+
if (nameParts.size() == 1) {
5938+
dbName = nameParts.get(0);
5939+
} else if (nameParts.size() == 2) {
5940+
ctlName = nameParts.get(0);
5941+
dbName = nameParts.get(1);
5942+
} else {
5943+
throw new AnalysisException("nameParts in analyze database should be [ctl.]db");
5944+
}
5945+
}
5946+
5947+
if (ctx.wildWhere() != null) {
5948+
if (ctx.wildWhere().LIKE() != null) {
5949+
return new ShowTableStatusCommand(dbName, ctlName,
5950+
stripQuotes(ctx.wildWhere().STRING_LITERAL().getText()), null);
5951+
} else {
5952+
Expression expr = (Expression) ctx.wildWhere().expression().accept(this);
5953+
return new ShowTableStatusCommand(dbName, ctlName, null, expr);
5954+
}
5955+
}
5956+
return new ShowTableStatusCommand(dbName, ctlName);
5957+
}
5958+
59305959
@Override
59315960
public LogicalPlan visitShowTables(DorisParser.ShowTablesContext ctx) {
59325961
String ctlName = null;

fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java

+1
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ public enum PlanType {
264264
SHOW_SYNC_JOB_COMMAND,
265265
SHOW_TABLE_ID_COMMAND,
266266
SHOW_TABLES,
267+
SHOW_TABLES_STATUS,
267268
SHOW_TRASH_COMMAND,
268269
SHOW_TABLET_STORAGE_FORMAT_COMMAND,
269270
SHOW_TRIGGERS_COMMAND,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.doris.nereids.trees.plans.commands;
19+
20+
import org.apache.doris.catalog.Column;
21+
import org.apache.doris.catalog.Env;
22+
import org.apache.doris.catalog.InfoSchemaDb;
23+
import org.apache.doris.catalog.PrimitiveType;
24+
import org.apache.doris.catalog.ScalarType;
25+
import org.apache.doris.common.AnalysisException;
26+
import org.apache.doris.common.ErrorCode;
27+
import org.apache.doris.common.ErrorReport;
28+
import org.apache.doris.common.Pair;
29+
import org.apache.doris.mysql.privilege.PrivPredicate;
30+
import org.apache.doris.nereids.analyzer.UnboundSlot;
31+
import org.apache.doris.nereids.glue.LogicalPlanAdapter;
32+
import org.apache.doris.nereids.parser.NereidsParser;
33+
import org.apache.doris.nereids.trees.expressions.Expression;
34+
import org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
35+
import org.apache.doris.nereids.trees.plans.PlanType;
36+
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
37+
import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
38+
import org.apache.doris.qe.ConnectContext;
39+
import org.apache.doris.qe.ShowResultSet;
40+
import org.apache.doris.qe.ShowResultSetMetaData;
41+
import org.apache.doris.qe.StmtExecutor;
42+
import org.apache.doris.statistics.ResultRow;
43+
44+
import com.google.common.base.Strings;
45+
import com.google.common.collect.ImmutableMap;
46+
47+
import java.util.ArrayList;
48+
import java.util.List;
49+
import java.util.Locale;
50+
import java.util.Map;
51+
import java.util.stream.Collectors;
52+
53+
/**
54+
* ShowTableStautsCommand
55+
*/
56+
public class ShowTableStatusCommand extends ShowCommand {
57+
private static final ShowResultSetMetaData META_DATA = ShowResultSetMetaData.builder()
58+
.addColumn(new Column("Name", ScalarType.createVarchar(64)))
59+
.addColumn(new Column("Engine", ScalarType.createVarchar(10)))
60+
.addColumn(new Column("Version", ScalarType.createType(PrimitiveType.BIGINT)))
61+
.addColumn(new Column("Row_format", ScalarType.createVarchar(64)))
62+
.addColumn(new Column("Rows", ScalarType.createType(PrimitiveType.BIGINT)))
63+
.addColumn(new Column("Avg_row_length", ScalarType.createType(PrimitiveType.BIGINT)))
64+
.addColumn(new Column("Data_length", ScalarType.createType(PrimitiveType.BIGINT)))
65+
.addColumn(new Column("Max_data_length", ScalarType.createType(PrimitiveType.BIGINT)))
66+
.addColumn(new Column("Index_length", ScalarType.createType(PrimitiveType.BIGINT)))
67+
.addColumn(new Column("Data_free", ScalarType.createType(PrimitiveType.BIGINT)))
68+
.addColumn(new Column("Auto_increment", ScalarType.createType(PrimitiveType.BIGINT)))
69+
.addColumn(new Column("Create_time", ScalarType.createType(PrimitiveType.DATETIME)))
70+
.addColumn(new Column("Update_time", ScalarType.createType(PrimitiveType.DATETIME)))
71+
.addColumn(new Column("Check_time", ScalarType.createType(PrimitiveType.DATETIME)))
72+
.addColumn(new Column("Collation", ScalarType.createVarchar(64)))
73+
.addColumn(new Column("Checksum", ScalarType.createType(PrimitiveType.BIGINT)))
74+
.addColumn(new Column("Create_options", ScalarType.createVarchar(64)))
75+
.addColumn(new Column("Comment", ScalarType.createVarchar(64)))
76+
.build();
77+
78+
private static Map<String, String> ALIAS_COLUMN_MAP = ImmutableMap.<String, String>builder()
79+
.put("name", "TABLE_NAME")
80+
.put("engine", "ENGINE")
81+
.put("version", "VERSION")
82+
.put("row_format", "ROW_FORMAT")
83+
.put("rows", "TABLE_ROWS")
84+
.put("avg_row_length", "AVG_ROW_LENGTH")
85+
.put("data_length", "DATA_LENGTH")
86+
.put("max_data_length", "MAX_DATA_LENGTH")
87+
.put("index_length", "INDEX_LENGTH")
88+
.put("data_free", "DATA_FREE")
89+
.put("auto_increment", "AUTO_INCREMENT")
90+
.put("create_time", "CREATE_TIME")
91+
.put("update_time", "UPDATE_TIME")
92+
.put("check_time", "CHECK_TIME")
93+
.put("collation", "TABLE_COLLATION")
94+
.put("checksum", "CHECKSUM")
95+
.put("create_options", "CREATE_OPTIONS")
96+
.put("comment", "TABLE_COMMENT")
97+
.build();
98+
99+
private String catalog;
100+
private String db;
101+
private final String likePattern;
102+
private final Expression whereClause;
103+
104+
public ShowTableStatusCommand(String db, String catalog) {
105+
this(db, catalog, null, null);
106+
}
107+
108+
/**
109+
* ShowTableStautsCommand
110+
*/
111+
public ShowTableStatusCommand(String db, String catalog,
112+
String likePattern, Expression whereClause) {
113+
super(PlanType.SHOW_TABLES_STATUS);
114+
this.catalog = catalog;
115+
this.db = db;
116+
this.likePattern = likePattern;
117+
this.whereClause = whereClause;
118+
}
119+
120+
/**
121+
* validate
122+
*/
123+
public void validate(ConnectContext ctx) throws AnalysisException {
124+
if (Strings.isNullOrEmpty(db)) {
125+
db = ctx.getDatabase();
126+
if (Strings.isNullOrEmpty(db)) {
127+
ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR);
128+
}
129+
}
130+
if (Strings.isNullOrEmpty(catalog)) {
131+
catalog = ctx.getDefaultCatalog();
132+
if (Strings.isNullOrEmpty(catalog)) {
133+
ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_NAME_FOR_CATALOG);
134+
}
135+
}
136+
137+
if (!Env.getCurrentEnv().getAccessManager().checkDbPriv(ConnectContext.get(),
138+
catalog, db, PrivPredicate.SHOW)) {
139+
ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR, ctx.getQualifiedUser(), db);
140+
}
141+
}
142+
143+
/**
144+
* sql to logical plan
145+
* @param sql sql
146+
*/
147+
private LogicalPlan toLogicalPlan(String sql) {
148+
return new NereidsParser().parseSingle(sql);
149+
}
150+
151+
/**
152+
* Construct a basic SQL without query conditions
153+
* @param columns (original name, alias)
154+
* @param tableName ctl.db.tbl
155+
*/
156+
private String toBaseSql(List<Pair<String, String>> columns, String tableName) throws AnalysisException {
157+
if (columns == null || columns.isEmpty()) {
158+
throw new AnalysisException("columns cannot be empty");
159+
}
160+
if (tableName == null || tableName.isEmpty()) {
161+
throw new AnalysisException("tableName cannot be empty");
162+
}
163+
164+
StringBuilder sb = new StringBuilder("SELECT ");
165+
columns.forEach(column -> {
166+
sb.append(column.first);
167+
if (column.second != null && !column.second.isEmpty()) {
168+
sb.append(" AS ").append(column.second);
169+
}
170+
sb.append(", ");
171+
});
172+
sb.setLength(sb.length() - 2);
173+
sb.append(" FROM ").append(tableName);
174+
return sb.toString();
175+
}
176+
177+
/**
178+
* replaceColumnNameVisitor
179+
* replace column name to real column name
180+
*/
181+
private static class ReplaceColumnNameVisitor extends DefaultExpressionRewriter<Void> {
182+
@Override
183+
public Expression visitUnboundSlot(UnboundSlot slot, Void context) {
184+
String columnName = ALIAS_COLUMN_MAP.get(slot.getName().toLowerCase(Locale.ROOT));
185+
if (columnName != null) {
186+
return UnboundSlot.quoted(columnName);
187+
}
188+
return slot;
189+
}
190+
}
191+
192+
/**
193+
* execute sql and return result
194+
*/
195+
private ShowResultSet execute(ConnectContext ctx, StmtExecutor executor, String whereClause)
196+
throws AnalysisException {
197+
List<Pair<String, String>> columns = new ArrayList<>();
198+
ALIAS_COLUMN_MAP.forEach((key, value) -> {
199+
columns.add(Pair.of("`" + value + "`", "'" + key + "'"));
200+
});
201+
202+
String fullTblName = String.format("`%s`.`%s`.`%s`",
203+
catalog,
204+
InfoSchemaDb.DATABASE_NAME,
205+
"tables");
206+
207+
// We need to use TABLE_SCHEMA as a condition to query When querying external catalogs.
208+
// This also applies to the internal catalog.
209+
LogicalPlan plan = toLogicalPlan(toBaseSql(columns, fullTblName) + whereClause);
210+
LogicalPlanAdapter adapter = new LogicalPlanAdapter(plan, ctx.getStatementContext());
211+
executor.setParsedStmt(adapter);
212+
List<ResultRow> resultRows = executor.executeInternalQuery();
213+
List<List<String>> rows = resultRows.stream()
214+
.map(ResultRow::getValues).collect(Collectors.toList());
215+
return new ShowResultSet(getMetaData(), rows);
216+
}
217+
218+
@Override
219+
public ShowResultSet doRun(ConnectContext ctx, StmtExecutor executor) throws Exception {
220+
validate(ctx);
221+
if (whereClause != null) {
222+
Expression rewrited = whereClause.accept(new ReplaceColumnNameVisitor(), null);
223+
String whereCondition = " WHERE `TABLE_SCHEMA` = '" + db + "' AND " + rewrited.toSql();
224+
return execute(ctx, executor, whereCondition);
225+
} else if (likePattern != null) {
226+
return execute(ctx, executor, " WHERE TABLE_NAME LIKE '"
227+
+ likePattern + "' and `TABLE_SCHEMA` = '" + db + "'");
228+
}
229+
return execute(ctx, executor, "WHERE `TABLE_SCHEMA` = '" + db + "'");
230+
}
231+
232+
/**
233+
* getMetaData
234+
*/
235+
public ShowResultSetMetaData getMetaData() {
236+
return META_DATA;
237+
}
238+
239+
@Override
240+
public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
241+
return visitor.visitShowTableStatusCommand(this, context);
242+
}
243+
}

fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java

+5
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
import org.apache.doris.nereids.trees.plans.commands.ShowTableCommand;
151151
import org.apache.doris.nereids.trees.plans.commands.ShowTableCreationCommand;
152152
import org.apache.doris.nereids.trees.plans.commands.ShowTableIdCommand;
153+
import org.apache.doris.nereids.trees.plans.commands.ShowTableStatusCommand;
153154
import org.apache.doris.nereids.trees.plans.commands.ShowTabletStorageFormatCommand;
154155
import org.apache.doris.nereids.trees.plans.commands.ShowTabletsBelongCommand;
155156
import org.apache.doris.nereids.trees.plans.commands.ShowTrashCommand;
@@ -831,6 +832,10 @@ default R visitDescribeCommand(DescribeCommand describeCommand, C context) {
831832
return visitCommand(describeCommand, context);
832833
}
833834

835+
default R visitShowTableStatusCommand(ShowTableStatusCommand showTableStatusCommand, C context) {
836+
return visitCommand(showTableStatusCommand, context);
837+
}
838+
834839
default R visitShowTableCommand(ShowTableCommand showTableCommand, C context) {
835840
return visitCommand(showTableCommand, context);
836841
}

regression-test/suites/auth_call/test_dml_analyze_auth.groovy

+5-4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ suite("test_dml_analyze_auth","p0,auth_call") {
2525
String dbName = 'test_dml_analyze_auth_db'
2626
String tableName = 'test_dml_analyze_auth_tb'
2727

28+
try_sql("DROP USER ${user}")
29+
try_sql """drop database if exists ${dbName}"""
30+
sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'"""
31+
sql """grant select_priv on regression_test to ${user}"""
32+
2833
//cloud-mode
2934
if (isCloudMode()) {
3035
def clusters = sql " SHOW CLUSTERS; "
@@ -33,10 +38,6 @@ suite("test_dml_analyze_auth","p0,auth_call") {
3338
sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}""";
3439
}
3540

36-
try_sql("DROP USER ${user}")
37-
try_sql """drop database if exists ${dbName}"""
38-
sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'"""
39-
sql """grant select_priv on regression_test to ${user}"""
4041
sql """create database ${dbName}"""
4142

4243
sql """create table ${dbName}.${tableName} (

0 commit comments

Comments
 (0)