This is an automated email from the ASF dual-hosted git repository. JackieTien97 pushed a commit to branch rc/2.0.10 in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 29ffef335267f2b84041a70eb4486729353c9f93 Author: Jiang Tian <[email protected]> AuthorDate: Thu Jun 4 10:33:43 2026 +0800 Fix table delete with renamed time column (#17841) * reviewed AnalyzeUtils * Fix delete with non-default time column --- .../relational/it/db/it/IoTDBDeletionTableIT.java | 21 +++++++++ .../iotdb/db/i18n/DataNodeQueryMessages.java | 2 + .../iotdb/db/i18n/DataNodeQueryMessages.java | 2 + .../db/queryengine/plan/analyze/AnalyzeUtils.java | 14 +++++- .../plan/relational/planner/PredicateUtils.java | 28 +++++++---- .../queryengine/plan/analyze/AnalyzeUtilsTest.java | 55 ++++++++++++++++++++++ .../relational/planner/PredicateUtilsTest.java | 13 +++++ 7 files changed, 126 insertions(+), 9 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java index e6939c5226a..66d0e3a6a0d 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java @@ -414,6 +414,27 @@ public class IoTDBDeletionTableIT { } } + @Test + public void testDeleteWithRenamedTimeColumn() throws SQLException { + try (Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + Statement statement = connection.createStatement()) { + statement.execute("CREATE DATABASE time_column_rename"); + statement.execute("use time_column_rename"); + statement.execute("CREATE TABLE vehicle(ts time, deviceId STRING TAG, s0 INT32 FIELD)"); + statement.execute("INSERT INTO vehicle(ts, deviceId, s0) VALUES(1, 'd0', 1)"); + statement.execute("INSERT INTO vehicle(ts, deviceId, s0) VALUES(2, 'd0', 2)"); + + statement.execute("DELETE FROM vehicle WHERE ts <= 1"); + + try (ResultSet resultSet = statement.executeQuery("SELECT ts, s0 FROM vehicle")) { + assertTrue(resultSet.next()); + assertEquals(2, resultSet.getLong("ts")); + assertEquals(2, resultSet.getInt("s0")); + assertFalse(resultSet.next()); + } + } + } + @Test public void testRangeDelete() throws SQLException { prepareData(4, 1); 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 c1c26550f9d..2c4978618a6 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 @@ -259,6 +259,8 @@ public final class DataNodeQueryMessages { "Left hand expression is not an identifier: "; public static final String THE_LEFT_HAND_VALUE_MUST_BE_AN_IDENTIFIER = "The left hand value must be an identifier: "; + public static final String THE_TABLE_S_DOES_NOT_CONTAIN_A_TIME_COLUMN = + "The table '%s' does not contain a time column"; public static final String THE_OPERATOR_OF_TAG_PREDICATE_MUST_BE_FOR = "The operator of tag predicate must be '=' for "; public static final String ONLY_TIME_FILTERS_ARE_SUPPORTED_IN_LAST_QUERY = 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 3d2783c81b6..7a7de06471b 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 @@ -258,6 +258,8 @@ public final class DataNodeQueryMessages { "左侧表达式不是标识符:"; public static final String THE_LEFT_HAND_VALUE_MUST_BE_AN_IDENTIFIER = "左侧值必须是标识符:"; + public static final String THE_TABLE_S_DOES_NOT_CONTAIN_A_TIME_COLUMN = + "表 '%s' 不包含时间列"; public static final String THE_OPERATOR_OF_TAG_PREDICATE_MUST_BE_FOR = "标签谓词的运算符必须为 '=',目标:"; public static final String ONLY_TIME_FILTERS_ARE_SUPPORTED_IN_LAST_QUERY = diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java index c9cf278051f..d00ee54428b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java @@ -37,6 +37,7 @@ import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.LongLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.NullLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.StringLiteral; import org.apache.iotdb.commons.schema.table.TsTable; +import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema; import org.apache.iotdb.commons.service.metric.PerformanceOverviewMetrics; import org.apache.iotdb.confignode.rpc.thrift.TRegionRouteMapResp; import org.apache.iotdb.db.i18n.DataNodeQueryMessages; @@ -520,7 +521,7 @@ public class AnalyzeUtils { } Identifier identifier = (Identifier) left; // time predicate - if (identifier.getValue().equalsIgnoreCase("time")) { + if (identifier.getValue().equalsIgnoreCase(getTimeColumnName(table))) { long rightHandValue; if (right instanceof LongLiteral) { rightHandValue = ((LongLiteral) right).getParsedValue(); @@ -567,6 +568,17 @@ public class AnalyzeUtils { return combinePredicates(oldPredicate, newPredicate); } + private static String getTimeColumnName(final TsTable table) { + final TsTableColumnSchema timeColumnSchema = table.getTimeColumnSchema(); + if (Objects.isNull(timeColumnSchema)) { + throw new SemanticException( + String.format( + DataNodeQueryMessages.THE_TABLE_S_DOES_NOT_CONTAIN_A_TIME_COLUMN, + table.getTableName())); + } + return timeColumnSchema.getColumnName(); + } + private static IDPredicate getTagPredicate( ComparisonExpression comparisonExpression, Expression right, int tagColumnOrdinal) { if (comparisonExpression.getOperator() != ComparisonExpression.Operator.EQUAL) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PredicateUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PredicateUtils.java index d7748e2347d..041be454c92 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PredicateUtils.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PredicateUtils.java @@ -57,14 +57,25 @@ public class PredicateUtils { */ public static Pair<Expression, Boolean> extractGlobalTimePredicate( Expression predicate, boolean canRewrite, boolean isFirstOr) { + return extractGlobalTimePredicate(predicate, canRewrite, isFirstOr, TIME); + } + + public static Pair<Expression, Boolean> extractGlobalTimePredicate( + Expression predicate, boolean canRewrite, boolean isFirstOr, String timeColumnName) { if (predicate instanceof LogicalExpression && ((LogicalExpression) predicate).getOperator().equals(AND)) { Pair<Expression, Boolean> leftResultPair = extractGlobalTimePredicate( - ((LogicalExpression) predicate).getTerms().get(0), canRewrite, isFirstOr); + ((LogicalExpression) predicate).getTerms().get(0), + canRewrite, + isFirstOr, + timeColumnName); Pair<Expression, Boolean> rightResultPair = extractGlobalTimePredicate( - ((LogicalExpression) predicate).getTerms().get(1), canRewrite, isFirstOr); + ((LogicalExpression) predicate).getTerms().get(1), + canRewrite, + isFirstOr, + timeColumnName); // rewrite predicate to avoid duplicate calculation on time filter // If Left-child or Right-child does not contain value filter @@ -104,10 +115,10 @@ public class PredicateUtils { && ((LogicalExpression) predicate).getOperator().equals(OR)) { Pair<Expression, Boolean> leftResultPair = extractGlobalTimePredicate( - ((LogicalExpression) predicate).getTerms().get(0), false, false); + ((LogicalExpression) predicate).getTerms().get(0), false, false, timeColumnName); Pair<Expression, Boolean> rightResultPair = extractGlobalTimePredicate( - ((LogicalExpression) predicate).getTerms().get(1), false, false); + ((LogicalExpression) predicate).getTerms().get(1), false, false, timeColumnName); if (leftResultPair.left != null && rightResultPair.left != null) { if (Boolean.TRUE.equals(isFirstOr && !leftResultPair.right && !rightResultPair.right)) { @@ -132,8 +143,8 @@ public class PredicateUtils { else if (predicate instanceof ComparisonExpression) { Expression leftExpression = ((ComparisonExpression) predicate).getLeft(); Expression rightExpression = ((ComparisonExpression) predicate).getRight(); - if (checkIsTimeFilter(leftExpression, rightExpression) - || checkIsTimeFilter(rightExpression, leftExpression)) { + if (checkIsTimeFilter(leftExpression, rightExpression, timeColumnName) + || checkIsTimeFilter(rightExpression, leftExpression, timeColumnName)) { return new Pair<>(predicate, false); } return new Pair<>(null, true); @@ -190,9 +201,10 @@ public class PredicateUtils { } } - private static boolean checkIsTimeFilter(Expression timeExpression, Expression valueExpression) { + private static boolean checkIsTimeFilter( + Expression timeExpression, Expression valueExpression, String timeColumnName) { return timeExpression instanceof Identifier - && ((Identifier) timeExpression).getValue().equalsIgnoreCase(TIME) + && ((Identifier) timeExpression).getValue().equalsIgnoreCase(timeColumnName) && valueExpression instanceof LongLiteral; } } diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java new file mode 100644 index 00000000000..5d0ccd74f45 --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java @@ -0,0 +1,55 @@ +/* + * 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.analyze; + +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression; +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.LongLiteral; +import org.apache.iotdb.commons.schema.table.TsTable; +import org.apache.iotdb.commons.schema.table.column.TimeColumnSchema; +import org.apache.iotdb.db.storageengine.dataregion.modification.TableDeletionEntry; + +import org.apache.tsfile.enums.TSDataType; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class AnalyzeUtilsTest { + + @Test + public void testParseDeletePredicateWithRenamedTimeColumn() { + TsTable table = new TsTable("table1"); + table.addColumnSchema(new TimeColumnSchema("ts", TSDataType.TIMESTAMP)); + Expression expression = + new ComparisonExpression( + ComparisonExpression.Operator.LESS_THAN_OR_EQUAL, + new Identifier("ts"), + new LongLiteral("100")); + + List<TableDeletionEntry> entries = AnalyzeUtils.parseExpressions2ModEntries(expression, table); + + assertEquals(1, entries.size()); + assertEquals(Long.MIN_VALUE, entries.get(0).getStartTime()); + assertEquals(100, entries.get(0).getEndTime()); + } +} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PredicateUtilsTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PredicateUtilsTest.java index 51df25a68d0..48a012a21ca 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PredicateUtilsTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PredicateUtilsTest.java @@ -22,7 +22,10 @@ package org.apache.iotdb.db.queryengine.plan.relational.planner; import org.apache.iotdb.commons.conf.IoTDBConstant; import org.apache.iotdb.commons.queryengine.common.SessionInfo; import org.apache.iotdb.commons.queryengine.common.SqlDialect; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression; 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.LongLiteral; import org.apache.iotdb.db.queryengine.common.MPPQueryContext; import org.apache.iotdb.db.queryengine.common.QueryId; import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis; @@ -36,6 +39,7 @@ import java.time.ZoneId; import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.AnalyzerTest.analyzeSQL; import static org.apache.iotdb.db.queryengine.plan.relational.planner.PredicateUtils.extractGlobalTimePredicate; +import static org.junit.Assert.assertNotNull; public class PredicateUtilsTest { @Test @@ -69,4 +73,13 @@ public class PredicateUtilsTest { actualAnalysis.getWhereMap().values().iterator().next(), true, true); System.out.println(ret.getLeft()); } + + @Test + public void extractGlobalTimePredicateWithCustomTimeColumnTest() { + Expression expression = + new ComparisonExpression( + ComparisonExpression.Operator.GREATER_THAN, new Identifier("ts"), new LongLiteral("1")); + Pair<Expression, Boolean> ret = extractGlobalTimePredicate(expression, true, true, "ts"); + assertNotNull(ret.getLeft()); + } }
