AlterTableCommand.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.analysis.AlterTableClause;
import org.apache.doris.analysis.ColumnPosition;
import org.apache.doris.analysis.StmtType;
import org.apache.doris.catalog.AggregateType;
import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.DatabaseIf;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.KeysType;
import org.apache.doris.catalog.MaterializedIndex;
import org.apache.doris.catalog.OlapTable;
import org.apache.doris.catalog.TableIf;
import org.apache.doris.catalog.Type;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.DdlException;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.common.UserException;
import org.apache.doris.common.util.InternalDatabaseUtil;
import org.apache.doris.common.util.PropertyAnalyzer;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.nereids.trees.plans.PlanType;
import org.apache.doris.nereids.trees.plans.commands.info.AddColumnOp;
import org.apache.doris.nereids.trees.plans.commands.info.AddColumnsOp;
import org.apache.doris.nereids.trees.plans.commands.info.AddRollupOp;
import org.apache.doris.nereids.trees.plans.commands.info.AlterTableOp;
import org.apache.doris.nereids.trees.plans.commands.info.ColumnDefinition;
import org.apache.doris.nereids.trees.plans.commands.info.DropColumnOp;
import org.apache.doris.nereids.trees.plans.commands.info.DropRollupOp;
import org.apache.doris.nereids.trees.plans.commands.info.EnableFeatureOp;
import org.apache.doris.nereids.trees.plans.commands.info.ModifyColumnOp;
import org.apache.doris.nereids.trees.plans.commands.info.ModifyEngineOp;
import org.apache.doris.nereids.trees.plans.commands.info.ModifyTablePropertiesOp;
import org.apache.doris.nereids.trees.plans.commands.info.RenameTableOp;
import org.apache.doris.nereids.trees.plans.commands.info.ReorderColumnsOp;
import org.apache.doris.nereids.trees.plans.commands.info.TableNameInfo;
import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
import org.apache.doris.nereids.types.DataType;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.StmtExecutor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* AlterTableCommand
*/
public class AlterTableCommand extends Command implements ForwardWithSync {
private TableNameInfo tbl;
private List<AlterTableOp> ops;
private List<AlterTableOp> originOps;
public AlterTableCommand(TableNameInfo tbl, List<AlterTableOp> ops) {
super(PlanType.ALTER_TABLE_COMMAND);
this.tbl = tbl;
this.ops = ops;
this.originOps = ops;
}
public TableNameInfo getTbl() {
return tbl;
}
/**
* getOps
*/
public List<AlterTableClause> getOps() {
List<AlterTableClause> alterTableClauses = new ArrayList<>(ops.size());
for (AlterTableOp op : ops) {
AlterTableClause alter = op.translateToLegacyAlterClause();
alter.setTableName(tbl.transferToTableName());
alterTableClauses.add(alter);
}
return alterTableClauses;
}
/**
* validate
*/
private void validate(ConnectContext ctx) throws UserException {
if (tbl == null) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_TABLES_USED);
}
tbl.analyze(ctx);
InternalDatabaseUtil.checkDatabase(tbl.getDb(), ConnectContext.get());
if (!Env.getCurrentEnv().getAccessManager()
.checkTblPriv(ConnectContext.get(), tbl.getCtl(), tbl.getDb(), tbl.getTbl(),
PrivPredicate.ALTER)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "ALTER TABLE",
ConnectContext.get().getQualifiedUser(),
ConnectContext.get().getRemoteIP(),
tbl.getDb() + ": " + tbl.getTbl());
}
if (ops == null || ops.isEmpty()) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_ALTER_OPERATION);
}
for (AlterTableOp op : ops) {
op.setTableName(tbl);
op.validate(ctx);
}
String ctlName = tbl.getCtl();
String dbName = tbl.getDb();
String tableName = tbl.getTbl();
DatabaseIf dbIf = Env.getCurrentEnv().getCatalogMgr()
.getCatalogOrException(ctlName, catalog -> new DdlException("Unknown catalog " + catalog))
.getDbOrDdlException(dbName);
TableIf tableIf = dbIf.getTableOrDdlException(tableName);
if (tableIf.isTemporary()) {
throw new AnalysisException("Do not support alter temporary table[" + tableName + "]");
}
if (tableIf instanceof OlapTable) {
rewriteAlterOpForOlapTable(ctx, (OlapTable) tableIf);
} else {
checkExternalTableOperationAllow(tableIf);
}
}
private void rewriteAlterOpForOlapTable(ConnectContext ctx, OlapTable table) throws UserException {
List<AlterTableOp> alterTableOps = new ArrayList<>();
for (AlterTableOp alterClause : ops) {
if (alterClause instanceof EnableFeatureOp) {
EnableFeatureOp.Features alterFeature = ((EnableFeatureOp) alterClause).getFeature();
if (alterFeature == null || alterFeature == EnableFeatureOp.Features.UNKNOWN) {
throw new AnalysisException("unknown feature for alter clause");
}
if (table.getKeysType() != KeysType.UNIQUE_KEYS
&& alterFeature == EnableFeatureOp.Features.BATCH_DELETE) {
throw new AnalysisException("Batch delete only supported in unique tables.");
}
if (table.getKeysType() != KeysType.UNIQUE_KEYS
&& alterFeature == EnableFeatureOp.Features.SEQUENCE_LOAD) {
throw new AnalysisException("Sequence load only supported in unique tables.");
}
if (alterFeature == EnableFeatureOp.Features.UPDATE_FLEXIBLE_COLUMNS) {
if (!(table.getKeysType() == KeysType.UNIQUE_KEYS && table.getEnableUniqueKeyMergeOnWrite())) {
throw new AnalysisException("Update flexible columns feature is only supported"
+ " on merge-on-write unique tables.");
}
if (table.hasSkipBitmapColumn()) {
throw new AnalysisException("table " + table.getName()
+ " has enabled update flexible columns feature already.");
}
}
// analyse sequence column
Type sequenceColType = null;
if (alterFeature == EnableFeatureOp.Features.SEQUENCE_LOAD) {
Map<String, String> propertyMap = new HashMap<>(alterClause.getProperties());
try {
sequenceColType = PropertyAnalyzer.analyzeSequenceType(propertyMap, table.getKeysType());
if (sequenceColType == null) {
throw new AnalysisException("unknown sequence column type");
}
} catch (Exception e) {
throw new AnalysisException(e.getMessage());
}
}
// has rollup table
if (table.getVisibleIndex().size() > 1) {
for (MaterializedIndex idx : table.getVisibleIndex()) {
// add a column to rollup index it will add to base table automatically,
// if add a column here it will duplicated
if (idx.getId() == table.getBaseIndexId()) {
continue;
}
AddColumnOp addColumnOp = null;
if (alterFeature == EnableFeatureOp.Features.BATCH_DELETE) {
addColumnOp = new AddColumnOp(ColumnDefinition.newDeleteSignColumnDefinition(), null,
table.getIndexNameById(idx.getId()), null);
} else if (alterFeature == EnableFeatureOp.Features.SEQUENCE_LOAD) {
addColumnOp = new AddColumnOp(
ColumnDefinition.newSequenceColumnDefinition(
DataType.fromCatalogType(sequenceColType)),
null,
table.getIndexNameById(idx.getId()), null);
} else {
throw new AnalysisException("unknown feature : " + alterFeature);
}
addColumnOp.setTableName(tbl);
addColumnOp.validate(ctx);
alterTableOps.add(addColumnOp);
}
} else {
// no rollup tables
AddColumnOp addColumnOp = null;
if (alterFeature == EnableFeatureOp.Features.BATCH_DELETE) {
addColumnOp = new AddColumnOp(ColumnDefinition.newDeleteSignColumnDefinition(), null,
null, null);
} else if (alterFeature == EnableFeatureOp.Features.SEQUENCE_LOAD) {
addColumnOp = new AddColumnOp(
ColumnDefinition.newSequenceColumnDefinition(DataType.fromCatalogType(sequenceColType)),
null,
null, null);
} else if (alterFeature == EnableFeatureOp.Features.UPDATE_FLEXIBLE_COLUMNS) {
ColumnDefinition skipBItmapCol = ColumnDefinition.newSkipBitmapColumnDef(AggregateType.NONE);
List<Column> fullSchema = table.getBaseSchema(true);
String lastCol = fullSchema.get(fullSchema.size() - 1).getName();
addColumnOp = new AddColumnOp(skipBItmapCol, new ColumnPosition(lastCol), null, null);
}
addColumnOp.setTableName(tbl);
addColumnOp.validate(ctx);
alterTableOps.add(addColumnOp);
}
// add hidden column to rollup table
} else {
alterTableOps.add(alterClause);
}
}
ops = alterTableOps;
}
/**
* checkExternalTableOperationAllow
*/
private void checkExternalTableOperationAllow(TableIf table) throws UserException {
List<AlterTableOp> alterTableOps = new ArrayList<>();
for (AlterTableOp alterClause : ops) {
if (alterClause instanceof RenameTableOp
|| alterClause instanceof AddColumnOp
|| alterClause instanceof AddColumnsOp
|| alterClause instanceof DropColumnOp
|| alterClause instanceof ModifyColumnOp
|| alterClause instanceof ReorderColumnsOp
|| alterClause instanceof ModifyEngineOp
|| alterClause instanceof ModifyTablePropertiesOp) {
alterTableOps.add(alterClause);
} else {
throw new AnalysisException(table.getType().toString() + " [" + table.getName() + "] "
+ "do not support " + alterClause.getOpType().toString() + " clause now");
}
}
ops = alterTableOps;
}
/**
* toSql
*/
public String toSql() {
StringBuilder sb = new StringBuilder();
sb.append("ALTER TABLE ").append(tbl.toSql()).append(" ");
int idx = 0;
for (AlterTableOp op : originOps) {
if (idx != 0) {
sb.append(", \n");
}
if (op instanceof AddRollupOp) {
if (idx == 0) {
sb.append("ADD ROLLUP");
}
sb.append(op.toSql().replace("ADD ROLLUP", ""));
} else if (op instanceof DropRollupOp) {
if (idx == 0) {
sb.append("DROP ROLLUP ");
}
sb.append(((DropRollupOp) op).getRollupName());
} else {
sb.append(op.toSql());
}
idx++;
}
return sb.toString();
}
@Override
public String toString() {
return toSql();
}
@Override
public StmtType stmtType() {
return StmtType.ALTER;
}
@Override
public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
return visitor.visitAlterTableCommand(this, context);
}
@Override
public void run(ConnectContext ctx, StmtExecutor executor) throws Exception {
validate(ctx);
ctx.getEnv().alterTable(this);
}
}