From 704769162b255b9366877b158719176adb2852c8 Mon Sep 17 00:00:00 2001 From: Zihan Dai <1436286758@qq.com> Date: Wed, 21 Oct 2026 01:31:33 +1100 Subject: [PATCH] Accept constant TIMESTAMP expressions in table-model time filters A predicate such as time >= CAST(1000 AS TIMESTAMP) failed with error 301 (GenericLiteral cannot be cast to LongLiteral), and an IN list of such values with error 305. The constant is folded into a TIMESTAMP GenericLiteral, which ConvertPredicateToTimeFilterVisitor cast to LongLiteral. Read its value as a long in the current precision, as the TIMESTAMP field path in TypeServices already does. Signed-off-by: Zihan Dai <1436286758@qq.com> --- .../ConvertPredicateToTimeFilterVisitor.java | 23 ++++++++- .../TablePredicateConversionTest.java | 49 +++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToTimeFilterVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToTimeFilterVisitor.java index 18a1aa33f50eb..4f9b8dab9b06f 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToTimeFilterVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToTimeFilterVisitor.java @@ -23,6 +23,7 @@ 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.Extract; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.GenericLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.IfExpression; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.InListExpression; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.InPredicate; @@ -39,6 +40,7 @@ import org.apache.iotdb.db.i18n.DataNodeQueryMessages; import com.google.common.collect.ImmutableList; +import org.apache.tsfile.read.common.type.TimestampType; import org.apache.tsfile.read.filter.basic.Filter; import org.apache.tsfile.read.filter.factory.FilterFactory; import org.apache.tsfile.read.filter.factory.TimeFilterApi; @@ -70,14 +72,14 @@ public Filter visitInPredicate(InPredicate node, Void context) { checkArgument(valueList instanceof InListExpression); List values = ((InListExpression) valueList).getValues(); for (Expression value : values) { - checkArgument(value instanceof LongLiteral); + checkArgument(isTimeLiteral(value)); } if (values.size() == 1) { return TimeFilterApi.eq(getLongValue(values.get(0))); } Set longValues = new HashSet<>(); for (Expression value : values) { - longValues.add(((LongLiteral) value).getParsedValue()); + longValues.add(getLongValue(value)); } return TimeFilterApi.in(longValues); } @@ -317,6 +319,23 @@ public Filter visitBetweenPredicate(BetweenPredicate node, Void context) { } public static long getLongValue(Expression expression) { + if (isTimestampLiteral(expression)) { + return Long.parseLong(((GenericLiteral) expression).getValue()); + } return ((LongLiteral) expression).getParsedValue(); } + + private static boolean isTimeLiteral(Expression expression) { + return expression instanceof LongLiteral || isTimestampLiteral(expression); + } + + // A constant TIMESTAMP expression, such as CAST(1000 AS TIMESTAMP), is folded into a + // GenericLiteral whose value is the timestamp as a long in the current precision. + private static boolean isTimestampLiteral(Expression expression) { + return expression instanceof GenericLiteral + && TimestampType.TIMESTAMP + .getTypeEnum() + .name() + .equals(((GenericLiteral) expression).getType()); + } } diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/predicate/TablePredicateConversionTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/predicate/TablePredicateConversionTest.java index 3c38de79b2c85..8f821b20d315b 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/predicate/TablePredicateConversionTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/predicate/TablePredicateConversionTest.java @@ -21,9 +21,13 @@ import org.apache.iotdb.commons.queryengine.plan.relational.metadata.ColumnSchema; import org.apache.iotdb.commons.queryengine.plan.relational.planner.Symbol; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.BetweenPredicate; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.DoubleLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Expression; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.GenericLiteral; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.InListExpression; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.InPredicate; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.LongLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SymbolReference; import org.apache.iotdb.db.queryengine.plan.analyze.PredicateUtils; @@ -31,14 +35,19 @@ import org.apache.tsfile.read.common.type.IntType; import org.apache.tsfile.read.common.type.LongType; import org.apache.tsfile.read.filter.basic.Filter; +import org.apache.tsfile.read.filter.factory.TimeFilterApi; import org.apache.tsfile.read.filter.operator.FalseLiteralFilter; import org.apache.tsfile.read.filter.operator.LongFilterOperators; import org.apache.tsfile.read.filter.operator.ValueIsNotNullOperator; import org.junit.Assert; import org.junit.Test; +import java.time.ZoneId; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Map; +import java.util.concurrent.TimeUnit; import static org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory.FIELD; @@ -503,4 +512,44 @@ public void testInt64LiteralAndInt32Column() { Assert.assertEquals(new LongFilterOperators.ValueGtEq(0, 123), actualFilter); } + + @Test + public void testTimestampConstantAndTimeColumn() { + // A constant TIMESTAMP expression such as CAST(1000 AS TIMESTAMP) reaches the conversion as a + // folded GenericLiteral, not a LongLiteral. + SymbolReference time = new SymbolReference("time"); + GenericLiteral ts1000 = new GenericLiteral("TIMESTAMP", "1000"); + GenericLiteral ts2000 = new GenericLiteral("TIMESTAMP", "2000"); + + // time >= CAST(1000 AS TIMESTAMP) + Assert.assertEquals( + TimeFilterApi.gtEq(1000), + convertTimePredicate( + new ComparisonExpression( + ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL, time, ts1000))); + // CAST(1000 AS TIMESTAMP) < time + Assert.assertEquals( + TimeFilterApi.gt(1000), + convertTimePredicate( + new ComparisonExpression(ComparisonExpression.Operator.LESS_THAN, ts1000, time))); + // time IN (CAST(1000 AS TIMESTAMP), CAST(2000 AS TIMESTAMP)) + Assert.assertEquals( + TimeFilterApi.in(new HashSet<>(Arrays.asList(1000L, 2000L))), + convertTimePredicate( + new InPredicate(time, new InListExpression(Arrays.asList(ts1000, ts2000))))); + // time BETWEEN CAST(1000 AS TIMESTAMP) AND CAST(2000 AS TIMESTAMP) + Assert.assertEquals( + TimeFilterApi.between(1000, 2000), + convertTimePredicate(new BetweenPredicate(time, ts1000, ts2000))); + } + + private static Filter convertTimePredicate(Expression predicate) { + return PredicateUtils.convertPredicateToFilter( + predicate, + Collections.emptyMap(), + Collections.emptyMap(), + "time", + ZoneId.of("UTC"), + TimeUnit.MILLISECONDS); + } }