diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBInsertTableIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBInsertTableIT.java index b3a601c83dbe6..b079d022e7946 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBInsertTableIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBInsertTableIT.java @@ -535,6 +535,7 @@ public void testInsertMultiRowWithWrongTimestampPrecision() throws SQLException Statement st1 = connection.createStatement()) { try { st1.execute("use \"test\""); + st1.execute("CREATE TABLE wf16 (tag1 string tag, status boolean field)"); st1.execute( "insert into wf16(tag1, time, status) values('wt01', 1618283005586000, true), ('wt01', 1618283005586001, false)"); fail(); diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/rest/it/IoTDBRestServiceInsertValuesIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/rest/it/IoTDBRestServiceInsertValuesIT.java index d4c95063bab8e..1e2a82ad1f3fb 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/rest/it/IoTDBRestServiceInsertValuesIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/rest/it/IoTDBRestServiceInsertValuesIT.java @@ -284,7 +284,7 @@ public void testInsertWithWrongMeasurementNum1() { public void testInsertWithWrongMeasurementNum2() { nonQuery( sqlHandler( - "t1", "create table wf04 (tag1 string tag, status int32, temperature int32 field)")); + "t1", "create table wf05 (tag1 string tag, status int32, temperature int32 field)")); JsonObject jsonObject = nonQuery( sqlHandler( 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 index 9e322b840ae82..9730c909ba2be 100644 --- 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 @@ -369,10 +369,13 @@ public void testInformationSchema() throws SQLException { Arrays.asList( "create database information_schema", "drop database information_schema", - "create table information_schema.tableA ()", + // 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", - "insert into information_schema.tables (database) values('db')", + // 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) { 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 index 0923877d9771b..57d482dd1b057 100644 --- 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 @@ -610,31 +610,9 @@ public void testManageTable() { } // Test time column + // More time column tests are included in other IT statement.execute("create table test100 (time time)"); statement.execute("create table test101 (time timestamp time)"); - - try { - statement.execute("create table test102 (time timestamp tag)"); - fail(); - } catch (final SQLException e) { - assertEquals( - "701: The time column category shall be bounded with column name 'time'.", - e.getMessage()); - } - - try { - statement.execute("create table test102 (time tag)"); - fail(); - } catch (final SQLException e) { - assertEquals("701: The time column's type shall be 'timestamp'.", e.getMessage()); - } - - try { - statement.execute("create table test102 (time time, time time)"); - fail(); - } catch (final SQLException e) { - assertEquals("701: Columns in table shall not share the same name time.", e.getMessage()); - } } catch (final SQLException e) { e.printStackTrace(); fail(e.getMessage()); diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableWithDefinedTimeIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableWithDefinedTimeIT.java index 4055e8fb5d908..0d37085ad484b 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableWithDefinedTimeIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableWithDefinedTimeIT.java @@ -129,7 +129,8 @@ public void testCreateTable() { "create table shared_time_name(device string tag, time int64 field, s1 int32 field)"); fail("Columns in table shall not share the same name time when creating table"); } catch (SQLException e) { - assertEquals("701: Columns in table shall not share the same name time.", e.getMessage()); + assertEquals( + "701: Columns in table shall not share the same name: 'time'.", e.getMessage()); } } catch (SQLException e) { @@ -217,7 +218,8 @@ public void testCreateView() { "create view shared_time_time(device string tag, time int64 field, s1 int32 field) as root.tt.**"); fail("Columns in view shall not share the same name time when creating table"); } catch (SQLException e) { - assertEquals("701: Columns in table shall not share the same name time.", e.getMessage()); + assertEquals( + "701: Columns in table shall not share the same name: 'time'.", e.getMessage()); } } catch (SQLException e) { diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBUserDefinedTimeIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBUserDefinedTimeIT.java deleted file mode 100644 index 60cb94d5bba3e..0000000000000 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBUserDefinedTimeIT.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * 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.AfterClass; -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.SQLException; -import java.sql.Statement; -import java.util.Arrays; - -import static org.apache.iotdb.db.it.utils.TestUtils.prepareTableData; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -@RunWith(IoTDBTestRunner.class) -@Category({TableLocalStandaloneIT.class, TableClusterIT.class}) -public class IoTDBUserDefinedTimeIT { - - private static final String TABLE_DATABASE = "user_defined_time"; - private static final String VIEW_DATABASE = "user_defined_time_for_view"; - private static final String SELECT_DATABASE = "select_agg_function"; - private static final String[] SQLS = - new String[] { - "CREATE DATABASE " + TABLE_DATABASE, - "CREATE DATABASE " + VIEW_DATABASE, - "CREATE DATABASE " + SELECT_DATABASE - }; - private final String header = "ColumnName,DataType,Category,"; - - @BeforeClass - public static void setUp() throws Exception { - EnvFactory.getEnv().initClusterEnvironment(); - prepareTableData(SQLS); - } - - @AfterClass - public static void tearDown() throws Exception { - EnvFactory.getEnv().cleanClusterEnvironment(); - } - - @Test - public void testCreateTable() { - try (final Connection connection = - EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); - final Statement statement = connection.createStatement()) { - statement.execute("use " + TABLE_DATABASE); - - // create table and do not assign the time column name - try { - statement.execute( - "create table default_not_assign_time(device string tag, s1 int32 field)"); - TestUtils.assertResultSetEqual( - statement.executeQuery("describe default_not_assign_time"), - header, - Arrays.asList("time,TIMESTAMP,TIME,", "device,STRING,TAG,", "s1,INT32,FIELD,")); - } catch (SQLException e) { - fail("create table without time info fails, the specific message: " + e.getMessage()); - } - - // create table and assign the time column name - try { - statement.execute( - "create table time_in_first(date_time timestamp time, device string tag, s1 int32 field)"); - TestUtils.assertResultSetEqual( - statement.executeQuery("describe time_in_first"), - header, - Arrays.asList("date_time,TIMESTAMP,TIME,", "device,STRING,TAG,", "s1,INT32,FIELD,")); - } catch (SQLException e) { - fail("assign the name of time column fails, the specific message: " + e.getMessage()); - } - - // create table which of the time column not at the first column - try { - statement.execute( - "create table time_not_in_first(device string tag, date_time timestamp time, s1 int32 field)"); - TestUtils.assertResultSetEqual( - statement.executeQuery("describe time_not_in_first"), - header, - Arrays.asList("device,STRING,TAG,", "date_time,TIMESTAMP,TIME,", "s1,INT32,FIELD,")); - } catch (SQLException e) { - fail("assign the name of time column fails, the specific message: " + e.getMessage()); - } - - // create table with multi time-column - try { - statement.execute( - "create table with_multi_time(time_type timestamp time, device string tag, date_time timestamp time, s1 int32 field)"); - fail("Creating table is not be allowed to assign two time columns"); - } catch (SQLException e) { - assertEquals("701: A table cannot have more than one time column", e.getMessage()); - } - - // create table with time column that is not timestamp data type - try { - statement.execute( - "create table time_other_type(device string tag, date_time int64 time, s1 int32 field)"); - fail("The time column has to be assigned a timestamp data type when creating table"); - } catch (SQLException e) { - assertEquals("701: The time column's type shall be 'timestamp'.", e.getMessage()); - } - - // Columns in table shall not share the same name time when creating table - try { - statement.execute( - "create table shared_time_name(device string tag, time int64 field, s1 int32 field)"); - fail("Columns in table shall not share the same name time when creating table"); - } catch (SQLException e) { - assertEquals("701: Columns in table shall not share the same name time.", e.getMessage()); - } - - } catch (SQLException e) { - e.printStackTrace(); - fail(e.getMessage()); - } - } - - private void prepareTreeData() { - try (final Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TREE_SQL_DIALECT); - final Statement statement = connection.createStatement()) { - statement.execute("create timeseries root.tt.device.s1 with datatype=int32"); - } catch (SQLException e) { - e.printStackTrace(); - fail(e.getMessage()); - } - } - - @Test - public void testCreateView() { - prepareTreeData(); - - try (final Connection connection = - EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); - final Statement statement = connection.createStatement()) { - statement.execute("use " + VIEW_DATABASE); - - // create view and do not assign the time column name - try { - statement.execute( - "create view default_not_assign_time(device string tag, s1 int32 field) as root.tt.**"); - TestUtils.assertResultSetEqual( - statement.executeQuery("describe default_not_assign_time"), - header, - Arrays.asList("time,TIMESTAMP,TIME,", "device,STRING,TAG,", "s1,INT32,FIELD,")); - } catch (SQLException e) { - fail("create table without time info fails, the specific message: " + e.getMessage()); - } - - // create view which of the time column at the first column - try { - statement.execute( - "create view time_in_first(date_time timestamp time, device string tag, s1 int32 field) as root.tt.**"); - TestUtils.assertResultSetEqual( - statement.executeQuery("describe time_in_first"), - header, - Arrays.asList("date_time,TIMESTAMP,TIME,", "device,STRING,TAG,", "s1,INT32,FIELD,")); - } catch (SQLException e) { - fail("assign the name of time column fails, the specific message: " + e.getMessage()); - } - - // create view which of the time column not at the first column - try { - statement.execute( - "create view time_not_in_first(device string tag, date_time timestamp time, s1 int32 field) as root.tt.**"); - TestUtils.assertResultSetEqual( - statement.executeQuery("describe time_not_in_first"), - header, - Arrays.asList("device,STRING,TAG,", "date_time,TIMESTAMP,TIME,", "s1,INT32,FIELD,")); - } catch (SQLException e) { - fail("assign the name of time column fails, the specific message: " + e.getMessage()); - } - - // create view with multi time-column - try { - statement.execute( - "create view with_multi_time(time_type timestamp time, device string tag, date_time timestamp time, s1 int32 field) as root.tt.**"); - fail("Creating view is not be allowed to assign two time columns"); - } catch (SQLException e) { - assertEquals("701: A table cannot have more than one time column", e.getMessage()); - } - - // create table with time column that is not timestamp data type - try { - statement.execute( - "create view time_other_type(device string tag, date_time int64 time, s1 int32 field) as root.tt.**"); - fail("The time column has to be assigned a timestamp data type when creating view"); - } catch (SQLException e) { - assertEquals("701: The time column's type shall be 'timestamp'.", e.getMessage()); - } - - // Columns in table shall not share the same name time when creating table - try { - statement.execute( - "create view shared_time_time(device string tag, time int64 field, s1 int32 field) as root.tt.**"); - fail("Columns in view shall not share the same name time when creating table"); - } catch (SQLException e) { - assertEquals("701: Columns in table shall not share the same name time.", e.getMessage()); - } - - } catch (SQLException e) { - e.printStackTrace(); - fail(e.getMessage()); - } - } -} diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java index bf7d0c91ba6f0..6f2df1e129ae5 100644 --- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java +++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java @@ -401,7 +401,8 @@ public void testTableSerialization() throws Exception { final TsTable table = tables.get(0); assertEquals("table" + i, table.getTableName()); assertEquals(1, table.getTagNum()); - assertEquals(4, table.getColumnNum()); + // currently, only construct the TsTable would not carry the time column + assertEquals(3, table.getColumnNum()); } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AccumulatorFactory.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AccumulatorFactory.java index 81106f8645ae4..cef6c365c915f 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AccumulatorFactory.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AccumulatorFactory.java @@ -101,6 +101,8 @@ public static TableAccumulator createAccumulator( boolean distinct) { TableAccumulator result; + // Input expression size of 1 indicates aggregation split has occurred and this is a final + // aggregation if (aggregationType == TAggregationType.UDAF) { // If UDAF accumulator receives raw input, it needs to check input's attribute result = createUDAFAccumulator(functionName, inputDataTypes, inputAttributes); @@ -139,7 +141,7 @@ public static TableAccumulator createAccumulator( : new FirstByDescAccumulator( inputDataTypes.get(0), inputDataTypes.get(1), xIsTimeColumn, yIsTimeColumn); } - } else if (LAST.getFunctionName().equals(functionName)) { + } else if (LAST.getFunctionName().equals(functionName) && inputExpressions.size() > 1) { boolean orderKeyIsTimeColumn = isTimeColumn(inputExpressions.get(1), timeColumnName); result = ascending || !orderKeyIsTimeColumn @@ -149,9 +151,8 @@ public static TableAccumulator createAccumulator( isTimeColumn(inputExpressions.get(0), timeColumnName), isMeasurementColumn(inputExpressions.get(0), measurementColumnNames), isAggTableScan); - } else if (FIRST.getFunctionName().equals(functionName)) { + } else if (FIRST.getFunctionName().equals(functionName) && inputExpressions.size() > 1) { boolean orderKeyIsTimeColumn = isTimeColumn(inputExpressions.get(1), timeColumnName); - result = ascending && orderKeyIsTimeColumn ? new FirstAccumulator(inputDataTypes.get(0), isAggTableScan) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java index 6989414a91819..aa1f43d95ea42 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java @@ -607,7 +607,7 @@ private Pair parseTable4CreateTableOrView( if (table.getColumnSchema(columnName) != null) { throw new SemanticException( - String.format("Columns in table shall not share the same name %s.", columnName)); + String.format("Columns in table shall not share the same name: '%s'.", columnName)); } // allow the user create time column diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java index 32d6d94460948..a2be5ec968f9e 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java @@ -1188,20 +1188,20 @@ private boolean checkArgumentIsTimestamp(Expression argument, List visibl (argument instanceof DereferenceExpression) ? ((DereferenceExpression) argument) .getField() - .orElseThrow(() -> new SemanticException("the input field do not exists")) + .orElseThrow(() -> new SemanticException("the input field does not exist")) .toString() : argument.toString(); for (Field field : visibleFields) { if (field .getName() - .orElseThrow(() -> new SemanticException("the field in table do not hava the name")) + .orElseThrow(() -> new SemanticException("the field in table does not have a name")) .equalsIgnoreCase(argumentName)) { return field.getType() == TIMESTAMP; } } // should never reach here - throw new SemanticException("the input argument do not exists"); + throw new SemanticException("the input argument does not exist"); } /** Retrieves the effective time column name from the relation's visible fields. */ diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java index ae15c70100b70..bc6d54c37e7fa 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java @@ -1877,12 +1877,12 @@ protected List visitFunctionCall(FunctionCall node, Scope context) { (childrenResultList.get(i).size() == maxSize) ? baseIndex : new AtomicInteger(0); } for (int i = 0; i < maxSize; i++) { - ImmutableList.Builder operandListBuilder = new ImmutableList.Builder<>(); + List operandListBuilder = new ArrayList<>(childrenIndexes.length); for (int j = 0; j < childrenIndexes.length; j++) { int operandIndexInResult = childrenIndexes[j].get(); operandListBuilder.add(childrenResultList.get(j).get(operandIndexInResult)); } - resultBuilder.add(new FunctionCall(node.getName(), operandListBuilder.build())); + resultBuilder.add(new FunctionCall(node.getName(), operandListBuilder)); baseIndex.getAndIncrement(); } return resultBuilder.build(); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java index 8becef3efb114..5eb90feae2e6f 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java @@ -64,6 +64,7 @@ import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; @@ -659,6 +660,18 @@ public TsTable toTsTable(InsertNodeMeasurementInfo measurementInfo) { return tsTable; } + long timeCandidateNums = + Arrays.stream(measurementInfo.getColumnCategories()) + .filter(columnDefinition -> TsTableColumnCategory.TIME == columnDefinition) + .count(); + if (timeCandidateNums > 1) { + throw new SemanticException("A table cannot have more than one time column"); + } + if (timeCandidateNums == 0) { + // append the time column with default name "time" if user do not specify the time column + tsTable.addColumnSchema(new TimeColumnSchema(TIME_COLUMN_NAME, TSDataType.TIMESTAMP)); + } + boolean hasObject = false; for (int i = 0; i < measurements.length; i++) { if (measurements[i] == null) { @@ -682,12 +695,16 @@ public TsTable toTsTable(InsertNodeMeasurementInfo measurementInfo) { throw new SemanticException( String.format("Columns in table shall not share the same name %s.", columnName)); } - TSDataType dataType = measurementInfo.getType(i); if (dataType == null && (dataType = measurementInfo.getTypeForFirstValue(i)) == null) { throw new ColumnCreationFailException( "Cannot create column " + columnName + " datatype is not provided"); } + + if (category == TsTableColumnCategory.TIME && dataType != TSDataType.TIMESTAMP) { + throw new SemanticException("The time column's type shall be 'timestamp'."); + } + hasObject |= dataType == TSDataType.OBJECT; tsTable.addColumnSchema(generateColumnSchema(category, columnName, dataType, null, null)); } @@ -761,8 +778,6 @@ public static TsTableColumnSchema generateColumnSchema( schema = new AttributeColumnSchema(columnName, dataType); break; case TIME: - // throw new SemanticException("Add column statement shall not specify column category - // TIME"); schema = new TimeColumnSchema(columnName, dataType); break; case FIELD: diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/distribution/AggregationTableScanTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/distribution/AggregationTableScanTest.java index 698af137f3caa..f78cae18f37d5 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/distribution/AggregationTableScanTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/distribution/AggregationTableScanTest.java @@ -19,84 +19,159 @@ package org.apache.iotdb.db.queryengine.plan.planner.distribution; -import org.apache.iotdb.common.rpc.thrift.TEndPoint; -import org.apache.iotdb.commons.concurrent.IoTDBThreadPoolFactory; -import org.apache.iotdb.db.protocol.session.IClientSession; -import org.apache.iotdb.db.queryengine.common.FragmentInstanceId; -import org.apache.iotdb.db.queryengine.common.MPPQueryContext; -import org.apache.iotdb.db.queryengine.common.PlanFragmentId; -import org.apache.iotdb.db.queryengine.common.QueryId; -import org.apache.iotdb.db.queryengine.common.SessionInfo; -import org.apache.iotdb.db.queryengine.execution.fragment.DataNodeQueryContext; -import org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceContext; -import org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceStateMachine; -import org.apache.iotdb.db.queryengine.plan.analyze.Analysis; -import org.apache.iotdb.db.queryengine.plan.analyze.Analyzer; -import org.apache.iotdb.db.queryengine.plan.analyze.FakePartitionFetcherImpl; -import org.apache.iotdb.db.queryengine.plan.analyze.FakeSchemaFetcherImpl; -import org.apache.iotdb.db.queryengine.plan.parser.StatementGenerator; -import org.apache.iotdb.db.queryengine.plan.planner.LocalExecutionPlanner; -import org.apache.iotdb.db.queryengine.plan.planner.LogicalPlanner; -import org.apache.iotdb.db.queryengine.plan.planner.plan.FragmentInstance; -import org.apache.iotdb.db.queryengine.plan.planner.plan.LogicalQueryPlan; -import org.apache.iotdb.db.queryengine.plan.statement.Statement; -import org.apache.iotdb.db.storageengine.dataregion.DataRegion; -import org.apache.iotdb.db.storageengine.dataregion.IDataRegionForQuery; +import org.apache.iotdb.common.rpc.thrift.TAggregationType; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.AccumulatorFactory; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.FirstAccumulator; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.FirstByAccumulator; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.FirstByDescAccumulator; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.FirstDescAccumulator; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.LastAccumulator; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.LastByAccumulator; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.LastByDescAccumulator; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.LastDescAccumulator; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.TableAccumulator; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SymbolReference; +import org.apache.tsfile.enums.TSDataType; import org.junit.Test; -import org.mockito.Mockito; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.concurrent.ExecutorService; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertTrue; -import static org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceContext.createFragmentInstanceContext; public class AggregationTableScanTest { + private static final String TIME_COL = "time"; + private static final String S1_COL = "s1"; + private static final String S2_COL = "s2"; + + @Test + public void testFirstAccumulator() { + // Case 1: Ascending scan ordered by time -> Use optimized FirstAccumulator + verifyAccumulator("first", TAggregationType.FIRST, true, true, FirstAccumulator.class); + + // Case 2: Descending scan ordered by time -> Use FirstDescAccumulator + verifyAccumulator("first", TAggregationType.FIRST, false, true, FirstDescAccumulator.class); + + // Case 3: Ordered by non-time column -> Fallback to FirstDescAccumulator + verifyAccumulator("first", TAggregationType.FIRST, true, false, FirstDescAccumulator.class); + } + + @Test + public void testLastAccumulator() { + // Case 1: Ascending scan ordered by time -> Use LastAccumulator + verifyAccumulator("last", TAggregationType.LAST, true, true, LastAccumulator.class); + + // Case 2: Descending scan ordered by time -> Use LastDescAccumulator + verifyAccumulator("last", TAggregationType.LAST, false, true, LastDescAccumulator.class); + + // Case 3: Ordered by non-time column -> Use LastAccumulator + verifyAccumulator("last", TAggregationType.LAST, false, false, LastAccumulator.class); + } + + @Test + public void testFirstByAccumulator() { + // Case 1: Ascending scan ordered by time -> Use optimized FirstByAccumulator + verifyByAccumulator( + "first_by", TAggregationType.FIRST_BY, true, true, FirstByAccumulator.class); + + // Case 2: Descending scan ordered by time -> Use FirstByDescAccumulator + verifyByAccumulator( + "first_by", TAggregationType.FIRST_BY, false, true, FirstByDescAccumulator.class); + + // Case 3: Ordered by non-time column -> Fallback to FirstByDescAccumulator + verifyByAccumulator( + "first_by", TAggregationType.FIRST_BY, true, false, FirstByDescAccumulator.class); + } + @Test - public void lastAggTest() { - final String sql = null; - DataNodeQueryContext dataNodeQueryContext = new DataNodeQueryContext(1); - - SessionInfo sessionInfo = - new SessionInfo( - 0, "root", ZoneId.systemDefault(), "last_agg_db", IClientSession.SqlDialect.TABLE); - QueryId queryId = new QueryId("test"); - MPPQueryContext context = - new MPPQueryContext( - sql, - queryId, - sessionInfo, - new TEndPoint("127.0.0.1", 6667), - new TEndPoint("127.0.0.1", 6667)); - Analyzer analyzer = - new Analyzer(context, new FakePartitionFetcherImpl(), new FakeSchemaFetcherImpl()); - Statement statement = StatementGenerator.createStatement(sql, ZonedDateTime.now().getOffset()); - Analysis analysis = analyzer.analyze(statement); - LogicalPlanner logicalPlanner = new LogicalPlanner(context); - LogicalQueryPlan logicalPlan = logicalPlanner.plan(analysis); - DistributionPlanner distributionPlanner = new DistributionPlanner(analysis, logicalPlan); - FragmentInstance instance = distributionPlanner.planFragments().getInstances().get(0); - - LocalExecutionPlanner localExecutionPlanner = LocalExecutionPlanner.getInstance(); - localExecutionPlanner.plan( - instance.getFragment().getPlanNodeTree(), - instance.getFragment().getTypeProvider(), - mockFIContext(queryId), - dataNodeQueryContext); + public void testLastByAccumulator() { + // Case 1: Ascending scan ordered by time -> Use LastByAccumulator + verifyByAccumulator("last_by", TAggregationType.LAST_BY, true, true, LastByAccumulator.class); + + // Case 2: Descending scan ordered by time -> Use LastByDescAccumulator + verifyByAccumulator( + "last_by", TAggregationType.LAST_BY, false, true, LastByDescAccumulator.class); + + // Case 3: Ordered by non-time column -> Use LastByAccumulator + verifyByAccumulator("last_by", TAggregationType.LAST_BY, false, false, LastByAccumulator.class); } - private FragmentInstanceContext mockFIContext(QueryId queryId) { - ExecutorService instanceNotificationExecutor = - IoTDBThreadPoolFactory.newFixedThreadPool(1, "last_agg-instance-notification"); - FragmentInstanceId instanceId = - new FragmentInstanceId(new PlanFragmentId(queryId, 0), "stub-instance"); - FragmentInstanceStateMachine stateMachine = - new FragmentInstanceStateMachine(instanceId, instanceNotificationExecutor); - FragmentInstanceContext instanceContext = - createFragmentInstanceContext(instanceId, stateMachine); - IDataRegionForQuery dataRegionForQuery = Mockito.mock(DataRegion.class); - instanceContext.setDataRegion(dataRegionForQuery); - return instanceContext; + private void verifyAccumulator( + String funcName, + TAggregationType aggType, + boolean ascending, + boolean isTimeOrder, + Class expectedClass) { + List inputExpressions = + Arrays.asList( + new SymbolReference(S1_COL), new SymbolReference(isTimeOrder ? TIME_COL : S2_COL)); + List inputDataTypes = Collections.singletonList(TSDataType.INT32); + + doCreateAndAssert( + funcName, aggType, inputDataTypes, inputExpressions, ascending, expectedClass); + } + + private void verifyByAccumulator( + String funcName, + TAggregationType aggType, + boolean ascending, + boolean isTimeOrder, + Class expectedClass) { + + List inputExpressions = + Arrays.asList( + new SymbolReference(S1_COL), + new SymbolReference(S2_COL), + new SymbolReference(isTimeOrder ? TIME_COL : S1_COL)); + List inputDataTypes = Arrays.asList(TSDataType.INT32, TSDataType.INT64); + + doCreateAndAssert( + funcName, aggType, inputDataTypes, inputExpressions, ascending, expectedClass); + } + + private void doCreateAndAssert( + String funcName, + TAggregationType aggType, + List types, + List expressions, + boolean ascending, + Class expectedClass) { + + Map inputAttribute = new HashMap<>(); + boolean isAggTableScan = true; + Set measurementColumnNames = new HashSet<>(Collections.singletonList(S1_COL)); + boolean distinct = false; + + TableAccumulator accumulator = + AccumulatorFactory.createAccumulator( + funcName, + aggType, + types, + expressions, + inputAttribute, + ascending, + isAggTableScan, + TIME_COL, + measurementColumnNames, + distinct); + + String msg = + String.format( + "Func: %s, Asc: %s, Expressions: %s. Expected: %s, Actual: %s", + funcName, + ascending, + expressions, + expectedClass.getSimpleName(), + accumulator.getClass().getSimpleName()); + + assertTrue(msg, expectedClass.isInstance(accumulator)); } } 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 index df3bbc045e58f..8f484f4e231d5 100644 --- 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 @@ -25,7 +25,6 @@ import org.apache.iotdb.commons.schema.table.column.AttributeColumnSchema; import org.apache.iotdb.commons.schema.table.column.FieldColumnSchema; import org.apache.iotdb.commons.schema.table.column.TagColumnSchema; -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; @@ -212,60 +211,42 @@ public void addColumnSchema(final TsTableColumnSchema columnSchema) { }); } - /** - * Renames a column in the table schema while strictly preserving its original ordinal position. - */ public void renameColumnSchema(final String oldName, final String newName) { executeWrite( () -> { - if (!columnSchemaMap.containsKey(oldName)) { - return; - } - - // Capture the current strict order of current columns - List> snapshotOfColumns = - new ArrayList<>(columnSchemaMap.entrySet()); - columnSchemaMap.clear(); - - // Re-insert all entries in their original sequence, substituting the renamed column at - // its exact original index - for (Map.Entry entry : snapshotOfColumns) { - String currentKey = entry.getKey(); - TsTableColumnSchema currentColumnSchema = entry.getValue(); - - if (currentKey.equals(oldName)) { - TsTableColumnSchema newSchema = createRenamedSchema(currentColumnSchema, newName); - columnSchemaMap.put(newName, newSchema); - } else { - columnSchemaMap.put(currentKey, currentColumnSchema); + // Ensures idempotency + if (columnSchemaMap.containsKey(oldName)) { + final TsTableColumnSchema schema = columnSchemaMap.remove(oldName); + final Map oldProps = schema.getProps(); + oldProps.computeIfAbsent(TreeViewSchema.ORIGINAL_NAME, k -> schema.getColumnName()); + switch (schema.getColumnCategory()) { + case TAG: + columnSchemaMap.put( + newName, new TagColumnSchema(newName, schema.getDataType(), oldProps)); + break; + case FIELD: + columnSchemaMap.put( + newName, + new FieldColumnSchema( + newName, + schema.getDataType(), + ((FieldColumnSchema) schema).getEncoding(), + ((FieldColumnSchema) schema).getCompressor(), + oldProps)); + break; + case ATTRIBUTE: + columnSchemaMap.put( + newName, new AttributeColumnSchema(newName, schema.getDataType(), oldProps)); + break; + case TIME: + default: + // Do nothing + columnSchemaMap.put(oldName, schema); } } }); } - private TsTableColumnSchema createRenamedSchema(TsTableColumnSchema oldSchema, String newName) { - Map oldProps = oldSchema.getProps(); - oldProps.computeIfAbsent(TreeViewSchema.ORIGINAL_NAME, k -> oldSchema.getColumnName()); - - switch (oldSchema.getColumnCategory()) { - case TAG: - return new TagColumnSchema(newName, oldSchema.getDataType(), oldProps); - case FIELD: - return new FieldColumnSchema( - newName, - oldSchema.getDataType(), - ((FieldColumnSchema) oldSchema).getEncoding(), - ((FieldColumnSchema) oldSchema).getCompressor(), - oldProps); - case ATTRIBUTE: - return new AttributeColumnSchema(newName, oldSchema.getDataType(), oldProps); - case TIME: - return new TimeColumnSchema(newName, oldSchema.getDataType(), oldProps); - default: - return oldSchema; - } - } - public void removeColumnSchema(final String columnName) { executeWrite( () -> {