ShowTableCommand.java

// 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.InfoSchemaDb;
import org.apache.doris.catalog.OlapTable;
import org.apache.doris.catalog.ScalarType;
import org.apache.doris.catalog.TableIf;
import org.apache.doris.cluster.ClusterNamespace;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.CaseSensibility;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.common.FeConstants;
import org.apache.doris.common.PatternMatcher;
import org.apache.doris.common.PatternMatcherWrapper;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.nereids.trees.plans.PlanType;
import org.apache.doris.nereids.trees.plans.commands.info.AliasInfo;
import org.apache.doris.nereids.trees.plans.commands.info.TableNameInfo;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
import org.apache.doris.nereids.util.Utils;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.GlobalVariable;
import org.apache.doris.qe.ShowResultSet;
import org.apache.doris.qe.ShowResultSetMetaData;
import org.apache.doris.qe.StmtExecutor;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * ShowTableCommand
 */
public class ShowTableCommand extends ShowCommand {
    private static final String NAME_COL_PREFIX = "Tables_in_";
    private static final String TYPE_COL = "Table_type";
    private static final String STORAGE_FORMAT_COL = "Storage_format";
    private static final String INVERTED_INDEX_STORAGE_FORMAT_COL = "Inverted_index_storage_format";
    private String db;
    private String catalog;
    private final boolean isVerbose;
    private final String likePattern;
    private String whereClause;

    public ShowTableCommand(String db, String catalog, boolean isVerbose, PlanType planType) {
        this(db, catalog, isVerbose, null, null, planType);
    }

    /**
     * ShowTableCommand
     */
    public ShowTableCommand(String db, String catalog, boolean isVerbose,
            String likePattern, String whereClause, PlanType planType) {
        super(planType);
        this.catalog = catalog;
        this.db = db;
        this.isVerbose = isVerbose;
        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);
            }
        }

        // we do not check db privs here. because user may not have any db privs,
        // but if it has privs of tbls inside this db,it should be allowed to see this db.
    }

    /**
     * isShowTablesCaseSensitive
     */
    public boolean isShowTablesCaseSensitive() {
        if (GlobalVariable.lowerCaseTableNames == 0) {
            return CaseSensibility.TABLE.getCaseSensibility();
        }
        return false;
    }

    private ShowResultSet executeWhere(ConnectContext ctx, StmtExecutor executor)
            throws AnalysisException {
        List<AliasInfo> selectList = new ArrayList<>();
        selectList.add(AliasInfo.of("TABLE_NAME",
                NAME_COL_PREFIX + ClusterNamespace.getNameFromFullName(db)));
        if (isVerbose) {
            selectList.add(AliasInfo.of("TABLE_TYPE", TYPE_COL));
        }

        TableNameInfo fullTblName = new TableNameInfo(catalog, InfoSchemaDb.DATABASE_NAME, "tables");

        if (type.equals(PlanType.SHOW_VIEWS)) {
            whereClause = whereClause + " and `ENGINE` = '" + TableIf.TableType.VIEW.toEngineName() + "'";
        }

        // We need to use TABLE_SCHEMA as a condition to query When querying external catalogs.
        // This also applies to the internal catalog.
        LogicalPlan plan = Utils.buildLogicalPlan(selectList, fullTblName,
                whereClause + " and `TABLE_SCHEMA` = '" + db + "'");
        List<List<String>> rows = Utils.executePlan(ctx, executor, plan);
        return new ShowResultSet(getMetaData(), rows);
    }

    @Override
    public ShowResultSet doRun(ConnectContext ctx, StmtExecutor executor) throws Exception {
        validate(ctx);
        if (whereClause != null) {
            return executeWhere(ctx, executor);
        }
        List<List<String>> rows = Lists.newArrayList();
        DatabaseIf<TableIf> dbIf = ctx.getEnv().getCatalogMgr()
                .getCatalogOrAnalysisException(catalog)
                .getDbOrAnalysisException(db);
        PatternMatcher matcher = null;
        if (likePattern != null) {
            matcher = PatternMatcherWrapper.createMysqlPattern(likePattern, isShowTablesCaseSensitive());
        }
        for (TableIf tbl : dbIf.getTables()) {
            if (type.equals(PlanType.SHOW_VIEWS) && !tbl.getEngine().equals(TableIf.TableType.VIEW.toEngineName())) {
                continue;
            }
            if (tbl.getName().startsWith(FeConstants.TEMP_MATERIZLIZE_DVIEW_PREFIX)) {
                continue;
            }
            if (matcher != null && !matcher.match(tbl.getName())) {
                continue;
            }
            if (tbl.isTemporary()) {
                continue;
            }
            // check tbl privs
            if (!Env.getCurrentEnv().getAccessManager()
                    .checkTblPriv(ConnectContext.get(), catalog, dbIf.getFullName(), tbl.getName(),
                            PrivPredicate.SHOW)) {
                continue;
            }
            if (isVerbose) {
                String storageFormat = "NONE";
                String invertedIndexFileStorageFormat = "NONE";
                if (tbl instanceof OlapTable) {
                    storageFormat = ((OlapTable) tbl).getStorageFormat().toString();
                    invertedIndexFileStorageFormat = ((OlapTable) tbl).getInvertedIndexFileStorageFormat().toString();
                }
                rows.add(Lists.newArrayList(tbl.getName(), tbl.getMysqlType(), storageFormat,
                        invertedIndexFileStorageFormat));
            } else {
                rows.add(Lists.newArrayList(tbl.getName()));
            }
        }
        // sort by table name
        rows.sort(Comparator.comparing(x -> x.get(0)));
        return new ShowResultSet(getMetaData(), rows);
    }

    /**
     * getMetaData
     */
    public ShowResultSetMetaData getMetaData() {
        ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder();
        builder.addColumn(
                new Column(NAME_COL_PREFIX + ClusterNamespace.getNameFromFullName(db), ScalarType.createVarchar(20)));
        if (isVerbose) {
            builder.addColumn(new Column(TYPE_COL, ScalarType.createVarchar(20)));
            // TODO: using where can only show two columns, maybe this is a bug?
            if (whereClause == null) {
                builder.addColumn(new Column(STORAGE_FORMAT_COL, ScalarType.createVarchar(20)));
                builder.addColumn(new Column(INVERTED_INDEX_STORAGE_FORMAT_COL, ScalarType.createVarchar(20)));
            }
        }
        return builder.build();
    }

    @Override
    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
        return visitor.visitShowTableCommand(this, context);
    }
}