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());
+ }
+}