diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBSessionRelationalIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBSessionRelationalIT.java index fc9855a98355c..151d981f81f85 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBSessionRelationalIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBSessionRelationalIT.java @@ -271,7 +271,9 @@ public void insertWrongTimeSqlTest() session.executeNonQueryStatement("insert into wrong_time values(1+1,'bb','cc','dd')"); fail("No exception thrown"); } catch (StatementExecutionException e) { - assertEquals("701: Unsupported expression: (1 + 1)", e.getMessage()); + assertEquals( + "701: Insert expression must be constant after constant folding: (1 + 1) (folded to (1 + 1))", + e.getMessage()); } try { session.executeNonQueryStatement("insert into wrong_time values(1.0,'bb','cc','dd')"); @@ -333,6 +335,14 @@ public void insertRelationalSqlTest() row, "tag:" + row, "attr:" + row, row)); } + session.executeNonQueryStatement( + "INSERT INTO table1 (time, tag1, attr1, m1) " + + "VALUES (if(true, 50, 0), if(true, 'tag:50', 'x'), " + + "if(false, 'x', 'attr:50'), if(true, 50.0, 0.0))"); + session.executeNonQueryStatement( + "INSERT INTO table1 VALUES (if(true, 51, 0), if(true, 'tag:51', 'x'), " + + "if(true, 'attr:51', 'x'), if(false, 0, 51))"); + SessionDataSet dataSet = session.executeQueryStatement("select * from table1 order by time"); int cnt = 0; while (dataSet.hasNext()) { @@ -343,7 +353,7 @@ public void insertRelationalSqlTest() assertEquals(timestamp * 1.0, rowRecord.getFields().get(3).getDoubleV(), 0.0001); cnt++; } - assertEquals(50, cnt); + assertEquals(52, cnt); // sql cannot create column assertThrows( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertRowsNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertRowsNode.java index 741c45f32564c..a36b5a07da5cf 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertRowsNode.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertRowsNode.java @@ -165,17 +165,17 @@ public List splitByPartition(IAnalysis analysis) { List writePlanNodeList = new ArrayList<>(); Map splitMap = new HashMap<>(); List redirectInfo = new ArrayList<>(); + String databaseName = getDatabaseName(analysis); for (int i = 0; i < getInsertRowNodeList().size(); i++) { InsertRowNode insertRowNode = getInsertRowNodeList().get(i); // Data region for insert row node - // each row may belong to different database, pass null for auto-detection TRegionReplicaSet dataRegionReplicaSet = analysis .getDataPartitionInfo() .getDataRegionReplicaSetForWriting( insertRowNode.getDeviceID(), TimePartitionUtils.getTimePartitionSlot(insertRowNode.getTime()), - analysis.getDatabaseName()); + databaseName); // Collect redirectInfo redirectInfo.add(dataRegionReplicaSet.getDataNodeLocations().get(0).getClientRpcEndPoint()); @@ -195,6 +195,14 @@ public List splitByPartition(IAnalysis analysis) { return writePlanNodeList; } + private String getDatabaseName(IAnalysis analysis) { + if (analysis.getDataPartitionInfo() != null + && analysis.getDataPartitionInfo().getDataPartitionMap().size() == 1) { + return analysis.getDataPartitionInfo().getDataPartitionMap().keySet().iterator().next(); + } + return analysis.getDatabaseName(); + } + public RelationalInsertRowsNode emptyClone() { return new RelationalInsertRowsNode(this.getPlanNodeId()); } 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 d0779ee24a5b8..6b9cefadc1446 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 @@ -178,6 +178,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InsertRow; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InsertRows; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InsertTablet; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InsertValues; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.LoadTsFile; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.PipeEnriched; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Property; @@ -791,6 +792,11 @@ public Scope visitInsertRows(InsertRows node, Optional context) { } private Scope visitInsert(WrappedInsertStatement insert, Optional scope) { + if (insert instanceof InsertValues) { + ((InsertValues) insert) + .materialize(new PlannerContext(metadata, typeManager), sessionContext); + } + final Scope ret = Scope.create(); final MPPQueryContext context = insert.getContext(); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertValues.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertValues.java new file mode 100644 index 0000000000000..4d22a863f2f13 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertValues.java @@ -0,0 +1,297 @@ +/* + * 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.relational.sql.ast; + +import org.apache.iotdb.calc.exception.QueryProcessException; +import org.apache.iotdb.commons.exception.SemanticException; +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.commons.queryengine.common.SessionInfo; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Expression; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Identifier; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Row; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Values; +import org.apache.iotdb.commons.queryengine.utils.TimestampPrecisionUtils; +import org.apache.iotdb.commons.schema.table.TsTable; +import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory; +import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema; +import org.apache.iotdb.commons.utils.CommonDateTimeUtils; +import org.apache.iotdb.db.i18n.DataNodeQueryMessages; +import org.apache.iotdb.db.queryengine.plan.relational.planner.PlannerContext; +import org.apache.iotdb.db.queryengine.plan.relational.sql.util.AstUtil; +import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement; +import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowsStatement; +import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache; + +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.write.schema.MeasurementSchema; + +import javax.annotation.Nullable; + +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; +import static org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory.TIME; + +public class InsertValues extends InsertRows { + + private final String databaseName; + private final String tableName; + @Nullable private final List columnNames; + private final List> rows; + private final ZoneId zoneId; + private boolean materialized; + + public InsertValues( + final String databaseName, + final String tableName, + @Nullable final List columnIdentifiers, + final Values values, + final ZoneId zoneId) { + super(createPlaceholderStatement(databaseName), null); + this.databaseName = requireNonNull(databaseName, "databaseName is null"); + this.tableName = requireNonNull(tableName, "tableName is null"); + this.columnNames = + columnIdentifiers == null + ? null + : columnIdentifiers.stream().map(Identifier::getValue).collect(Collectors.toList()); + this.rows = + requireNonNull(values, "values is null").getRows().stream() + .map(InsertValues::normalizeRow) + .collect(Collectors.toList()); + this.zoneId = requireNonNull(zoneId, "zoneId is null"); + } + + public void materialize(PlannerContext plannerContext, SessionInfo session) { + if (materialized) { + return; + } + + InsertRowsStatement insertRowsStatement = new InsertRowsStatement(); + insertRowsStatement.setInsertRowStatementList( + columnNames == null + ? toInsertRowStatementsWithTableSchema(plannerContext, session) + : toInsertRowStatementsWithColumns(plannerContext, session)); + insertRowsStatement.setWriteToTable(true); + insertRowsStatement.setDatabaseName(databaseName); + setInnerTreeStatement(insertRowsStatement); + materialized = true; + } + + private static InsertRowsStatement createPlaceholderStatement(String databaseName) { + InsertRowsStatement statement = new InsertRowsStatement(); + statement.setInsertRowStatementList(Collections.emptyList()); + statement.setWriteToTable(true); + statement.setDatabaseName(databaseName); + return statement; + } + + @Override + public String getDatabase() { + return databaseName; + } + + private static List normalizeRow(Expression row) { + if (row instanceof Row) { + return ((Row) row).getItems(); + } + return Collections.singletonList(row); + } + + private List toInsertRowStatementsWithTableSchema( + PlannerContext plannerContext, SessionInfo session) { + final TsTable table = getTable(); + return rows.stream() + .map(row -> toInsertRowStatement(row, table, plannerContext, session)) + .collect(Collectors.toList()); + } + + private List toInsertRowStatementsWithColumns( + PlannerContext plannerContext, SessionInfo session) { + final List nonTimeColumnNames = new ArrayList<>(columnNames); + final int timeColumnIndex = findTimeColumnIndex(nonTimeColumnNames); + if (timeColumnIndex != -1) { + nonTimeColumnNames.remove(timeColumnIndex); + } + if (timeColumnIndex == -1 && rows.size() > 1) { + throw new SemanticException(DataNodeQueryMessages.NEED_TIMESTAMPS_WHEN_INSERT_MULTI_ROWS); + } + + final String[] nonTimeColumnArray = nonTimeColumnNames.toArray(new String[0]); + return rows.stream() + .map( + row -> + toInsertRowStatement( + row, timeColumnIndex, nonTimeColumnArray.clone(), plannerContext, session)) + .collect(Collectors.toList()); + } + + private TsTable getTable() { + TsTable table = DataNodeTableCache.getInstance().getTable(databaseName, tableName); + if (table == null) { + throw new SemanticException( + "Insert target table does not exist: " + databaseName + "." + tableName); + } + return table; + } + + private int findTimeColumnIndex(final List columnNames) { + List timeColumnCandidates = + getTable().getColumnList().stream() + .filter(column -> column.getColumnCategory() == TIME) + .collect(Collectors.toList()); + if (timeColumnCandidates.size() != 1) { + throw new SemanticException( + DataNodeQueryMessages.THE_TABLE_SHOULD_ONLY_HAVE_ONE_COLUMN_FOUND); + } + + int timeColumnIndex = -1; + String timeColumnName = timeColumnCandidates.get(0).getColumnName(); + for (int i = 0; i < columnNames.size(); i++) { + if (timeColumnName.equalsIgnoreCase(columnNames.get(i))) { + if (timeColumnIndex == -1) { + timeColumnIndex = i; + } else { + throw new SemanticException( + DataNodeQueryMessages.ONE_ROW_SHOULD_ONLY_HAVE_ONE_TIME_VALUE); + } + } + } + return timeColumnIndex; + } + + private InsertRowStatement toInsertRowStatement( + List expressions, + TsTable table, + PlannerContext plannerContext, + SessionInfo session) { + InsertRowStatement insertRowStatement = new InsertRowStatement(); + insertRowStatement.setWriteToTable(true); + insertRowStatement.setDevicePath(new PartialPath(new String[] {table.getTableName()})); + + List columnList = table.getColumnList(); + if (expressions.size() != columnList.size()) { + throw new SemanticException( + "expressions and columns do not match, expressions size: " + + expressions.size() + + ", columns size: " + + columnList.size()); + } + + String[] nonTimeColumnNames = new String[columnList.size() - 1]; + Object[] nonTimeValues = new Object[columnList.size() - 1]; + TsTableColumnCategory[] nonTimeColumnCategories = + new TsTableColumnCategory[columnList.size() - 1]; + MeasurementSchema[] columnSchemas = new MeasurementSchema[columnList.size() - 1]; + TSDataType[] dataTypes = new TSDataType[columnList.size() - 1]; + int nonTimeColumnIndex = 0; + long timestamp = -1; + for (int i = 0; i < columnList.size(); i++) { + TsTableColumnSchema columnSchema = columnList.get(i); + Expression expression = expressions.get(i); + + if (columnSchema.getColumnCategory().equals(TIME)) { + timestamp = AstUtil.expressionToTimestamp(expression, zoneId, plannerContext, session); + } else { + Object value = AstUtil.expressionToTsValue(expression, plannerContext, session); + nonTimeValues[nonTimeColumnIndex] = value; + nonTimeColumnNames[nonTimeColumnIndex] = columnSchema.getColumnName(); + dataTypes[nonTimeColumnIndex] = columnSchema.getDataType(); + nonTimeColumnCategories[nonTimeColumnIndex] = columnSchema.getColumnCategory(); + columnSchemas[nonTimeColumnIndex] = + new MeasurementSchema(columnSchema.getColumnName(), columnSchema.getDataType()); + nonTimeColumnIndex++; + } + } + + TimestampPrecisionUtils.checkTimestampPrecision(timestamp); + insertRowStatement.setTime(timestamp); + insertRowStatement.setMeasurements(nonTimeColumnNames); + insertRowStatement.setDataTypes(dataTypes); + insertRowStatement.setMeasurementSchemas(columnSchemas); + insertRowStatement.setValues(nonTimeValues); + insertRowStatement.setColumnCategories(nonTimeColumnCategories); + insertRowStatement.setNeedInferType(false); + insertRowStatement.setDatabaseName(databaseName); + + try { + insertRowStatement.transferType(zoneId); + } catch (QueryProcessException e) { + throw new SemanticException(e); + } + return insertRowStatement; + } + + private InsertRowStatement toInsertRowStatement( + List expressions, + int timeColumnIndex, + String[] nonTimeColumnNames, + PlannerContext plannerContext, + SessionInfo session) { + InsertRowStatement insertRowStatement = new InsertRowStatement(); + insertRowStatement.setWriteToTable(true); + insertRowStatement.setDevicePath(new PartialPath(new String[] {tableName})); + long timestamp; + int nonTimeColumnCount; + if (timeColumnIndex == -1) { + timestamp = CommonDateTimeUtils.currentTime(); + nonTimeColumnCount = expressions.size(); + } else { + if (timeColumnIndex >= expressions.size()) { + throw new SemanticException( + String.format( + "TimeColumnIndex out of bound: %d-%d", timeColumnIndex, expressions.size())); + } + + timestamp = + AstUtil.expressionToTimestamp( + expressions.get(timeColumnIndex), zoneId, plannerContext, session); + nonTimeColumnCount = expressions.size() - 1; + } + + if (nonTimeColumnCount != nonTimeColumnNames.length) { + throw new SemanticException( + String.format( + "Inconsistent numbers of non-time column names and values: %d-%d", + nonTimeColumnNames.length, nonTimeColumnCount)); + } + + TimestampPrecisionUtils.checkTimestampPrecision(timestamp); + insertRowStatement.setTime(timestamp); + insertRowStatement.setMeasurements(nonTimeColumnNames); + + Object[] values = new Object[nonTimeColumnNames.length]; + int valuePosition = 0; + for (int i = 0; i < expressions.size(); i++) { + if (i != timeColumnIndex) { + values[valuePosition++] = + AstUtil.expressionToTsValue(expressions.get(i), plannerContext, session); + } + } + + insertRowStatement.setValues(values); + insertRowStatement.setNeedInferType(true); + insertRowStatement.setDatabaseName(databaseName); + return insertRowStatement; + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java index 25d68eba6beb5..d6fc472f16506 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java @@ -19,7 +19,6 @@ package org.apache.iotdb.db.queryengine.plan.relational.sql.parser; -import org.apache.iotdb.calc.exception.QueryProcessException; import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType; import org.apache.iotdb.commons.auth.entity.PrivilegeType; import org.apache.iotdb.commons.cluster.NodeStatus; @@ -142,12 +141,9 @@ import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ZeroOrMoreQuantifier; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ZeroOrOneQuantifier; import org.apache.iotdb.commons.queryengine.plan.relational.sql.parser.ParsingException; -import org.apache.iotdb.commons.queryengine.utils.TimestampPrecisionUtils; import org.apache.iotdb.commons.schema.cache.CacheClearOptions; import org.apache.iotdb.commons.schema.table.InformationSchema; -import org.apache.iotdb.commons.schema.table.TsTable; import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory; -import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema; import org.apache.iotdb.commons.udf.builtin.relational.TableBuiltinScalarFunction; import org.apache.iotdb.commons.utils.CommonDateTimeUtils; import org.apache.iotdb.commons.utils.PathUtils; @@ -196,7 +192,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExtendRegion; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Flush; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Insert; -import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InsertRows; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InsertValues; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.KillQuery; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.LoadConfiguration; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.LoadModel; @@ -256,12 +252,9 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.UpdateAssignment; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Use; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ViewFieldDefinition; -import org.apache.iotdb.db.queryengine.plan.relational.sql.util.AstUtil; import org.apache.iotdb.db.queryengine.plan.relational.sql.util.QueryUtil; import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType; import org.apache.iotdb.db.queryengine.plan.statement.StatementType; -import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement; -import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowsStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.FlushStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.LoadConfigurationStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.SetConfigurationStatement; @@ -272,7 +265,6 @@ import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlBaseVisitor; import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlLexer; import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlParser; -import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache; import org.apache.iotdb.db.storageengine.load.config.LoadTsFileConfigurator; import org.apache.iotdb.db.utils.DataNodeDateTimeUtils; @@ -282,9 +274,7 @@ import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; import org.apache.tsfile.common.constant.TsFileConstant; -import org.apache.tsfile.enums.TSDataType; import org.apache.tsfile.utils.TimeDuration; -import org.apache.tsfile.write.schema.MeasurementSchema; import javax.annotation.Nullable; @@ -829,237 +819,18 @@ public Node visitInsertStatement(final RelationalSqlParser.InsertStatementContex final List identifiers = visit(ctx.columnAliases().identifier(), Identifier.class); if (query.getQueryBody() instanceof Values) { - return visitInsertValues( - databaseName, tableName, identifiers, ((Values) query.getQueryBody())); + return new InsertValues( + databaseName, tableName, identifiers, ((Values) query.getQueryBody()), zoneId); } else { return new Insert(new Table(qualifiedName), identifiers, query); } } else { return query.getQueryBody() instanceof Values - ? visitInsertValues( - databaseName, - DataNodeTableCache.getInstance().getTable(databaseName, tableName), - ((Values) query.getQueryBody())) + ? new InsertValues(databaseName, tableName, null, ((Values) query.getQueryBody()), zoneId) : new Insert(new Table(qualifiedName), query); } } - private Node visitInsertValues( - final String databaseName, final TsTable table, final Values queryBody) { - final List rows = queryBody.getRows(); - final List rowStatements = - rows.stream() - .map( - r -> { - List expressions; - if (r instanceof Row) { - expressions = ((Row) r).getItems(); - } else if (r instanceof Literal) { - expressions = Collections.singletonList(r); - } else { - throw new SemanticException(DataNodeQueryMessages.UNEXPECTED_EXPRESSION_2 + r); - } - return toInsertRowStatement(expressions, table, databaseName); - }) - .collect(toList()); - - InsertRowsStatement insertRowsStatement = new InsertRowsStatement(); - insertRowsStatement.setInsertRowStatementList(rowStatements); - insertRowsStatement.setWriteToTable(true); - return new InsertRows(insertRowsStatement, null); - } - - private Node visitInsertValues( - final String databaseName, - final String tableName, - final List identifiers, - final Values queryBody) { - final List columnNames = - identifiers.stream().map(Identifier::getValue).collect(toList()); - int timeColumnIndex = -1; - - // retrieve the table schema to identify the actual time column - TsTable table = DataNodeTableCache.getInstance().getTable(databaseName, tableName); - List timeColumnCandidates = - table.getColumnList().stream() - .filter(col -> col.getColumnCategory() == TIME) - .collect(toList()); - if (timeColumnCandidates.size() != 1) { - throw new SemanticException( - DataNodeQueryMessages.THE_TABLE_SHOULD_ONLY_HAVE_ONE_COLUMN_FOUND); - } else { - // locate the time column index in the input identifiers if time column exists in the schema - for (int i = 0; i < columnNames.size(); i++) { - if (timeColumnCandidates.get(0).getColumnName().equalsIgnoreCase(columnNames.get(i))) { - if (timeColumnIndex == -1) { - timeColumnIndex = i; - } else { - throw new SemanticException( - DataNodeQueryMessages.ONE_ROW_SHOULD_ONLY_HAVE_ONE_TIME_VALUE); - } - } - } - } - - if (timeColumnIndex != -1) { - columnNames.remove(timeColumnIndex); - } - - List rows = queryBody.getRows(); - if (timeColumnIndex == -1 && rows.size() > 1) { - throw new SemanticException(DataNodeQueryMessages.NEED_TIMESTAMPS_WHEN_INSERT_MULTI_ROWS); - } - int finalTimeColumnIndex = timeColumnIndex; - List rowStatements = - rows.stream() - .map( - r -> { - List expressions; - if (r instanceof Row) { - expressions = ((Row) r).getItems(); - } else if (r instanceof Literal) { - expressions = Collections.singletonList(r); - } else { - throw new SemanticException(DataNodeQueryMessages.UNEXPECTED_EXPRESSION_2 + r); - } - String[] columnNameArray = columnNames.toArray(new String[0]); - return toInsertRowStatement( - expressions, - finalTimeColumnIndex, - columnNameArray, - tableName, - databaseName, - columnNames.size()); - }) - .collect(toList()); - - InsertRowsStatement insertRowsStatement = new InsertRowsStatement(); - insertRowsStatement.setInsertRowStatementList(rowStatements); - insertRowsStatement.setWriteToTable(true); - return new InsertRows(insertRowsStatement, null); - } - - private InsertRowStatement toInsertRowStatement( - List expressions, TsTable table, String databaseName) { - InsertRowStatement insertRowStatement = new InsertRowStatement(); - insertRowStatement.setWriteToTable(true); - insertRowStatement.setDevicePath(new PartialPath(new String[] {table.getTableName()})); - - List columnList = table.getColumnList(); - if (expressions.size() != columnList.size()) { - throw new SemanticException( - "expressions and columns do not match, expressions size: " - + expressions.size() - + ", columns size: " - + columnList.size()); - } - - String[] nonTimeColumnNames = new String[columnList.size() - 1]; - Object[] nonTimeValues = new Object[columnList.size() - 1]; - TsTableColumnCategory[] nonTimeColumnCategories = - new TsTableColumnCategory[columnList.size() - 1]; - MeasurementSchema[] columnSchemas = new MeasurementSchema[columnList.size() - 1]; - TSDataType[] dataTypes = new TSDataType[columnList.size() - 1]; - int nonTimeColumnIndex = 0; - long timestamp = -1; - for (int i = 0; i < columnList.size(); i++) { - TsTableColumnSchema columnSchema = columnList.get(i); - Expression expression = expressions.get(i); - - if (columnSchema.getColumnCategory().equals(TIME)) { - timestamp = AstUtil.expressionToTimestamp(expression, zoneId); - } else { - Object value = AstUtil.expressionToTsValue(expression); - nonTimeValues[nonTimeColumnIndex] = value; - nonTimeColumnNames[nonTimeColumnIndex] = columnSchema.getColumnName(); - dataTypes[nonTimeColumnIndex] = columnSchema.getDataType(); - nonTimeColumnCategories[nonTimeColumnIndex] = columnSchema.getColumnCategory(); - columnSchemas[nonTimeColumnIndex] = - new MeasurementSchema(columnSchema.getColumnName(), columnSchema.getDataType()); - nonTimeColumnIndex++; - } - } - - TimestampPrecisionUtils.checkTimestampPrecision(timestamp); - insertRowStatement.setTime(timestamp); - insertRowStatement.setMeasurements(nonTimeColumnNames); - insertRowStatement.setDataTypes(dataTypes); - insertRowStatement.setMeasurementSchemas(columnSchemas); - insertRowStatement.setValues(nonTimeValues); - insertRowStatement.setColumnCategories(nonTimeColumnCategories); - insertRowStatement.setNeedInferType(false); - insertRowStatement.setDatabaseName(databaseName); - - try { - insertRowStatement.transferType(zoneId); - } catch (QueryProcessException e) { - throw new SemanticException(e); - } - return insertRowStatement; - } - - private InsertRowStatement toInsertRowStatement( - List expressions, - int timeColumnIndex, - String[] nonTimeColumnNames, - String tableName, - String databaseName, - int columnSize) { - InsertRowStatement insertRowStatement = new InsertRowStatement(); - insertRowStatement.setWriteToTable(true); - insertRowStatement.setDevicePath(new PartialPath(new String[] {tableName})); - long timestamp; - int nonTimeColCnt; - if (timeColumnIndex == -1) { - timestamp = CommonDateTimeUtils.currentTime(); - nonTimeColCnt = expressions.size(); - } else { - if (timeColumnIndex >= expressions.size()) { - throw new SemanticException( - String.format( - "TimeColumnIndex out of bound: %d-%d", timeColumnIndex, expressions.size())); - } - - Expression timeExpression = expressions.get(timeColumnIndex); - if (timeExpression instanceof LongLiteral) { - timestamp = ((LongLiteral) timeExpression).getParsedValue(); - } else if (timeExpression instanceof NullLiteral) { - throw new SemanticException(DataNodeQueryMessages.TIMESTAMP_CANNOT_BE_NULL); - } else { - timestamp = - parseDateTimeFormat( - ((StringLiteral) timeExpression).getValue(), - CommonDateTimeUtils.currentTime(), - zoneId); - } - nonTimeColCnt = expressions.size() - 1; - } - - if (nonTimeColCnt != nonTimeColumnNames.length) { - throw new SemanticException( - String.format( - "Inconsistent numbers of non-time column names and values: %d-%d", - nonTimeColumnNames.length, nonTimeColCnt)); - } - - TimestampPrecisionUtils.checkTimestampPrecision(timestamp); - insertRowStatement.setTime(timestamp); - insertRowStatement.setMeasurements(nonTimeColumnNames); - - Object[] values = new Object[nonTimeColumnNames.length]; - int valuePos = 0; - for (int i = 0; i < expressions.size(); i++) { - if (i != timeColumnIndex) { - values[valuePos++] = AstUtil.expressionToTsValue(expressions.get(i)); - } - } - - insertRowStatement.setValues(values); - insertRowStatement.setNeedInferType(true); - insertRowStatement.setDatabaseName(databaseName); - return insertRowStatement; - } - @Override public Node visitDeleteStatement(RelationalSqlParser.DeleteStatementContext ctx) { if (ctx.booleanExpression() != null) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/AstUtil.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/AstUtil.java index 986501a197614..4208ed58166f9 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/AstUtil.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/AstUtil.java @@ -20,6 +20,8 @@ package org.apache.iotdb.db.queryengine.plan.relational.sql.util; import org.apache.iotdb.commons.exception.SemanticException; +import org.apache.iotdb.commons.queryengine.common.SessionInfo; +import org.apache.iotdb.commons.queryengine.plan.relational.analyzer.NodeRef; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Expression; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Identifier; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Literal; @@ -29,12 +31,21 @@ import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.StringLiteral; import org.apache.iotdb.commons.utils.CommonDateTimeUtils; import org.apache.iotdb.db.i18n.DataNodeQueryMessages; +import org.apache.iotdb.db.queryengine.plan.analyze.TypeProvider; +import org.apache.iotdb.db.queryengine.plan.relational.planner.IrExpressionInterpreter; +import org.apache.iotdb.db.queryengine.plan.relational.planner.IrTypeAnalyzer; +import org.apache.iotdb.db.queryengine.plan.relational.planner.NoOpSymbolResolver; +import org.apache.iotdb.db.queryengine.plan.relational.planner.PlannerContext; import com.google.common.graph.SuccessorsFunction; import com.google.common.graph.Traverser; +import org.apache.tsfile.common.conf.TSFileConfig; +import org.apache.tsfile.read.common.type.Type; +import org.apache.tsfile.utils.Binary; import java.time.ZoneId; import java.util.List; +import java.util.Map; import java.util.OptionalInt; import java.util.function.BiFunction; import java.util.function.Function; @@ -117,6 +128,14 @@ public static Object expressionToTsValue(Expression expression) { throw new SemanticException(DataNodeQueryMessages.UNSUPPORTED_EXPRESSION + expression); } + public static Object expressionToTsValue( + Expression expression, PlannerContext plannerContext, SessionInfo session) { + if (expression instanceof Literal) { + return expressionToTsValue(expression); + } + return evaluateInsertConstantExpression(expression, plannerContext, session); + } + public static long expressionToTimestamp(Expression expression, ZoneId zoneId) { long timestamp; if (expression instanceof LongLiteral) { @@ -133,5 +152,59 @@ public static long expressionToTimestamp(Expression expression, ZoneId zoneId) { return timestamp; } + public static long expressionToTimestamp( + Expression expression, ZoneId zoneId, PlannerContext plannerContext, SessionInfo session) { + if (expression instanceof LongLiteral + || expression instanceof NullLiteral + || expression instanceof StringLiteral) { + return expressionToTimestamp(expression, zoneId); + } + + Object value = evaluateInsertConstantExpression(expression, plannerContext, session); + if (value == null) { + throw new SemanticException(DataNodeQueryMessages.TIMESTAMP_CANNOT_BE_NULL); + } + if (value instanceof Integer || value instanceof Long) { + return ((Number) value).longValue(); + } + if (value instanceof Binary) { + return parseDateTimeFormat( + ((Binary) value).getStringValue(TSFileConfig.STRING_CHARSET), + CommonDateTimeUtils.currentTime(), + zoneId); + } + throw new SemanticException(DataNodeQueryMessages.UNSUPPORTED_EXPRESSION + expression); + } + + private static Object evaluateInsertConstantExpression( + Expression expression, PlannerContext plannerContext, SessionInfo session) { + if (expression instanceof Identifier) { + throw new SemanticException( + String.format("Cannot insert identifier %s, please use string literal", expression)); + } + + try { + Map, Type> types = + new IrTypeAnalyzer(plannerContext).getTypes(session, TypeProvider.empty(), expression); + Object value = + new IrExpressionInterpreter(expression, plannerContext, session, types) + .optimize(NoOpSymbolResolver.INSTANCE); + if (value instanceof Expression) { + throw new SemanticException( + String.format( + "Insert expression must be constant after constant folding: %s (folded to %s)", + expression, value)); + } + return value; + } catch (SemanticException e) { + throw e; + } catch (RuntimeException e) { + throw new SemanticException( + String.format( + "Cannot evaluate insert expression as a constant: %s. %s", + expression, e.getMessage())); + } + } + private AstUtil() {} }