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 3abe7497130a5..9ea4881eb8caa 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 @@ -401,7 +401,7 @@ public void testInsertUnsupportedTimeExpressions() throws SQLException { statement.execute( "CREATE TABLE rejected_custom_time_expression " + "(device_id STRING TAG, event_time TIMESTAMP TIME, value INT64 FIELD)"); - String[] expressions = {"1735660800000 + 1000", "?", "1.5", "true", "abs(1)"}; + String[] expressions = {"?", "1.5", "true", "abs(1)"}; for (String expression : expressions) { for (String columns : new String[] {" (time, device_id, value)", ""}) { assertUnsupportedInsertExpression( @@ -453,7 +453,10 @@ private static void assertUnsupportedInsertExpression(Statement statement, Strin SQLException exception = assertThrows(SQLException.class, () -> statement.execute(sql)); assertEquals(sql, TSStatusCode.SEMANTIC_ERROR.getStatusCode(), exception.getErrorCode()); String message = exception.getMessage(); - assertTrue(message, message.contains("Unsupported expression:")); + assertTrue( + message, + message.contains("Unsupported expression:") + || message.contains("Insert expression must be constant after constant folding:")); assertFalse(message, message.contains("ClassCastException")); assertFalse(message, message.contains("cannot be cast")); assertFalse(message, message.contains("org.apache.iotdb")); @@ -467,7 +470,12 @@ public void testInsertSupportedTimeValues() throws SQLException { statement.execute( "CREATE TABLE supported_time_expression (device_id STRING TAG, value INT64 FIELD)"); String[] expressions = { - "1735660800000", "'2025-01-01T00:00:00+08:00'", "NOW()", "NOW() + 1d", "NOW() - 1d" + "1735660800000", + "'2025-01-01T00:00:00+08:00'", + "1735660799000 + 1000", + "NOW()", + "NOW() + 1d", + "NOW() - 1d" }; for (boolean explicitColumns : new boolean[] {true, false}) { for (int i = 0; i < expressions.length; i++) { @@ -491,10 +499,10 @@ public void testInsertSupportedTimeValues() throws SQLException { + "'")) { assertTrue(resultSet.next()); long timestamp = resultSet.getLong(1); - if (i < 2) { + if (i < 3) { assertEquals(1735660800000L, timestamp); } else { - long offset = i == 3 ? 86400000L : i == 4 ? -86400000L : 0; + long offset = i == 4 ? 86400000L : i == 5 ? -86400000L : 0; // Allow clock skew between the test client and cluster nodes. assertTrue(timestamp >= before + offset - 10000); assertTrue(timestamp <= after + offset + 10000); 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 134a7a2bea0fd..0b5c2b2da26f7 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 @@ -260,29 +260,36 @@ public void insertWrongTimeSqlTest() try (ITableSession session = EnvFactory.getEnv().getTableSessionConnection()) { session.executeNonQueryStatement("USE \"db1\""); session.executeNonQueryStatement( - "create table wrong_time(color string tag, device_id string tag,city string attribute)"); + "create table wrong_time(color string tag, device_id string tag,city string attribute," + + " value int32 field)"); try { - session.executeNonQueryStatement("insert into wrong_time values('aa','bb','cc','dd')"); + session.executeNonQueryStatement("insert into wrong_time values('aa','bb','cc','dd',1)"); fail("No exception thrown"); } catch (StatementExecutionException e) { assertEquals( "701: Input time format aa error. Input like yyyy-MM-dd HH:mm:ss, yyyy-MM-ddTHH:mm:ss or refer to user document for more info.", e.getMessage()); } - try { - 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()); + session.executeNonQueryStatement("insert into wrong_time values(1+1,'bb','cc','dd',1)"); + try (SessionDataSet dataSet = + session.executeQueryStatement("select * from wrong_time where time = 2")) { + assertTrue(dataSet.hasNext()); + RowRecord rowRecord = dataSet.next(); + assertEquals(2L, rowRecord.getFields().get(0).getLongV()); + assertEquals("bb", rowRecord.getFields().get(1).getBinaryV().toString()); + assertEquals("cc", rowRecord.getFields().get(2).getBinaryV().toString()); + assertEquals("dd", rowRecord.getFields().get(3).getBinaryV().toString()); + assertEquals(1, rowRecord.getFields().get(4).getIntV()); + assertFalse(dataSet.hasNext()); } try { - session.executeNonQueryStatement("insert into wrong_time values(1.0,'bb','cc','dd')"); + session.executeNonQueryStatement("insert into wrong_time values(1.0,'bb','cc','dd',1)"); fail("No exception thrown"); } catch (StatementExecutionException e) { assertEquals("701: Unsupported expression: 1E0", e.getMessage()); } try { - session.executeNonQueryStatement("insert into wrong_time values(true,'bb','cc','dd')"); + session.executeNonQueryStatement("insert into wrong_time values(true,'bb','cc','dd',1)"); fail("No exception thrown"); } catch (StatementExecutionException e) { assertEquals("701: Unsupported expression: true", e.getMessage()); @@ -335,6 +342,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()) { @@ -345,7 +360,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/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java index b030af64cfa77..3b8bf4b3d26fe 100644 --- a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java +++ b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java @@ -3941,4 +3941,37 @@ private DataNodeQueryMessages() {} public static final String EXCEPTION_ONLY_INMEMORYDEVICEENTRYDATASET_SUPPORTS_GET_INLINE_DEVICE_ENTRIES_07A52CAB = "Only InMemoryDeviceEntryDataSet supports get inline device entries"; + + public static final String EXCEPTION_DATABASENAME_IS_NULL_A811DE1B = + "databaseName is null"; + + public static final String EXCEPTION_VALUES_IS_NULL_A580EDAA = + "values is null"; + + public static final String EXCEPTION_ZONEID_IS_NULL_7DAA5E2C = + "zoneId is null"; + + public static final String EXCEPTION_INSERT_TARGET_TABLE_DOES_NOT_EXIST_ARG_ARG_78F05787 = + "Insert target table does not exist: %s.%s"; + + public static final String EXCEPTION_EXPRESSIONS_AND_COLUMNS_DO_NOT_MATCH_EXPRESSIONS_SIZE_ARG_COLUMNS_SIZE_ARG_F933A8C1 = + "expressions and columns do not match, expressions size: %s, columns size: %s"; + + public static final String EXCEPTION_INSERT_EXPRESSION_MUST_BE_CONSTANT_AFTER_CONSTANT_FOLDING_ARG_FOLDED_TO_ARG_D9EE1433 = + "Insert expression must be constant after constant folding: %s (folded to %s)"; + + public static final String EXCEPTION_CANNOT_EVALUATE_INSERT_EXPRESSION_AS_A_CONSTANT_ARG_ARG_F478C134 = + "Cannot evaluate insert expression as a constant: %s. %s"; + + public static final String EXCEPTION_THE_ARG_IS_OUT_OF_RANGE_OF_INT_F8E58471 = + "The %s is out of range of int."; + + public static final String EXCEPTION_THE_ARG_IS_OUT_OF_RANGE_OF_LONG_9B1C872A = + "The %s is out of range of long."; + + public static final String EXCEPTION_INT_OVERFLOW_ARG_ARG_BB287017 = + "int overflow: %s / %s"; + + public static final String EXCEPTION_LONG_OVERFLOW_ARG_ARG_46CC95E2 = + "long overflow: %s / %s"; } diff --git a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java index 5a504e2a0175f..df3ee9691b51e 100644 --- a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java +++ b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java @@ -4697,4 +4697,37 @@ private DataNodeQueryMessages() {} public static final String EXCEPTION_ONLY_INMEMORYDEVICEENTRYDATASET_SUPPORTS_GET_INLINE_DEVICE_ENTRIES_07A52CAB = "只有 InMemoryDeviceEntryDataSet 支持获取内存中的设备条目"; + + public static final String EXCEPTION_DATABASENAME_IS_NULL_A811DE1B = + "databaseName 为空"; + + public static final String EXCEPTION_VALUES_IS_NULL_A580EDAA = + "values 为空"; + + public static final String EXCEPTION_ZONEID_IS_NULL_7DAA5E2C = + "zoneId 为空"; + + public static final String EXCEPTION_INSERT_TARGET_TABLE_DOES_NOT_EXIST_ARG_ARG_78F05787 = + "写入目标表不存在:%s.%s"; + + public static final String EXCEPTION_EXPRESSIONS_AND_COLUMNS_DO_NOT_MATCH_EXPRESSIONS_SIZE_ARG_COLUMNS_SIZE_ARG_F933A8C1 = + "表达式与列不匹配,表达式数量:%s,列数量:%s"; + + public static final String EXCEPTION_INSERT_EXPRESSION_MUST_BE_CONSTANT_AFTER_CONSTANT_FOLDING_ARG_FOLDED_TO_ARG_D9EE1433 = + "写入表达式在常量折叠后必须为常量:%s(折叠结果为 %s)"; + + public static final String EXCEPTION_CANNOT_EVALUATE_INSERT_EXPRESSION_AS_A_CONSTANT_ARG_ARG_F478C134 = + "无法将写入表达式计算为常量:%s。%s"; + + public static final String EXCEPTION_THE_ARG_IS_OUT_OF_RANGE_OF_INT_F8E58471 = + "%s 超出 int 的范围。"; + + public static final String EXCEPTION_THE_ARG_IS_OUT_OF_RANGE_OF_LONG_9B1C872A = + "%s 超出 long 的范围。"; + + public static final String EXCEPTION_INT_OVERFLOW_ARG_ARG_BB287017 = + "int 溢出:%s / %s"; + + public static final String EXCEPTION_LONG_OVERFLOW_ARG_ARG_46CC95E2 = + "long 溢出:%s / %s"; } 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 4a98819c6487f..619ffd7ac13f8 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 @@ -197,17 +197,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()); @@ -230,6 +230,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 e1c9db8d0f2bf..038ce75010d0a 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 @@ -189,6 +189,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; @@ -1335,6 +1336,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/planner/IrExpressionInterpreter.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrExpressionInterpreter.java index 464c18019bdd0..bf44f7d336f74 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrExpressionInterpreter.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrExpressionInterpreter.java @@ -73,7 +73,6 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Predicates.instanceOf; -import static com.google.common.base.Throwables.throwIfInstanceOf; import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.getOnlyElement; @@ -84,6 +83,11 @@ import static org.apache.iotdb.commons.queryengine.plan.relational.type.TypeSignatureTranslator.toTypeSignature; import static org.apache.iotdb.db.queryengine.plan.relational.planner.ir.DeterminismEvaluator.isDeterministic; import static org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils.isEffectivelyLiteral; +import static org.apache.tsfile.read.common.type.DoubleType.DOUBLE; +import static org.apache.tsfile.read.common.type.FloatType.FLOAT; +import static org.apache.tsfile.read.common.type.IntType.INT32; +import static org.apache.tsfile.read.common.type.LongType.INT64; +import static org.apache.tsfile.read.common.type.TimestampType.TIMESTAMP; public class IrExpressionInterpreter { @@ -528,23 +532,13 @@ public Object visitArithmeticUnary(ArithmeticUnaryExpression node, Object contex } if (node.getSign() == PLUS) { - return value; + return normalizeArithmeticValue(value, type(node)); } else { - try { - Expression valueExpression = toExpression(value, type(node.getValue())); - if (valueExpression instanceof ArithmeticUnaryExpression - && ((ArithmeticUnaryExpression) valueExpression).getSign().equals(MINUS)) { - return ((ArithmeticUnaryExpression) valueExpression).getValue(); - } - return new ArithmeticUnaryExpression(MINUS, valueExpression); - // TODO use the following after we implement InterpretedFunctionInvoker - // return invokeOperator(OperatorType.NEGATION, types(node.getValue()), - // Collections.singletonList(value)); - } catch (Throwable throwable) { - throwIfInstanceOf(throwable, RuntimeException.class); - throwIfInstanceOf(throwable, Error.class); - throw new RuntimeException(throwable.getMessage(), throwable); + Type returnType = type(node); + if (isSupportedArithmeticUnaryType(returnType)) { + return evaluateArithmeticNegation(value, returnType); } + return new ArithmeticUnaryExpression(MINUS, toExpression(value, type(node.getValue()))); } } @@ -564,16 +558,21 @@ public Object visitArithmeticBinary(ArithmeticBinaryExpression node, Object cont node.getOperator(), toExpression(left, type(node.getLeft())), toExpression(right, type(node.getRight()))); - } else { - return new ArithmeticBinaryExpression( - node.getOperator(), - toExpression(left, type(node.getLeft())), - toExpression(right, type(node.getRight()))); - // TODO use the following after we implement InterpretedFunctionInvoker - // return invokeOperator(OperatorType.valueOf(node.getOperator().name()), - // types(node.getLeft(), node.getRight()), - // ImmutableList.of(left, right)); } + + Type leftType = type(node.getLeft()); + Type rightType = type(node.getRight()); + Type returnType = type(node); + if (isSupportedArithmeticBinaryType(leftType) + && isSupportedArithmeticBinaryType(rightType) + && isSupportedArithmeticBinaryType(returnType)) { + return evaluateArithmeticBinary(node.getOperator(), left, leftType, right, returnType); + } + + return new ArithmeticBinaryExpression( + node.getOperator(), + toExpression(left, type(node.getLeft())), + toExpression(right, type(node.getRight()))); } @Override @@ -657,67 +656,12 @@ private Object processComparisonExpression( toExpression(left, type(leftExpression)), toExpression(right, type(rightExpression))); } else { - if (!(left instanceof Number) || !(right instanceof Number)) { - throw new IllegalArgumentException( - DataNodeQueryMessages.BOTH_OBJECT_MUST_BE_TYPE_OF_NUMBER); - } - - if (left instanceof Integer && right instanceof Integer) { - Integer leftNum = (Integer) left; - Integer rightNum = (Integer) right; - if (operator == ComparisonExpression.Operator.LESS_THAN) { - return leftNum < rightNum; - } else if (operator == ComparisonExpression.Operator.LESS_THAN_OR_EQUAL) { - return leftNum <= rightNum; - } else if (operator == ComparisonExpression.Operator.EQUAL) { - return leftNum.equals(rightNum); - } - } - - if (left instanceof Long && right instanceof Long) { - Long leftNum = (Long) left; - Long rightNum = (Long) right; - if (operator == ComparisonExpression.Operator.LESS_THAN) { - return leftNum < rightNum; - } else if (operator == ComparisonExpression.Operator.LESS_THAN_OR_EQUAL) { - return leftNum <= rightNum; - } else if (operator == ComparisonExpression.Operator.EQUAL) { - return leftNum.equals(rightNum); - } - } - - if (left instanceof Float && right instanceof Float) { - Float leftNum = (Float) left; - Float rightNum = (Float) right; - if (operator == ComparisonExpression.Operator.LESS_THAN) { - return leftNum < rightNum; - } else if (operator == ComparisonExpression.Operator.LESS_THAN_OR_EQUAL) { - return leftNum <= rightNum; - } else if (operator == ComparisonExpression.Operator.EQUAL) { - return leftNum.equals(rightNum); - } - } - - if (left instanceof Double && right instanceof Double) { - Double leftNum = (Double) left; - Double rightNum = (Double) right; - if (operator == ComparisonExpression.Operator.LESS_THAN) { - return leftNum < rightNum; - } else if (operator == ComparisonExpression.Operator.LESS_THAN_OR_EQUAL) { - return leftNum <= rightNum; - } else if (operator == ComparisonExpression.Operator.EQUAL) { - return leftNum.equals(rightNum); - } + if (left instanceof Number && right instanceof Number) { + return evaluateNumericComparison(operator, (Number) left, (Number) right); } - return new ComparisonExpression( - operator, - toExpression(left, type(leftExpression)), - toExpression(right, type(rightExpression))); - // TODO use the following after we implement InterpretedFunctionInvoker - // return invokeOperator(OperatorType.valueOf(operator.name()), types(leftExpression, - // rightExpression), - // ImmutableList.of(left, right)); + throw new IllegalArgumentException( + DataNodeQueryMessages.BOTH_OBJECT_MUST_BE_TYPE_OF_NUMBER); } } @@ -981,6 +925,213 @@ private boolean hasUnresolvedValue(List values) { return values.stream().anyMatch(instanceOf(Expression.class)); } + private boolean isSupportedArithmeticBinaryType(Type type) { + return type.equals(INT32) || type.equals(INT64) || type.equals(FLOAT) || type.equals(DOUBLE); + } + + private boolean isSupportedArithmeticUnaryType(Type type) { + return isSupportedArithmeticBinaryType(type) || type.equals(TIMESTAMP); + } + + private Object evaluateArithmeticNegation(Object value, Type returnType) { + if (returnType.equals(INT32)) { + int intValue = toInt(value); + if (intValue == Integer.MIN_VALUE) { + throw new SemanticException( + String.format( + DataNodeQueryMessages.EXCEPTION_THE_ARG_IS_OUT_OF_RANGE_OF_INT_F8E58471, + intValue)); + } + return -intValue; + } + if (returnType.equals(INT64) || returnType.equals(TIMESTAMP)) { + long longValue = toLong(value); + if (longValue == Long.MIN_VALUE) { + throw new SemanticException( + String.format( + DataNodeQueryMessages.EXCEPTION_THE_ARG_IS_OUT_OF_RANGE_OF_LONG_9B1C872A, + longValue)); + } + return -longValue; + } + if (returnType.equals(FLOAT)) { + return -toFloat(value); + } + if (returnType.equals(DOUBLE)) { + return -toDouble(value); + } + throw new SemanticException(DataNodeQueryMessages.UNKNOWN_TYPE_2 + returnType); + } + + private Object evaluateArithmeticBinary( + ArithmeticBinaryExpression.Operator operator, + Object left, + Type leftType, + Object right, + Type returnType) { + if (returnType.equals(INT32)) { + return evaluateIntArithmetic(operator, toInt(left), toInt(right)); + } + if (returnType.equals(INT64)) { + return evaluateLongArithmetic(operator, toLong(left), leftType, toLong(right)); + } + if (returnType.equals(FLOAT)) { + return evaluateFloatArithmetic(operator, toFloat(left), toFloat(right)); + } + if (returnType.equals(DOUBLE)) { + return evaluateDoubleArithmetic(operator, toDouble(left), toDouble(right)); + } + throw new SemanticException(DataNodeQueryMessages.UNKNOWN_TYPE_2 + returnType); + } + + private int evaluateIntArithmetic( + ArithmeticBinaryExpression.Operator operator, int left, int right) { + try { + switch (operator) { + case ADD: + return Math.addExact(left, right); + case SUBTRACT: + return Math.subtractExact(left, right); + case MULTIPLY: + return Math.multiplyExact(left, right); + case DIVIDE: + if (left == Integer.MIN_VALUE && right == -1) { + throw new SemanticException( + String.format( + DataNodeQueryMessages.EXCEPTION_INT_OVERFLOW_ARG_ARG_BB287017, left, right)); + } + return left / right; + case MODULUS: + return left % right; + default: + throw new UnsupportedOperationException( + DataNodeQueryMessages.UNSUPPORTED_EXPRESSION + operator); + } + } catch (ArithmeticException e) { + throw new SemanticException(e.getMessage()); + } + } + + private long evaluateLongArithmetic( + ArithmeticBinaryExpression.Operator operator, long left, Type leftType, long right) { + try { + switch (operator) { + case ADD: + return Math.addExact(left, right); + case SUBTRACT: + return Math.subtractExact(left, right); + case MULTIPLY: + return Math.multiplyExact(left, right); + case DIVIDE: + if ((leftType.equals(INT32) && left == Integer.MIN_VALUE && right == -1) + || (leftType.equals(INT64) && left == Long.MIN_VALUE && right == -1)) { + throw new SemanticException( + String.format( + DataNodeQueryMessages.EXCEPTION_LONG_OVERFLOW_ARG_ARG_46CC95E2, left, right)); + } + return left / right; + case MODULUS: + return left % right; + default: + throw new UnsupportedOperationException( + DataNodeQueryMessages.UNSUPPORTED_EXPRESSION + operator); + } + } catch (ArithmeticException e) { + throw new SemanticException(e.getMessage()); + } + } + + private float evaluateFloatArithmetic( + ArithmeticBinaryExpression.Operator operator, float left, float right) { + switch (operator) { + case ADD: + return left + right; + case SUBTRACT: + return left - right; + case MULTIPLY: + return left * right; + case DIVIDE: + return left / right; + case MODULUS: + return left % right; + default: + throw new UnsupportedOperationException( + DataNodeQueryMessages.UNSUPPORTED_EXPRESSION + operator); + } + } + + private double evaluateDoubleArithmetic( + ArithmeticBinaryExpression.Operator operator, double left, double right) { + switch (operator) { + case ADD: + return left + right; + case SUBTRACT: + return left - right; + case MULTIPLY: + return left * right; + case DIVIDE: + return left / right; + case MODULUS: + return left % right; + default: + throw new UnsupportedOperationException( + DataNodeQueryMessages.UNSUPPORTED_EXPRESSION + operator); + } + } + + private Object normalizeArithmeticValue(Object value, Type type) { + if (type.equals(INT32)) { + return toInt(value); + } + if (type.equals(INT64) || type.equals(TIMESTAMP)) { + return toLong(value); + } + if (type.equals(FLOAT)) { + return toFloat(value); + } + if (type.equals(DOUBLE)) { + return toDouble(value); + } + return value; + } + + private Boolean evaluateNumericComparison( + ComparisonExpression.Operator operator, Number left, Number right) { + int comparison = + (left instanceof Float + || left instanceof Double + || right instanceof Float + || right instanceof Double) + ? Double.compare(left.doubleValue(), right.doubleValue()) + : Long.compare(left.longValue(), right.longValue()); + switch (operator) { + case LESS_THAN: + return comparison < 0; + case LESS_THAN_OR_EQUAL: + return comparison <= 0; + case EQUAL: + return comparison == 0; + default: + return null; + } + } + + private int toInt(Object value) { + return ((Number) value).intValue(); + } + + private long toLong(Object value) { + return ((Number) value).longValue(); + } + + private float toFloat(Object value) { + return ((Number) value).floatValue(); + } + + private double toDouble(Object value) { + return ((Number) value).doubleValue(); + } + private Object invokeOperator( OperatorType operatorType, List argumentTypes, 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..08ff471bd4b01 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertValues.java @@ -0,0 +1,310 @@ +/* + * 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, DataNodeQueryMessages.EXCEPTION_DATABASENAME_IS_NULL_A811DE1B); + this.tableName = + requireNonNull(tableName, DataNodeQueryMessages.EXCEPTION_TABLENAME_IS_NULL_20708596); + this.columnNames = + columnIdentifiers == null + ? null + : columnIdentifiers.stream().map(Identifier::getValue).collect(Collectors.toList()); + this.rows = + requireNonNull(values, DataNodeQueryMessages.EXCEPTION_VALUES_IS_NULL_A580EDAA) + .getRows() + .stream() + .map(InsertValues::normalizeRow) + .collect(Collectors.toList()); + this.zoneId = requireNonNull(zoneId, DataNodeQueryMessages.EXCEPTION_ZONEID_IS_NULL_7DAA5E2C); + } + + 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); + // All rows of an INSERT VALUES statement share the same table and columns. + insertRowsStatement.setUniformTargets(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( + String.format( + DataNodeQueryMessages.EXCEPTION_INSERT_TARGET_TABLE_DOES_NOT_EXIST_ARG_ARG_78F05787, + 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( + String.format( + DataNodeQueryMessages + .EXCEPTION_EXPRESSIONS_AND_COLUMNS_DO_NOT_MATCH_EXPRESSIONS_SIZE_ARG_COLUMNS_SIZE_ARG_F933A8C1, + expressions.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( + DataNodeQueryMessages.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( + DataNodeQueryMessages.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 4e502e7d3bde9..b625e202f0edc 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; @@ -143,12 +142,9 @@ 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.plan.statement.component.FillPolicy; -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; @@ -199,7 +195,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; @@ -262,12 +258,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; @@ -278,7 +271,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; @@ -288,9 +280,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; @@ -845,234 +835,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); - // All rows of a parsed `INSERT INTO ... VALUES ...` share the same table and columns. - insertRowsStatement.setUniformTargets(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); - // All rows of a parsed `INSERT INTO
... VALUES ...` share the same table and columns. - insertRowsStatement.setUniformTargets(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( - DataNodeQueryMessages.EXPRESSIONS_AND_COLUMNS_DO_NOT_MATCH_EXPRESSIONS_SIZE - + expressions.size() - + DataNodeQueryMessages.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( - DataNodeQueryMessages.TIMECOLUMNINDEX_OUT_OF_BOUND_D_D, - timeColumnIndex, - expressions.size())); - } - - Expression timeExpression = expressions.get(timeColumnIndex); - timestamp = AstUtil.expressionToTimestamp(timeExpression, zoneId); - nonTimeColCnt = expressions.size() - 1; - } - - if (nonTimeColCnt != nonTimeColumnNames.length) { - throw new SemanticException( - String.format( - DataNodeQueryMessages.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 92d4a7032e128..e49e49e7d973f 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,21 +20,33 @@ 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; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.LongLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Node; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.NullLiteral; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Parameter; 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; @@ -120,6 +132,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) { @@ -136,5 +156,68 @@ 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 (preOrder(expression).anyMatch(Parameter.class::isInstance)) { + throw new SemanticException(DataNodeQueryMessages.UNSUPPORTED_EXPRESSION + expression); + } + if (expression instanceof Identifier) { + throw new SemanticException( + String.format( + DataNodeQueryMessages.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( + DataNodeQueryMessages + .EXCEPTION_INSERT_EXPRESSION_MUST_BE_CONSTANT_AFTER_CONSTANT_FOLDING_ARG_FOLDED_TO_ARG_D9EE1433, + expression, + value)); + } + return value; + } catch (SemanticException e) { + throw e; + } catch (RuntimeException e) { + throw new SemanticException( + String.format( + DataNodeQueryMessages + .EXCEPTION_CANNOT_EVALUATE_INSERT_EXPRESSION_AS_A_CONSTANT_ARG_ARG_F478C134, + expression, + e.getMessage())); + } + } + private AstUtil() {} } diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertValuesTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertValuesTest.java new file mode 100644 index 0000000000000..b29acbec45ad6 --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertValuesTest.java @@ -0,0 +1,137 @@ +/* + * 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.commons.exception.SemanticException; +import org.apache.iotdb.commons.queryengine.common.SessionInfo; +import org.apache.iotdb.commons.queryengine.common.SqlDialect; +import org.apache.iotdb.commons.queryengine.plan.relational.type.InternalTypeManager; +import org.apache.iotdb.commons.schema.table.TsTable; +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.db.i18n.DataNodeQueryMessages; +import org.apache.iotdb.db.protocol.session.InternalClientSession; +import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableMetadataImpl; +import org.apache.iotdb.db.queryengine.plan.relational.planner.PlannerContext; +import org.apache.iotdb.db.queryengine.plan.relational.sql.parser.SqlParser; +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 com.google.common.collect.ImmutableList; +import org.apache.tsfile.enums.TSDataType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.ZoneId; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +public class InsertValuesTest { + private static final String DATABASE = "insert_values_test"; + private static final String TABLE = "values_table"; + private static final ZoneId ZONE = ZoneId.of("UTC"); + private final PlannerContext plannerContext = + new PlannerContext(new TableMetadataImpl(), new InternalTypeManager()); + private final SessionInfo session = new SessionInfo(0, "root", ZONE, DATABASE, SqlDialect.TABLE); + + @Before + public void setUp() { + TsTable table = new TsTable(TABLE); + table.addColumnSchema(new TimeColumnSchema("time", TSDataType.TIMESTAMP)); + table.addColumnSchema(new TagColumnSchema("device_id", TSDataType.STRING)); + table.addColumnSchema(new FieldColumnSchema("value", TSDataType.INT32)); + cacheTable(table); + } + + @After + public void tearDown() { + DataNodeTableCache.getInstance().invalid(DATABASE); + } + + @Test + public void testFoldedRowsRetainUniformTargets() { + for (String columns : new String[] {" (time, device_id, value)", ""}) { + InsertValues insert = parse(columns + " VALUES (1 + 1, 'd1', 2 + 3), (2 + 1, 'd2', 3 + 4)"); + insert.materialize(plannerContext, session); + InsertRowsStatement statement = insert.getInnerTreeStatement(); + assertTrue(statement.isUniformTargets()); + assertEquals(2, statement.getInsertRowStatementList().size()); + for (int i = 0; i < 2; i++) { + InsertRowStatement row = statement.getInsertRowStatementList().get(i); + assertEquals(i + 2, row.getTime()); + assertArrayEquals(new String[] {"device_id", "value"}, row.getMeasurements()); + assertEquals(i * 2 + 5, ((Number) row.getValues()[1]).intValue()); + } + } + } + + @Test + public void testFoldedCustomTimeColumn() { + cacheTable( + new TsTable( + TABLE, + ImmutableList.of( + new TagColumnSchema("device_id", TSDataType.STRING), + new TimeColumnSchema("event_time", TSDataType.TIMESTAMP), + new FieldColumnSchema("value", TSDataType.INT32)))); + for (String columns : new String[] {" (device_id, event_time, value)", ""}) { + InsertValues insert = parse(columns + " VALUES ('d1', 10 + 5, 20 + 2)"); + insert.materialize(plannerContext, session); + InsertRowStatement row = insert.getInnerTreeStatement().getInsertRowStatementList().get(0); + assertEquals(15, row.getTime()); + assertEquals(22, ((Number) row.getValues()[1]).intValue()); + } + } + + @Test + public void testUnboundParametersRemainSemanticErrors() { + for (String columns : new String[] {" (time, device_id, value)", ""}) { + for (String values : + new String[] {" (?, 'd1', 1)", " (1, 'd1', 1 + ?)", " (1, 'd1', 1), (?, 'd2', 2)"}) { + InsertValues insert = parse(columns + " VALUES" + values); + SemanticException exception = + assertThrows( + SemanticException.class, () -> insert.materialize(plannerContext, session)); + assertTrue(exception.getMessage().startsWith(DataNodeQueryMessages.UNSUPPORTED_EXPRESSION)); + assertTrue(insert.getInnerTreeStatement().getInsertRowStatementList().isEmpty()); + } + } + } + + private InsertValues parse(String suffix) { + return (InsertValues) + new SqlParser() + .createStatement( + "INSERT INTO " + DATABASE + "." + TABLE + suffix, + ZONE, + new InternalClientSession("insert_values_test")); + } + + private void cacheTable(TsTable table) { + DataNodeTableCache.getInstance().preUpdateTable(DATABASE, table, null); + DataNodeTableCache.getInstance().commitUpdateTable(DATABASE, TABLE, null); + } +}