This is an automated email from the ASF dual-hosted git repository.

JackieTien97 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new ec72c599ce6 Show pre-delete tables in SHOW TABLES and reject 
modifications on them (#18770)
ec72c599ce6 is described below

commit ec72c599ce63525aff524fbd1070f84e394033cc
Author: alpass163 <[email protected]>
AuthorDate: Wed Sep 30 14:53:53 2026 +0800

    Show pre-delete tables in SHOW TABLES and reject modifications on them 
(#18770)
---
 .../java/org/apache/iotdb/rpc/TSStatusCode.java    |   1 +
 .../manager/schema/ClusterSchemaManager.java       |  24 ++++-
 .../persistence/schema/ClusterSchemaInfo.java      |   6 +-
 .../confignode/persistence/schema/ConfigMTree.java |  56 ++++++++--
 .../impl/schema/table/DeleteDevicesProcedure.java  |   2 +-
 .../manager/ClusterSchemaManagerTest.java          |  89 +++++++++++++++
 .../persistence/schema/ClusterSchemaInfoTest.java  | 119 +++++++++++++++++++++
 .../persistence/schema/ConfigMTreeTest.java        |  31 ++++++
 .../iotdb/db/i18n/DataNodeSchemaMessages.java      |   2 -
 .../iotdb/db/i18n/DataNodeSchemaMessages.java      |   2 -
 .../db/schemaengine/table/DataNodeTableCache.java  |   7 +-
 .../apache/iotdb/db/utils/ErrorHandlingUtils.java  |   7 ++
 .../schemaengine/table/DataNodeTableCacheTest.java |  55 ++++++++++
 .../apache/iotdb/commons/i18n/CommonMessages.java  |   3 +
 .../apache/iotdb/commons/i18n/CommonMessages.java  |   3 +
 .../exception/table/TableInDeletionException.java  |  38 +++++++
 16 files changed, 424 insertions(+), 21 deletions(-)

diff --git 
a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java 
b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
index 97b5144678d..90c90bb0b55 100644
--- 
a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
+++ 
b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
@@ -99,6 +99,7 @@ public enum TSStatusCode {
   COLUMN_ALREADY_EXISTS(552),
   TABLE_IS_LOST(553),
   TABLE_INCOMPATIBLE(554),
+  TABLE_IN_PRE_DELETE(555),
   ONLY_LOGICAL_VIEW(560),
 
   // Storage Engine
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java
index 840be42d48c..b43e3599bec 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java
@@ -24,6 +24,7 @@ import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.exception.table.TableInDeletionException;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.path.PathPatternTree;
 import org.apache.iotdb.commons.schema.SchemaConstant;
@@ -1540,13 +1541,26 @@ public class ClusterSchemaManager {
     return clusterSchemaInfo.getTsTableIfExists(database, tableName);
   }
 
+  public Optional<TsTable> getTableWithUsingStatusIfExists(
+      final String database, final String tableName) throws MetadataException {
+    final Optional<Pair<TsTable, TableNodeStatus>> tableAndStatus =
+        getTableAndStatusIfExists(database, tableName);
+    if (tableAndStatus.isEmpty()) {
+      return Optional.empty();
+    }
+    if (TableNodeStatus.PRE_DELETE == tableAndStatus.get().getRight()) {
+      throw new TableInDeletionException(database, tableName);
+    }
+    return Optional.of(tableAndStatus.get().getLeft());
+  }
+
   public synchronized Pair<TSStatus, TsTable> 
tableColumnCheckForColumnExtension(
       final String database,
       final String tableName,
       final List<TsTableColumnSchema> columnSchemaList,
       final boolean isTableView)
       throws MetadataException {
-    final TsTable originalTable = getTableIfExists(database, 
tableName).orElse(null);
+    final TsTable originalTable = getTableWithUsingStatusIfExists(database, 
tableName).orElse(null);
 
     if (Objects.isNull(originalTable)) {
       return new Pair<>(
@@ -1601,7 +1615,7 @@ public class ClusterSchemaManager {
       final TSDataType dataType,
       final boolean isGeneratedByPipe)
       throws MetadataException {
-    final TsTable originalTable = getTableIfExists(database, 
tableName).orElse(null);
+    final TsTable originalTable = getTableWithUsingStatusIfExists(database, 
tableName).orElse(null);
 
     if (Objects.isNull(originalTable)) {
       return new Pair<>(
@@ -1638,7 +1652,7 @@ public class ClusterSchemaManager {
       final String newName,
       final boolean isTableView)
       throws MetadataException {
-    final TsTable originalTable = getTableIfExists(database, 
tableName).orElse(null);
+    final TsTable originalTable = getTableWithUsingStatusIfExists(database, 
tableName).orElse(null);
 
     if (Objects.isNull(originalTable)) {
       return new Pair<>(
@@ -1691,7 +1705,7 @@ public class ClusterSchemaManager {
       final String newName,
       final boolean isTableView)
       throws MetadataException {
-    final TsTable originalTable = getTableIfExists(database, 
tableName).orElse(null);
+    final TsTable originalTable = getTableWithUsingStatusIfExists(database, 
tableName).orElse(null);
 
     if (Objects.isNull(originalTable)) {
       return new Pair<>(
@@ -1776,7 +1790,7 @@ public class ClusterSchemaManager {
       final Map<String, String> updatedProperties,
       final boolean isTableView)
       throws MetadataException {
-    final TsTable originalTable = getTableIfExists(database, 
tableName).orElse(null);
+    final TsTable originalTable = getTableWithUsingStatusIfExists(database, 
tableName).orElse(null);
 
     if (Objects.isNull(originalTable)) {
       return new Pair<>(
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfo.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfo.java
index 1b9c880cd6a..913c1f04c3c 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfo.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfo.java
@@ -1352,9 +1352,11 @@ public class ClusterSchemaInfo implements 
SnapshotProcessor {
                       })
                   .collect(Collectors.toList())
               : tableModelMTree
-                  .getAllUsingTablesUnderSpecificDatabase(
+                  .getAllTablesUnderSpecificDatabase(
                       getQualifiedDatabasePartialPath(plan.getDatabase()))
                   .stream()
+                  .filter(pair -> pair.getRight() != 
TableNodeStatus.PRE_CREATE)
+                  .map(Pair::getLeft)
                   .map(
                       tsTable ->
                           new TTableInfo(
@@ -1447,7 +1449,7 @@ public class ClusterSchemaInfo implements 
SnapshotProcessor {
       }
       return new DescTableResp(
           StatusUtils.OK,
-          tableModelMTree.getUsingTableSchema(databasePath, 
plan.getTableName()),
+          tableModelMTree.getTableSchemaForDesc(databasePath, 
plan.getTableName()),
           null,
           null);
     } catch (final MetadataException e) {
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java
index 12f6a2baaa1..f362778bcf3 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java
@@ -27,6 +27,7 @@ import org.apache.iotdb.commons.exception.MetadataException;
 import org.apache.iotdb.commons.exception.SemanticException;
 import org.apache.iotdb.commons.exception.table.ColumnNotExistsException;
 import org.apache.iotdb.commons.exception.table.TableAlreadyExistsException;
+import org.apache.iotdb.commons.exception.table.TableInDeletionException;
 import org.apache.iotdb.commons.exception.table.TableNotExistsException;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.path.PathPatternTree;
@@ -796,7 +797,7 @@ public class ConfigMTree {
       final String comment,
       final boolean isView)
       throws MetadataException {
-    final TsTable table = getTable(database, tableName);
+    final TsTable table = getTableWithUsingStatus(database, 
tableName).getTable();
     final Optional<Pair<TSStatus, TsTable>> check =
         ClusterSchemaManager.checkTable4View(database.getTailNode(), table, 
isView);
     if (check.isPresent()) {
@@ -816,9 +817,8 @@ public class ConfigMTree {
       final @Nonnull String columnName,
       final @Nullable String comment)
       throws MetadataException {
-    final TsTable table = getTable(database, tableName);
-
-    final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName);
+    final TsTableColumnSchema columnSchema =
+        getTableWithUsingStatus(database, 
tableName).getTable().getColumnSchema(columnName);
 
     if (Objects.isNull(columnSchema)) {
       throw new ColumnNotExistsException(
@@ -988,14 +988,13 @@ public class ConfigMTree {
       final String columnName,
       final boolean isView)
       throws MetadataException, SemanticException {
-    final ConfigTableNode node = getTableNode(database, tableName);
+    final ConfigTableNode node = getTableWithUsingStatus(database, tableName);
     final Optional<Pair<TSStatus, TsTable>> check =
         ClusterSchemaManager.checkTable4View(database.getTailNode(), 
node.getTable(), isView);
     if (check.isPresent()) {
       throw new SemanticException(
           check.get().getLeft().getMessage(), check.get().getLeft().getCode());
     }
-
     final TsTableColumnSchema columnSchema = 
node.getTable().getColumnSchema(columnName);
     if (Objects.isNull(columnSchema)) {
       throw new ColumnNotExistsException(
@@ -1024,7 +1023,7 @@ public class ConfigMTree {
   public void preAlterColumnDataType(
       PartialPath database, String tableName, String columnName, TSDataType 
dataType)
       throws MetadataException {
-    final ConfigTableNode node = getTableNode(database, tableName);
+    final ConfigTableNode node = getTableWithUsingStatus(database, tableName);
     final TsTableColumnSchema columnSchema = 
node.getTable().getColumnSchema(columnName);
 
     if (Objects.isNull(columnSchema)) {
@@ -1089,6 +1088,36 @@ public class ConfigMTree {
     return newTable;
   }
 
+  /**
+   * The schema to report in DESC. Unlike {@link #getUsingTableSchema}, a 
column whose deletion is
+   * still pending is kept, so that the user can see which columns exist and 
are only waiting for
+   * the pending procedure. A pending data type change is applied on top.
+   */
+  public TsTable getTableSchemaForDesc(final PartialPath database, final 
String tableName)
+      throws MetadataException {
+    final ConfigTableNode node = getTableNode(database, tableName);
+    if (node.getPreAlteredColumns().isEmpty()) {
+      return node.getTable();
+    }
+    final TsTable table = new TsTable(node.getTable());
+    node.getPreAlteredColumns()
+        .forEach(
+            (columnName, dataType) -> {
+              final TsTableColumnSchema columnSchema = 
table.getColumnSchema(columnName);
+              if (columnSchema == null) {
+                return;
+              }
+              columnSchema.setDataType(dataType);
+              if (columnSchema instanceof FieldColumnSchema) {
+                final FieldColumnSchema fieldColumnSchema = 
(FieldColumnSchema) columnSchema;
+                fieldColumnSchema.setEncoding(
+                    SchemaUtils.getDataTypeCompatibleEncoding(
+                        dataType, fieldColumnSchema.getEncoding()));
+              }
+            });
+    return table;
+  }
+
   public TableSchemaDetails getTableSchemaDetails(
       final PartialPath database, final String tableName) throws 
MetadataException {
     final ConfigTableNode node = getTableNode(database, tableName);
@@ -1130,6 +1159,19 @@ public class ConfigMTree {
     return ((ConfigTableNode) databaseNode.getChild(tableName));
   }
 
+  /**
+   * A table in the pre-delete status is about to be dropped, pre-create 
status is a temporary
+   * status, ignore it.
+   */
+  private ConfigTableNode getTableWithUsingStatus(
+      final PartialPath database, final String tableName) throws 
MetadataException {
+    final ConfigTableNode tableNode = getTableNode(database, tableName);
+    if (tableNode.getStatus() == TableNodeStatus.PRE_DELETE) {
+      throw new TableInDeletionException(database.getFullPath(), tableName);
+    }
+    return tableNode;
+  }
+
   // endregion
 
   // region Serialization and Deserialization
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DeleteDevicesProcedure.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DeleteDevicesProcedure.java
index 36b2ce3fbb1..8751a8fef62 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DeleteDevicesProcedure.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DeleteDevicesProcedure.java
@@ -148,7 +148,7 @@ public class DeleteDevicesProcedure extends 
AbstractAlterOrDropTableProcedure<De
     try {
       if (!env.getConfigManager()
           .getClusterSchemaManager()
-          .getTableIfExists(database, tableName)
+          .getTableWithUsingStatusIfExists(database, tableName)
           .isPresent()) {
         setFailure(
             new ProcedureException(
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java
index 7e080458c2d..1be38df602a 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java
@@ -18,18 +18,27 @@
  */
 package org.apache.iotdb.confignode.manager;
 
+import org.apache.iotdb.commons.exception.table.TableInDeletionException;
 import org.apache.iotdb.commons.schema.table.TsTable;
 import org.apache.iotdb.commons.schema.table.TsTableInternalRPCUtil;
+import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlanType;
+import 
org.apache.iotdb.confignode.consensus.request.write.database.DatabaseSchemaPlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.CommitCreateTablePlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.PreCreateTablePlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.PreDeleteTablePlan;
 import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager;
 import org.apache.iotdb.confignode.manager.schema.ClusterSchemaQuotaStatistics;
 import org.apache.iotdb.confignode.persistence.schema.ClusterSchemaInfo;
 import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
+import org.apache.iotdb.rpc.TSStatusCode;
 
+import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.utils.Pair;
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -89,4 +98,84 @@ public class ClusterSchemaManagerTest {
     Assert.assertEquals(Collections.singleton("test"), 
tableInfo.right.keySet());
     Assert.assertTrue(tableInfo.right.get("test").isEmpty());
   }
+
+  @Test
+  public void testGetTableWithUsingStatusIfExists() throws Exception {
+    final String database = "root.pre_delete_manager_test";
+    final String table = "table1";
+    final ClusterSchemaInfo clusterSchemaInfo = new ClusterSchemaInfo();
+    final ClusterSchemaManager clusterSchemaManager = 
managerOf(clusterSchemaInfo);
+    clusterSchemaInfo.createDatabase(
+        new DatabaseSchemaPlan(
+            ConfigPhysicalPlanType.CreateDatabase,
+            new TDatabaseSchema(database).setIsTableModel(true)));
+
+    // A missing table yields an empty result instead of an exception.
+    Assert.assertFalse(
+        clusterSchemaManager.getTableWithUsingStatusIfExists(database, 
table).isPresent());
+
+    clusterSchemaInfo.preCreateTable(new PreCreateTablePlan(database, new 
TsTable(table)));
+    clusterSchemaInfo.commitCreateTable(new CommitCreateTablePlan(database, 
table));
+
+    // A table in the using status is returned.
+    Assert.assertTrue(
+        clusterSchemaManager.getTableWithUsingStatusIfExists(database, 
table).isPresent());
+
+    clusterSchemaInfo.preDeleteTable(new PreDeleteTablePlan(database, table));
+
+    // A table in the pre-delete status is rejected with the dedicated status 
code.
+    final TableInDeletionException exception =
+        Assert.assertThrows(
+            TableInDeletionException.class,
+            () -> 
clusterSchemaManager.getTableWithUsingStatusIfExists(database, table));
+    Assert.assertEquals(TSStatusCode.TABLE_IN_PRE_DELETE.getStatusCode(), 
exception.getErrorCode());
+  }
+
+  @Test
+  public void testTableChecksRejectTableInPreDelete() throws Exception {
+    final String database = "root.pre_delete_manager_test";
+    final String table = "table1";
+    final ClusterSchemaInfo clusterSchemaInfo = new ClusterSchemaInfo();
+    final ClusterSchemaManager clusterSchemaManager = 
managerOf(clusterSchemaInfo);
+    clusterSchemaInfo.createDatabase(
+        new DatabaseSchemaPlan(
+            ConfigPhysicalPlanType.CreateDatabase,
+            new TDatabaseSchema(database).setIsTableModel(true)));
+    clusterSchemaInfo.preCreateTable(new PreCreateTablePlan(database, new 
TsTable(table)));
+    clusterSchemaInfo.commitCreateTable(new CommitCreateTablePlan(database, 
table));
+    clusterSchemaInfo.preDeleteTable(new PreDeleteTablePlan(database, table));
+
+    // Every check that guards a table procedure must reject the table, so 
that no procedure keeps
+    // modifying a table that is being deleted.
+    Assert.assertThrows(
+        TableInDeletionException.class,
+        () ->
+            clusterSchemaManager.tableColumnCheckForColumnExtension(
+                database, table, new ArrayList<>(), false));
+    Assert.assertThrows(
+        TableInDeletionException.class,
+        () ->
+            clusterSchemaManager.tableColumnCheckForColumnAltering(
+                database, table, "field", TSDataType.INT32, false));
+    Assert.assertThrows(
+        TableInDeletionException.class,
+        () ->
+            clusterSchemaManager.tableColumnCheckForColumnRenaming(
+                database, table, "field", "field2", false));
+    Assert.assertThrows(
+        TableInDeletionException.class,
+        () -> clusterSchemaManager.tableCheckForRenaming(database, table, 
"table2", false));
+    Assert.assertThrows(
+        TableInDeletionException.class,
+        () ->
+            clusterSchemaManager.updateTableProperties(
+                database, table, new HashMap<>(), new HashMap<>(), false));
+  }
+
+  private static ClusterSchemaManager managerOf(final ClusterSchemaInfo 
clusterSchemaInfo) {
+    return new ClusterSchemaManager(
+        Mockito.mock(IManager.class),
+        clusterSchemaInfo,
+        Mockito.mock(ClusterSchemaQuotaStatistics.class));
+  }
 }
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfoTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfoTest.java
index 142d015ebf9..82e94d083a4 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfoTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfoTest.java
@@ -19,20 +19,32 @@
 
 package org.apache.iotdb.confignode.persistence.schema;
 
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.exception.IllegalPathException;
 import org.apache.iotdb.commons.exception.MetadataException;
 import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.schema.table.TableNodeStatus;
 import org.apache.iotdb.commons.schema.table.TsTable;
+import org.apache.iotdb.commons.schema.table.column.FieldColumnSchema;
 import org.apache.iotdb.commons.schema.template.Template;
 import org.apache.iotdb.commons.utils.PathUtils;
 import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlanType;
 import 
org.apache.iotdb.confignode.consensus.request.read.database.GetDatabasePlan;
+import org.apache.iotdb.confignode.consensus.request.read.table.ShowTablePlan;
 import 
org.apache.iotdb.confignode.consensus.request.read.template.GetPathsSetTemplatePlan;
 import 
org.apache.iotdb.confignode.consensus.request.read.template.GetTemplateSetInfoPlan;
 import 
org.apache.iotdb.confignode.consensus.request.write.database.DatabaseSchemaPlan;
 import 
org.apache.iotdb.confignode.consensus.request.write.database.DeleteDatabasePlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.CommitCreateTablePlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.CommitDeleteTablePlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.PreAlterColumnDataTypePlan;
 import 
org.apache.iotdb.confignode.consensus.request.write.table.PreCreateTablePlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.PreDeleteColumnPlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.PreDeleteTablePlan;
 import 
org.apache.iotdb.confignode.consensus.request.write.table.RollbackCreateTablePlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.RollbackPreDeleteTablePlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnCommentPlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.table.SetTableCommentPlan;
 import 
org.apache.iotdb.confignode.consensus.request.write.template.CreateSchemaTemplatePlan;
 import 
org.apache.iotdb.confignode.consensus.request.write.template.PreSetSchemaTemplatePlan;
 import 
org.apache.iotdb.confignode.consensus.request.write.template.SetSchemaTemplatePlan;
@@ -40,6 +52,7 @@ import 
org.apache.iotdb.confignode.consensus.response.template.AllTemplateSetInf
 import 
org.apache.iotdb.confignode.consensus.response.template.TemplateInfoResp;
 import 
org.apache.iotdb.confignode.consensus.response.template.TemplateSetInfoResp;
 import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
+import org.apache.iotdb.confignode.rpc.thrift.TTableInfo;
 import org.apache.iotdb.db.schemaengine.template.TemplateInternalRPCUtil;
 import org.apache.iotdb.rpc.TSStatusCode;
 
@@ -57,12 +70,14 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.stream.Collectors;
 
 import static org.apache.iotdb.commons.schema.SchemaConstant.ALL_MATCH_SCOPE;
 import static org.apache.iotdb.db.utils.constant.TestConstant.BASE_OUTPUT_PATH;
@@ -164,6 +179,110 @@ public class ClusterSchemaInfoTest {
     Assert.assertEquals(0, statistics.getBaseTableNum(database));
   }
 
+  @Test
+  public void testShowTablesIncludesPreDeleteButNotPreCreate() {
+    final String database = "root.pre_delete_test";
+    final String table = "table1";
+    clusterSchemaInfo.createDatabase(
+        new DatabaseSchemaPlan(
+            ConfigPhysicalPlanType.CreateDatabase,
+            new TDatabaseSchema(database).setIsTableModel(true)));
+    clusterSchemaInfo.preCreateTable(new PreCreateTablePlan(database, new 
TsTable("using_table")));
+    clusterSchemaInfo.commitCreateTable(new CommitCreateTablePlan(database, 
"using_table"));
+    clusterSchemaInfo.preCreateTable(new PreCreateTablePlan(database, new 
TsTable(table)));
+    clusterSchemaInfo.commitCreateTable(new CommitCreateTablePlan(database, 
table));
+    // the table with pre-create status should not be listed in SHOW TABLES
+    clusterSchemaInfo.preCreateTable(new PreCreateTablePlan(database, new 
TsTable("creating")));
+    // the table with pre-delete status should be listed in SHOW TABLES
+    clusterSchemaInfo.preDeleteTable(new PreDeleteTablePlan(database, table));
+
+    Assert.assertEquals(
+        Arrays.asList(table, "using_table"),
+        clusterSchemaInfo
+            .showTables(new ShowTablePlan(database, false))
+            .convertToTShowTableResp()
+            .getTableInfoList()
+            .stream()
+            .map(TTableInfo::getTableName)
+            .sorted()
+            .collect(Collectors.toList()));
+
+    final Map<String, Integer> states =
+        clusterSchemaInfo
+            .showTables(new ShowTablePlan(database, true))
+            .convertToTShowTableResp()
+            .getTableInfoList()
+            .stream()
+            .collect(Collectors.toMap(TTableInfo::getTableName, 
TTableInfo::getState));
+    Assert.assertEquals(Integer.valueOf(TableNodeStatus.PRE_DELETE.ordinal()), 
states.get(table));
+    Assert.assertEquals(
+        Integer.valueOf(TableNodeStatus.PRE_CREATE.ordinal()), 
states.get("creating"));
+
+    clusterSchemaInfo.dropTable(new CommitDeleteTablePlan(database, table));
+    Assert.assertEquals(
+        Collections.singletonList("using_table"),
+        clusterSchemaInfo
+            .showTables(new ShowTablePlan(database, false))
+            .convertToTShowTableResp()
+            .getTableInfoList()
+            .stream()
+            .map(TTableInfo::getTableName)
+            .collect(Collectors.toList()));
+  }
+
+  @Test
+  public void testModificationOfTableInPreDeleteIsRejected() {
+    final String database = "root.pre_delete_modification_test";
+    final String table = "table1";
+    final String column = "field";
+    clusterSchemaInfo.createDatabase(
+        new DatabaseSchemaPlan(
+            ConfigPhysicalPlanType.CreateDatabase,
+            new TDatabaseSchema(database).setIsTableModel(true)));
+    final TsTable created = new TsTable(table);
+    created.addColumnSchema(
+        new FieldColumnSchema(column, TSDataType.INT32, TSEncoding.RLE, 
CompressionType.LZ4));
+    clusterSchemaInfo.preCreateTable(new PreCreateTablePlan(database, 
created));
+    clusterSchemaInfo.commitCreateTable(new CommitCreateTablePlan(database, 
table));
+    clusterSchemaInfo.preDeleteTable(new PreDeleteTablePlan(database, table));
+
+    // Every modification entry point must reject a table that is in the 
pre-delete status, and
+    // report the dedicated status code.
+    assertInPreDelete(
+        table,
+        clusterSchemaInfo.setTableComment(new SetTableCommentPlan(database, 
table, "comment")));
+    assertInPreDelete(
+        table,
+        clusterSchemaInfo.setTableColumnComment(
+            new SetTableColumnCommentPlan(database, table, column, 
"comment")));
+    assertInPreDelete(
+        table, clusterSchemaInfo.preDeleteColumn(new 
PreDeleteColumnPlan(database, table, column)));
+    assertInPreDelete(
+        table,
+        clusterSchemaInfo.preAlterColumnDataType(
+            new PreAlterColumnDataTypePlan(database, table, column, 
TSDataType.INT64)));
+
+    // Pre-delete is not a terminal status: after a rollback the same 
operations must work again.
+    assertSuccess(
+        clusterSchemaInfo.rollbackPreDeleteTable(new 
RollbackPreDeleteTablePlan(database, table)));
+    assertSuccess(
+        clusterSchemaInfo.setTableComment(new SetTableCommentPlan(database, 
table, "comment")));
+    assertSuccess(
+        clusterSchemaInfo.preDeleteColumn(new PreDeleteColumnPlan(database, 
table, column)));
+  }
+
+  private static void assertInPreDelete(final String table, final TSStatus 
status) {
+    Assert.assertEquals(TSStatusCode.TABLE_IN_PRE_DELETE.getStatusCode(), 
status.getCode());
+    // Assert on the table name rather than on the wording, which differs 
between the en and zh
+    // message bundles.
+    Assert.assertTrue(status.getMessage(), 
status.getMessage().contains(table));
+  }
+
+  private static void assertSuccess(final TSStatus status) {
+    Assert.assertEquals(
+        status.getMessage(), TSStatusCode.SUCCESS_STATUS.getStatusCode(), 
status.getCode());
+  }
+
   @Test
   public void testDatabasePathWithEmptyNodeIsInvalid() throws 
MetadataException {
     clusterSchemaInfo.isDatabaseNameValid("root.database", false);
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java
index c2519dcbfae..f66edecbbcc 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java
@@ -453,6 +453,37 @@ public class ConfigMTreeTest {
         root.getTableSchemaDetails(database, 
table.getTableName()).preAlteredColumns.isEmpty());
   }
 
+  @Test
+  public void testDescKeepsPreDeletedColumns() throws Exception {
+    root = new ConfigMTree(true);
+
+    final PartialPath database = new PartialPath("root.sg");
+    root.setStorageGroup(database);
+    final IDatabaseMNode<IConfigMNode> databaseNode = 
root.getDatabaseNodeByDatabasePath(database);
+    databaseNode
+        .getAsMNode()
+        .getDatabaseSchema()
+        .setName(PathUtils.unQualifyDatabaseName(database.getFullPath()));
+    databaseNode.getAsMNode().getDatabaseSchema().setIsTableModel(true);
+
+    final TsTable table = new TsTable("table1");
+    table.addColumnSchema(new TagColumnSchema("id", TSDataType.STRING));
+    table.addColumnSchema(
+        new FieldColumnSchema(
+            "measurement", TSDataType.DOUBLE, TSEncoding.GORILLA, 
CompressionType.SNAPPY));
+    root.preCreateTable(database, table);
+    root.commitCreateTable(database, table.getTableName());
+
+    root.preDeleteColumn(database, table.getTableName(), "measurement", false);
+
+    // DESC keeps a column whose deletion is still pending, so that the user 
can see it.
+    Assert.assertNotNull(
+        root.getTableSchemaForDesc(database, 
table.getTableName()).getColumnSchema("measurement"));
+    // A DataNode must not see that column any more.
+    Assert.assertNull(
+        root.getUsingTableSchema(database, 
table.getTableName()).getColumnSchema("measurement"));
+  }
+
   @Test
   public void testSetTemplate() throws MetadataException {
     root.setStorageGroup(new PartialPath("root.a"));
diff --git 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
index 70112fa26f4..c0e0d67346b 100644
--- 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
@@ -587,8 +587,6 @@ public final class DataNodeSchemaMessages {
   public static final String UPDATE_TABLE_BY_FETCH_WITH_DETAIL =
       "Update table {}.{} by table fetch, {}";
   public static final String UPDATE_TABLE_BY_FETCH = "Update table {}.{} by 
table fetch.";
-  public static final String THE_TABLE_IS_IN_PRE_DELETE_STATE =
-      "The table %s.%s is in the pre-delete state. Please wait a few seconds. 
If the table is still in this state, please drop it again.";
   public static final String COMPARE_TABLE_ADDED = "Added table: ";
   public static final String COMPARE_TABLE_REMOVED = "Removed table: ";
   public static final String COMPARE_TABLE_NAME = "Table name: ";
diff --git 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
index 7b370b78bbf..417f547573a 100644
--- 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
@@ -581,8 +581,6 @@ public final class DataNodeSchemaMessages {
       "尝试获取信号量以从 ConfigNode 获取表时被中断,已忽略。";
   public static final String UPDATE_TABLE_BY_FETCH_WITH_DETAIL = "获取表 {}.{} 
信息, {}";
   public static final String UPDATE_TABLE_BY_FETCH = "通过表拉取更新表 {}.{}";
-  public static final String THE_TABLE_IS_IN_PRE_DELETE_STATE =
-      "表 %s.%s 处于预删除的状态,请稍等,如之后重试还是此状态,请输入sql再次删除";
   public static final String COMPARE_TABLE_ADDED = "新增表:";
   public static final String COMPARE_TABLE_REMOVED = "已移除表:";
   public static final String COMPARE_TABLE_NAME = "表名:";
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
index 807387f8c29..98ca71e0eac 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
@@ -26,6 +26,7 @@ import org.apache.iotdb.commons.consensus.ConfigRegionId;
 import org.apache.iotdb.commons.exception.IoTDBRuntimeException;
 import 
org.apache.iotdb.commons.exception.MetadataLeaseFencedException.LeaseFencedRetryPolicy;
 import org.apache.iotdb.commons.exception.SemanticException;
+import org.apache.iotdb.commons.i18n.CommonMessages;
 import org.apache.iotdb.commons.schema.table.NonCommittableTsTable;
 import org.apache.iotdb.commons.schema.table.PreDeleteTsTable;
 import org.apache.iotdb.commons.schema.table.TableNodeStatus;
@@ -711,9 +712,11 @@ public class DataNodeTableCache implements ITableCache {
       if (targetTableIsStillDeleting) {
         throw new SemanticException(
             String.format(
-                DataNodeSchemaMessages.THE_TABLE_IS_IN_PRE_DELETE_STATE,
+                CommonMessages
+                    
.EXCEPTION_THE_TABLE_ARG_ARG_IS_IN_THE_PRE_DELETE_STATE_PLEASE_WAIT_A_FEW_SECONDS_IF_THE_TABLE_IS_STILL_IN_THIS_STATE_PLEASE_DROP_IT_AGAIN_13B9D39F,
                 targetDatabase,
-                targetTable));
+                targetTable),
+            TSStatusCode.TABLE_IN_PRE_DELETE.getStatusCode());
       }
     } finally {
       readWriteLock.writeLock().unlock();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java
index b6154b059d5..7f0ad8b9a14 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java
@@ -196,6 +196,13 @@ public class ErrorHandlingUtils {
         return RpcUtils.getStatus(
             ((IoTDBException) t.getCause()).getErrorCode(), 
rootCause.getMessage());
       }
+      // A SemanticException is reported as SEMANTIC_ERROR whatever code it 
carries, except
+      // TABLE_IN_PRE_DELETE, which the client needs in order to tell a table 
that is being deleted
+      // from an ordinary semantic error.
+      if (((SemanticException) t).getErrorCode()
+          == TSStatusCode.TABLE_IN_PRE_DELETE.getStatusCode()) {
+        return RpcUtils.getStatus(TSStatusCode.TABLE_IN_PRE_DELETE, 
rootCause.getMessage());
+      }
       return RpcUtils.getStatus(TSStatusCode.SEMANTIC_ERROR, 
rootCause.getMessage());
     } else if (t instanceof IoTDBRuntimeException) {
       return Objects.nonNull(((IoTDBRuntimeException) t).getStatus())
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
index fd97da843bd..d8d1eddafe4 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
@@ -19,8 +19,12 @@
 
 package org.apache.iotdb.db.schemaengine.table;
 
+import 
org.apache.iotdb.commons.exception.MetadataLeaseFencedException.LeaseFencedRetryPolicy;
+import org.apache.iotdb.commons.exception.SemanticException;
+import org.apache.iotdb.commons.schema.table.PreDeleteTsTable;
 import org.apache.iotdb.commons.schema.table.TsTable;
 import org.apache.iotdb.commons.schema.table.column.FieldColumnSchema;
+import org.apache.iotdb.rpc.TSStatusCode;
 
 import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.file.metadata.enums.CompressionType;
@@ -29,6 +33,11 @@ import org.junit.Assert;
 import org.junit.Test;
 
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.Semaphore;
 
 public class DataNodeTableCacheTest {
@@ -111,6 +120,52 @@ public class DataNodeTableCacheTest {
     }
   }
 
+  @Test
+  public void fetchingPreDeleteTableReportsDedicatedStatusCode() throws 
Exception {
+    final ITableCache cache = DataNodeTableCache.getInstance();
+    cache.invalid(DATABASE);
+    try {
+      // Hold the table as pending-delete, which is what makes the fetch below 
conclude that the
+      // table is still being deleted.
+      cache.preUpdateTable(DATABASE, new PreDeleteTsTable(TABLE_NAME), null);
+
+      final Map<String, Map<String, TsTable>> fetchedTables = new HashMap<>();
+      fetchedTables.put(
+          DATABASE, Collections.singletonMap(TABLE_NAME, new 
PreDeleteTsTable(TABLE_NAME)));
+
+      final InvocationTargetException thrown =
+          Assert.assertThrows(
+              InvocationTargetException.class,
+              () -> updateDeleteTable(cache, fetchedTables, TABLE_NAME));
+
+      final SemanticException cause = (SemanticException) thrown.getCause();
+      Assert.assertEquals(TSStatusCode.TABLE_IN_PRE_DELETE.getStatusCode(), 
cause.getErrorCode());
+    } finally {
+      cache.invalid(DATABASE);
+    }
+  }
+
+  /**
+   * The pre-delete branch lives in the private {@code updateDeleteTable}, 
which is only reachable
+   * through a ConfigNode fetch, so invoke it directly.
+   */
+  private static void updateDeleteTable(
+      final ITableCache cache,
+      final Map<String, Map<String, TsTable>> fetchedTables,
+      final String tableName)
+      throws Exception {
+    final Method method =
+        DataNodeTableCache.class.getDeclaredMethod(
+            "updateDeleteTable",
+            Map.class,
+            String.class,
+            String.class,
+            LeaseFencedRetryPolicy.class);
+    method.setAccessible(true);
+    method.invoke(
+        cache, fetchedTables, DATABASE, tableName, 
LeaseFencedRetryPolicy.RETRY_UNTIL_SUCCESS);
+  }
+
   private Semaphore getFetchTableSemaphore(final ITableCache cache) throws 
Exception {
     final Field field = 
DataNodeTableCache.class.getDeclaredField("fetchTableSemaphore");
     field.setAccessible(true);
diff --git 
a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/CommonMessages.java
 
b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/CommonMessages.java
index aaafa014cc0..b8f18377293 100644
--- 
a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/CommonMessages.java
+++ 
b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/CommonMessages.java
@@ -338,4 +338,7 @@ public final class CommonMessages {
   public static final String
       
EXCEPTION_SNAPSHOT_BUFFER_SIZE_MUST_NOT_EXCEED_ARG_BYTES_BUT_WAS_ARG_D1DA6F7E =
           "Snapshot buffer size must not exceed %d bytes, but was %d.";
+  public static final String
+      
EXCEPTION_THE_TABLE_ARG_ARG_IS_IN_THE_PRE_DELETE_STATE_PLEASE_WAIT_A_FEW_SECONDS_IF_THE_TABLE_IS_STILL_IN_THIS_STATE_PLEASE_DROP_IT_AGAIN_13B9D39F
 =
+          "The table %s.%s is in the pre-delete state. Please wait a few 
seconds. If the table is still in this state, please drop it again.";
 }
diff --git 
a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/CommonMessages.java
 
b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/CommonMessages.java
index 9f7151bebdc..0028dfcd7d3 100644
--- 
a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/CommonMessages.java
+++ 
b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/CommonMessages.java
@@ -235,4 +235,7 @@ public final class CommonMessages {
   public static final String
       
EXCEPTION_SNAPSHOT_BUFFER_SIZE_MUST_NOT_EXCEED_ARG_BYTES_BUT_WAS_ARG_D1DA6F7E =
           "快照缓冲区大小不得超过 %d 字节,但实际为 %d。";
+  public static final String
+      
EXCEPTION_THE_TABLE_ARG_ARG_IS_IN_THE_PRE_DELETE_STATE_PLEASE_WAIT_A_FEW_SECONDS_IF_THE_TABLE_IS_STILL_IN_THIS_STATE_PLEASE_DROP_IT_AGAIN_13B9D39F
 =
+          "表 %s.%s 处于预删除的状态,请稍等,如之后重试还是此状态,请输入sql再次删除";
 }
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/table/TableInDeletionException.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/table/TableInDeletionException.java
new file mode 100644
index 00000000000..d5a676e4b0f
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/table/TableInDeletionException.java
@@ -0,0 +1,38 @@
+/*
+ * 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.iotdb.commons.exception.table;
+
+import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.i18n.CommonMessages;
+import org.apache.iotdb.commons.utils.PathUtils;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+public class TableInDeletionException extends MetadataException {
+
+  public TableInDeletionException(final String database, final String 
tableName) {
+    super(
+        String.format(
+            CommonMessages
+                
.EXCEPTION_THE_TABLE_ARG_ARG_IS_IN_THE_PRE_DELETE_STATE_PLEASE_WAIT_A_FEW_SECONDS_IF_THE_TABLE_IS_STILL_IN_THIS_STATE_PLEASE_DROP_IT_AGAIN_13B9D39F,
+            PathUtils.unQualifyDatabaseName(database),
+            tableName),
+        TSStatusCode.TABLE_IN_PRE_DELETE.getStatusCode());
+  }
+}

Reply via email to