This is an automated email from the ASF dual-hosted git repository.
Caideyipi pushed a commit to branch 1.3-mutli
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/1.3-mutli by this push:
new 5dbdde5b70f Fixed the missing time column with non-default name & INF
without '' when explicitly defined in show create && the bug that the db ttl is
non-default for replacing tree views && Pipe: NPE of Deletion Sync & failed
logic for compressing progressReportEvent (#17457)
5dbdde5b70f is described below
commit 5dbdde5b70fb8f54c18946e5fdce2b611b43eafb
Author: Caideyipi <[email protected]>
AuthorDate: Mon Apr 27 10:47:10 2026 +0800
Fixed the missing time column with non-default name & INF without '' when
explicitly defined in show create && the bug that the db ttl is non-default for
replacing tree views && Pipe: NPE of Deletion Sync & failed logic for
compressing progressReportEvent (#17457)
---
.../relational/it/schema/IoTDBDatabaseIT.java | 909 ++++++++++++++++
.../iotdb/relational/it/schema/IoTDBTableIT.java | 1117 ++++++++++++++++++++
.../table/view/CreateTableViewProcedure.java | 189 ++++
.../realtime/PipeRealtimeDataRegionSource.java | 7 +-
.../metadata/relational/ShowCreateTableTask.java | 157 +++
.../metadata/relational/ShowCreateViewTask.java | 162 +++
.../apache/iotdb/commons/schema/table/TsTable.java | 431 ++++++++
.../schema/table/column/TsTableColumnSchema.java | 112 ++
8 files changed, 3082 insertions(+), 2 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
new file mode 100644
index 00000000000..2b367b70b22
--- /dev/null
+++
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
@@ -0,0 +1,909 @@
+/*
+ * 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.relational.it.schema;
+
+import org.apache.iotdb.db.it.utils.TestUtils;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+import org.apache.iotdb.itbase.env.BaseEnv;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static
org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.showDBColumnHeaders;
+import static
org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.showDBDetailsColumnHeaders;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBDatabaseIT {
+
+ @Before
+ public void setUp() throws Exception {
+ EnvFactory.getEnv()
+ .getConfig()
+ .getCommonConfig()
+ .setEnforceStrongPassword(false)
+ .setPipeMemoryManagementEnabled(false)
+ .setIsPipeEnableMemoryCheck(false)
+ .setPipeAutoSplitFullEnabled(false);
+ // enable subscription
+ EnvFactory.getEnv()
+ .getConfig()
+ .getCommonConfig()
+ .setPipeMemoryManagementEnabled(false)
+ .setIsPipeEnableMemoryCheck(false)
+ .setPipeAutoSplitFullEnabled(false);
+ EnvFactory.getEnv().initClusterEnvironment();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ EnvFactory.getEnv().cleanClusterEnvironment();
+ }
+
+ @Test
+ public void testManageDatabase() {
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+
+ // create
+ statement.execute("create database test with (ttl='INF')");
+
+ // create duplicated database without IF NOT EXISTS
+ try {
+ statement.execute("create database test");
+ fail("create database test shouldn't succeed because test already
exists");
+ } catch (final SQLException e) {
+ assertEquals("501: Database test already exists", e.getMessage());
+ }
+
+ // create duplicated database with IF NOT EXISTS
+ statement.execute("create database IF NOT EXISTS test");
+
+ // alter non-exist
+ try {
+ statement.execute("alter database test1 set properties ttl='INF'");
+ fail("alter database test1 shouldn't succeed because test does not
exist");
+ } catch (final SQLException e) {
+ assertEquals("500: Database test1 doesn't exist", e.getMessage());
+ }
+
+ statement.execute("alter database if exists test1 set properties
ttl='INF'");
+ statement.execute("alter database test set properties ttl=default");
+
+ String[] databaseNames = new String[] {"test"};
+ String[] TTLs = new String[] {"INF"};
+ int[] schemaReplicaFactors = new int[] {1};
+ int[] dataReplicaFactors = new int[] {1};
+ int[] timePartitionInterval = new int[] {604800000};
+
+ // show
+ try (final ResultSet resultSet = statement.executeQuery("SHOW
DATABASES")) {
+ int cnt = 0;
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showDBColumnHeaders.size(), metaData.getColumnCount());
+ for (int i = 0; i < showDBColumnHeaders.size(); i++) {
+ assertEquals(showDBColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ if (resultSet.getString(1).equals("information_schema")) {
+ continue;
+ }
+ assertEquals(databaseNames[cnt], resultSet.getString(1));
+ assertEquals(TTLs[cnt], resultSet.getString(2));
+ assertEquals(schemaReplicaFactors[cnt], resultSet.getInt(3));
+ assertEquals(dataReplicaFactors[cnt], resultSet.getInt(4));
+ assertEquals(timePartitionInterval[cnt], resultSet.getLong(5));
+ cnt++;
+ }
+ assertEquals(databaseNames.length, cnt);
+ }
+
+ final int[] schemaRegionGroupNum = new int[] {0};
+ final int[] dataRegionGroupNum = new int[] {0};
+ // show
+ try (final ResultSet resultSet = statement.executeQuery("SHOW DATABASES
DETAILS")) {
+ int cnt = 0;
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showDBDetailsColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < showDBDetailsColumnHeaders.size(); i++) {
+ assertEquals(
+ showDBDetailsColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ if (resultSet.getString(1).equals("information_schema")) {
+ continue;
+ }
+ assertEquals(databaseNames[cnt], resultSet.getString(1));
+ assertEquals(TTLs[cnt], resultSet.getString(2));
+ assertEquals(schemaReplicaFactors[cnt], resultSet.getInt(3));
+ assertEquals(dataReplicaFactors[cnt], resultSet.getInt(4));
+ assertEquals(timePartitionInterval[cnt], resultSet.getLong(5));
+ assertEquals(schemaRegionGroupNum[cnt], resultSet.getInt(6));
+ assertEquals(dataRegionGroupNum[cnt], resultSet.getInt(7));
+ cnt++;
+ }
+ assertEquals(databaseNames.length, cnt);
+ }
+
+ // use
+ statement.execute("use test");
+
+ // use nonexistent database
+ try {
+ statement.execute("use test1");
+ fail("use test1 shouldn't succeed because test1 doesn't exist");
+ } catch (final SQLException e) {
+ assertEquals("500: Unknown database test1", e.getMessage());
+ }
+
+ // drop
+ statement.execute("drop database test");
+ try (final ResultSet resultSet = statement.executeQuery("SHOW
DATABASES")) {
+ // Information_schema
+ assertTrue(resultSet.next());
+ assertFalse(resultSet.next());
+ }
+
+ // drop nonexistent database without IF EXISTS
+ try {
+ statement.execute("drop database test");
+ fail("drop database test shouldn't succeed because test doesn't
exist");
+ } catch (final SQLException e) {
+ assertEquals("500: Database test doesn't exist", e.getMessage());
+ }
+
+ // drop nonexistent database with IF EXISTS
+ statement.execute("drop database IF EXISTS test");
+
+ // Test create database with properties
+ statement.execute(
+ "create database test_prop with (ttl=300,
schema_region_group_num=DEFAULT, time_partition_interval=100000)");
+ databaseNames = new String[] {"test_prop"};
+ TTLs = new String[] {"300"};
+ timePartitionInterval = new int[] {100000};
+
+ // show
+ try (final ResultSet resultSet = statement.executeQuery("SHOW
DATABASES")) {
+ int cnt = 0;
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showDBColumnHeaders.size(), metaData.getColumnCount());
+ for (int i = 0; i < showDBColumnHeaders.size(); i++) {
+ assertEquals(showDBColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ if (resultSet.getString(1).equals("information_schema")) {
+ continue;
+ }
+ assertEquals(databaseNames[cnt], resultSet.getString(1));
+ assertEquals(TTLs[cnt], resultSet.getString(2));
+ assertEquals(schemaReplicaFactors[cnt], resultSet.getInt(3));
+ assertEquals(dataReplicaFactors[cnt], resultSet.getInt(4));
+ assertEquals(timePartitionInterval[cnt], resultSet.getLong(5));
+ cnt++;
+ }
+ assertEquals(databaseNames.length, cnt);
+ }
+
+ try {
+ statement.execute("create database test_prop_2 with
(non_exist_prop=DEFAULT)");
+ fail(
+ "create database test_prop_2 shouldn't succeed because the
property key does not exist.");
+ } catch (final SQLException e) {
+ assertTrue(
+ e.getMessage(),
+ e.getMessage().contains("Unsupported database property key:
non_exist_prop"));
+ }
+
+ // create with strange name
+ try {
+ statement.execute("create database 1test");
+ fail(
+ "create database 1test shouldn't succeed because 1test is not a
legal identifier; identifiers must not start with a digit; surround the
identifier with double quotes");
+ } catch (final SQLException e) {
+ assertTrue(e.getMessage(), e.getMessage().contains("mismatched input
'1'"));
+ }
+
+ statement.execute("create database \"1test\"");
+ statement.execute("use \"1test\"");
+ statement.execute("drop database \"1test\"");
+
+ try {
+ statement.execute("create database 1");
+ fail("create database 1 shouldn't succeed because 1 is not a legal
identifier");
+ } catch (final SQLException e) {
+ assertTrue(e.getMessage(), e.getMessage().contains("mismatched input
'1'"));
+ }
+
+ statement.execute("create database \"1\"");
+ statement.execute("use \"1\"");
+ statement.execute("drop database \"1\"");
+
+ try {
+ statement.execute("create database a.b");
+ fail("create database a.b shouldn't succeed because a.b is not a legal
identifier");
+ } catch (final SQLException e) {
+ assertTrue(e.getMessage(), e.getMessage().contains("mismatched input
'.'"));
+ }
+
+ // Test length limitation
+ statement.execute(
+ "create database
thisDatabaseLengthIsPreciselySixtyFourThusItCanBeNormallyCreated");
+
+ try {
+ statement.execute(
+ "create database
thisDatabaseLengthHasExceededSixtyFourThusItCantBeNormallyCreated");
+ fail(
+ "create database
thisDatabaseLengthHasExceededSixtyFourThusItCantBeNormallyCreated shouldn't
succeed because it's length has exceeded 64");
+ } catch (final SQLException e) {
+ assertTrue(
+ e.getMessage(),
+ e.getMessage().contains("the length of database name shall not
exceed 64"));
+ }
+
+ } catch (final SQLException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+ @Test
+ public void testDatabaseWithSpecificCharacters() throws SQLException {
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ try {
+ statement.execute("create database \"````x.\"");
+ fail("create database ````x. shouldn't succeed because it contains
'.'");
+ } catch (final SQLException e) {
+ assertEquals(
+ "509: ````x. is not a legal path, because the database name can
only contain english or chinese characters, numbers, backticks and
underscores.",
+ e.getMessage());
+ }
+
+ try {
+ statement.execute("create database \"#\"");
+ fail("create database # shouldn't succeed because it contains illegal
character '#'");
+ } catch (final SQLException e) {
+ assertEquals(
+ "509: # is not a legal path, because the database name can only
contain english or chinese characters, numbers, backticks and underscores.",
+ e.getMessage());
+ }
+
+ statement.execute("create database \"````x\"");
+
+ try (final ResultSet resultSet = statement.executeQuery("SHOW
DATABASES")) {
+ assertTrue(resultSet.next());
+ assertEquals("````x", resultSet.getString(1));
+ assertTrue(resultSet.next());
+ assertEquals("information_schema", resultSet.getString(1));
+ assertFalse(resultSet.next());
+ }
+
+ statement.execute("use \"````x\"");
+
+ statement.execute("create table table0 (a tag, b attribute, c int32)");
+
+ statement.execute("desc table0");
+ statement.execute("desc \"````x\".table0");
+
+ statement.execute("show tables");
+ statement.execute("show tables from \"````x\"");
+
+ statement.execute("insert into table0 (time, a, b, c) values(0, '1',
'2', 3)");
+ statement.execute("insert into \"````x\".table0 (time, a, b, c)
values(1, '1', '2', 3)");
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("select a, b, c from \"````x\".table0 where
time = 0"),
+ "a,b,c,",
+ Collections.singleton("1,2,3,"));
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show devices from table0"),
+ "a,b,",
+ Collections.singleton("1,2,"));
+
+ statement.execute("update \"````x\".table0 set b = '4'");
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show devices from table0"),
+ "a,b,",
+ Collections.singleton("1,4,"));
+ }
+ }
+
+ @Test
+ public void testInformationSchema() throws SQLException {
+ // Use a normal user to test visibility
+ try (final Connection adminCon =
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement adminStmt = adminCon.createStatement()) {
+ adminStmt.execute("create user test 'password123456'");
+ adminStmt.execute("create pipe test ('sink'='do-nothing-sink')");
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection("test", "password123456",
BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ // Test unsupported write plans
+ final Set<String> writeSQLs =
+ new HashSet<>(
+ Arrays.asList(
+ "create database information_schema",
+ "drop database information_schema",
+ // table in information_schema do not have the time column,
add time column just
+ // for simulate the base table
+ "create table information_schema.tableA (time timestamp
time)",
+ "alter table information_schema.tableA add column a id",
+ "alter table information_schema.tableA set properties
ttl=default",
+ // given that create table in information_schema is not
allowed, skip insert
+ // "insert into information_schema.tables (database)
values('db')",
+ "update information_schema.tables set status='RUNNING'"));
+
+ for (final String writeSQL : writeSQLs) {
+ try {
+ statement.execute(writeSQL);
+ fail("information_schema does not support write");
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: The database 'information_schema' can only be queried",
e.getMessage());
+ }
+ }
+
+ statement.execute("use information_schema");
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show databases"),
+
"Database,TTL(ms),SchemaReplicationFactor,DataReplicationFactor,TimePartitionInterval,",
+ Collections.singleton("information_schema,INF,null,null,null,"));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show tables"),
+ "TableName,TTL(ms),",
+ Arrays.asList(
+ "columns,INF,",
+ "config_nodes,INF,",
+ "configurations,INF,",
+ "connections,INF,",
+ "current_queries,INF,",
+ "data_nodes,INF,",
+ "databases,INF,",
+ "functions,INF,",
+ "keywords,INF,",
+ "nodes,INF,",
+ "pipe_plugins,INF,",
+ "pipes,INF,",
+ "queries,INF,",
+ "queries_costs_histogram,INF,",
+ "regions,INF,",
+ "services,INF,",
+ "subscriptions,INF,",
+ "table_disk_usage,INF,",
+ "tables,INF,",
+ "topics,INF,",
+ "views,INF,"));
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc databases"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "database,STRING,TAG,",
+ "ttl(ms),STRING,ATTRIBUTE,",
+ "schema_replication_factor,INT32,ATTRIBUTE,",
+ "data_replication_factor,INT32,ATTRIBUTE,",
+ "time_partition_interval,INT64,ATTRIBUTE,",
+ "schema_region_group_num,INT32,ATTRIBUTE,",
+ "data_region_group_num,INT32,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc tables"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "database,STRING,TAG,",
+ "table_name,STRING,TAG,",
+ "ttl(ms),STRING,ATTRIBUTE,",
+ "status,STRING,ATTRIBUTE,",
+ "comment,STRING,ATTRIBUTE,",
+ "table_type,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc columns"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "database,STRING,TAG,",
+ "table_name,STRING,TAG,",
+ "column_name,STRING,TAG,",
+ "datatype,STRING,ATTRIBUTE,",
+ "category,STRING,ATTRIBUTE,",
+ "status,STRING,ATTRIBUTE,",
+ "comment,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc queries"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "query_id,STRING,TAG,",
+ "start_time,TIMESTAMP,ATTRIBUTE,",
+ "datanode_id,INT32,ATTRIBUTE,",
+ "elapsed_time,FLOAT,ATTRIBUTE,",
+ "statement,STRING,ATTRIBUTE,",
+ "user,STRING,ATTRIBUTE,",
+ "wait_time_in_server,FLOAT,ATTRIBUTE,",
+ "client_ip,STRING,ATTRIBUTE,",
+ "timeout,INT64,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc pipes"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "id,STRING,TAG,",
+ "creation_time,TIMESTAMP,ATTRIBUTE,",
+ "state,STRING,ATTRIBUTE,",
+ "pipe_source,STRING,ATTRIBUTE,",
+ "pipe_processor,STRING,ATTRIBUTE,",
+ "pipe_sink,STRING,ATTRIBUTE,",
+ "exception_message,STRING,ATTRIBUTE,",
+ "remaining_event_count,INT64,ATTRIBUTE,",
+ "estimated_remaining_seconds,DOUBLE,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc pipe_plugins"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "plugin_name,STRING,TAG,",
+ "plugin_type,STRING,ATTRIBUTE,",
+ "class_name,STRING,ATTRIBUTE,",
+ "plugin_jar,STRING,ATTRIBUTE,",
+ "exception_message,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc topics"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList("topic_name,STRING,TAG,",
"topic_configs,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc subscriptions"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "topic_name,STRING,TAG,",
+ "consumer_group_name,STRING,TAG,",
+ "subscribed_consumers,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc services"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "service_name,STRING,TAG,",
+ "datanode_id,INT32,ATTRIBUTE,",
+ "state,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc views"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "database,STRING,TAG,",
+ "table_name,STRING,TAG,",
+ "view_definition,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc functions"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "function_table,STRING,TAG,",
+ "function_type,STRING,ATTRIBUTE,",
+ "class_name(udf),STRING,ATTRIBUTE,",
+ "state,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc configurations"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(Arrays.asList("variable,STRING,TAG,",
"value,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc keywords"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(Arrays.asList("word,STRING,TAG,",
"reserved,INT32,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc nodes"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "node_id,INT32,TAG,",
+ "node_type,STRING,ATTRIBUTE,",
+ "status,STRING,ATTRIBUTE,",
+ "internal_address,STRING,ATTRIBUTE,",
+ "internal_port,INT32,ATTRIBUTE,",
+ "version,STRING,ATTRIBUTE,",
+ "build_info,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc config_nodes"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "node_id,INT32,TAG,",
+ "config_consensus_port,INT32,ATTRIBUTE,",
+ "role,STRING,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc data_nodes"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "node_id,INT32,TAG,",
+ "data_region_num,INT32,ATTRIBUTE,",
+ "schema_region_num,INT32,ATTRIBUTE,",
+ "rpc_address,STRING,ATTRIBUTE,",
+ "rpc_port,INT32,ATTRIBUTE,",
+ "mpp_port,INT32,ATTRIBUTE,",
+ "data_consensus_port,INT32,ATTRIBUTE,",
+ "schema_consensus_port,INT32,ATTRIBUTE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc table_disk_usage"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "database,STRING,FIELD,",
+ "table_name,STRING,FIELD,",
+ "datanode_id,INT32,FIELD,",
+ "region_id,INT32,FIELD,",
+ "time_partition,INT64,FIELD,",
+ "size_in_bytes,INT64,FIELD,")));
+
+ // Only root user is allowed
+ Assert.assertThrows(SQLException.class, () -> statement.execute("select
* from regions"));
+ Assert.assertThrows(SQLException.class, () -> statement.execute("select
* from topics"));
+ Assert.assertThrows(
+ SQLException.class, () -> statement.execute("select * from
subscriptions"));
+ Assert.assertThrows(
+ SQLException.class, () -> statement.execute("select * from
configurations"));
+ Assert.assertThrows(SQLException.class, () -> statement.execute("select
* from nodes"));
+ Assert.assertThrows(
+ SQLException.class, () -> statement.execute("select * from
config_nodes"));
+ Assert.assertThrows(SQLException.class, () -> statement.execute("select
* from data_nodes"));
+ Assert.assertThrows(
+ SQLException.class, () -> statement.executeQuery("select * from
pipe_plugins"));
+ Assert.assertThrows(
+ SQLException.class, () -> statement.executeQuery("select * from
table_disk_usage"));
+
+ // Filter out not self-created pipes
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("select * from pipes"),
+
"id,creation_time,state,pipe_source,pipe_processor,pipe_sink,exception_message,remaining_event_count,estimated_remaining_seconds,",
+ Collections.emptySet());
+
+ // No auth needed
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery(
+ "select distinct(function_type) from
information_schema.functions"),
+ "function_type,",
+ new HashSet<>(
+ Arrays.asList(
+ "built-in scalar function,",
+ "built-in aggregate function,",
+ "built-in table function,")));
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery(
+ "select * from information_schema.keywords where reserved > 0
limit 1"),
+ "word,reserved,",
+ Collections.singleton("ACCOUNT,1,"));
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ // Test table query
+ statement.execute("use information_schema");
+
+ statement.execute("create database test");
+ statement.execute(
+ "create table test.test (a tag, b attribute, c int32 comment
'turbine') comment 'test'");
+ statement.execute(
+ "CREATE VIEW test.view_table (tag1 STRING TAG,tag2 STRING TAG,s11
INT32 FIELD,s3 INT32 FIELD FROM s2) RESTRICT WITH (ttl=100) AS root.\"a\".**");
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("select * from databases"),
+
"database,ttl(ms),schema_replication_factor,data_replication_factor,time_partition_interval,schema_region_group_num,data_region_group_num,",
+ new HashSet<>(
+ Arrays.asList(
+ "information_schema,INF,null,null,null,null,null,",
+ "test,INF,1,1,604800000,0,0,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show devices from tables where status =
'USING'"),
+ "database,table_name,ttl(ms),status,comment,table_type,",
+ new HashSet<>(
+ Arrays.asList(
+ "information_schema,databases,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,tables,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,columns,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,queries,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,regions,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,topics,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,pipe_plugins,INF,USING,null,SYSTEM
VIEW,",
+ "information_schema,pipes,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,services,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,subscriptions,INF,USING,null,SYSTEM
VIEW,",
+ "information_schema,views,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,functions,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,configurations,INF,USING,null,SYSTEM
VIEW,",
+ "information_schema,keywords,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,nodes,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,table_disk_usage,INF,USING,null,SYSTEM
VIEW,",
+ "information_schema,config_nodes,INF,USING,null,SYSTEM
VIEW,",
+ "information_schema,data_nodes,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,connections,INF,USING,null,SYSTEM VIEW,",
+ "information_schema,current_queries,INF,USING,null,SYSTEM
VIEW,",
+
"information_schema,queries_costs_histogram,INF,USING,null,SYSTEM VIEW,",
+ "test,test,INF,USING,test,BASE TABLE,",
+ "test,view_table,100,USING,null,VIEW FROM TREE,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("count devices from tables where status =
'USING'"),
+ "count(devices),",
+ Collections.singleton("23,"));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery(
+ "select * from columns where table_name = 'queries' or database
= 'test'"),
+ "database,table_name,column_name,datatype,category,status,comment,",
+ new HashSet<>(
+ Arrays.asList(
+ "information_schema,queries,query_id,STRING,TAG,USING,null,",
+
"information_schema,queries,start_time,TIMESTAMP,ATTRIBUTE,USING,null,",
+
"information_schema,queries,datanode_id,INT32,ATTRIBUTE,USING,null,",
+
"information_schema,queries,elapsed_time,FLOAT,ATTRIBUTE,USING,null,",
+
"information_schema,queries,statement,STRING,ATTRIBUTE,USING,null,",
+
"information_schema,queries,user,STRING,ATTRIBUTE,USING,null,",
+
"information_schema,queries,wait_time_in_server,FLOAT,ATTRIBUTE,USING,null,",
+
"information_schema,queries,client_ip,STRING,ATTRIBUTE,USING,null,",
+
"information_schema,queries,timeout,INT64,ATTRIBUTE,USING,null,",
+ "test,test,time,TIMESTAMP,TIME,USING,null,",
+ "test,test,a,STRING,TAG,USING,null,",
+ "test,test,b,STRING,ATTRIBUTE,USING,null,",
+ "test,test,c,INT32,FIELD,USING,turbine,",
+ "test,view_table,time,TIMESTAMP,TIME,USING,null,",
+ "test,view_table,tag1,STRING,TAG,USING,null,",
+ "test,view_table,tag2,STRING,TAG,USING,null,",
+ "test,view_table,s11,INT32,FIELD,USING,null,",
+ "test,view_table,s3,INT32,FIELD,USING,null,")));
+
+ statement.execute(
+ "create pipe a2b with source('double-living'='true') with sink
('sink'='write-back-sink')");
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("select id from pipes where creation_time >
0"),
+ "id,",
+ new HashSet<>(Arrays.asList("a2b,", "test,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery(
+ "select * from pipe_plugins where plugin_name =
'IOTDB-THRIFT-SINK'"),
+ "plugin_name,plugin_type,class_name,plugin_jar,exception_message,",
+ Collections.singleton(
+
"IOTDB-THRIFT-SINK,Builtin,org.apache.iotdb.commons.pipe.agent.plugin.builtin.sink.iotdb.thrift.IoTDBThriftSink,null,null,"));
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("select * from views"),
+ "database,table_name,view_definition,",
+ Collections.singleton(
+ "test,view_table,CREATE VIEW \"view_table\" (\"time\" TIMESTAMP
TIME,\"tag1\" STRING TAG,\"tag2\" STRING TAG,\"s11\" INT32 FIELD,\"s3\" INT32
FIELD FROM \"s2\") RESTRICT WITH (ttl=100) AS root.\"a\".**,"));
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery(
+ "select distinct(function_type) from
information_schema.functions"),
+ "function_type,",
+ new HashSet<>(
+ Arrays.asList(
+ "built-in scalar function,",
+ "built-in aggregate function,",
+ "built-in table function,")));
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery(
+ "select value from information_schema.configurations where
variable = 'TimestampPrecision'"),
+ "value,",
+ Collections.singleton("ms,"));
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery(
+ "select * from information_schema.keywords where reserved > 0
limit 1"),
+ "word,reserved,",
+ Collections.singleton("ACCOUNT,1,"));
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("select distinct(status) from
information_schema.nodes"),
+ "status,",
+ Collections.singleton("Running,"));
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("select count(*) from
information_schema.config_nodes"),
+ "_col0,",
+
Collections.singleton(EnvFactory.getEnv().getConfigNodeWrapperList().size() +
","));
+
+ Set<String> resultSet = new HashSet<>();
+ resultSet.add("0,");
+ for (int i = 1; i < EnvFactory.getEnv().getDataNodeWrapperList().size();
i++) {
+ resultSet.add("0,");
+ }
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("select data_region_num from
information_schema.data_nodes"),
+ "data_region_num,",
+ resultSet);
+ }
+ }
+
+ @Test
+ public void testMixedDatabase() throws SQLException {
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("create database test");
+ statement.execute("use test");
+ statement.execute("create table table1(id1 tag, s1 string)");
+ statement.execute("insert into table1 values(0, 'd1', null), (1,'d1',
1)");
+ }
+
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ statement.execute("create database root.test");
+ statement.execute(
+ "alter database root.test WITH SCHEMA_REGION_GROUP_NUM=2,
DATA_REGION_GROUP_NUM=3");
+ statement.execute("insert into root.test.d1 (s1) values(1)");
+ statement.execute("drop database root.test");
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("use test");
+ // Avoid clearing table cache
+ statement.execute("select * from table1");
+
+ try (final ResultSet resultSet = statement.executeQuery("SHOW DATABASES
DETAILS")) {
+ assertTrue(resultSet.next());
+ assertEquals("information_schema", resultSet.getString(1));
+ assertTrue(resultSet.next());
+ assertEquals("test", resultSet.getString(1));
+ assertFalse(resultSet.next());
+ }
+
+ // Test adjustMaxRegionGroupNum
+ statement.execute(
+ "create table table2(region_id STRING TAG, plant_id STRING TAG,
color STRING ATTRIBUTE, temperature FLOAT FIELD, speed DOUBLE FIELD)");
+ statement.execute(
+ "insert into table2(region_id, plant_id, color, temperature, speed)
values(1, 1, 1, 1, 1)");
+
+ statement.execute("create database test1");
+ }
+
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ statement.execute("create database root.test");
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("drop database test");
+ }
+
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ // One for AUDIT database
+ TestUtils.assertResultSetSize(statement.executeQuery("show databases"),
1);
+ }
+ }
+
+ @Test
+ public void testDBAuth() throws SQLException {
+ try (final Connection adminCon =
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement adminStmt = adminCon.createStatement()) {
+ adminStmt.execute("create user test 'password123456'");
+ adminStmt.execute("create database db");
+ adminStmt.execute(
+ "create pipe a2b with source('double-living'='true') with sink
('sink'='write-back-sink')");
+ }
+
+ try (final Connection userCon =
+ EnvFactory.getEnv().getConnection("test", "password123456",
BaseEnv.TABLE_SQL_DIALECT);
+ final Statement userStmt = userCon.createStatement()) {
+ TestUtils.assertResultSetEqual(
+ userStmt.executeQuery("show databases"),
+
"Database,TTL(ms),SchemaReplicationFactor,DataReplicationFactor,TimePartitionInterval,",
+ Collections.singleton("information_schema,INF,null,null,null,"));
+ TestUtils.assertResultSetEqual(
+ userStmt.executeQuery("select * from information_schema.databases"),
+
"database,ttl(ms),schema_replication_factor,data_replication_factor,time_partition_interval,schema_region_group_num,data_region_group_num,",
+
Collections.singleton("information_schema,INF,null,null,null,null,null,"));
+ }
+
+ try (final Connection adminCon =
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement adminStmt = adminCon.createStatement()) {
+ adminStmt.execute("GRANT SELECT ON DATABASE DB to user test");
+
+ // Information_schema does not support grant & revoke
+ Assert.assertThrows(
+ SQLException.class,
+ () -> {
+ adminStmt.execute("GRANT SELECT ON DATABASE information_schema to
user test");
+ });
+
+ Assert.assertThrows(
+ SQLException.class,
+ () -> {
+ adminStmt.execute("REVOKE SELECT ON information_schema.tables from
user test");
+ });
+ }
+
+ try (final Connection userCon =
+ EnvFactory.getEnv().getConnection("test", "password123456",
BaseEnv.TABLE_SQL_DIALECT);
+ final Statement userStmt = userCon.createStatement()) {
+ try (final ResultSet resultSet = userStmt.executeQuery("SHOW
DATABASES")) {
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showDBColumnHeaders.size(), metaData.getColumnCount());
+ for (int i = 0; i < showDBColumnHeaders.size(); i++) {
+ assertEquals(showDBColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ Assert.assertTrue(resultSet.next());
+ assertEquals("db", resultSet.getString(1));
+ Assert.assertTrue(resultSet.next());
+ assertEquals("information_schema", resultSet.getString(1));
+ Assert.assertFalse(resultSet.next());
+ }
+
+ Assert.assertThrows(
+ SQLException.class,
+ () -> {
+ userStmt.execute("alter database db set properties ttl=6600000");
+ });
+
+ Assert.assertThrows(
+ SQLException.class,
+ () -> {
+ userStmt.execute("drop database db");
+ });
+ }
+
+ try (final Connection adminCon =
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement adminStmt = adminCon.createStatement()) {
+ adminStmt.execute("GRANT DROP ON ANY to user test");
+ }
+
+ try (final Connection userCon =
+ EnvFactory.getEnv().getConnection("test", "password123456",
BaseEnv.TABLE_SQL_DIALECT);
+ final Statement userStmt = userCon.createStatement()) {
+ userStmt.execute("drop database db");
+ }
+ }
+}
diff --git
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
new file mode 100644
index 00000000000..c3a72d3c23a
--- /dev/null
+++
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java
@@ -0,0 +1,1117 @@
+/*
+ * 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.relational.it.schema;
+
+import org.apache.iotdb.db.it.utils.TestUtils;
+import org.apache.iotdb.isession.ITableSession;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+import org.apache.iotdb.itbase.env.BaseEnv;
+import org.apache.iotdb.rpc.IoTDBConnectionException;
+import org.apache.iotdb.rpc.StatementExecutionException;
+
+import org.apache.tsfile.enums.ColumnCategory;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.write.record.Tablet;
+import org.apache.tsfile.write.schema.IMeasurementSchema;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static
org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.describeTableColumnHeaders;
+import static
org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.describeTableDetailsColumnHeaders;
+import static
org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.showDBColumnHeaders;
+import static
org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.showTablesColumnHeaders;
+import static
org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.showTablesDetailsColumnHeaders;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBTableIT {
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+
EnvFactory.getEnv().getConfig().getCommonConfig().setEnforceStrongPassword(false);
+
EnvFactory.getEnv().getConfig().getCommonConfig().setRestrictObjectLimit(true);
+ EnvFactory.getEnv().initClusterEnvironment();
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ EnvFactory.getEnv().cleanClusterEnvironment();
+ }
+
+ @Test
+ public void testManageTable() {
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+
+ statement.execute("create database test1");
+ statement.execute("create database test2 with (ttl=3000000)");
+
+ // should specify database before create table
+ try {
+ statement.execute(
+ "create table table1(region_id STRING TAG, plant_id STRING TAG,
device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity
DOUBLE FIELD)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("701: database is not specified", e.getMessage());
+ }
+
+ // Show tables shall succeed in a newly created database with no tables
+ try (final ResultSet resultSet = statement.executeQuery("SHOW tables
from test1")) {
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showTablesColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < showTablesColumnHeaders.size(); i++) {
+ assertEquals(
+ showTablesColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ assertFalse(resultSet.next());
+ }
+
+ try {
+ statement.execute(
+ "create table test1.table1(region_id STRING TAG, plant_id STRING
TAG, device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD,
humidity DOUBLE FIELD) with (ttl=100, ttl=200, ttl=300)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("701: Duplicated property: ttl", e.getMessage());
+ }
+
+ // or use full qualified table name
+ // test "TTL=INF"
+ // "FIELD" can be omitted when type is specified
+ // "STRING" can be omitted when tag/attribute is specified
+ statement.execute(
+ "create table test1.table1(time TIMESTAMP TIME COMMENT
'column_comment', region_id STRING TAG, plant_id STRING TAG, device_id TAG,
model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity DOUBLE) comment
'test' with (TTL='INF')");
+
+ try {
+ statement.execute(
+ "create table test1.table1(region_id STRING TAG, plant_id STRING
TAG, device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD,
humidity DOUBLE FIELD)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("551: Table 'test1.table1' already exists.",
e.getMessage());
+ }
+
+ String[] tableNames = new String[] {"table1"};
+ String[] ttls = new String[] {"INF"};
+ String[] statuses = new String[] {"USING"};
+ String[] comments = new String[] {"test"};
+
+ statement.execute("use test2");
+
+ // show tables by specifying another database
+ // Check duplicate create table won't affect table state
+ // using SHOW tables in
+ try (final ResultSet resultSet = statement.executeQuery("SHOW tables
details in test1")) {
+ int cnt = 0;
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showTablesDetailsColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < showTablesDetailsColumnHeaders.size(); i++) {
+ assertEquals(
+ showTablesDetailsColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ assertEquals(tableNames[cnt], resultSet.getString(1));
+ assertEquals(ttls[cnt], resultSet.getString(2));
+ assertEquals(statuses[cnt], resultSet.getString(3));
+ assertEquals(comments[cnt], resultSet.getString(4));
+ cnt++;
+ }
+ assertEquals(tableNames.length, cnt);
+ }
+
+ // Test unsupported, to be deleted
+ try {
+ statement.execute("alter table test1.table1 rename to tableN");
+ } catch (final SQLException e) {
+ assertEquals("701: The renaming for base table is currently
unsupported", e.getMessage());
+ }
+
+ // Test unsupported, to be deleted
+ try {
+ statement.execute(
+ "alter table if exists test_db.table1 rename column if exists
model to modelType");
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: The renaming for base table column is currently
unsupported", e.getMessage());
+ }
+
+ // Alter table properties
+ statement.execute("alter table test1.table1 set properties ttl=1000000");
+ ttls = new String[] {"1000000"};
+
+ // Alter non-exist table
+ try {
+ statement.execute("alter table test1.nonExist set properties
ttl=1000000");
+ } catch (final SQLException e) {
+ assertEquals("550: Table 'test1.nonexist' does not exist",
e.getMessage());
+ }
+
+ // If exists
+ statement.execute("alter table if exists test1.nonExist set properties
ttl=1000000");
+
+ // Alter non-supported properties
+ try {
+ statement.execute("alter table test1.table1 set properties
nonSupport=1000000");
+ } catch (final SQLException e) {
+ assertEquals("701: Table property 'nonsupport' is currently not
allowed.", e.getMessage());
+ }
+
+ statement.execute("comment on table test1.table1 is 'new_test'");
+ comments = new String[] {"new_test"};
+ // using SHOW tables from
+ try (final ResultSet resultSet = statement.executeQuery("SHOW tables
details from test1")) {
+ int cnt = 0;
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showTablesDetailsColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < showTablesDetailsColumnHeaders.size(); i++) {
+ assertEquals(
+ showTablesDetailsColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ assertEquals(tableNames[cnt], resultSet.getString(1));
+ assertEquals(ttls[cnt], resultSet.getString(2));
+ assertEquals(comments[cnt], resultSet.getString(4));
+ cnt++;
+ }
+ assertEquals(tableNames.length, cnt);
+ }
+
+ // Set back to default
+ statement.execute("alter table test1.table1 set properties ttl=DEFAULT");
+ ttls = new String[] {"INF"};
+
+ try (final ResultSet resultSet = statement.executeQuery("SHOW tables
from test1")) {
+ int cnt = 0;
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showTablesColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < showTablesColumnHeaders.size(); i++) {
+ assertEquals(
+ showTablesColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ assertEquals(tableNames[cnt], resultSet.getString(1));
+ assertEquals(ttls[cnt], resultSet.getString(2));
+ cnt++;
+ }
+ assertEquals(tableNames.length, cnt);
+ }
+
+ // Create if not exist
+ statement.execute(
+ "create table if not exists test1.table1(region_id STRING TAG,
plant_id STRING TAG, device_id STRING TAG, model STRING ATTRIBUTE, temperature
FLOAT FIELD, humidity DOUBLE FIELD)");
+
+ try {
+ statement.execute(
+ "create table table2(region_id STRING TAG, plant_id STRING TAG,
device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity
DOUBLE FIELD) with (UNKNOWN=3600000)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("701: Table property 'unknown' is currently not
allowed.", e.getMessage());
+ }
+
+ try {
+ statement.execute(
+ "create table table2(region_id STRING TAG, plant_id STRING TAG,
device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity
DOUBLE FIELD) with (TTL=null)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: ttl value must be a LongLiteral, but now is NullLiteral,
value: null",
+ e.getMessage());
+ }
+
+ try {
+ statement.execute(
+ "create table table2(region_id STRING TAG, plant_id STRING TAG,
device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity
DOUBLE FIELD) with (TTL=-1)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: ttl value must be equal to or greater than 0, but now is:
-1", e.getMessage());
+ }
+
+ try {
+ statement.execute(
+ "create table table2(region_id TEXT TAG, plant_id STRING TAG,
device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity
DOUBLE FIELD) with (TTL=3600000)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: DataType of TAG Column should only be STRING, current is
TEXT", e.getMessage());
+ }
+
+ try {
+ statement.execute(
+ "create table table2(region_id INT32 TAG, plant_id STRING TAG,
device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity
DOUBLE FIELD) with (TTL=3600000)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: DataType of TAG Column should only be STRING, current is
INT32", e.getMessage());
+ }
+
+ try {
+ statement.execute(
+ "create table table2(region_id STRING TAG, plant_id STRING TAG,
device_id STRING TAG, model TEXT ATTRIBUTE, temperature FLOAT FIELD, humidity
DOUBLE FIELD) with (TTL=3600000)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: DataType of ATTRIBUTE Column should only be STRING, current
is TEXT",
+ e.getMessage());
+ }
+
+ try {
+ statement.execute(
+ "create table table2(region_id STRING TAG, plant_id STRING TAG,
device_id STRING TAG, model DOUBLE ATTRIBUTE, temperature FLOAT FIELD, humidity
DOUBLE FIELD) with (TTL=3600000)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: DataType of ATTRIBUTE Column should only be STRING, current
is DOUBLE",
+ e.getMessage());
+ }
+
+ statement.execute(
+ "create table table2(t1 TIMESTAMP TIME, region_id STRING TAG,
plant_id STRING TAG, color STRING ATTRIBUTE, temperature FLOAT FIELD) with
(TTL=6600000)");
+
+ statement.execute("alter table table2 add column speed DOUBLE FIELD
COMMENT 'fast'");
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show create table table2"),
+ "Table,Create Table,",
+ Collections.singleton(
+ "table2,CREATE TABLE \"table2\" (\"t1\" TIMESTAMP
TIME,\"region_id\" STRING TAG,\"plant_id\" STRING TAG,\"color\" STRING
ATTRIBUTE,\"temperature\" FLOAT FIELD,\"speed\" DOUBLE FIELD COMMENT 'fast')
WITH (ttl=6600000),"));
+
+ try {
+ statement.execute("alter table table2 add column speed DOUBLE FIELD");
+ } catch (final SQLException e) {
+ assertEquals("552: Column 'speed' already exist", e.getMessage());
+ }
+
+ statement.execute("alter table table2 add column if not exists speed
DOUBLE FIELD");
+
+ try {
+ statement.execute("alter table table3 add column speed DOUBLE FIELD");
+ } catch (final SQLException e) {
+ assertEquals("550: Table 'test2.table3' does not exist",
e.getMessage());
+ }
+
+ statement.execute("alter table if exists table3 add column speed DOUBLE
FIELD");
+
+ // Test create table with only time column
+ statement.execute("create table table3()");
+
+ tableNames = new String[] {"table2", "table3"};
+ ttls = new String[] {"6600000", "3000000"};
+
+ // show tables from current database
+ try (final ResultSet resultSet = statement.executeQuery("SHOW tables")) {
+ int cnt = 0;
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showTablesColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < showTablesColumnHeaders.size(); i++) {
+ assertEquals(
+ showTablesColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ assertEquals(tableNames[cnt], resultSet.getString(1));
+ assertEquals(ttls[cnt], resultSet.getString(2));
+ cnt++;
+ }
+ assertEquals(tableNames.length, cnt);
+ }
+
+ // Will not affect the manual "6600000"
+ statement.execute("alter database test2 set properties ttl=6600000");
+ statement.execute("alter database test2 set properties ttl=DEFAULT");
+
+ statement.execute("alter table table3 set properties ttl=1000000");
+ statement.execute("alter table table3 set properties ttl=DEFAULT");
+
+ ttls = new String[] {"6600000", "INF"};
+ // The table3's ttl shall be "INF"
+ try (final ResultSet resultSet = statement.executeQuery("SHOW tables")) {
+ int cnt = 0;
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showTablesColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < showTablesColumnHeaders.size(); i++) {
+ assertEquals(
+ showTablesColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ assertEquals(tableNames[cnt], resultSet.getString(1));
+ assertEquals(ttls[cnt], resultSet.getString(2));
+ cnt++;
+ }
+ assertEquals(tableNames.length, cnt);
+ }
+
+ // show tables from a non-exist database
+ try {
+ statement.executeQuery("SHOW tables from test3");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("500: Unknown database test3", e.getMessage());
+ }
+
+ // describe
+ try {
+ statement.executeQuery("describe table1");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("550: Table 'test2.table1' does not exist.",
e.getMessage());
+ }
+
+ String[] columnNames =
+ new String[] {
+ "time", "region_id", "plant_id", "device_id", "model",
"temperature", "humidity"
+ };
+ String[] dataTypes =
+ new String[] {"TIMESTAMP", "STRING", "STRING", "STRING", "STRING",
"FLOAT", "DOUBLE"};
+ String[] categories =
+ new String[] {"TIME", "TAG", "TAG", "TAG", "ATTRIBUTE", "FIELD",
"FIELD"};
+
+ try (final ResultSet resultSet = statement.executeQuery("describe
test1.table1")) {
+ int cnt = 0;
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(describeTableColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < describeTableColumnHeaders.size(); i++) {
+ assertEquals(
+ describeTableColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ assertEquals(columnNames[cnt], resultSet.getString(1));
+ assertEquals(dataTypes[cnt], resultSet.getString(2));
+ assertEquals(categories[cnt], resultSet.getString(3));
+ cnt++;
+ }
+ assertEquals(columnNames.length, cnt);
+ }
+
+ columnNames = new String[] {"t1", "region_id", "plant_id", "color",
"temperature", "speed"};
+ dataTypes = new String[] {"TIMESTAMP", "STRING", "STRING", "STRING",
"FLOAT", "DOUBLE"};
+ categories = new String[] {"TIME", "TAG", "TAG", "ATTRIBUTE", "FIELD",
"FIELD"};
+
+ try (final ResultSet resultSet = statement.executeQuery("desc table2")) {
+ int cnt = 0;
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(describeTableColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < describeTableColumnHeaders.size(); i++) {
+ assertEquals(
+ describeTableColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ assertEquals(columnNames[cnt], resultSet.getString(1));
+ assertEquals(dataTypes[cnt], resultSet.getString(2));
+ assertEquals(categories[cnt], resultSet.getString(3));
+ cnt++;
+ }
+ assertEquals(columnNames.length, cnt);
+ }
+
+ statement.execute(
+ "insert into table2(region_id, plant_id, color, temperature, speed)
values(1, 1, 1, 1, 1)");
+
+ // Test drop column
+ statement.execute("alter table table2 drop column color");
+
+ // Test comment
+ // Before
+ columnNames = new String[] {"t1", "region_id", "plant_id",
"temperature", "speed"};
+ dataTypes = new String[] {"TIMESTAMP", "STRING", "STRING", "FLOAT",
"DOUBLE"};
+ categories = new String[] {"TIME", "TAG", "TAG", "FIELD", "FIELD"};
+ statuses = new String[] {"USING", "USING", "USING", "USING", "USING"};
+
+ comments = new String[] {null, null, null, null, "fast"};
+ try (final ResultSet resultSet = statement.executeQuery("describe table2
details")) {
+ int cnt = 0;
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(describeTableDetailsColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < describeTableDetailsColumnHeaders.size(); i++) {
+ assertEquals(
+ describeTableDetailsColumnHeaders.get(i).getColumnName(),
+ metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ assertEquals(columnNames[cnt], resultSet.getString(1));
+ assertEquals(dataTypes[cnt], resultSet.getString(2));
+ assertEquals(categories[cnt], resultSet.getString(3));
+ assertEquals(statuses[cnt], resultSet.getString(4));
+ assertEquals(comments[cnt], resultSet.getString(5));
+ cnt++;
+ }
+ assertEquals(columnNames.length, cnt);
+ }
+
+ // After
+ statement.execute("COMMENT ON COLUMN table2.region_id IS '重庆'");
+ statement.execute("COMMENT ON COLUMN table2.region_id IS NULL");
+ statement.execute("COMMENT ON COLUMN test2.table2.t1 IS 'recent'");
+ statement.execute("COMMENT ON COLUMN test2.table2.region_id IS ''");
+
+ comments = new String[] {"recent", "", null, null, "fast"};
+ try (final ResultSet resultSet = statement.executeQuery("describe table2
details")) {
+ int cnt = 0;
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(describeTableDetailsColumnHeaders.size(),
metaData.getColumnCount());
+ for (int i = 0; i < describeTableDetailsColumnHeaders.size(); i++) {
+ assertEquals(
+ describeTableDetailsColumnHeaders.get(i).getColumnName(),
+ metaData.getColumnName(i + 1));
+ }
+ while (resultSet.next()) {
+ assertEquals(columnNames[cnt], resultSet.getString(1));
+ assertEquals(dataTypes[cnt], resultSet.getString(2));
+ assertEquals(categories[cnt], resultSet.getString(3));
+ assertEquals(statuses[cnt], resultSet.getString(4));
+ assertEquals(comments[cnt], resultSet.getString(5));
+ cnt++;
+ }
+ assertEquals(columnNames.length, cnt);
+ }
+
+ statement.execute("alter table table2 drop column speed");
+
+ try {
+ statement.executeQuery("select color from table2");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("616: Column 'color' cannot be resolved", e.getMessage());
+ }
+
+ try {
+ statement.executeQuery("select speed from table2");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("616: Column 'speed' cannot be resolved", e.getMessage());
+ }
+
+ try {
+ statement.execute("alter table table2 drop column speed");
+ } catch (final SQLException e) {
+ assertEquals("616: Column speed in table 'test2.table2' does not
exist.", e.getMessage());
+ }
+
+ try {
+ statement.execute("alter table table2 drop column t1");
+ } catch (final SQLException e) {
+ assertEquals("701: Dropping tag or time column is not supported.",
e.getMessage());
+ }
+
+ // test data deletion by drop column
+ statement.execute("alter table table2 add column speed double");
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("select speed from table2"),
+ "speed,",
+ Collections.singleton("null,"));
+
+ statement.execute("drop table table2");
+ try {
+ statement.executeQuery("describe table2");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("550: Table 'test2.table2' does not exist.",
e.getMessage());
+ }
+ statement.execute(
+ "create table table2(region_id STRING TAG, plant_id STRING TAG,
color STRING ATTRIBUTE, temperature FLOAT FIELD, speed DOUBLE FIELD)");
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("count devices from table2"),
+ "count(devices),",
+ Collections.singleton("0,"));
+
+ // Test data deletion by drop table
+ statement.execute(
+ "insert into table2(region_id, plant_id, color, temperature, speed)
values(1, 1, 1, 1, 1)");
+ TestUtils.assertResultSetSize(statement.executeQuery("select * from
table2"), 1);
+
+ try {
+ statement.executeQuery("describe test3.table3");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("500: Unknown database test3", e.getMessage());
+ }
+
+ statement.execute("drop database test1");
+
+ // Test error messages
+ try {
+ statement.executeQuery("SHOW tables from test1");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("500: Unknown database test1", e.getMessage());
+ }
+
+ try {
+ statement.execute("create table test1.test()");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("500: Unknown database test1", e.getMessage());
+ }
+
+ try {
+ statement.execute("alter table test1.test add column a int32");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("500: Unknown database test1", e.getMessage());
+ }
+
+ try {
+ statement.execute("alter table test1.test drop column a");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("500: Unknown database test1", e.getMessage());
+ }
+
+ try {
+ statement.execute("alter table test1.test set properties ttl=default");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("500: Unknown database test1", e.getMessage());
+ }
+
+ try {
+ statement.execute("desc test1.test");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("500: Unknown database test1", e.getMessage());
+ }
+
+ try {
+ statement.execute("drop table test1.test");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("500: Unknown database test1", e.getMessage());
+ }
+
+ // Test time column
+ // More time column tests are included in other IT
+ statement.execute("create table test100 (t1 time) with (ttl='INF')");
+ statement.execute("create table test101 (t1 timestamp time)");
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show create table test100"),
+ "Table,Create Table,",
+ Collections.singleton(
+ "test100,CREATE TABLE \"test100\" (\"t1\" TIMESTAMP TIME) WITH
(ttl='INF'),"));
+ } catch (final SQLException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+ @Test
+ public void testTableAuth() throws SQLException {
+ try (final Connection adminCon =
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement adminStmt = adminCon.createStatement()) {
+ adminStmt.execute("create user test 'password123456'");
+ adminStmt.execute("create database db");
+ adminStmt.execute("use db");
+ adminStmt.execute("create table test (a tag, b attribute, c int32)");
+ }
+
+ try (final Connection userCon =
+ EnvFactory.getEnv().getConnection("test", "password123456",
BaseEnv.TABLE_SQL_DIALECT);
+ final Statement userStmt = userCon.createStatement()) {
+ Assert.assertThrows(SQLException.class, () -> userStmt.execute("select *
from db.test"));
+ TestUtils.assertResultSetEqual(
+ userStmt.executeQuery("select * from information_schema.tables where
database = 'db'"),
+ "database,table_name,ttl(ms),status,comment,table_type,",
+ Collections.emptySet());
+ TestUtils.assertResultSetEqual(
+ userStmt.executeQuery("select * from information_schema.columns
where database = 'db'"),
+ "database,table_name,column_name,datatype,category,status,comment,",
+ Collections.emptySet());
+ }
+
+ try (final Connection adminCon =
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement adminStmt = adminCon.createStatement()) {
+ adminStmt.execute("GRANT SELECT ON db.test to user test");
+ }
+
+ try (final Connection userCon =
+ EnvFactory.getEnv().getConnection("test", "password123456",
BaseEnv.TABLE_SQL_DIALECT);
+ final Statement userStmt = userCon.createStatement()) {
+ try (final ResultSet resultSet = userStmt.executeQuery("SHOW
DATABASES")) {
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(showDBColumnHeaders.size(), metaData.getColumnCount());
+ for (int i = 0; i < showDBColumnHeaders.size(); i++) {
+ assertEquals(showDBColumnHeaders.get(i).getColumnName(),
metaData.getColumnName(i + 1));
+ }
+ Assert.assertTrue(resultSet.next());
+ assertEquals("db", resultSet.getString(1));
+ Assert.assertTrue(resultSet.next());
+ assertEquals("information_schema", resultSet.getString(1));
+ Assert.assertFalse(resultSet.next());
+ }
+
+ userStmt.execute("select * from db.test");
+ }
+
+ try (final Connection adminCon =
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement adminStmt = adminCon.createStatement()) {
+ adminStmt.execute("GRANT DROP ON DATABASE DB to user test");
+ }
+
+ try (final Connection userCon =
+ EnvFactory.getEnv().getConnection("test", "password123456",
BaseEnv.TABLE_SQL_DIALECT);
+ final Statement userStmt = userCon.createStatement()) {
+ userStmt.execute("use db");
+ userStmt.execute("drop table test");
+ }
+ }
+
+ // Test deadlock
+ @Test(timeout = 60000)
+ public void testConcurrentAutoCreateAndDropColumn() throws Exception {
+ try (final ITableSession session =
EnvFactory.getEnv().getTableSessionConnection();
+ final Connection adminCon =
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement adminStmt = adminCon.createStatement()) {
+ adminStmt.execute("create database db1");
+ session.executeNonQueryStatement("USE \"db1\"");
+
+ final StringBuilder sb = new StringBuilder("CREATE TABLE table8 (tag1
string tag");
+ for (int i = 0; i < 100; ++i) {
+ sb.append(String.format(", m%s string", i));
+ }
+ sb.append(")");
+ session.executeNonQueryStatement(sb.toString());
+
+ final Thread insertThread =
+ new Thread(
+ () -> {
+ for (int i = 0; i < 100; ++i) {
+ final List<IMeasurementSchema> schemaList = new
ArrayList<>();
+ schemaList.add(new MeasurementSchema("tag1",
TSDataType.STRING));
+ schemaList.add(new MeasurementSchema("attr1",
TSDataType.STRING));
+ schemaList.add(
+ new MeasurementSchema(String.format("m%s", 100 + i),
TSDataType.DOUBLE));
+ final List<ColumnCategory> columnTypes =
+ Arrays.asList(
+ ColumnCategory.TAG, ColumnCategory.ATTRIBUTE,
ColumnCategory.FIELD);
+
+ long timestamp = 0;
+
+ final Tablet tablet =
+ new Tablet(
+ "table8",
+
IMeasurementSchema.getMeasurementNameList(schemaList),
+ IMeasurementSchema.getDataTypeList(schemaList),
+ columnTypes,
+ 15);
+
+ for (int row = 0; row < 15; row++) {
+ tablet.addTimestamp(row, timestamp);
+ tablet.addValue("tag1", row, "tag:" + timestamp);
+ tablet.addValue("attr1", row, "attr:" + timestamp);
+ tablet.addValue(String.format("m%s", 100 + i), row,
timestamp * 1.0);
+ timestamp++;
+ }
+
+ try {
+ session.insert(tablet);
+ } catch (final StatementExecutionException |
IoTDBConnectionException e) {
+ throw new RuntimeException(e);
+ }
+ tablet.reset();
+ }
+ });
+
+ final Thread deletionThread =
+ new Thread(
+ () -> {
+ for (int i = 0; i < 100; ++i) {
+ try {
+ adminStmt.execute(String.format("alter table db1.table8
drop column m%s", i));
+ } catch (final SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+
+ insertThread.start();
+ deletionThread.start();
+
+ insertThread.join();
+ deletionThread.join();
+ }
+ }
+
+ @Test
+ public void testTreeViewTable() throws Exception {
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ statement.execute("create database root.another");
+ statement.execute("create database root.`重庆`.`1`.b");
+ statement.execute("create timeSeries root.`重庆`.`1`.b.`2`.S1 int32");
+ statement.execute("create timeSeries root.`重庆`.`1`.b.`2`.s2 string");
+ statement.execute("create timeSeries root.`重庆`.`1`.b.S1 int32");
+ } catch (SQLException e) {
+ fail(e.getMessage());
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("create database tree_view_db");
+ statement.execute("use tree_view_db");
+
+ try {
+ statement.execute("create view tree_table (tag1 tag, tag2 tag) as
root.**");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: Cannot specify view pattern to match more than one tree
database.",
+ e.getMessage());
+ }
+ statement.execute("create view tree_table (tag1 tag, tag2 tag) as
root.\"重庆\".\"1\".**");
+ statement.execute("drop view tree_table");
+ }
+
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ statement.execute("create timeSeries root.`重庆`.`1`.b.`1`.s1 int32");
+ } catch (SQLException e) {
+ fail(e.getMessage());
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("use tree_view_db");
+
+ try {
+ statement.execute("create view tree_table (tag1 tag, tag2 tag) as
root.\"重庆\".\"1\".**");
+ fail();
+ } catch (final SQLException e) {
+ final Set<String> result =
+ new HashSet<>(
+ Arrays.asList(
+ "617: The measurements s1 and S1 share the same lower case
when auto detecting type, please check",
+ "617: The measurements S1 and s1 share the same lower case
when auto detecting type, please check"));
+ assertTrue(result.contains(e.getMessage()));
+ }
+ }
+
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ statement.execute("drop timeSeries root.`重庆`.`1`.b.`1`.s1");
+ statement.execute("create device template t1 (S1 boolean, s9 int32)");
+ statement.execute("set schema template t1 to root.`重庆`.`1`.b.`1`");
+ statement.execute("create timeSeries root.`重庆`.`1`.b.`2`.f.g.h.S1
int32");
+
+ // Put schema cache
+ statement.execute("select S1, s2 from root.`重庆`.`1`.b.`2`");
+ } catch (SQLException e) {
+ fail(e.getMessage());
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("use tree_view_db");
+
+ try {
+ statement.execute("create view tree_table (tag1 tag, tag2 tag) as
root.\"重庆\".\"1\".**");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "614: Multiple types encountered when auto detecting type of
measurement 'S1', please check",
+ e.getMessage());
+ }
+
+ try {
+ statement.execute(
+ "create view tree_table (tag1 tag, tag2 tag, S1 field) as
root.\"重庆\".\"1\".**");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "614: Multiple types encountered when auto detecting type of
measurement 'S1', please check",
+ e.getMessage());
+ }
+ }
+
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ statement.execute("create timeSeries root.`重庆`.`1`.b.e.s1 int32");
+ } catch (SQLException e) {
+ fail(e.getMessage());
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("use tree_view_db");
+
+ // Test error message
+ try {
+ statement.execute("alter view view_not_exist add column col from col");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("550: Table 'tree_view_db.view_not_exist' does not
exist", e.getMessage());
+ }
+
+ // Temporary
+ try {
+ statement.execute(
+ "create or replace view tree_table (tag1 tag, tag2 tag, S1 int32
field, s3 boolean from S1) as root.\"重庆\".\"1\".**");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: The duplicated source measurement S1 is unsupported yet.",
e.getMessage());
+ }
+
+ try {
+ statement.execute(
+ "create or replace view tree_table (tag1 tag, tag2 tag, S1 int32
field, s3 from s2, s8 field) as root.\"重庆\".\"1\".**");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("528: Measurements not found for s8, cannot auto detect",
e.getMessage());
+ }
+
+ statement.execute(
+ "create or replace view tree_table (tag1 tag, tag2 tag, S1 int32
field, s3 from s2) as root.\"重庆\".\"1\".**");
+
+ // Cannot be written
+ try {
+ statement.execute(
+ "insert into tree_table(time, tag1, tag2, S1, s3) values (1, 1, 1,
1, 1)");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: The table tree_view_db.tree_table is a view from tree,
cannot be written or deleted from",
+ e.getMessage());
+ }
+
+ statement.execute("alter view tree_table rename to view_table");
+
+ // Test clear cache
+ try {
+ statement.execute("select * from tree_table");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("550: Table 'tree_view_db.tree_table' does not exist.",
e.getMessage());
+ }
+
+ statement.execute("alter view view_table rename column s1 to s11");
+ statement.execute("alter view view_table set properties ttl=100");
+ statement.execute("comment on view view_table is 'comment'");
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show tables details"),
+ "TableName,TTL(ms),Status,Comment,TableType,",
+ Collections.singleton("view_table,100,USING,comment,VIEW FROM
TREE,"));
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("desc view_table"),
+ "ColumnName,DataType,Category,",
+ new HashSet<>(
+ Arrays.asList(
+ "time,TIMESTAMP,TIME,",
+ "tag1,STRING,TAG,",
+ "tag2,STRING,TAG,",
+ "s11,INT32,FIELD,",
+ "s3,STRING,FIELD,")));
+ // Currently we show the device even if all of its measurements does not
match,
+ // the handling logic at query because validate it at fetching will
potentially cause a
+ // lot of time
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show devices from view_table where tag1 =
'b'"),
+ "tag1,tag2,",
+ new HashSet<>(Arrays.asList("b,`2`,", "b,null,", "b,e,")));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show devices from view_table where tag1 =
'b' and tag2 is null"),
+ "tag1,tag2,",
+ Collections.singleton("b,null,"));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show devices from view_table where tag1 =
'b' and tag2 = '`2`'"),
+ "tag1,tag2,",
+ Collections.singleton("b,`2`,"));
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("count devices from view_table"),
+ "count(devices),",
+ Collections.singleton("3,"));
+ }
+
+ // Test tree session
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ // Test create & replace + restrict
+ statement.execute(
+ "create or replace view tree_view_db.view_table (tag1 tag, tag2 tag,
s11 int32 field, s3 from s2) restrict with (ttl=100) as root.`重庆`.`1`.**");
+ fail();
+ } catch (final SQLException e) {
+ assertTrue(
+ e.getMessage().contains("The 'CreateTableView' is unsupported in
tree sql-dialect."));
+ }
+
+ // Test permission
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ // Test create & replace + restrict
+ statement.execute("create user testUser 'testUser123456'");
+ } catch (final SQLException e) {
+ fail(e.getMessage());
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv()
+ .getConnection("testUser", "testUser123456",
BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute(
+ "create or replace view tree_view_db.view_table (tag1 tag, tag2 tag,
s11 int32 field, s3 from s2) restrict with (ttl=100) as root.\"重庆\".\"1\".**");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "803: Access Denied: No permissions for this operation, please add
privilege CREATE ON tree_view_db.view_table",
+ e.getMessage());
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("grant create on tree_view_db.view_table to user
testUser");
+ } catch (final SQLException e) {
+ fail(e.getMessage());
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv()
+ .getConnection("testUser", "testUser123456",
BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute(
+ "create or replace view tree_view_db.view_table (tag1 tag, tag2 tag,
s11 int32 field, s3 from s2) restrict with (ttl=100) as root.\"重庆\".\"1\".**");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "803: Access Denied: No permissions for this operation, please add
privilege READ_SCHEMA",
+ e.getMessage());
+ }
+
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ statement.execute("grant read_schema on root.`重庆`.** to user testUser");
+ } catch (final SQLException e) {
+ fail(e.getMessage());
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv()
+ .getConnection("testUser", "testUser123456",
BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute(
+ "create or replace view tree_view_db.view_table (tag1 tag, tag2 tag,
s11 int32 field, s3 from s2) restrict with (ttl=100) as root.\"重庆\".\"1\".**");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "803: Access Denied: No permissions for this operation, please add
privilege READ_DATA",
+ e.getMessage());
+ }
+
+ try (final Connection connection = EnvFactory.getEnv().getConnection();
+ final Statement statement = connection.createStatement()) {
+ statement.execute("grant read_data on root.`重庆`.** to user testUser");
+ } catch (final SQLException e) {
+ fail(e.getMessage());
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("alter database tree_view_db set properties ttl=100");
+ statement.execute(
+ "create or replace view tree_view_db.view_table (tag1 tag, tag2 tag,
s11 int32 field, s3 from s2) restrict as root.\"重庆\".\"1\".**");
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show tables from tree_view_db"),
+ "TableName,TTL(ms),",
+ Collections.singleton("view_table,100,"));
+ } catch (final SQLException e) {
+ fail(e.getMessage());
+ }
+
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("use tree_view_db");
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show devices from view_table where tag1 =
'b' and tag2 is null"),
+ "tag1,tag2,",
+ Collections.emptySet());
+
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show create view view_table"),
+ "View,Create View,",
+ Collections.singleton(
+ "view_table,CREATE VIEW \"view_table\" (\"time\" TIMESTAMP
TIME,\"tag1\" STRING TAG,\"tag2\" STRING TAG,\"s11\" INT32 FIELD,\"s3\" STRING
FIELD FROM \"s2\") RESTRICT WITH (ttl=100) AS root.\"重庆\".\"1\".**,"));
+
+ // Can also use "show create table"
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show create table view_table"),
+ "View,Create View,",
+ Collections.singleton(
+ "view_table,CREATE VIEW \"view_table\" (\"time\" TIMESTAMP
TIME,\"tag1\" STRING TAG,\"tag2\" STRING TAG,\"s11\" INT32 FIELD,\"s3\" STRING
FIELD FROM \"s2\") RESTRICT WITH (ttl=100) AS root.\"重庆\".\"1\".**,"));
+
+ statement.execute("create table a ()");
+ try {
+ statement.execute("show create view a");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals(
+ "701: The table a is a base table, does not support show create
view.", e.getMessage());
+ }
+ try {
+ statement.execute("show create view information_schema.tables");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("701: The system view does not support show create.",
e.getMessage());
+ }
+ try {
+ statement.execute("show create table information_schema.tables");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("701: The system view does not support show create.",
e.getMessage());
+ }
+ try {
+ statement.execute("create or replace view a () as root.b.**");
+ fail();
+ } catch (final SQLException e) {
+ assertEquals("551: Table 'tree_view_db.a' already exists.",
e.getMessage());
+ }
+ }
+ }
+}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/view/CreateTableViewProcedure.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/view/CreateTableViewProcedure.java
new file mode 100644
index 00000000000..c6defba4c86
--- /dev/null
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/view/CreateTableViewProcedure.java
@@ -0,0 +1,189 @@
+/*
+ * 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.confignode.procedure.impl.schema.table.view;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.exception.IoTDBException;
+import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.schema.table.TableNodeStatus;
+import org.apache.iotdb.commons.schema.table.TreeViewSchema;
+import org.apache.iotdb.commons.schema.table.TsTable;
+import
org.apache.iotdb.confignode.consensus.request.write.table.view.PreCreateTableViewPlan;
+import org.apache.iotdb.confignode.exception.DatabaseNotExistsException;
+import
org.apache.iotdb.confignode.persistence.schema.TreeDeviceViewFieldDetector;
+import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
+import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
+import org.apache.iotdb.confignode.procedure.impl.schema.SchemaUtils;
+import
org.apache.iotdb.confignode.procedure.impl.schema.table.CreateTableProcedure;
+import org.apache.iotdb.confignode.procedure.state.schema.CreateTableState;
+import org.apache.iotdb.confignode.procedure.store.ProcedureType;
+import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.apache.tsfile.utils.Pair;
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Optional;
+
+import static org.apache.iotdb.rpc.TSStatusCode.TABLE_ALREADY_EXISTS;
+
+public class CreateTableViewProcedure extends CreateTableProcedure {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(CreateTableViewProcedure.class);
+ private boolean replace;
+ private TsTable oldView;
+ private TableNodeStatus oldStatus;
+
+ public CreateTableViewProcedure(final boolean isGeneratedByPipe) {
+ super(isGeneratedByPipe);
+ }
+
+ public CreateTableViewProcedure(
+ final String database,
+ final TsTable table,
+ final boolean replace,
+ final boolean isGeneratedByPipe) {
+ super(database, table, isGeneratedByPipe);
+ this.replace = replace;
+ }
+
+ @Override
+ protected void checkTableExistence(final ConfigNodeProcedureEnv env) {
+ if (!replace) {
+ super.checkTableExistence(env);
+ } else {
+ try {
+ final Optional<Pair<TsTable, TableNodeStatus>> oldTableAndStatus =
+ env.getConfigManager()
+ .getClusterSchemaManager()
+ .getTableAndStatusIfExists(database, table.getTableName());
+ if (oldTableAndStatus.isPresent()) {
+ if
(!TreeViewSchema.isTreeViewTable(oldTableAndStatus.get().getLeft())) {
+ setFailure(
+ new ProcedureException(
+ new IoTDBException(
+ String.format(
+ "Table '%s.%s' already exists.", database,
table.getTableName()),
+ TABLE_ALREADY_EXISTS.getStatusCode())));
+ return;
+ } else {
+ oldView = oldTableAndStatus.get().getLeft();
+ oldStatus = oldTableAndStatus.get().getRight();
+ }
+ }
+ final TDatabaseSchema schema =
+
env.getConfigManager().getClusterSchemaManager().getDatabaseSchemaByName(database);
+ if (!table.getPropValue(TsTable.TTL_PROPERTY).isPresent()
+ && schema.isSetTTL()
+ && schema.getTTL() != Long.MAX_VALUE) {
+ table.addProp(TsTable.TTL_PROPERTY, String.valueOf(schema.getTTL()));
+ }
+ setNextState(CreateTableState.PRE_CREATE);
+ } catch (final MetadataException | DatabaseNotExistsException e) {
+ setFailure(new ProcedureException(e));
+ }
+ }
+ final TSStatus status =
+ new TreeDeviceViewFieldDetector(env.getConfigManager(), table, null)
+ .detectMissingFieldTypes();
+ if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ setFailure(new ProcedureException(new IoTDBException(status)));
+ }
+ }
+
+ @Override
+ protected void preCreateTable(final ConfigNodeProcedureEnv env) {
+ final TSStatus status =
+ SchemaUtils.executeInConsensusLayer(
+ new PreCreateTableViewPlan(database, table,
TableNodeStatus.PRE_CREATE), env, LOGGER);
+ if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ setNextState(CreateTableState.PRE_RELEASE);
+ } else {
+ setFailure(new ProcedureException(new IoTDBException(status)));
+ }
+ }
+
+ @Override
+ protected void rollbackCreate(final ConfigNodeProcedureEnv env) {
+ if (Objects.isNull(oldView)) {
+ super.rollbackCreate(env);
+ return;
+ }
+ final TSStatus status =
+ SchemaUtils.executeInConsensusLayer(
+ new PreCreateTableViewPlan(database, oldView, oldStatus), env,
LOGGER);
+ if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ LOGGER.warn("Failed to rollback table creation {}.{}", database,
table.getTableName());
+ setFailure(new ProcedureException(new IoTDBException(status)));
+ }
+ }
+
+ @Override
+ public void serialize(final DataOutputStream stream) throws IOException {
+ stream.writeShort(
+ isGeneratedByPipe
+ ?
ProcedureType.PIPE_ENRICHED_CREATE_TABLE_VIEW_PROCEDURE.getTypeCode()
+ : ProcedureType.CREATE_TABLE_VIEW_PROCEDURE.getTypeCode());
+ innerSerialize(stream);
+ ReadWriteIOUtils.write(replace, stream);
+
+ ReadWriteIOUtils.write(Objects.nonNull(oldView), stream);
+ if (Objects.nonNull(oldView)) {
+ oldView.serialize(stream);
+ }
+
+ ReadWriteIOUtils.write(Objects.nonNull(oldStatus), stream);
+ if (Objects.nonNull(oldStatus)) {
+ oldStatus.serialize(stream);
+ }
+ }
+
+ @Override
+ public void deserialize(final ByteBuffer byteBuffer) {
+ super.deserialize(byteBuffer);
+ replace = ReadWriteIOUtils.readBool(byteBuffer);
+
+ if (ReadWriteIOUtils.readBool(byteBuffer)) {
+ this.oldView = TsTable.deserialize(byteBuffer);
+ }
+
+ if (ReadWriteIOUtils.readBool(byteBuffer)) {
+ this.oldStatus = TableNodeStatus.deserialize(byteBuffer);
+ }
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ return super.equals(o)
+ && replace == ((CreateTableViewProcedure) o).replace
+ && Objects.equals(oldView, ((CreateTableViewProcedure) o).oldView)
+ && Objects.equals(oldStatus, ((CreateTableViewProcedure) o).oldStatus);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), replace, oldView, oldStatus);
+ }
+}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
index 3c03ee732d0..367c8327421 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
@@ -382,8 +382,11 @@ public abstract class PipeRealtimeDataRegionSource
implements PipeExtractor {
}
pendingQueue.pollLast();
}
- if (pendingQueue.peekLast() instanceof ProgressReportEvent) {
- final ProgressReportEvent oldEvent = (ProgressReportEvent)
pendingQueue.peekLast();
+ final Event last = pendingQueue.peekLast();
+ if (last instanceof PipeRealtimeEvent
+ && ((PipeRealtimeEvent) last).getEvent() instanceof
ProgressReportEvent) {
+ final ProgressReportEvent oldEvent =
+ (ProgressReportEvent) ((PipeRealtimeEvent) last).getEvent();
oldEvent.bindProgressIndex(
oldEvent
.getProgressIndex()
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/ShowCreateTableTask.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/ShowCreateTableTask.java
new file mode 100644
index 00000000000..5acb26d90fc
--- /dev/null
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/ShowCreateTableTask.java
@@ -0,0 +1,157 @@
+/*
+ * 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.db.queryengine.plan.execution.config.metadata.relational;
+
+import org.apache.iotdb.commons.schema.column.ColumnHeader;
+import org.apache.iotdb.commons.schema.column.ColumnHeaderConstant;
+import org.apache.iotdb.commons.schema.table.TreeViewSchema;
+import org.apache.iotdb.commons.schema.table.TsTable;
+import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema;
+import org.apache.iotdb.db.queryengine.common.header.DatasetHeader;
+import org.apache.iotdb.db.queryengine.common.header.DatasetHeaderFactory;
+import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult;
+import
org.apache.iotdb.db.queryengine.plan.execution.config.executor.IConfigTaskExecutor;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.tsfile.common.conf.TSFileConfig;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.read.common.block.TsBlockBuilder;
+import org.apache.tsfile.utils.Binary;
+
+import javax.annotation.Nonnull;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.apache.iotdb.commons.conf.IoTDBConstant.TTL_INFINITE;
+
+public class ShowCreateTableTask extends AbstractTableTask {
+ public ShowCreateTableTask(final String database, final String tableName) {
+ super(database, tableName);
+ }
+
+ @Override
+ public ListenableFuture<ConfigTaskResult> execute(final IConfigTaskExecutor
configTaskExecutor)
+ throws InterruptedException {
+ return configTaskExecutor.describeTable(database, tableName, false, false);
+ }
+
+ public static void buildTsBlock(
+ final TsTable table, final SettableFuture<ConfigTaskResult> future) {
+ if (TreeViewSchema.isTreeViewTable(table)) {
+ ShowCreateViewTask.buildTsBlock(table, future);
+ return;
+ }
+ final List<TSDataType> outputDataTypes =
+ ColumnHeaderConstant.showCreateTableColumnHeaders.stream()
+ .map(ColumnHeader::getColumnType)
+ .collect(Collectors.toList());
+
+ final TsBlockBuilder builder = new TsBlockBuilder(outputDataTypes);
+ builder.getTimeColumnBuilder().writeLong(0L);
+ builder
+ .getColumnBuilder(0)
+ .writeBinary(new Binary(table.getTableName(),
TSFileConfig.STRING_CHARSET));
+ builder
+ .getColumnBuilder(1)
+ .writeBinary(new Binary(getShowCreateTableSQL(table),
TSFileConfig.STRING_CHARSET));
+ builder.declarePosition();
+
+ final DatasetHeader datasetHeader =
DatasetHeaderFactory.getShowCreateTableColumnHeader();
+ future.set(new ConfigTaskResult(TSStatusCode.SUCCESS_STATUS,
builder.build(), datasetHeader));
+ }
+
+ private static String getShowCreateTableSQL(final TsTable table) {
+ final StringBuilder builder =
+ new StringBuilder("CREATE TABLE
").append(getIdentifier(table.getTableName())).append(" (");
+
+ for (final TsTableColumnSchema schema : table.getColumnList()) {
+ switch (schema.getColumnCategory()) {
+ case TAG:
+ builder
+ .append(getIdentifier(schema.getColumnName()))
+ .append(" ")
+ .append(schema.getDataType())
+ .append(" ")
+ .append("TAG");
+ break;
+ case TIME:
+ builder
+ .append(getIdentifier(schema.getColumnName()))
+ .append(" ")
+ .append(schema.getDataType())
+ .append(" ")
+ .append("TIME");
+ break;
+ case FIELD:
+ builder
+ .append(getIdentifier(schema.getColumnName()))
+ .append(" ")
+ .append(schema.getDataType())
+ .append(" ")
+ .append("FIELD");
+ break;
+ case ATTRIBUTE:
+ builder
+ .append(getIdentifier(schema.getColumnName()))
+ .append(" ")
+ .append(schema.getDataType())
+ .append(" ")
+ .append("ATTRIBUTE");
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "Unsupported column type: " + schema.getColumnCategory());
+ }
+ if (Objects.nonNull(schema.getProps().get(TsTable.COMMENT_KEY))) {
+ builder.append(" COMMENT
").append(getString(schema.getProps().get(TsTable.COMMENT_KEY)));
+ }
+ builder.append(",");
+ }
+
+ if (!table.getColumnList().isEmpty()) {
+ builder.deleteCharAt(builder.length() - 1);
+ }
+
+ builder.append(")");
+ if (table.getPropValue(TsTable.COMMENT_KEY).isPresent()) {
+ builder.append(" COMMENT
").append(getString(table.getPropValue(TsTable.COMMENT_KEY).get()));
+ }
+
+ String ttlString =
table.getPropValue(TsTable.TTL_PROPERTY).orElse(TTL_INFINITE);
+ if (ttlString.equals(TTL_INFINITE)) {
+ ttlString = "'" + ttlString + "'";
+ }
+ builder.append(" WITH (ttl=").append(ttlString).append(")");
+
+ return builder.toString();
+ }
+
+ public static String getIdentifier(@Nonnull final String identifier) {
+ return "\"" + identifier.replace("\"", "\"\"") + "\"";
+ }
+
+ public static String getString(@Nonnull final String string) {
+ return "'" + string.replace("'", "''") + "'";
+ }
+}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/ShowCreateViewTask.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/ShowCreateViewTask.java
new file mode 100644
index 00000000000..9fc573f8988
--- /dev/null
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/ShowCreateViewTask.java
@@ -0,0 +1,162 @@
+/*
+ * 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.db.queryengine.plan.execution.config.metadata.relational;
+
+import org.apache.iotdb.commons.exception.SemanticException;
+import org.apache.iotdb.commons.schema.column.ColumnHeader;
+import org.apache.iotdb.commons.schema.column.ColumnHeaderConstant;
+import org.apache.iotdb.commons.schema.table.TreeViewSchema;
+import org.apache.iotdb.commons.schema.table.TsTable;
+import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema;
+import org.apache.iotdb.db.queryengine.common.header.DatasetHeader;
+import org.apache.iotdb.db.queryengine.common.header.DatasetHeaderFactory;
+import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult;
+import
org.apache.iotdb.db.queryengine.plan.execution.config.executor.IConfigTaskExecutor;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.tsfile.common.conf.TSFileConfig;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.read.common.block.TsBlockBuilder;
+import org.apache.tsfile.utils.Binary;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.apache.iotdb.commons.conf.IoTDBConstant.TTL_INFINITE;
+import static
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.ShowCreateTableTask.getIdentifier;
+import static
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.ShowCreateTableTask.getString;
+
+public class ShowCreateViewTask extends AbstractTableTask {
+ public ShowCreateViewTask(final String database, final String tableName) {
+ super(database, tableName);
+ }
+
+ @Override
+ public ListenableFuture<ConfigTaskResult> execute(final IConfigTaskExecutor
configTaskExecutor)
+ throws InterruptedException {
+ return configTaskExecutor.describeTable(database, tableName, false, true);
+ }
+
+ public static void buildTsBlock(
+ final TsTable table, final SettableFuture<ConfigTaskResult> future) {
+ if (!TreeViewSchema.isTreeViewTable(table)) {
+ throw new SemanticException(
+ "The table "
+ + table.getTableName()
+ + " is a base table, does not support show create view.");
+ }
+ final List<TSDataType> outputDataTypes =
+ ColumnHeaderConstant.showCreateViewColumnHeaders.stream()
+ .map(ColumnHeader::getColumnType)
+ .collect(Collectors.toList());
+
+ final TsBlockBuilder builder = new TsBlockBuilder(outputDataTypes);
+ builder.getTimeColumnBuilder().writeLong(0L);
+ builder
+ .getColumnBuilder(0)
+ .writeBinary(new Binary(table.getTableName(),
TSFileConfig.STRING_CHARSET));
+ builder
+ .getColumnBuilder(1)
+ .writeBinary(new Binary(getShowCreateViewSQL(table),
TSFileConfig.STRING_CHARSET));
+ builder.declarePosition();
+
+ final DatasetHeader datasetHeader =
DatasetHeaderFactory.getShowCreateViewColumnHeader();
+ future.set(new ConfigTaskResult(TSStatusCode.SUCCESS_STATUS,
builder.build(), datasetHeader));
+ }
+
+ public static String getShowCreateViewSQL(final TsTable table) {
+ final StringBuilder builder =
+ new StringBuilder("CREATE VIEW
").append(getIdentifier(table.getTableName())).append(" (");
+
+ for (final TsTableColumnSchema schema : table.getColumnList()) {
+ switch (schema.getColumnCategory()) {
+ case TAG:
+ builder
+ .append(getIdentifier(schema.getColumnName()))
+ .append(" ")
+ .append(schema.getDataType())
+ .append(" ")
+ .append("TAG");
+ break;
+ case TIME:
+ builder
+ .append(getIdentifier(schema.getColumnName()))
+ .append(" ")
+ .append(schema.getDataType())
+ .append(" ")
+ .append("TIME");
+ break;
+ case FIELD:
+ builder
+ .append(getIdentifier(schema.getColumnName()))
+ .append(" ")
+ .append(schema.getDataType())
+ .append(" ")
+ .append("FIELD");
+ if (Objects.nonNull(TreeViewSchema.getOriginalName(schema))) {
+ builder.append(" FROM
").append(getIdentifier(TreeViewSchema.getOriginalName(schema)));
+ }
+ break;
+ case ATTRIBUTE:
+ default:
+ throw new UnsupportedOperationException(
+ "Unsupported column type: " + schema.getColumnCategory());
+ }
+ if (Objects.nonNull(schema.getProps().get(TsTable.COMMENT_KEY))) {
+ builder.append(" COMMENT
").append(getString(schema.getProps().get(TsTable.COMMENT_KEY)));
+ }
+ builder.append(",");
+ }
+
+ if (!table.getColumnList().isEmpty()) {
+ builder.deleteCharAt(builder.length() - 1);
+ }
+
+ builder.append(")");
+
+ if (table.getPropValue(TsTable.COMMENT_KEY).isPresent()) {
+ builder.append(" COMMENT
").append(getString(table.getPropValue(TsTable.COMMENT_KEY).get()));
+ }
+
+ if (TreeViewSchema.isRestrict(table)) {
+ builder.append(" RESTRICT");
+ }
+
+ String ttlString =
table.getPropValue(TsTable.TTL_PROPERTY).orElse(TTL_INFINITE);
+ if (ttlString.equals(TTL_INFINITE)) {
+ ttlString = "'" + ttlString + "'";
+ }
+ builder.append(" WITH (ttl=").append(ttlString).append(")");
+
+ builder.append(" AS ");
+
+ final String[] pathNodes =
TreeViewSchema.getPrefixPattern(table).getNodes();
+ builder.append(pathNodes[0]);
+ for (int i = 1; i < pathNodes.length - 1; ++i) {
+ builder.append(".\"").append(pathNodes[i].replace("`", "")).append("\"");
+ }
+ builder.append(".").append(pathNodes[pathNodes.length - 1]);
+
+ return builder.toString();
+ }
+}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
new file mode 100644
index 00000000000..4a82bdd70a0
--- /dev/null
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
@@ -0,0 +1,431 @@
+/*
+ * 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.schema.table;
+
+import org.apache.iotdb.commons.conf.CommonDescriptor;
+import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.exception.runtime.SchemaExecutionException;
+import org.apache.iotdb.commons.schema.table.column.TimeColumnSchema;
+import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
+import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema;
+import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchemaUtil;
+import org.apache.iotdb.commons.utils.CommonDateTimeUtils;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.tsfile.utils.Pair;
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import static org.apache.iotdb.commons.conf.IoTDBConstant.TTL_INFINITE;
+
+@ThreadSafe
+public class TsTable {
+
+ public static final String TIME_COLUMN_NAME = "time";
+ public static final String COMMENT_KEY = "__comment";
+ public static final String TTL_PROPERTY = "ttl";
+ public static final Set<String> TABLE_ALLOWED_PROPERTIES =
Collections.singleton(TTL_PROPERTY);
+ private static final String OBJECT_STRING_ERROR =
+ "When there are object fields, the %s %s shall not be '.', '..' or
contain './', '.\\'.";
+ protected String tableName;
+
+ private final Map<String, TsTableColumnSchema> columnSchemaMap = new
LinkedHashMap<>();
+ private final Map<String, Integer> tagColumnIndexMap = new HashMap<>();
+
+ private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+
+ /**
+ * Global sequence generator providing unique, monotonically increasing IDs
across all instances.
+ * Initialized to -1 to ensure the first ID is 0.
+ */
+ private static final AtomicLong GLOBAL_SEQUENCE = new AtomicLong(-1);
+
+ private final transient Long creationId = GLOBAL_SEQUENCE.getAndIncrement();
+ private final transient AtomicLong instanceVersion = new AtomicLong(0L);
+
+ private final transient AtomicBoolean isNotWrite = new AtomicBoolean(true);
+ private final AtomicReference<Pair<Long, List<TsTableColumnSchema>>>
tagColumnSchemas =
+ new AtomicReference<>();
+
+ private Map<String, String> props = null;
+
+ // Cache, avoid string parsing
+ private transient long ttlValue = Long.MIN_VALUE;
+ private transient int tagNums = 0;
+ private transient int fieldNum = 0;
+
+ // Initiated during creation and never changed the reference
+ private transient TsTableColumnSchema timeColumnSchema;
+
+ public TsTable(final String tableName) {
+ this.tableName = tableName;
+ }
+
+ // This interface is used by InformationSchema table, so time column is not
necessary
+ public TsTable(String tableName, ImmutableList<TsTableColumnSchema>
columnSchemas) {
+ this.tableName = tableName;
+ columnSchemas.forEach(
+ columnSchema -> {
+ columnSchemaMap.put(columnSchema.getColumnName(), columnSchema);
+ if (columnSchema instanceof TimeColumnSchema) {
+ timeColumnSchema = columnSchema;
+ }
+ });
+ }
+
+ public TsTable(TsTable origin) {
+ this.tableName = origin.tableName;
+ origin.columnSchemaMap.forEach((col, schema) ->
this.columnSchemaMap.put(col, schema.copy()));
+ this.tagColumnIndexMap.putAll(origin.tagColumnIndexMap);
+ this.props = origin.props == null ? null : new HashMap<>(origin.props);
+ this.ttlValue = origin.ttlValue;
+ this.tagNums = origin.tagNums;
+ this.fieldNum = origin.fieldNum;
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ /**
+ * Get column schema with optimistic lock for fast reads. This method uses a
lock-free fast path
+ * when there's no concurrent write operation, significantly improving read
performance.
+ *
+ * @param columnName the column name to query
+ * @return the column schema, or null if not found
+ */
+ public TsTableColumnSchema getColumnSchema(final String columnName) {
+ final long versionBefore = instanceVersion.get();
+ final TsTableColumnSchema result = columnSchemaMap.get(columnName);
+ if (isNotWrite.get() && instanceVersion.get() == versionBefore) {
+ return result;
+ }
+
+ // Slow path: write in progress or version changed, acquire read lock
+ readWriteLock.readLock().lock();
+ try {
+ return columnSchemaMap.get(columnName);
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
+ }
+
+ // No need to acquire lock, because the time column is fixed after table
creation
+ // And the inner name is protected by the volatile keyword
+ public TsTableColumnSchema getTimeColumnSchema() {
+ if (Objects.isNull(timeColumnSchema)) {
+ timeColumnSchema =
+ columnSchemaMap.values().stream()
+ .filter(column -> column instanceof TimeColumnSchema)
+ .findFirst()
+ .orElse(null);
+ }
+ return timeColumnSchema;
+ }
+
+ /**
+ * Execute a write operation with optimistic lock support. This method
handles the write flag and
+ * version increment automatically.
+ *
+ * @param writeOperation the write operation to execute
+ */
+ private void executeWrite(Runnable writeOperation) {
+ readWriteLock.writeLock().lock();
+ isNotWrite.set(false);
+ try {
+ writeOperation.run();
+ } finally {
+ instanceVersion.incrementAndGet();
+ isNotWrite.set(true);
+ readWriteLock.writeLock().unlock();
+ }
+ }
+
+ public int getTagColumnOrdinal(final String columnName) {
+ readWriteLock.readLock().lock();
+ try {
+ return tagColumnIndexMap.getOrDefault(columnName.toLowerCase(), -1);
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
+ }
+
+ public List<TsTableColumnSchema> getTagColumnSchemaList() {
+ Pair<Long, List<TsTableColumnSchema>> VersionAndTagColumnSchemas =
tagColumnSchemas.get();
+ if (VersionAndTagColumnSchemas != null
+ && isNotWrite.get()
+ && VersionAndTagColumnSchemas.getLeft() == instanceVersion.get()) {
+ return VersionAndTagColumnSchemas.getRight();
+ }
+
+ readWriteLock.readLock().lock();
+ try {
+ List<TsTableColumnSchema> tagColumnSchemaList = new
ArrayList<>(tagColumnIndexMap.size());
+ for (final TsTableColumnSchema columnSchema : columnSchemaMap.values()) {
+ if
(TsTableColumnCategory.TAG.equals(columnSchema.getColumnCategory())) {
+ tagColumnSchemaList.add(columnSchema);
+ }
+ }
+ VersionAndTagColumnSchemas = new Pair<>(instanceVersion.get(),
tagColumnSchemaList);
+ return tagColumnSchemaList;
+ } finally {
+ tagColumnSchemas.set(VersionAndTagColumnSchemas);
+ readWriteLock.readLock().unlock();
+ }
+ }
+
+ // Currently only supports device view
+ public void renameTable(final String newName) {
+ executeWrite(() -> tableName = newName);
+ }
+
+ public void addColumnSchema(final TsTableColumnSchema columnSchema) {
+ executeWrite(
+ () -> {
+ columnSchemaMap.put(columnSchema.getColumnName(), columnSchema);
+ if
(columnSchema.getColumnCategory().equals(TsTableColumnCategory.TAG)) {
+ tagNums++;
+ tagColumnIndexMap.put(columnSchema.getColumnName(), tagNums - 1);
+ } else if
(columnSchema.getColumnCategory().equals(TsTableColumnCategory.FIELD)) {
+ fieldNum++;
+ }
+ });
+ }
+
+ public void renameColumnSchema(final String oldName, final String newName) {
+ executeWrite(
+ () -> {
+ // Ensures idempotency
+ if (columnSchemaMap.containsKey(oldName)) {
+ final TsTableColumnSchema schema = columnSchemaMap.get(oldName);
+ final Map<String, String> oldProps = schema.getProps();
+ oldProps.computeIfAbsent(TreeViewSchema.ORIGINAL_NAME, k ->
schema.getColumnName());
+ schema.setColumnName(newName);
+ }
+ });
+ }
+
+ public void removeColumnSchema(final String columnName) {
+ executeWrite(
+ () -> {
+ final TsTableColumnSchema columnSchema =
columnSchemaMap.get(columnName);
+ if (columnSchema != null
+ &&
columnSchema.getColumnCategory().equals(TsTableColumnCategory.TAG)) {
+ throw new SchemaExecutionException("Cannot remove an tag column: "
+ columnName);
+ } else if (columnSchema != null) {
+ columnSchemaMap.remove(columnName);
+ if
(columnSchema.getColumnCategory().equals(TsTableColumnCategory.FIELD)) {
+ fieldNum--;
+ }
+ }
+ });
+ }
+
+ public int getColumnNum() {
+ readWriteLock.readLock().lock();
+ try {
+ return columnSchemaMap.size();
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
+ }
+
+ public int getTagNum() {
+ readWriteLock.readLock().lock();
+ try {
+ return tagNums;
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
+ }
+
+ public int getFieldNum() {
+ readWriteLock.readLock().lock();
+ try {
+ return fieldNum;
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
+ }
+
+ public List<TsTableColumnSchema> getColumnList() {
+ readWriteLock.readLock().lock();
+ try {
+ return new ArrayList<>(columnSchemaMap.values());
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
+ }
+
+ // This shall only be called on DataNode, where the tsTable is replaced
completely thus an old
+ // cache won't pollute the newest value
+ public long getCachedTableTTL() {
+ // Cache for performance
+ if (ttlValue < 0) {
+ ttlValue = getTableTTL();
+ }
+ return ttlValue;
+ }
+
+ public long getTableTTL() {
+ final Optional<String> ttl = getPropValue(TTL_PROPERTY);
+ return ttl.isPresent() && !ttl.get().equalsIgnoreCase(TTL_INFINITE)
+ ? CommonDateTimeUtils.convertMilliTimeWithPrecision(
+ Long.parseLong(ttl.get()),
+ CommonDescriptor.getInstance().getConfig().getTimestampPrecision())
+ : Long.MAX_VALUE;
+ }
+
+ public Map<String, String> getProps() {
+ readWriteLock.readLock().lock();
+ try {
+ return props;
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
+ }
+
+ public Pair<Long, Long> getInstanceVersion() {
+ return new Pair<>(creationId, instanceVersion.get());
+ }
+
+ public boolean containsPropWithoutLock(final String propKey) {
+ return props != null && props.containsKey(propKey);
+ }
+
+ public Optional<String> getPropValue(final String propKey) {
+ readWriteLock.readLock().lock();
+ try {
+ return props != null && props.containsKey(propKey)
+ ? Optional.of(props.get(propKey))
+ : Optional.empty();
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
+ }
+
+ public void addProp(final String key, final String value) {
+ executeWrite(
+ () -> {
+ if (props == null) {
+ props = new HashMap<>();
+ }
+ props.put(key, value);
+ });
+ }
+
+ public void removeProp(final String key) {
+ executeWrite(
+ () -> {
+ if (props == null) {
+ return;
+ }
+ props.remove(key);
+ });
+ }
+
+ public void serialize(final OutputStream stream) throws IOException {
+ ReadWriteIOUtils.write(tableName, stream);
+ ReadWriteIOUtils.write(columnSchemaMap.size(), stream);
+ for (final TsTableColumnSchema columnSchema : columnSchemaMap.values()) {
+ TsTableColumnSchemaUtil.serialize(columnSchema, stream);
+ }
+ ReadWriteIOUtils.write(props, stream);
+ }
+
+ public static TsTable deserialize(final InputStream inputStream) throws
IOException {
+ final String name = ReadWriteIOUtils.readString(inputStream);
+ final int columnNum = ReadWriteIOUtils.readInt(inputStream);
+ if (columnNum < 0) {
+ return new NonCommittableTsTable(name);
+ }
+ final TsTable table = new TsTable(name);
+ for (int i = 0; i < columnNum; i++) {
+ table.addColumnSchema(TsTableColumnSchemaUtil.deserialize(inputStream));
+ }
+ table.props = ReadWriteIOUtils.readMap(inputStream);
+ return table;
+ }
+
+ public static TsTable deserialize(final ByteBuffer buffer) {
+ final String name = ReadWriteIOUtils.readString(buffer);
+ final int columnNum = ReadWriteIOUtils.readInt(buffer);
+ if (columnNum < 0) {
+ return new NonCommittableTsTable(name);
+ }
+ final TsTable table = new TsTable(name);
+ for (int i = 0; i < columnNum; i++) {
+ table.addColumnSchema(TsTableColumnSchemaUtil.deserialize(buffer));
+ }
+ table.props = ReadWriteIOUtils.readMap(buffer);
+ return table;
+ }
+
+ public void setProps(Map<String, String> props) {
+ executeWrite(() -> this.props = props);
+ }
+
+ public void checkTableNameAndObjectNames4Object() throws MetadataException {
+ throw new MetadataException("The object type column is not supported.");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(tableName);
+ }
+
+ @Override
+ public String toString() {
+ return "TsTable{"
+ + "tableName='"
+ + tableName
+ + '\''
+ + ", columnSchemaMap="
+ + columnSchemaMap
+ + ", props="
+ + props
+ + '}';
+ }
+}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/column/TsTableColumnSchema.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/column/TsTableColumnSchema.java
new file mode 100644
index 00000000000..8342c08365f
--- /dev/null
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/column/TsTableColumnSchema.java
@@ -0,0 +1,112 @@
+/*
+ * 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.schema.table.column;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+import org.apache.tsfile.write.schema.IMeasurementSchema;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+public abstract class TsTableColumnSchema {
+
+ protected volatile String columnName;
+
+ protected TSDataType dataType;
+
+ protected Map<String, String> props = null;
+
+ TsTableColumnSchema(final String columnName, final TSDataType dataType) {
+ this.columnName = columnName;
+ this.dataType = dataType;
+ }
+
+ TsTableColumnSchema(
+ final String columnName, final TSDataType dataType, final Map<String,
String> props) {
+ this.columnName = columnName;
+ this.dataType = dataType;
+ this.props = props;
+ }
+
+ // Only used for column renaming
+ public TsTableColumnSchema setColumnName(String columnName) {
+ this.columnName = columnName;
+ return this;
+ }
+
+ public String getColumnName() {
+ return columnName;
+ }
+
+ public TSDataType getDataType() {
+ return dataType;
+ }
+
+ public Map<String, String> getProps() {
+ if (Objects.isNull(props)) {
+ props = new HashMap<>();
+ }
+ return props;
+ }
+
+ public abstract TsTableColumnCategory getColumnCategory();
+
+ public IMeasurementSchema getMeasurementSchema() {
+ return new MeasurementSchema(columnName, dataType);
+ }
+
+ void serialize(final OutputStream outputStream) throws IOException {
+ ReadWriteIOUtils.write(columnName, outputStream);
+ ReadWriteIOUtils.write(dataType, outputStream);
+ ReadWriteIOUtils.write(props, outputStream);
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ return super.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(columnName);
+ }
+
+ public void setDataType(final TSDataType dataType) {
+ this.dataType = dataType;
+ }
+
+ public abstract TsTableColumnSchema copy();
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("columnName", columnName)
+ .add("dataType", dataType)
+ .add("props", props)
+ .toString();
+ }
+}