This is an automated email from the ASF dual-hosted git repository. jiangtian pushed a commit to branch force_ci/support_schema_evolution in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit f22906edede6f473259dd36ede237a1ae5d705f6 Merge: 7617259da03 607339cbeae Author: Tian Jiang <[email protected]> AuthorDate: Tue Jan 20 09:58:11 2026 +0800 Merge branch 'master' into force_ci/support_schema_evolution # Conflicts: # .gitignore # integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java # iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java # pom.xml .gitignore | 3 + integration-test/pom.xml | 2 +- .../treemodel/manual/IoTDBPipePermissionIT.java | 36 +- .../pipe/it/single/IoTDBPipePermissionIT.java | 51 ++ .../it/query/object/IoTDBObjectQuery2IT.java | 326 ------------- .../it/query/object/IoTDBObjectQueryIT.java | 309 ------------ .../it/query/old/IoTDBSimpleQueryTableIT.java | 63 +-- .../relational/it/query/recent/IoTDBCteIT.java | 27 + .../recent/informationschema/IoTDBServicesIT.java | 118 +++++ .../relational/it/schema/IoTDBDatabaseIT.java | 12 +- .../iotdb/relational/it/schema/IoTDBTableIT.java | 541 ++++++++------------- .../relational/it/session/IoTDBObjectDeleteIT.java | 363 -------------- .../relational/it/session/IoTDBObjectInsertIT.java | 339 ------------- .../it/session/IoTDBObjectInsertIT2.java | 170 ------- iotdb-api/external-service-api/pom.xml | 60 +++ .../externalservice/api/IExternalService.java | 33 ++ iotdb-api/pom.xml | 1 + .../src/test/cpp/sessionRelationalIT.cpp | 12 +- iotdb-client/client-py/README.md | 2 + .../client-py/iotdb/tsfile/utils/tsblock_serde.py | 4 +- iotdb-client/client-py/session_example_date.py | 109 +++++ .../java/org/apache/iotdb/rpc/TSStatusCode.java | 8 + iotdb-core/ainode/build_binary.py | 54 +- iotdb-core/ainode/iotdb/ainode/core/ai_node.py | 11 +- iotdb-core/ainode/iotdb/ainode/core/constant.py | 2 +- .../ainode/core/inference/batcher/basic_batcher.py | 27 +- .../ainode/core/inference/inference_request.py | 15 +- .../core/inference/inference_request_pool.py | 2 +- .../pool_scheduler/basic_pool_scheduler.py | 11 +- .../iotdb/ainode/core/manager/inference_manager.py | 2 +- .../ainode/iotdb/ainode/core/manager/utils.py | 28 +- iotdb-core/ainode/iotdb/ainode/core/rpc/service.py | 2 +- .../org/apache/iotdb/db/qp/sql/IdentifierParser.g4 | 3 + .../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 | 24 + .../antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 | 13 + .../client/async/CnToDnAsyncRequestType.java | 3 + .../CnToDnInternalServiceAsyncRequestManager.java | 5 + .../rpc/DataNodeAsyncRequestRPCHandler.java | 9 + .../rpc/GetBuiltInExternalServiceRPCHandler.java | 82 ++++ .../confignode/conf/SystemPropertiesUtils.java | 15 - .../consensus/request/ConfigPhysicalPlan.java | 16 + .../consensus/request/ConfigPhysicalPlanType.java | 6 + .../exernalservice/ShowExternalServicePlan.java | 58 +++ .../externalservice/CreateExternalServicePlan.java | 89 ++++ .../externalservice/DropExternalServicePlan.java | 87 ++++ .../externalservice/StartExternalServicePlan.java | 87 ++++ .../externalservice/StopExternalServicePlan.java | 87 ++++ .../externalservice/ShowExternalServiceResp.java | 53 ++ .../iotdb/confignode/manager/ConfigManager.java | 64 +++ .../apache/iotdb/confignode/manager/IManager.java | 15 + .../externalservice/ExternalServiceInfo.java | 312 ++++++++++++ .../externalservice/ExternalServiceManager.java | 215 ++++++++ .../iotdb/confignode/manager/node/NodeManager.java | 13 +- .../persistence/executor/ConfigPlanExecutor.java | 22 + .../thrift/ConfigNodeRPCServiceProcessor.java | 27 + .../persistence/ExternalServiceInfoTest.java | 93 ++++ .../persistence/schema/ConfigMTreeTest.java | 58 +++ .../test/resources/oldsnapshot/cluster_schema.bin | Bin 0 -> 121 bytes .../resources/oldsnapshot/table_cluster_schema.bin | Bin 0 -> 5177 bytes iotdb-core/datanode/pom.xml | 5 + .../java/org/apache/iotdb/db/conf/IoTDBConfig.java | 9 + .../org/apache/iotdb/db/conf/IoTDBDescriptor.java | 9 - .../subtask/processor/PipeProcessorSubtask.java | 21 +- .../db/pipe/event/common/PipeInsertionEvent.java | 4 + .../common/tsfile/PipeTsFileInsertionEvent.java | 4 - .../scan/TsFileInsertionEventScanParser.java | 3 +- .../table/TsFileInsertionEventTableParser.java | 16 +- .../sink/protocol/writeback/WriteBackSink.java | 14 +- .../iotdb/db/protocol/client/ConfigNodeClient.java | 35 ++ .../iotdb/db/protocol/mqtt/MPPPublishHandler.java | 7 +- .../impl/DataNodeInternalRPCServiceImpl.java | 20 +- .../common/header/DatasetHeaderFactory.java | 4 + .../execution/operator/source/FileLoaderUtils.java | 3 +- .../execution/operator/source/SeriesScanUtil.java | 40 +- .../InformationSchemaContentSupplierFactory.java | 37 ++ .../relational/ColumnTransformerBuilder.java | 36 -- .../iotdb/db/queryengine/plan/Coordinator.java | 8 + .../execution/config/TableConfigTaskVisitor.java | 40 ++ .../execution/config/TreeConfigTaskVisitor.java | 41 ++ .../config/executor/ClusterConfigTaskExecutor.java | 114 +++++ .../config/executor/IConfigTaskExecutor.java | 10 + .../externalservice/CreateExternalServiceTask.java | 52 ++ .../externalservice/DropExternalServiceTask.java | 45 ++ .../externalservice/ShowExternalServiceTask.java | 56 +++ .../externalservice/StartExternalServiceTask.java | 33 +- .../externalservice/StopExternalServiceTask.java | 33 +- .../db/queryengine/plan/parser/ASTVisitor.java | 34 ++ .../plan/relational/analyzer/Scope.java | 3 +- .../relational/analyzer/StatementAnalyzer.java | 18 +- .../function/tvf/ClassifyTableFunction.java | 2 +- .../function/tvf/ForecastTableFunction.java | 2 +- .../relational/metadata/TableMetadataImpl.java | 18 - .../fetcher/TableDeviceSchemaValidator.java | 13 +- .../DataNodeLocationSupplierFactory.java | 1 + .../security/TreeAccessCheckVisitor.java | 42 ++ .../plan/relational/sql/ast/AstVisitor.java | 20 + .../relational/sql/ast/CreateExternalService.java | 97 ++++ .../relational/sql/ast/DropExternalService.java | 91 ++++ .../relational/sql/ast/ShowExternalService.java | 34 +- .../relational/sql/ast/StartExternalService.java | 85 ++++ .../relational/sql/ast/StopExternalService.java | 85 ++++ .../plan/relational/sql/parser/AstBuilder.java | 44 ++ .../plan/relational/sql/parser/ErrorHandler.java | 31 +- .../plan/relational/sql/rewrite/ShowRewrite.java | 15 + .../plan/relational/sql/util/AstUtil.java | 30 -- .../queryengine/plan/statement/StatementType.java | 6 + .../plan/statement/StatementVisitor.java | 31 ++ .../CreateExternalServiceStatement.java | 76 +++ .../DropExternalServiceStatement.java | 76 +++ .../ShowExternalServiceStatement.java | 68 +++ .../StartExternalServiceStatement.java | 67 +++ .../StopExternalServiceStatement.java | 67 +++ .../db/queryengine/plan/udf/UDTFForecast.java | 3 +- .../binary/ReadObject2ColumnTransformer.java | 78 --- .../ternary/ReadObject3ColumnTransformer.java | 99 ---- .../unary/scalar/ReadObjectColumnTransformer.java | 126 ----- .../schemaregion/utils/ResourceByPathUtils.java | 15 +- .../java/org/apache/iotdb/db/service/DataNode.java | 30 ++ .../db/service/ResourcesInformationHolder.java | 11 + .../externalservice/BuiltinExternalServices.java | 57 +++ .../ExternalServiceClassLoader.java | 57 +++ .../ExternalServiceManagementException.java | 30 ++ .../ExternalServiceManagementService.java | 379 +++++++++++++++ .../storageengine/dataregion/Base32ObjectPath.java | 169 ------- .../db/storageengine/dataregion/DataRegion.java | 4 +- .../db/storageengine/dataregion/IObjectPath.java | 9 +- .../storageengine/dataregion/PlainObjectPath.java | 126 ----- .../dataregion/tsfile/TsFileResource.java | 6 +- .../apache/iotdb/db/tools/TsFileSketchTool.java | 30 ++ .../org/apache/iotdb/db/utils/ObjectTypeUtils.java | 26 +- .../org/apache/iotdb/db/utils/SchemaUtils.java | 5 +- .../org/apache/iotdb/db/utils/TabletDecoder.java | 2 +- .../plan/function/RecordObjectTypeTest.java | 156 ------ .../plan/relational/planner/CteSubqueryTest.java | 2 - .../sql/ast/SqlParserErrorHandlerTest.java | 76 +++ .../unary/scalar/ObjectTypeFunctionTest.java | 186 ------- .../object/ObjectTypeCompactionTest.java | 459 ----------------- iotdb-core/node-commons/pom.xml | 5 + .../conf/iotdb-system.properties.template | 10 - .../iotdb/commons/conf/CommonDescriptor.java | 5 - .../apache/iotdb/commons/conf/IoTDBConstant.java | 1 + .../iotdb/commons/externalservice/ServiceInfo.java | 170 +++++++ .../schema/column/ColumnHeaderConstant.java | 13 + .../commons/schema/table/InformationSchema.java | 11 + .../apache/iotdb/commons/schema/table/TsTable.java | 37 +- .../relational/TableBuiltinScalarFunction.java | 1 - .../db/relational/grammar/sql/RelationalSql.g4 | 35 +- .../thrift-ainode/src/main/thrift/ainode.thrift | 2 +- .../thrift-commons/src/main/thrift/common.thrift | 14 + .../src/main/thrift/confignode.thrift | 23 + .../src/main/thrift/datanode.thrift | 5 + 151 files changed, 4690 insertions(+), 3737 deletions(-) diff --cc .gitignore index e4c7c982393,2c19b1b3a2c..3dcc328829a --- a/.gitignore +++ b/.gitignore @@@ -124,4 -124,6 +124,7 @@@ iotdb-core/tsfile/src/main/antlr4/org/a .mvn/.gradle-enterprise/ .mvn/.develocity/ .run/ +*.sevo + + # Relational Grammar ANTLR + iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/.antlr/ diff --cc integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java index c70342a8cfa,0923877d977..25666f8181f --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java @@@ -33,9 -31,6 +31,8 @@@ import org.apache.iotdb.rpc.StatementEx import org.apache.tsfile.enums.ColumnCategory; import org.apache.tsfile.enums.TSDataType; - import org.apache.tsfile.external.commons.lang3.SystemUtils; +import org.apache.tsfile.file.metadata.ColumnSchema; +import org.apache.tsfile.file.metadata.TableSchema; import org.apache.tsfile.write.record.Tablet; import org.apache.tsfile.write.schema.IMeasurementSchema; import org.apache.tsfile.write.schema.MeasurementSchema; @@@ -60,6 -52,6 +54,7 @@@ import java.util.Collections import java.util.HashSet; import java.util.List; import java.util.Set; ++import java.util.stream.Collectors; import static org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.describeTableColumnHeaders; import static org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.describeTableDetailsColumnHeaders; @@@ -1240,802 -1113,4 +1099,834 @@@ public class IoTDBTableIT } } } + + @Test + public void testAlterTableName() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + try { + statement.execute( + "CREATE TABLE IF NOT EXISTS alter_table_name_disabled () WITH (allow_alter_name=1)"); + fail("allow_alter_name must be boolean"); + } catch (SQLException e) { + assertEquals( + "701: allow_alter_name value must be a BooleanLiteral, but now is LongLiteral, value: 1", + e.getMessage()); + } + + statement.execute( + "CREATE TABLE IF NOT EXISTS alter_table_name_disabled () WITH (allow_alter_name=false)"); + + try { + statement.execute( + "ALTER TABLE alter_table_name_disabled SET PROPERTIES allow_alter_name=true"); + fail("allow_alter_name cannot be altered"); + } catch (SQLException e) { + assertEquals("701: The property allow_alter_name cannot be altered.", e.getMessage()); + } + + try { + statement.execute("ALTER TABLE alter_table_name_disabled RENAME TO alter_table_named"); + fail("the table cannot be renamed"); + } catch (SQLException e) { + assertEquals( + "701: Table 'testdb.alter_table_name_disabled' is created in a old version and cannot be renamed, please migrate its data to a new table manually", + e.getMessage()); + } + + // alter once + statement.execute("CREATE TABLE IF NOT EXISTS alter_table_name (s1 int32)"); + statement.execute("INSERT INTO alter_table_name (time, s1) VALUES (1, 1)"); + statement.execute("ALTER TABLE alter_table_name RENAME TO alter_table_named"); + try { + statement.execute("INSERT INTO alter_table_name (time, s1) VALUES (0, 0)"); + fail(); + } catch (SQLException e) { + assertEquals("550: Table 'testdb.alter_table_name' does not exist.", e.getMessage()); + } + statement.execute("INSERT INTO alter_table_named (time, s1) VALUES (2, 2)"); + + ResultSet resultSet = statement.executeQuery("SELECT * FROM alter_table_named"); + assertTrue(resultSet.next()); + assertEquals(1, resultSet.getLong(1)); + assertEquals(1, resultSet.getLong(2)); + assertTrue(resultSet.next()); + assertEquals(2, resultSet.getLong(1)); + assertEquals(2, resultSet.getLong(2)); + assertFalse(resultSet.next()); + + // alter twice + statement.execute("ALTER TABLE alter_table_named RENAME TO alter_table_named2"); + try { + statement.execute("INSERT INTO alter_table_named (time, s1) VALUES (0, 0)"); + fail(); + } catch (SQLException e) { + assertEquals("550: Table 'testdb.alter_table_named' does not exist.", e.getMessage()); + } + statement.execute("INSERT INTO alter_table_named2 (time, s1) VALUES (3, 3)"); + + resultSet = statement.executeQuery("SELECT * FROM alter_table_named2"); + for (int i = 1; i <= 3; i++) { + assertTrue(resultSet.next()); + assertEquals(i, resultSet.getLong(1)); + assertEquals(i, resultSet.getLong(2)); + } + assertFalse(resultSet.next()); + + // alter back + statement.execute("ALTER TABLE alter_table_named2 RENAME TO alter_table_name"); + try { + statement.execute("INSERT INTO alter_table_named2 (time, s1) VALUES (0, 0)"); + fail(); + } catch (SQLException e) { + assertEquals("550: Table 'testdb.alter_table_named2' does not exist.", e.getMessage()); + } + statement.execute("INSERT INTO alter_table_name (time, s1) VALUES (4, 4)"); + + resultSet = statement.executeQuery("SELECT * FROM alter_table_name"); + for (int i = 1; i <= 4; i++) { + assertTrue(resultSet.next()); + assertEquals(i, resultSet.getLong(1)); + assertEquals(i, resultSet.getLong(2)); + } + assertFalse(resultSet.next()); + } + } + + @Test + public void testAlterColumnName() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + statement.execute("CREATE TABLE IF NOT EXISTS alter_column_name (s1 int32)"); + statement.execute("INSERT INTO alter_column_name (time, s1) VALUES (1, 1)"); + // alter once + statement.execute("ALTER TABLE alter_column_name RENAME COLUMN s1 TO s2"); + try { + statement.execute("INSERT INTO alter_column_name (time, s1) VALUES (0, 0)"); + fail(); + } catch (SQLException e) { + assertEquals( + "616: Unknown column category for s1. Cannot auto create column.", e.getMessage()); + } + statement.execute("INSERT INTO alter_column_name (time, s2) VALUES (2, 2)"); + + ResultSet resultSet = statement.executeQuery("SELECT * FROM alter_column_name"); + ResultSetMetaData metaData = resultSet.getMetaData(); + assertEquals(2, metaData.getColumnCount()); + assertEquals("s2", metaData.getColumnName(2)); + + for (int i = 1; i <= 2; i++) { + assertTrue(resultSet.next()); + assertEquals(i, resultSet.getLong(1)); + assertEquals(i, resultSet.getInt(2)); + } + assertFalse(resultSet.next()); + } + } + + @Test + public void testTableRenameConflict() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + statement.execute("CREATE TABLE IF NOT EXISTS table_a ()"); + statement.execute("CREATE TABLE IF NOT EXISTS table_b ()"); + + try { + statement.execute("ALTER TABLE table_a RENAME TO table_b"); + fail(); + } catch (final SQLException e) { + // expect table already exists (use code 551) + assertTrue( + e.getMessage().startsWith("551") && e.getMessage().toLowerCase().contains("already")); + } + } + } + + @Test + public void testColumnRenameConflict() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + statement.execute("CREATE TABLE IF NOT EXISTS tconf (c1 int32, c2 int32)"); + + try { + statement.execute("ALTER TABLE tconf RENAME COLUMN c1 TO c2"); + fail(); + } catch (final SQLException e) { + // expect column already exist error (code 552) + assertTrue( + e.getMessage().startsWith("552") && e.getMessage().toLowerCase().contains("exist")); + } + } + } + + @Test + public void testAlterTableRenameToSameName() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + statement.execute("CREATE TABLE IF NOT EXISTS rename_same (s1 int32)"); + statement.execute("INSERT INTO rename_same (time, s1) VALUES (1, 1)"); + + // Renaming to the same name should be a no-op and not lose data + try { + statement.execute("ALTER TABLE rename_same RENAME TO rename_same"); + fail(); + } catch (SQLException e) { + assertEquals( + "701: The table's old name shall not be equal to the new one.", e.getMessage()); + } + } + } + + @Test + public void testAlterTableRenameToQuotedSpecialName() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + statement.execute("CREATE TABLE IF NOT EXISTS rename_special (s1 int32)"); + statement.execute("INSERT INTO rename_special (time, s1) VALUES (1, 1)"); + + // rename to a quoted name containing hyphen and unicode + statement.execute("ALTER TABLE rename_special RENAME TO \"rename-特殊\""); + + // old name should not exist + try { + statement.execute("INSERT INTO rename_special (time, s1) VALUES (2, 2)"); + fail(); + } catch (final SQLException e) { + assertTrue( + e.getMessage().startsWith("550") + || e.getMessage().toLowerCase().contains("does not exist")); + } + + // insert into new quoted name and verify + statement.execute("INSERT INTO \"rename-特殊\" (time, s1) VALUES (2, 2)"); + ResultSet rs = statement.executeQuery("SELECT * FROM \"rename-特殊\""); + for (int i = 1; i <= 2; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getLong(1)); + assertEquals(i, rs.getInt(2)); + } + assertFalse(rs.next()); + } + } + + @Test + public void testAlterTableRenameWithDots() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS db1"); + statement.execute("DROP DATABASE IF EXISTS db2"); + statement.execute("CREATE DATABASE IF NOT EXISTS db1"); + statement.execute("CREATE DATABASE IF NOT EXISTS db2"); + statement.execute("USE db1"); + + statement.execute("CREATE TABLE IF NOT EXISTS t1 (s1 int32)"); + statement.execute("INSERT INTO t1 (time, s1) VALUES (1, 1)"); + + statement.execute("ALTER TABLE t1 RENAME TO \"db2.t1\""); + + ResultSet rs = statement.executeQuery("SELECT * FROM \"db2.t1\""); + assertTrue(rs.next()); + assertEquals(1, rs.getLong(1)); + assertEquals(1, rs.getInt(2)); + assertFalse(rs.next()); + } + } + + @Test + public void testAlterColumnRenameCaseSensitivity() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + statement.execute("CREATE TABLE IF NOT EXISTS tcase (c1 int32)"); + statement.execute("INSERT INTO tcase (time, c1) VALUES (1, 1)"); + + statement.execute("ALTER TABLE tcase RENAME COLUMN c1 TO C1"); + + ResultSet rs = statement.executeQuery("SELECT * FROM tcase"); + ResultSetMetaData md = rs.getMetaData(); + assertEquals(2, md.getColumnCount()); + // server may normalize column names; accept either exact case or normalized lower-case + String colName = md.getColumnName(2); + assertTrue(colName.equals("C1") || colName.equals("c1")); + + // ensure data still accessible via the new identifier (try using the new name in insert) + try { + statement.execute("INSERT INTO tcase (time, c1) VALUES (2, 2)"); + // if server treats identifiers case-insensitively this may succeed + } catch (final SQLException ignored) { + // ignore - the purpose is to assert existence/behavior, not enforce one model here + } + } + } + + @Test + public void testAlterColumnRenameToQuotedSpecialChars() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + statement.execute("CREATE TABLE IF NOT EXISTS tcolspecial (s1 int32)"); + statement.execute("INSERT INTO tcolspecial (time, s1) VALUES (1, 1)"); + + statement.execute("ALTER TABLE tcolspecial RENAME COLUMN s1 TO \"s-特\""); + + try { + statement.execute("INSERT INTO tcolspecial (time, s1) VALUES (2, 2)"); + fail(); + } catch (final SQLException e) { + assertTrue( + e.getMessage().startsWith("616") || e.getMessage().toLowerCase().contains("unknown")); + } + + statement.execute("INSERT INTO tcolspecial (time, \"s-特\") VALUES (2, 2)"); + ResultSet rs = statement.executeQuery("SELECT * FROM tcolspecial"); + ResultSetMetaData md = rs.getMetaData(); + assertEquals(2, md.getColumnCount()); + String colName = md.getColumnName(2); + // accept either exact quoted name or normalized variant + assertTrue(colName.equals("s-特") || colName.equals("s特") || colName.equals("s_特")); + } + } + + @Test + public void testAlterColumnMultipleRenamesAndBack() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + statement.execute("CREATE TABLE IF NOT EXISTS tmulti (a int32)"); + statement.execute("INSERT INTO tmulti (time, a) VALUES (1, 1)"); + + statement.execute("ALTER TABLE tmulti RENAME COLUMN a TO b"); + statement.execute("INSERT INTO tmulti (time, b) VALUES (2, 2)"); + + statement.execute("ALTER TABLE tmulti RENAME COLUMN b TO c"); + statement.execute("INSERT INTO tmulti (time, c) VALUES (3, 3)"); + + statement.execute("ALTER TABLE tmulti RENAME COLUMN c TO a"); + statement.execute("INSERT INTO tmulti (time, a) VALUES (4, 4)"); + + ResultSet rs = statement.executeQuery("SELECT * FROM tmulti"); + for (int i = 1; i <= 4; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getLong(1)); + assertEquals(i, rs.getInt(2)); + } + assertFalse(rs.next()); + } + } + + @Test + public void testRenameNonExistentColumn() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + statement.execute("CREATE TABLE IF NOT EXISTS tnonexist (x int32)"); + + try { + statement.execute("ALTER TABLE tnonexist RENAME COLUMN y TO z"); + fail(); + } catch (final SQLException e) { + // error should indicate column does not exist (use code 616 + contains) + assertTrue(e.getMessage().startsWith("616")); + assertTrue( + e.getMessage().toLowerCase().contains("does not exist") + || e.getMessage().toLowerCase().contains("cannot be resolved")); + } + } + } + + @Test + public void testRenameTimeColumnForbidden() throws Exception { + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + statement.execute("DROP DATABASE IF EXISTS testdb"); + statement.execute("CREATE DATABASE IF NOT EXISTS testdb"); + statement.execute("USE testdb"); + + // create a table with explicit time column + statement.execute("CREATE TABLE IF NOT EXISTS ttime (time TIMESTAMP TIME, a INT32)"); + + try { + statement.execute("ALTER TABLE ttime RENAME COLUMN time TO newtime"); + fail(); + } catch (final SQLException e) { + // renaming time column should be forbidden (code 701 or similar) + assertTrue( + (e.getMessage().startsWith("701") && e.getMessage().toLowerCase().contains("time"))); + } + } + } + - // Helper: recognize SQLExceptions that mean the target table/device cannot be found. - private static boolean isTableNotFound(final SQLException e) { - if (e == null) return false; - final String msg = e.getMessage(); - if (msg == null) return false; - final String lm = msg.toLowerCase(); - // code 550 is commonly used for 'does not exist' in this project; also match textual phrases - return msg.startsWith("550") || lm.contains("not exist"); - } ++ // Helper: recognize SQLExceptions that mean the target table/device cannot be found. ++ private static boolean isTableNotFound(final SQLException e) { ++ if (e == null) return false; ++ final String msg = e.getMessage(); ++ if (msg == null) return false; ++ final String lm = msg.toLowerCase(); ++ // code 550 is commonly used for 'does not exist' in this project; also match textual phrases ++ return msg.startsWith("550") || lm.contains("not exist"); ++ } + - @Test(timeout = 120000) - @SuppressWarnings("resource") - public void testConcurrentRenameVsQueries() throws Throwable { - try (final Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); ++ @Test(timeout = 120000) ++ @SuppressWarnings("resource") ++ public void testConcurrentRenameVsQueries() throws Throwable { ++ try (final Connection connection = ++ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement stmt = connection.createStatement()) { + final String db = "concrenamedb"; + final int tableCount = 6; + final int rows = 50; + stmt.execute("DROP DATABASE IF EXISTS " + db); + stmt.execute("CREATE DATABASE IF NOT EXISTS " + db); + stmt.execute("USE " + db); + + final String[] names = new String[tableCount]; + for (int i = 0; i < tableCount; i++) { + names[i] = "crtable" + i; + stmt.execute(String.format("CREATE TABLE IF NOT EXISTS %s (v int32)", names[i])); + for (int r = 1; r <= rows; r++) { + stmt.execute(String.format("INSERT INTO %s (time, v) VALUES (%d, %d)", names[i], r, r)); + } + } + - final java.util.concurrent.atomic.AtomicReference<Throwable> err = new java.util.concurrent.atomic.AtomicReference<>(); - final java.util.concurrent.CountDownLatch startLatch = new java.util.concurrent.CountDownLatch(1); - final java.util.concurrent.CountDownLatch doneLatch = new java.util.concurrent.CountDownLatch(4); ++ final java.util.concurrent.atomic.AtomicReference<Throwable> err = ++ new java.util.concurrent.atomic.AtomicReference<>(); ++ final java.util.concurrent.CountDownLatch startLatch = ++ new java.util.concurrent.CountDownLatch(1); ++ final java.util.concurrent.CountDownLatch doneLatch = ++ new java.util.concurrent.CountDownLatch(4); + + java.util.concurrent.ExecutorService exec = null; + try { + exec = java.util.concurrent.Executors.newFixedThreadPool(8); + + // Renamer task: rotate rename a subset of tables repeatedly - exec.submit(() -> { - try (final Connection c = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); - final Statement s = c.createStatement()) { - startLatch.await(); - // ensure this thread's connection uses the test database - try { - s.execute("USE " + db); - } catch (final SQLException ignore) { - } - for (int round = 0; round < 20 && err.get() == null; round++) { - for (int i = 0; i < tableCount / 2; i++) { - final String oldName = names[i]; - final String newName = oldName + "_r" + round; ++ exec.submit( ++ () -> { ++ try (final Connection c = ++ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); ++ final Statement s = c.createStatement()) { ++ startLatch.await(); ++ // ensure this thread's connection uses the test database + try { - s.execute(String.format("ALTER TABLE %s RENAME TO %s", oldName, newName)); - // reflect change locally so queries target updated names - names[i] = newName; - } catch (final SQLException ex) { - // Only ignore if the failure is due to table not existing; otherwise record the error - if (isTableNotFound(ex)) { - // table not found: likely a transient race with concurrent rename — ignore and log - System.out.println("Ignored table-not-found during rename: " + ex.getMessage()); - } else { - err.compareAndSet(null, ex); ++ s.execute("USE " + db); ++ } catch (final SQLException ignore) { ++ } ++ for (int round = 0; round < 20 && err.get() == null; round++) { ++ for (int i = 0; i < tableCount / 2; i++) { ++ final String oldName = names[i]; ++ final String newName = oldName + "_r" + round; ++ try { ++ s.execute(String.format("ALTER TABLE %s RENAME TO %s", oldName, newName)); ++ // reflect change locally so queries target updated names ++ names[i] = newName; ++ } catch (final SQLException ex) { ++ // Only ignore if the failure is due to table not existing; otherwise record ++ // the error ++ if (isTableNotFound(ex)) { ++ // table not found: likely a transient race with concurrent rename — ignore ++ // and log ++ System.out.println( ++ "Ignored table-not-found during rename: " + ex.getMessage()); ++ } else { ++ err.compareAndSet(null, ex); ++ } ++ } ++ } ++ try { ++ Thread.sleep(50); ++ } catch (final InterruptedException ie) { ++ Thread.currentThread().interrupt(); ++ break; + } + } ++ } catch (final Throwable t) { ++ err.compareAndSet(null, t); ++ } finally { ++ doneLatch.countDown(); + } - try { - Thread.sleep(50); - } catch (final InterruptedException ie) { - Thread.currentThread().interrupt(); - break; - } - } - } catch (final Throwable t) { - err.compareAndSet(null, t); - } finally { - doneLatch.countDown(); - } - }); ++ }); + + // Queryer tasks: continuously query random tables + for (int q = 0; q < 2; q++) { - exec.submit(() -> { - try (final Connection c = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); - final Statement s = c.createStatement()) { - final java.util.Random rnd = new java.util.Random(); - startLatch.await(); - // ensure this thread's connection uses the test database - try { - s.execute("USE " + db); - } catch (final SQLException ignore) { - } - for (int iter = 0; iter < 200 && err.get() == null; iter++) { - final int idx = rnd.nextInt(tableCount); - final String tname = names[idx]; - try (final ResultSet rs = s.executeQuery("SELECT count(*) FROM " + tname)) { - if (rs.next()) { - rs.getLong(1); ++ exec.submit( ++ () -> { ++ try (final Connection c = ++ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); ++ final Statement s = c.createStatement()) { ++ final java.util.Random rnd = new java.util.Random(); ++ startLatch.await(); ++ // ensure this thread's connection uses the test database ++ try { ++ s.execute("USE " + db); ++ } catch (final SQLException ignore) { + } - } catch (final SQLException ex) { - // Only ignore table-not-found; otherwise surface the error to fail the test - if (!isTableNotFound(ex)) { - err.compareAndSet(null, ex); - break; ++ for (int iter = 0; iter < 200 && err.get() == null; iter++) { ++ final int idx = rnd.nextInt(tableCount); ++ final String tname = names[idx]; ++ try (final ResultSet rs = s.executeQuery("SELECT count(*) FROM " + tname)) { ++ if (rs.next()) { ++ rs.getLong(1); ++ } ++ } catch (final SQLException ex) { ++ // Only ignore table-not-found; otherwise surface the error to fail the test ++ if (!isTableNotFound(ex)) { ++ err.compareAndSet(null, ex); ++ break; ++ } ++ } + } ++ } catch (final Throwable t) { ++ err.compareAndSet(null, t); ++ } finally { ++ doneLatch.countDown(); + } - } - } catch (final Throwable t) { - err.compareAndSet(null, t); - } finally { - doneLatch.countDown(); - } - }); ++ }); + } + + // Another queryer to trigger more parallel access - exec.submit(() -> { - try (final Connection c = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); - final Statement s = c.createStatement()) { - startLatch.await(); - // ensure this thread's connection uses the test database - try { - s.execute("USE " + db); - } catch (final SQLException ignore) { - } - for (int iter = 0; iter < 200 && err.get() == null; iter++) { - for (int i = 0; i < tableCount; i++) { - try (final ResultSet rs = s.executeQuery("SELECT * FROM " + names[i] + " LIMIT 1")) { - // consume - while (rs.next()) { - rs.getLong(1); - } - } catch (final SQLException ex) { - if (!isTableNotFound(ex)) { - err.compareAndSet(null, ex); - break; ++ exec.submit( ++ () -> { ++ try (final Connection c = ++ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); ++ final Statement s = c.createStatement()) { ++ startLatch.await(); ++ // ensure this thread's connection uses the test database ++ try { ++ s.execute("USE " + db); ++ } catch (final SQLException ignore) { ++ } ++ for (int iter = 0; iter < 200 && err.get() == null; iter++) { ++ for (int i = 0; i < tableCount; i++) { ++ try (final ResultSet rs = ++ s.executeQuery("SELECT * FROM " + names[i] + " LIMIT 1")) { ++ // consume ++ while (rs.next()) { ++ rs.getLong(1); ++ } ++ } catch (final SQLException ex) { ++ if (!isTableNotFound(ex)) { ++ err.compareAndSet(null, ex); ++ break; ++ } ++ } + } + } ++ } catch (final Throwable t) { ++ err.compareAndSet(null, t); ++ } finally { ++ doneLatch.countDown(); + } - } - } catch (final Throwable t) { - err.compareAndSet(null, t); - } finally { - doneLatch.countDown(); - } - }); ++ }); + + // start + startLatch.countDown(); + // wait for tasks + doneLatch.await(); + + if (err.get() != null) { + throw err.get(); + } + } finally { + if (exec != null) { + exec.shutdownNow(); + } + } + } - } ++ } + - @Test - public void testMultiTableCrossCheckAfterRenames() throws Exception { - try (final Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); ++ @Test ++ public void testMultiTableCrossCheckAfterRenames() throws Exception { ++ try (final Connection connection = ++ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement stmt = connection.createStatement()) { + final String db = "multicheckdb"; + stmt.execute("DROP DATABASE IF EXISTS " + db); + stmt.execute("CREATE DATABASE IF NOT EXISTS " + db); + stmt.execute("USE " + db); + + // create two related tables + stmt.execute("CREATE TABLE IF NOT EXISTS mta (k int32)"); + stmt.execute("CREATE TABLE IF NOT EXISTS mtb (k int32)"); + + for (int i = 1; i <= 10; i++) { + stmt.execute(String.format("INSERT INTO mta (time, k) VALUES (%d, %d)", i, i)); + stmt.execute(String.format("INSERT INTO mtb (time, k) VALUES (%d, %d)", i, i)); + } + + // baseline: read aggregates + long aCount = 0, bCount = 0; + try (final ResultSet ra = stmt.executeQuery("SELECT count(*) FROM mta")) { - if (ra.next()) { ++ if (ra.next()) { + aCount = ra.getLong(1); + } + } + try (final ResultSet rb = stmt.executeQuery("SELECT count(*) FROM mtb")) { - if (rb.next()) { ++ if (rb.next()) { + bCount = rb.getLong(1); + } + } + + // rename one table and verify cross results remain consistent when queried separately + stmt.execute("ALTER TABLE mtb RENAME TO mtb_renamed"); + + long bCountAfter = 0; + try (final ResultSet rb2 = stmt.executeQuery("SELECT count(*) FROM mtb_renamed")) { + if (rb2.next()) { + bCountAfter = rb2.getLong(1); + } + } + + // assert counts unchanged + assertEquals(bCount, bCountAfter); + assertEquals(10, aCount); + + // rename the other table and verify again + stmt.execute("ALTER TABLE mta RENAME TO mta_renamed"); + long aCountAfter = 0; + try (final ResultSet ra2 = stmt.executeQuery("SELECT count(*) FROM mta_renamed")) { + if (ra2.next()) { + aCountAfter = ra2.getLong(1); + } + } + assertEquals(aCount, aCountAfter); + } - } ++ } + - @Test - public void testPerformanceWithQuotedSpecialNameRenames() throws Exception { - try (final Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); ++ @Test ++ public void testPerformanceWithQuotedSpecialNameRenames() throws Exception { ++ try (final Connection connection = ++ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement stmt = connection.createStatement(); + final ITableSession session = EnvFactory.getEnv().getTableSessionConnection()) { + final String db = "perfquotedb"; - final int tables = 3200; ++ final int colPerTable = 1; ++ final int tables = 8; + final int rows = 100; + final int numFile = 5; - final int colPerTable = 100; + stmt.execute("DROP DATABASE IF EXISTS " + db); + stmt.execute("CREATE DATABASE IF NOT EXISTS " + db); + stmt.execute("USE " + db); ++ //stmt.execute("set configuration enable_seq_space_compaction='false'"); + session.executeNonQueryStatement("USE " + db); + + final String[] names = new String[tables]; + StringBuilder createTableTemplate = new StringBuilder("CREATE TABLE IF NOT EXISTS %s ("); + for (int c = 0; c < colPerTable; c++) { + createTableTemplate.append(String.format("v%d int32,", c)); + } - createTableTemplate = new StringBuilder( - createTableTemplate.substring(0, createTableTemplate.length() - 1) + ")"); ++ createTableTemplate = ++ new StringBuilder( ++ createTableTemplate.substring(0, createTableTemplate.length() - 1) + ")"); + List<ColumnSchema> columns = new ArrayList<>(); + for (int i = 0; i < colPerTable; i++) { + columns.add(new ColumnSchema("v" + i, TSDataType.INT32, ColumnCategory.FIELD)); + } - TableSchema tableSchema = new TableSchema( - "", // place holder - columns - ); ++ TableSchema tableSchema = ++ new TableSchema( ++ "", // place holder ++ columns); + + System.out.println("Start data preparation..."); + for (int i = 0; i < tables; i++) { + names[i] = "qtable" + i; + stmt.execute(String.format(createTableTemplate.toString(), names[i])); + tableSchema.setTableName(names[i]); - Tablet tablet = new Tablet(tableSchema.getTableName(), tableSchema.getColumnSchemas().stream().map(IMeasurementSchema::getMeasurementName).collect( - Collectors.toList()), tableSchema.getColumnSchemas().stream().map(IMeasurementSchema::getType).collect( - Collectors.toList()), tableSchema.getColumnTypes(), rows); ++ Tablet tablet = ++ new Tablet( ++ tableSchema.getTableName(), ++ tableSchema.getColumnSchemas().stream() ++ .map(IMeasurementSchema::getMeasurementName) ++ .collect(Collectors.toList()), ++ tableSchema.getColumnSchemas().stream() ++ .map(IMeasurementSchema::getType) ++ .collect(Collectors.toList()), ++ tableSchema.getColumnTypes(), ++ rows); + for (int j = 0; j < numFile; j++) { + tablet.reset(); + for (int r = 1; r <= rows; r++) { + tablet.addTimestamp(r - 1, r + j * rows); + for (int c = 0; c < colPerTable; c++) { + tablet.addValue(r - 1, c, r + j * rows); + } + } + session.insert(tablet); + stmt.execute("FLUSH"); + } + } + System.out.println("Data preparation done."); + + // baseline measurement: simple average over a few runs + final int runs = 100; + double totalMs = 0.0; + for (int run = 0; run < runs; run++) { + final long start = System.nanoTime(); + for (int i = 0; i < tables; i++) { + try (final ResultSet rs = stmt.executeQuery("SELECT count(*) FROM " + names[i])) { + if (rs.next()) { + rs.getLong(1); + } + } + } + final long end = System.nanoTime(); + if (run > runs * 0.1) { + totalMs += (end - start) / 1_000_000.0; + } + } + final double baseline = totalMs / (runs * 0.9); + System.out.println("baseline_total_ms=" + String.format("%.3f", baseline)); + + // rename half of them to quoted special names and measure again + for (int i = 0; i < tables / 2; i++) { + final String oldName = names[i]; + final String newName = "\"" + oldName + "-特\""; // quoted name + stmt.execute(String.format("ALTER TABLE %s RENAME TO %s", oldName, newName)); + names[i] = newName; + } + + totalMs = 0.0; + for (int run = 0; run < runs; run++) { + final long start = System.nanoTime(); + for (int i = 0; i < tables; i++) { + try (final ResultSet rs = stmt.executeQuery("SELECT count(*) FROM " + names[i])) { + if (rs.next()) { + rs.getLong(1); + } + } + } + final long end = System.nanoTime(); + if (run > runs * 0.1) { + totalMs += (end - start) / 1_000_000.0; + } + } + final double after = totalMs / (runs * 0.9); + System.out.println("after_quoted_total_ms=" + String.format("%.3f", after)); + + // basic sanity: ensure queries still return counts + for (int i = 0; i < tables; i++) { + try (final ResultSet rs = stmt.executeQuery("SELECT count(*) FROM " + names[i])) { + assertTrue(rs.next()); + assertEquals(rows * numFile, rs.getLong(1)); + } + } + } - } ++ } + - @Test - public void testAlterTableAndColumnTogether() throws Exception { - try (final Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); - final Statement stmt = connection.createStatement()) { - final String db = "dualalterdb"; - stmt.execute("DROP DATABASE IF EXISTS " + db); - stmt.execute("CREATE DATABASE IF NOT EXISTS " + db); - stmt.execute("USE " + db); - - stmt.execute("CREATE TABLE IF NOT EXISTS tab1 (c1 int32, c2 int32)"); - stmt.execute("INSERT INTO tab1 (time, c1, c2) VALUES (1, 1, 10)"); - - // rename column first and then rename table - stmt.execute("ALTER TABLE tab1 RENAME COLUMN c1 TO c1_new"); - stmt.execute("ALTER TABLE tab1 RENAME TO tab1_new"); - - // old table name should not exist - try { - stmt.execute("INSERT INTO tab1 (time, c1_new) VALUES (2, 2)"); - fail(); - } catch (final SQLException e) { - assertTrue(e.getMessage().startsWith("550") || e.getMessage().toLowerCase().contains("does not exist")); - } ++ @Test ++ public void testAlterTableAndColumnTogether() throws Exception { ++ try (final Connection connection = ++ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); ++ final Statement stmt = connection.createStatement()) { ++ final String db = "dualalterdb"; ++ stmt.execute("DROP DATABASE IF EXISTS " + db); ++ stmt.execute("CREATE DATABASE IF NOT EXISTS " + db); ++ stmt.execute("USE " + db); + - // inserting using new table and new column names should succeed - stmt.execute("INSERT INTO tab1_new (time, c1_new, c2) VALUES (2, 2, 20)"); ++ stmt.execute("CREATE TABLE IF NOT EXISTS tab1 (c1 int32, c2 int32)"); ++ stmt.execute("INSERT INTO tab1 (time, c1, c2) VALUES (1, 1, 10)"); + - // verify data - try (final ResultSet rs = stmt.executeQuery("SELECT * FROM tab1_new ORDER BY time")) { - assertTrue(rs.next()); - assertEquals(1, rs.getLong(1)); - assertEquals(1, rs.getInt("c1_new")); - assertEquals(10, rs.getInt("c2")); ++ // rename column first and then rename table ++ stmt.execute("ALTER TABLE tab1 RENAME COLUMN c1 TO c1_new"); ++ stmt.execute("ALTER TABLE tab1 RENAME TO tab1_new"); + - assertTrue(rs.next()); - assertEquals(2, rs.getLong(1)); - assertEquals(2, rs.getInt("c1_new")); - assertEquals(20, rs.getInt("c2")); ++ // old table name should not exist ++ try { ++ stmt.execute("INSERT INTO tab1 (time, c1_new) VALUES (2, 2)"); ++ fail(); ++ } catch (final SQLException e) { ++ assertTrue( ++ e.getMessage().startsWith("550") ++ || e.getMessage().toLowerCase().contains("does not exist")); ++ } + - assertFalse(rs.next()); - } ++ // inserting using new table and new column names should succeed ++ stmt.execute("INSERT INTO tab1_new (time, c1_new, c2) VALUES (2, 2, 20)"); + - // rename column again on the renamed table and verify - stmt.execute("ALTER TABLE tab1_new RENAME COLUMN c1_new TO c1_final"); - try { - // old column identifier should fail - stmt.execute("INSERT INTO tab1_new (time, c1_new) VALUES (3, 3)"); - fail(); - } catch (final SQLException e) { - assertTrue(e.getMessage().startsWith("616") || e.getMessage().toLowerCase().contains("unknown") || e.getMessage().toLowerCase().contains("cannot be resolved")); - } ++ // verify data ++ try (final ResultSet rs = stmt.executeQuery("SELECT * FROM tab1_new ORDER BY time")) { ++ assertTrue(rs.next()); ++ assertEquals(1, rs.getLong(1)); ++ assertEquals(1, rs.getInt("c1_new")); ++ assertEquals(10, rs.getInt("c2")); + - // use final name - stmt.execute("INSERT INTO tab1_new (time, c1_final, c2) VALUES (3, 3, 30)"); - try (final ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1_new")) { - if (rs.next()) { - assertEquals(3L, rs.getLong(1)); - } else { - fail(); - } ++ assertTrue(rs.next()); ++ assertEquals(2, rs.getLong(1)); ++ assertEquals(2, rs.getInt("c1_new")); ++ assertEquals(20, rs.getInt("c2")); ++ ++ assertFalse(rs.next()); ++ } ++ ++ // rename column again on the renamed table and verify ++ stmt.execute("ALTER TABLE tab1_new RENAME COLUMN c1_new TO c1_final"); ++ try { ++ // old column identifier should fail ++ stmt.execute("INSERT INTO tab1_new (time, c1_new) VALUES (3, 3)"); ++ fail(); ++ } catch (final SQLException e) { ++ assertTrue( ++ e.getMessage().startsWith("616") ++ || e.getMessage().toLowerCase().contains("unknown") ++ || e.getMessage().toLowerCase().contains("cannot be resolved")); ++ } ++ ++ // use final name ++ stmt.execute("INSERT INTO tab1_new (time, c1_final, c2) VALUES (3, 3, 30)"); ++ try (final ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1_new")) { ++ if (rs.next()) { ++ assertEquals(3L, rs.getLong(1)); ++ } else { ++ fail(); + } + } + } ++ } } diff --cc iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java index d85caa47dbc,e51d1a43299..582ffd16220 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java @@@ -111,9 -108,10 +112,11 @@@ import org.apache.iotdb.confignode.cons import org.apache.iotdb.confignode.consensus.response.template.TemplateSetInfoResp; import org.apache.iotdb.confignode.consensus.response.ttl.ShowTTLResp; import org.apache.iotdb.confignode.consensus.statemachine.ConfigRegionStateMachine; +import org.apache.iotdb.confignode.exception.DatabaseNotExistsException; import org.apache.iotdb.confignode.manager.consensus.ConsensusManager; import org.apache.iotdb.confignode.manager.cq.CQManager; + import org.apache.iotdb.confignode.manager.externalservice.ExternalServiceInfo; + import org.apache.iotdb.confignode.manager.externalservice.ExternalServiceManager; import org.apache.iotdb.confignode.manager.load.LoadManager; import org.apache.iotdb.confignode.manager.load.cache.node.NodeHeartbeatSample; import org.apache.iotdb.confignode.manager.node.ClusterNodeStartUtils; diff --cc iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java index ec8b73d1c4f,70f3ce5e0e6..dc79dc560cc --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/FileLoaderUtils.java @@@ -124,8 -116,10 +124,9 @@@ public class FileLoaderUtils SchemaUtils.changeMetadataModified(timeSeriesMetadata, seriesPath.getSeriesType()); long t2 = System.nanoTime(); List<ModEntry> pathModifications = - context.getPathModifications( - resource, seriesPath.getDeviceId(), seriesPath.getMeasurement()); + context.getPathModifications(resource, deviceId, measurement); - timeSeriesMetadata.setModified(!pathModifications.isEmpty()); + timeSeriesMetadata.setModified( + timeSeriesMetadata.isModified() || !pathModifications.isEmpty()); timeSeriesMetadata.setChunkMetadataLoader( new DiskChunkMetadataLoader(resource, context, globalTimeFilter, pathModifications)); int modificationCount = pathModifications.size(); diff --cc iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java index 7bbf1964d90,b8101fbd443..ebcfd0c040a --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java @@@ -3496,13 -3249,17 +3496,15 @@@ public class DataRegion implements IDat continue; } - // the tsfile may not be closed here, it should not be added in deletedByFiles - if (!sealedTsFile.isClosed()) { - deletedByMods.add(sealedTsFile); - continue; - } - ITimeIndex timeIndex = sealedTsFile.getTimeIndex(); + EvolvedSchema evolvedSchema = sealedTsFile.getMergedEvolvedSchema(); ++ // the tsfile may not be closed here, it should not be added in deletedByFiles if ((timeIndex instanceof ArrayDeviceTimeIndex) - && (deletion.getType() == ModType.TABLE_DELETION)) { - && (deletion.getType() == ModEntry.ModType.TABLE_DELETION)) { ++ && (deletion.getType() == ModType.TABLE_DELETION) ++ && sealedTsFile.isClosed()) { ArrayDeviceTimeIndex deviceTimeIndex = (ArrayDeviceTimeIndex) timeIndex; + Set<IDeviceID> devicesInFile = deviceTimeIndex.getDevices(); boolean onlyOneTable = false;
