ShowTabletStmt.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.analysis;

import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.Replica;
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.UserException;
import org.apache.doris.common.proc.TabletsProcDir;
import org.apache.doris.common.util.OrderByPair;
import org.apache.doris.common.util.Util;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.ShowResultSetMetaData;

import com.google.common.base.Strings;

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

public class ShowTabletStmt extends ShowStmt implements NotFallbackInParser {
    private TableName dbTableName;
    private String dbName;
    private String tableName;
    private long tabletId;
    private PartitionNames partitionNames;
    private Expr whereClause;
    private List<OrderByElement> orderByElements;
    private LimitElement limitElement;

    private long version;
    private long backendId;
    private String indexName;
    private Replica.ReplicaState replicaState;
    private ArrayList<OrderByPair> orderByPairs;

    private boolean isShowSingleTablet;

    public ShowTabletStmt(TableName dbTableName, long tabletId) {
        this(dbTableName, tabletId, null, null, null, null);
    }

    public ShowTabletStmt(TableName dbTableName, long tabletId, PartitionNames partitionNames,
            Expr whereClause, List<OrderByElement> orderByElements, LimitElement limitElement) {
        this.dbTableName = dbTableName;
        if (dbTableName == null) {
            this.dbName = null;
            this.tableName = null;
            this.isShowSingleTablet = true;
            this.indexName = null;
        } else {
            this.dbName = dbTableName.getDb();
            this.tableName = dbTableName.getTbl();
            this.isShowSingleTablet = false;
            this.indexName = Strings.emptyToNull(indexName);
        }
        this.tabletId = tabletId;
        this.partitionNames = partitionNames;
        this.whereClause = whereClause;
        this.orderByElements = orderByElements;
        this.limitElement = limitElement;

        this.version = -1;
        this.backendId = -1;
        this.indexName = null;
        this.replicaState = null;
        this.orderByPairs = null;
    }

    public String getDbName() {
        return dbName;
    }

    public String getTableName() {
        return tableName;
    }

    public long getTabletId() {
        return tabletId;
    }

    public boolean isShowSingleTablet() {
        return isShowSingleTablet;
    }

    public boolean hasOffset() {
        return limitElement != null && limitElement.hasOffset();
    }

    public long getOffset() {
        return limitElement.getOffset();
    }

    public boolean hasPartition() {
        return partitionNames != null;
    }

    public PartitionNames getPartitionNames() {
        return partitionNames;
    }

    public boolean hasLimit() {
        return limitElement != null && limitElement.hasLimit();
    }

    public long getLimit() {
        return limitElement.getLimit();
    }

    public long getVersion() {
        return version;
    }

    public long getBackendId() {
        return backendId;
    }

    public String getIndexName() {
        return indexName;
    }

    public List<OrderByPair> getOrderByPairs() {
        return orderByPairs;
    }

    public Replica.ReplicaState getReplicaState() {
        return replicaState;
    }

    @Override
    public void analyze(Analyzer analyzer) throws UserException {
        // check access first
        if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) {
            ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "SHOW TABLET");
        }

        super.analyze(analyzer);
        if (dbTableName != null) {
            dbTableName.analyze(analyzer);
            // disallow external catalog
            Util.prohibitExternalCatalog(dbTableName.getCtl(), this.getClass().getSimpleName());
        }
        if (!isShowSingleTablet && Strings.isNullOrEmpty(dbName)) {
            dbName = analyzer.getDefaultDb();
            if (Strings.isNullOrEmpty(dbName)) {
                ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR);
            }
        }

        if (partitionNames != null) {
            partitionNames.analyze(analyzer);
        }

        if (limitElement != null) {
            limitElement.analyze(analyzer);
        }

        // analyze where clause if not null
        if (whereClause != null) {
            if (whereClause instanceof CompoundPredicate) {
                CompoundPredicate cp = (CompoundPredicate) whereClause;
                if (cp.getOp() != org.apache.doris.analysis.CompoundPredicate.Operator.AND) {
                    throw new AnalysisException("Only allow compound predicate with operator AND");
                }

                analyzeSubPredicate(cp.getChild(0));
                analyzeSubPredicate(cp.getChild(1));
            } else {
                analyzeSubPredicate(whereClause);
            }
        }

        // order by
        if (orderByElements != null && !orderByElements.isEmpty()) {
            orderByPairs = new ArrayList<OrderByPair>();
            for (OrderByElement orderByElement : orderByElements) {
                if (!(orderByElement.getExpr() instanceof SlotRef)) {
                    throw new AnalysisException("Should order by column");
                }
                SlotRef slotRef = (SlotRef) orderByElement.getExpr();
                int index = TabletsProcDir.analyzeColumn(slotRef.getColumnName());
                OrderByPair orderByPair = new OrderByPair(index, !orderByElement.getIsAsc());
                orderByPairs.add(orderByPair);
            }
        }
    }

    private void analyzeSubPredicate(Expr subExpr) throws AnalysisException {
        if (subExpr == null) {
            return;
        }
        if (subExpr instanceof CompoundPredicate) {
            CompoundPredicate cp = (CompoundPredicate) subExpr;
            if (cp.getOp() != org.apache.doris.analysis.CompoundPredicate.Operator.AND) {
                throw new AnalysisException("Only allow compound predicate with operator AND");
            }

            analyzeSubPredicate(cp.getChild(0));
            analyzeSubPredicate(cp.getChild(1));
            return;
        }
        boolean valid = true;
        do {
            if (!(subExpr instanceof  BinaryPredicate)) {
                valid = false;
                break;
            }
            BinaryPredicate binaryPredicate = (BinaryPredicate) subExpr;
            if (binaryPredicate.getOp() != BinaryPredicate.Operator.EQ) {
                valid = false;
                break;
            }

            if (!(subExpr.getChild(0) instanceof SlotRef)) {
                valid = false;
                break;
            }
            String leftKey = ((SlotRef) subExpr.getChild(0)).getColumnName();
            if (leftKey.equalsIgnoreCase("version")) {
                if (!(subExpr.getChild(1) instanceof IntLiteral) || version > -1) {
                    valid = false;
                    break;
                }
                version = ((IntLiteral) subExpr.getChild(1)).getValue();
            } else if (leftKey.equalsIgnoreCase("backendid")) {
                if (!(subExpr.getChild(1) instanceof IntLiteral) || backendId > -1) {
                    valid = false;
                    break;
                }
                backendId = ((IntLiteral) subExpr.getChild(1)).getValue();
            } else if (leftKey.equalsIgnoreCase("indexname")) {
                if (!(subExpr.getChild(1) instanceof StringLiteral) || indexName != null) {
                    valid = false;
                    break;
                }
                indexName = ((StringLiteral) subExpr.getChild(1)).getValue();
            } else if (leftKey.equalsIgnoreCase("state")) {
                if (!(subExpr.getChild(1) instanceof StringLiteral) || replicaState != null) {
                    valid = false;
                    break;
                }
                String state = ((StringLiteral) subExpr.getChild(1)).getValue().toUpperCase();
                try {
                    replicaState = Replica.ReplicaState.valueOf(state);
                } catch (Exception e) {
                    replicaState = null;
                    valid = false;
                    break;
                }
            } else {
                valid = false;
                break;
            }
        } while (false);

        if (!valid) {
            throw new AnalysisException("Where clause should looks like: Version = \"version\","
                    + " or state = \"NORMAL|ROLLUP|CLONE|DECOMMISSION\", or BackendId = 10000,"
                    + " indexname=\"rollup_name\" or compound predicate with operator AND");
        }
    }

    @Override
    public String toSql() {
        StringBuilder sb = new StringBuilder();
        sb.append("SHOW TABLET ");
        if (isShowSingleTablet) {
            sb.append(tabletId);
        } else {
            sb.append(" FROM ").append("`").append(dbName).append("`.`").append(tableName).append("`");
        }
        if (limitElement != null) {
            if (limitElement.hasOffset() && limitElement.hasLimit()) {
                sb.append(" ").append(limitElement.getOffset()).append(",").append(limitElement.getLimit());
            } else if (limitElement.hasLimit()) {
                sb.append(" ").append(limitElement.getLimit());
            }
        }
        return sb.toString();
    }

    @Override
    public ShowResultSetMetaData getMetaData() {
        ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder();
        if (isShowSingleTablet) {
            builder.addColumn(new Column("DbName", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("TableName", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("PartitionName", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("IndexName", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("DbId", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("TableId", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("PartitionId", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("IndexId", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("IsSync", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("Order", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("QueryHits", ScalarType.createVarchar(30)));
            builder.addColumn(new Column("DetailCmd", ScalarType.createVarchar(30)));
        } else {
            for (String title : TabletsProcDir.TITLE_NAMES) {
                builder.addColumn(new Column(title, ScalarType.createVarchar(30)));
            }
        }
        return builder.build();
    }

    @Override
    public RedirectStatus getRedirectStatus() {
        if (ConnectContext.get().getSessionVariable().getForwardToMaster()) {
            return RedirectStatus.FORWARD_NO_SYNC;
        } else {
            return RedirectStatus.NO_FORWARD;
        }
    }
}