diff --git a/docs/en/docs/sql-manual/sql-reference/Database-Administration-Statements/ADMIN-SET-TABLE-STATUS.md b/docs/en/docs/sql-manual/sql-reference/Database-Administration-Statements/ADMIN-SET-TABLE-STATUS.md new file mode 100644 index 00000000000000..fde3b5ed4b6632 --- /dev/null +++ b/docs/en/docs/sql-manual/sql-reference/Database-Administration-Statements/ADMIN-SET-TABLE-STATUS.md @@ -0,0 +1,81 @@ +--- +{ + "title": "ADMIN-SET-TABLE-STATUS", + "language": "zh-CN" +} +--- + + + +## ADMIN-SET-TABLE-STATUS + +### Name + +ADMIN SET TABLE STATUS + +### Description + +This statement is used to set the state of the specified table. Only supports OLAP tables. + +This command is currently only used to manually set the OLAP table state to the specified state, allowing some jobs that are stuck by the table state to continue running. + +grammar: + +```sql +ADMIN SET TABLE table_name STATUS + PROPERTIES ("key" = "value", ...); +``` + +The following properties are currently supported: + +1. "state":Required. Specifying a target state then the state of the OLAP table will change to this state. + +> The current target states include: +> +> 1. NORMAL +> 2. ROLLUP +> 3. SCHEMA_CHANGE +> 4. BACKUP +> 5. RESTORE +> 6. WAITING_STABLE +> +> If the current state of the table is already the specified state, it will be ignored. + +**Note: This command is generally only used for emergency fault repair, please proceed with caution.** + +### Example + +1. Set the state of table tbl1 to NORMAL. + +```sql +admin set table tbl1 status properties("state" = "NORMAL"); +``` + +2. Set the state of table tbl2 to SCHEMA_CHANGE + +```sql +admin set table test_set_table_status status properties("state" = "SCHEMA_CHANGE"); +``` + +### Keywords + + ADMIN, SET, TABLE, STATUS + +### Best Practice \ No newline at end of file diff --git a/docs/sidebars.json b/docs/sidebars.json index c1329d4f47dd7b..537fbd741bc59c 100644 --- a/docs/sidebars.json +++ b/docs/sidebars.json @@ -812,6 +812,7 @@ "sql-manual/sql-reference/Database-Administration-Statements/INSTALL-PLUGIN", "sql-manual/sql-reference/Database-Administration-Statements/UNINSTALL-PLUGIN", "sql-manual/sql-reference/Database-Administration-Statements/ADMIN-SET-REPLICA-STATUS", + "sql-manual/sql-reference/Database-Administration-Statements/ADMIN-SET-TABLE-STATUS", "sql-manual/sql-reference/Database-Administration-Statements/ADMIN-SHOW-REPLICA-DISTRIBUTION", "sql-manual/sql-reference/Database-Administration-Statements/ADMIN-SHOW-REPLICA-STATUS", "sql-manual/sql-reference/Database-Administration-Statements/ADMIN-REPAIR-TABLE", diff --git a/docs/zh-CN/docs/sql-manual/sql-reference/Database-Administration-Statements/ADMIN-SET-TABLE-STATUS.md b/docs/zh-CN/docs/sql-manual/sql-reference/Database-Administration-Statements/ADMIN-SET-TABLE-STATUS.md new file mode 100644 index 00000000000000..8a0d69503d8772 --- /dev/null +++ b/docs/zh-CN/docs/sql-manual/sql-reference/Database-Administration-Statements/ADMIN-SET-TABLE-STATUS.md @@ -0,0 +1,84 @@ +--- +{ + "title": "ADMIN-SET-TABLE-STATUS", + "language": "zh-CN" +} +--- + + + +## ADMIN-SET-TABLE-STATUS + +### Name + +ADMIN SET TABLE STATUS + +### Description + +该语句用于设置指定表的状态,仅支持OLAP表。 + +该命令目前仅用于手动将 OLAP 表状态设置为指定状态,从而使得某些由于表状态被阻碍的任务能够继续运行。 + +语法: + +```sql +ADMIN SET TABLE table_name STATUS + PROPERTIES ("key" = "value", ...); +``` + +目前支持以下属性: + +1. "state":必需。指定一个目标状态,将会修改 OLAP 表的状态至此状态。 + +> 当前可修改的目标状态包括: +> +> 1. NORMAL +> 2. ROLLUP +> 3. SCHEMA_CHANGE +> 4. BACKUP +> 5. RESTORE +> 6. WAITING_STABLE +> +> 如果表的状态已经是指定的状态,则会被忽略。 + +**注意:此命令一般只用于紧急故障修复,请谨慎操作。** + +### Example + +1. 设置表 tbl1 的状态为 NORMAL。 + +```sql +admin set table tbl1 status properties("state" = "NORMAL"); +``` + +2. 设置表 tbl2 的状态为 SCHEMA_CHANGE。 + +```sql +admin set table test_set_table_status status properties("state" = "SCHEMA_CHANGE"); +``` + +### Keywords + + ADMIN, SET, TABLE, STATUS + +### Best Practice + + + diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup index c4258b2330198f..ea2a6f8a7f371f 100644 --- a/fe/fe-core/src/main/cup/sql_parser.cup +++ b/fe/fe-core/src/main/cup/sql_parser.cup @@ -7232,6 +7232,10 @@ admin_stmt ::= {: RESULT = new AdminCopyTabletStmt(tabletId, properties); :} + | KW_ADMIN KW_SET KW_TABLE table_name:name KW_STATUS opt_properties:properties + {: + RESULT = new AdminSetTableStatusStmt(name, properties); + :} ; truncate_stmt ::= diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/AdminSetTableStatusStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/AdminSetTableStatusStmt.java new file mode 100644 index 00000000000000..e58d2af12785ea --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/AdminSetTableStatusStmt.java @@ -0,0 +1,91 @@ +// 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.Env; +import org.apache.doris.catalog.OlapTable.OlapTableState; +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.util.Util; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.qe.ConnectContext; + +import lombok.Getter; + +import java.util.Map; + +public class AdminSetTableStatusStmt extends DdlStmt { + + public static final String TABLE_STATE = "state"; + + private final TableName tableName; + private final Map properties; + @Getter + private OlapTableState tableState; + + public AdminSetTableStatusStmt(TableName tableName, Map properties) { + this.tableName = tableName; + this.properties = properties; + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, UserException { + super.analyze(analyzer); + + // check auth + if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); + } + + tableName.analyze(analyzer); + Util.prohibitExternalCatalog(tableName.getCtl(), this.getClass().getSimpleName()); + + checkProperties(); + } + + private void checkProperties() throws AnalysisException { + for (Map.Entry entry : properties.entrySet()) { + String key = entry.getKey(); + String val = entry.getValue(); + + if (key.equalsIgnoreCase(TABLE_STATE)) { + try { + tableState = OlapTableState.valueOf(val.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new AnalysisException("Invalid table state: " + val); + } + } else { + throw new AnalysisException("Unsupported property: " + key); + } + } + + if (tableState == null) { + throw new AnalysisException("Should add properties: STATE."); + } + } + + public String getDbName() { + return tableName.getDb(); + } + + public String getTblName() { + return tableName.getTbl(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java index f25386a83b4df4..1aa7579c2e4a3a 100755 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java @@ -31,6 +31,7 @@ import org.apache.doris.analysis.AdminCompactTableStmt; import org.apache.doris.analysis.AdminSetConfigStmt; import org.apache.doris.analysis.AdminSetReplicaStatusStmt; +import org.apache.doris.analysis.AdminSetTableStatusStmt; import org.apache.doris.analysis.AlterDatabasePropertyStmt; import org.apache.doris.analysis.AlterDatabaseQuotaStmt; import org.apache.doris.analysis.AlterDatabaseQuotaStmt.QuotaType; @@ -85,6 +86,7 @@ import org.apache.doris.catalog.DistributionInfo.DistributionInfoType; import org.apache.doris.catalog.MaterializedIndex.IndexExtState; import org.apache.doris.catalog.MetaIdGenerator.IdGeneratorBuffer; +import org.apache.doris.catalog.OlapTable.OlapTableState; import org.apache.doris.catalog.Replica.ReplicaStatus; import org.apache.doris.catalog.TableIf.TableType; import org.apache.doris.clone.ColocateTableCheckerAndBalancer; @@ -191,6 +193,7 @@ import org.apache.doris.persist.ReplacePartitionOperationLog; import org.apache.doris.persist.ReplicaPersistInfo; import org.apache.doris.persist.SetReplicaStatusOperationLog; +import org.apache.doris.persist.SetTableStatusOperationLog; import org.apache.doris.persist.Storage; import org.apache.doris.persist.StorageInfo; import org.apache.doris.persist.TableInfo; @@ -5242,6 +5245,40 @@ public void checkTablets(AdminCheckTabletsStmt stmt) { } } + public void setTableStatus(AdminSetTableStatusStmt stmt) throws MetaNotFoundException { + String dbName = stmt.getDbName(); + String tableName = stmt.getTblName(); + setTableStatusInternal(dbName, tableName, stmt.getTableState(), false); + } + + public void replaySetTableStatus(SetTableStatusOperationLog log) throws MetaNotFoundException { + setTableStatusInternal(log.getDbName(), log.getTblName(), log.getState(), true); + } + + public void setTableStatusInternal(String dbName, String tableName, OlapTableState state, boolean isReplay) + throws MetaNotFoundException { + Database db = getInternalCatalog().getDbOrMetaException(dbName); + OlapTable olapTable = (OlapTable) db.getTableOrMetaException(tableName, TableType.OLAP); + olapTable.writeLockOrMetaException(); + try { + OlapTableState oldState = olapTable.getState(); + if (state != null && oldState != state) { + olapTable.setState(state); + if (!isReplay) { + SetTableStatusOperationLog log = new SetTableStatusOperationLog(dbName, tableName, state); + editLog.logSetTableStatus(log); + } + LOG.info("set table {} state from {} to {}. is replay: {}.", + tableName, oldState, state, isReplay); + } else { + LOG.warn("ignore set same state {} for table {}. is replay: {}.", + olapTable.getState(), tableName, isReplay); + } + } finally { + olapTable.writeUnlock(); + } + } + // Set specified replica's status. If replica does not exist, just ignore it. public void setReplicaStatus(AdminSetReplicaStatusStmt stmt) throws MetaNotFoundException { long tabletId = stmt.getTabletId(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java b/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java index 34a6e2c77cdd3a..59e36c24be6277 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java +++ b/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java @@ -108,6 +108,7 @@ import org.apache.doris.persist.ReplicaPersistInfo; import org.apache.doris.persist.RoutineLoadOperation; import org.apache.doris.persist.SetReplicaStatusOperationLog; +import org.apache.doris.persist.SetTableStatusOperationLog; import org.apache.doris.persist.TableAddOrDropColumnsInfo; import org.apache.doris.persist.TableAddOrDropInvertedIndicesInfo; import org.apache.doris.persist.TableInfo; @@ -436,6 +437,11 @@ public void readFields(DataInput in) throws IOException { isRead = true; break; } + case OperationType.OP_SET_TABLE_STATUS: { + data = SetTableStatusOperationLog.read(in); + isRead = true; + break; + } case OperationType.OP_CREATE_REPOSITORY: { data = Repository.read(in); isRead = true; diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java b/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java index 06486fc49cb778..8b11493f2f8729 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java +++ b/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java @@ -555,6 +555,11 @@ public static void loadJournal(Env env, Long logId, JournalEntity journal) { Env.getCurrentGlobalTransactionMgr().replayBatchRemoveTransactionV2(operation); break; } + case OperationType.OP_SET_TABLE_STATUS: { + final SetTableStatusOperationLog log = (SetTableStatusOperationLog) journal.getData(); + env.replaySetTableStatus(log); + break; + } case OperationType.OP_CREATE_REPOSITORY: { Repository repository = (Repository) journal.getData(); env.getBackupHandler().getRepoMgr().addAndInitRepoIfNotExist(repository, true); @@ -1746,6 +1751,10 @@ public void logBatchRemoveTransactions(BatchRemoveTransactionsOperationV2 op) { logEdit(OperationType.OP_BATCH_REMOVE_TXNS_V2, op); } + public void logSetTableStatus(SetTableStatusOperationLog log) { + logEdit(OperationType.OP_SET_TABLE_STATUS, log); + } + public void logModifyComment(ModifyCommentOperationLog op) { logEdit(OperationType.OP_MODIFY_COMMENT, op); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java b/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java index 0285272bb5a381..6b039ac16f60d3 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java @@ -221,6 +221,9 @@ public class OperationType { // set table default distribution bucket num public static final short OP_MODIFY_DISTRIBUTION_BUCKET_NUM = 268; + // set table status + public static final short OP_SET_TABLE_STATUS = 269; + // plugin 270~275 public static final short OP_INSTALL_PLUGIN = 270; diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/SetTableStatusOperationLog.java b/fe/fe-core/src/main/java/org/apache/doris/persist/SetTableStatusOperationLog.java new file mode 100644 index 00000000000000..367fafaaba6792 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/persist/SetTableStatusOperationLog.java @@ -0,0 +1,65 @@ +// 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.persist; + +import org.apache.doris.catalog.OlapTable.OlapTableState; +import org.apache.doris.common.io.Text; +import org.apache.doris.common.io.Writable; +import org.apache.doris.persist.gson.GsonUtils; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class SetTableStatusOperationLog implements Writable { + @SerializedName(value = "db") + private String db; + @SerializedName(value = "table") + private String table; + @Getter + @SerializedName(value = "state") + private OlapTableState state; + + public SetTableStatusOperationLog(String db, String table, OlapTableState state) { + this.db = db; + this.table = table; + this.state = state; + } + + public String getDbName() { + return db; + } + + public String getTblName() { + return table; + } + + public static SetTableStatusOperationLog read(DataInput in) throws IOException { + String json = Text.readString(in); + return GsonUtils.GSON.fromJson(json, SetTableStatusOperationLog.class); + } + + @Override + public void write(DataOutput out) throws IOException { + String json = GsonUtils.GSON.toJson(this); + Text.writeString(out, json); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/DdlExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/DdlExecutor.java index 1d2d31f4163103..cd1993dbdcc43d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/DdlExecutor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/DdlExecutor.java @@ -26,6 +26,7 @@ import org.apache.doris.analysis.AdminRepairTableStmt; import org.apache.doris.analysis.AdminSetConfigStmt; import org.apache.doris.analysis.AdminSetReplicaStatusStmt; +import org.apache.doris.analysis.AdminSetTableStatusStmt; import org.apache.doris.analysis.AlterCatalogNameStmt; import org.apache.doris.analysis.AlterCatalogPropertyStmt; import org.apache.doris.analysis.AlterColumnStatsStmt; @@ -255,6 +256,8 @@ public static void execute(Env env, DdlStmt ddlStmt) throws Exception { env.compactTable((AdminCompactTableStmt) ddlStmt); } else if (ddlStmt instanceof AdminSetConfigStmt) { env.setConfig((AdminSetConfigStmt) ddlStmt); + } else if (ddlStmt instanceof AdminSetTableStatusStmt) { + env.setTableStatus((AdminSetTableStatusStmt) ddlStmt); } else if (ddlStmt instanceof CreateFileStmt) { env.getSmallFileMgr().createFile((CreateFileStmt) ddlStmt); } else if (ddlStmt instanceof DropFileStmt) { diff --git a/regression-test/suites/schema_change/test_alter_table_status.groovy b/regression-test/suites/schema_change/test_alter_table_status.groovy new file mode 100644 index 00000000000000..0fda21f33fef9a --- /dev/null +++ b/regression-test/suites/schema_change/test_alter_table_status.groovy @@ -0,0 +1,49 @@ +// 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. + +suite("test_alter_table_status") { + def tbName1 = "alter_table_status" + + try { + sql "DROP TABLE IF EXISTS ${tbName1}" + sql """ + CREATE TABLE IF NOT EXISTS ${tbName1} ( + k1 INT, + v1 INT, + v2 INT + ) + DUPLICATE KEY (k1) + DISTRIBUTED BY HASH(k1) BUCKETS 1 properties("replication_num" = "1", "light_schema_change" = "false", "disable_auto_compaction" = "true"); + """ + + // set table state to ROLLUP + sql """ADMIN SET TABLE ${tbName1} STATUS PROPERTIES ("state" = "rollup");""" + // try alter table comment + test { + sql """ ALTER TABLE ${tbName1} MODIFY COMMENT 'test'; """ + exception "Table[${tbName1}]'s state is not NORMAL. Do not allow doing ALTER ops" + } + + // set table state to NORMAL + sql """ADMIN SET TABLE ${tbName1} STATUS PROPERTIES ("state" = "normal");""" + // try alter table comment + sql """ ALTER TABLE ${tbName1} MODIFY COMMENT 'test'; """ + } finally { + // drop table + sql """ DROP TABLE ${tbName1} force""" + } +}