This is an automated email from the ASF dual-hosted git repository. jackietien pushed a commit to branch force_ci/object_type in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 8c75110b49e9a50c4cbbbb8797226642c504cfe6 Author: alpass163 <[email protected]> AuthorDate: Fri Nov 28 16:43:56 2025 +0800 implement the except (distinct | all ) for table model #16760 (cherry picked from commit 85b8f168d7c18e71eed86f907b9015456e7feb7b) --- .../it/query/recent/IoTDBExceptTableIT.java | 159 +++++++++++++++++++++ .../plan/planner/plan/node/PlanGraphPrinter.java | 10 ++ .../plan/planner/plan/node/PlanNodeType.java | 4 + .../plan/planner/plan/node/PlanVisitor.java | 5 + .../plan/relational/planner/RelationPlanner.java | 24 +++- ...istinctAsUnion.java => ImplementExceptAll.java} | 78 ++++++---- ...on.java => ImplementExceptDistinctAsUnion.java} | 52 ++++--- .../rule/ImplementIntersectDistinctAsUnion.java | 3 +- .../iterative/rule/PruneDistinctAggregation.java | 27 +--- .../iterative/rule/SetOperationNodeTranslator.java | 4 +- .../plan/relational/planner/node/ExceptNode.java | 99 +++++++++++++ .../plan/relational/planner/node/Patterns.java | 20 +-- .../optimizations/LogicalOptimizeFactory.java | 9 +- .../optimizations/UnaliasSymbolReferences.java | 31 ++++ .../plan/relational/analyzer/ExceptTest.java | 132 +++++++++++++++++ .../plan/relational/analyzer/TestMetadata.java | 11 +- 16 files changed, 574 insertions(+), 94 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBExceptTableIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBExceptTableIT.java new file mode 100644 index 00000000000..6c9c70d40e3 --- /dev/null +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBExceptTableIT.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.relational.it.query.recent; + +import org.apache.iotdb.it.env.EnvFactory; +import org.apache.iotdb.it.framework.IoTDBTestRunner; +import org.apache.iotdb.itbase.category.TableClusterIT; +import org.apache.iotdb.itbase.category.TableLocalStandaloneIT; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +import static org.apache.iotdb.db.it.utils.TestUtils.prepareTableData; +import static org.apache.iotdb.db.it.utils.TestUtils.tableAssertTestFail; +import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest; + +@RunWith(IoTDBTestRunner.class) +@Category({TableLocalStandaloneIT.class, TableClusterIT.class}) +public class IoTDBExceptTableIT { + protected static final String DATABASE_NAME = "test"; + protected static final String[] createSqls = + new String[] { + "CREATE DATABASE " + DATABASE_NAME, + "USE " + DATABASE_NAME, + // table1: ('d1', 1, 1) * 3, ('d1', 2, 2) * 1 + "create table table1(device STRING TAG, s1 INT32 FIELD, s2 INT32 FIELD)", + "insert into table1 values (1, 'd1', 1, 1)", + "insert into table1 values (2, 'd1', 1, 1)", + "insert into table1 values (3, 'd1', 1, 1)", + "insert into table1 values (4, 'd1', 2, 2)", + // table2: ('d1', 1, 1.0) * 1, ('d1', 3, 3.0) * 1 + "create table table2(device STRING TAG, s1 INT64 FIELD, s2 DOUBLE FIELD)", + "insert into table2 values (1, 'd1', 1, 1.0)", + "insert into table2 values (2, 'd1', 3, 3.0)", + // table3: use for testing alias, mirrors table2 + "create table table3(device STRING TAG, s1_testName INT64 FIELD, s2_testName DOUBLE FIELD)", + "insert into table3 values (1, 'd1', 1, 1.0)", + "insert into table3 values (2, 'd1', 3, 3.0)", + // table4: test type compatible + "create table table4(device STRING TAG, s1 TEXT FIELD, s2 DOUBLE FIELD)" + }; + + @BeforeClass + public static void setUp() throws Exception { + EnvFactory.getEnv().initClusterEnvironment(); + prepareTableData(createSqls); + } + + @AfterClass + public static void tearDown() throws Exception { + EnvFactory.getEnv().cleanClusterEnvironment(); + } + + @Test + public void normalTest() { + String[] expectedHeader = new String[] {"device", "s1", "s2"}; + + // --- EXCEPT (DISTINCT) --- + // table1 (distinct): {('d1', 1, 1.0), ('d1', 2, 2.0)} + // table2 (distinct): {('d1', 1, 1.0), ('d1', 3, 3.0)} + // expected one tuple : ('d1', 2, 2.0) + String[] retArray = + new String[] { + "d1,2,2.0,", + }; + tableResultSetEqualTest( + "select device, s1, s2 from table1 except select device, s1, s2 from table2", + expectedHeader, + retArray, + DATABASE_NAME); + tableResultSetEqualTest( + "select device, s1, s2 from table1 except distinct select device, s1, s2 from table2", + expectedHeader, + retArray, + DATABASE_NAME); + + // --- EXCEPT ALL --- + // Row ('d1', 1, 1.0): table1 has 3, table2 has 1. max(0, 3 - 1) = 2 tuples. + // Row ('d1', 2, 2.0): table1 has 1, table2 has 0. max(0, 1 - 0) = 1 tuple. + // Row ('d1', 3, 3.0): table1 has 0, table2 has 1. max(0, 0 - 1) = 0 tuples. + // expected: 2 * ('d1', 1, 1.0) and 1 * ('d1', 2, 2.0) + retArray = new String[] {"d1,1,1.0,", "d1,1,1.0,", "d1,2,2.0,"}; + tableResultSetEqualTest( + "select device, s1, s2 from table1 except all select device, s1, s2 from table2", + expectedHeader, + retArray, + DATABASE_NAME); + // test table3, the column name is different + tableResultSetEqualTest( + "select device, s1, s2 from table1 except all select device, s1_testName, s2_testName from table3", + expectedHeader, + retArray, + DATABASE_NAME); + } + + @Test + public void mappingTest() { + // table1 (aliased): (s1 as col_a) -> (1), (1), (1), (2) -> { (1.0) * 3, (2.0) * 1 } + // common value: (1.0) + + String[] expectedHeader = new String[] {"col_a"}; + + // --- EXCEPT (DISTINCT) with alias --- + // t1_distinct = {1.0, 2.0} + // t2_distinct = {1.0, 3.0} + // Result: {2.0} + String[] retArray = new String[] {"2.0,"}; + tableResultSetEqualTest( + "select col_a from ((select s1 as col_a, device as col_b from table1) except (select s2, device from table2)) order by col_a", + expectedHeader, + retArray, + DATABASE_NAME); + + // --- EXCEPT ALL with alias --- + // Row (1.0): t1 has 3, t2 has 1. max(0, 3 - 1) = 2. + // Row (2.0): t1 has 1, t2 has 0. max(0, 1 - 0) = 1. + // Result: {1.0, 1.0, 2.0} (query has order by) + retArray = new String[] {"1.0,", "1.0,", "2.0,"}; + tableResultSetEqualTest( + "select col_a from ((select s1 as col_a, device as col_b from table1) except all (select s2, device from table2)) order by col_a", + expectedHeader, + retArray, + DATABASE_NAME); + } + + @Test + public void exceptionTest() { + // type is incompatible (INT32 vs TEXT) + tableAssertTestFail( + "(select * from table1) except all (select * from table4)", + "has incompatible types: INT32, TEXT", + DATABASE_NAME); + + tableAssertTestFail( + "(select * from table1) except all (select time from table4)", + "EXCEPT query has different number of fields: 4, 1", + DATABASE_NAME); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanGraphPrinter.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanGraphPrinter.java index 82e03d8789b..bd4dd912e4c 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanGraphPrinter.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanGraphPrinter.java @@ -71,6 +71,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.planner.node.AggregationT import org.apache.iotdb.db.queryengine.plan.relational.planner.node.AssignUniqueId; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.DeviceTableScanNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.EnforceSingleRowNode; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExchangeNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExplainAnalyzeNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.GapFillNode; @@ -1116,6 +1117,15 @@ public class PlanGraphPrinter extends PlanVisitor<List<String>, PlanGraphPrinter return render(node, boxValue, context); } + @Override + public List<String> visitExcept(ExceptNode node, GraphContext context) { + List<String> boxValue = new ArrayList<>(); + boxValue.add(String.format("Except-%s", node.getPlanNodeId().getId())); + boxValue.add(String.format("OutputSymbols: %s", node.getOutputSymbols())); + boxValue.add(String.format("isDistinct: %s", node.isDistinct())); + return render(node, boxValue, context); + } + private List<String> render(PlanNode node, List<String> nodeBoxString, GraphContext context) { Box box = new Box(nodeBoxString); List<List<String>> children = new ArrayList<>(); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java index c4b77def053..6ebfa6dcfdc 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java @@ -120,6 +120,7 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalIn import org.apache.iotdb.db.queryengine.plan.relational.planner.node.AggregationTreeDeviceViewScanNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.AssignUniqueId; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.EnforceSingleRowNode; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.GapFillNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.GroupNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.InformationSchemaTableScanNode; @@ -315,6 +316,7 @@ public enum PlanNodeType { TABLE_INTO_NODE((short) 1033), TABLE_UNION_NODE((short) 1034), TABLE_INTERSECT_NODE((short) 1035), + TABLE_EXCEPT_NODE((short) 1036), RELATIONAL_INSERT_TABLET((short) 2000), RELATIONAL_INSERT_ROW((short) 2001), @@ -711,6 +713,8 @@ public enum PlanNodeType { return UnionNode.deserialize(buffer); case 1035: return IntersectNode.deserialize(buffer); + case 1036: + return ExceptNode.deserialize(buffer); case 2000: return RelationalInsertTabletNode.deserialize(buffer); case 2001: diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java index 3e237650a6a..4bebb692254 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java @@ -124,6 +124,7 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalIn import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.GroupReference; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.AggregationTreeDeviceViewScanNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.DeviceTableScanNode; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.GapFillNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.GroupNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.InformationSchemaTableScanNode; @@ -850,4 +851,8 @@ public abstract class PlanVisitor<R, C> { public R visitIntersect(IntersectNode node, C context) { return visitPlan(node, context); } + + public R visitExcept(ExceptNode node, C context) { + return visitPlan(node, context); + } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java index ce573ce1e2d..9732f8221d6 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java @@ -55,6 +55,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableMetadataImp import org.apache.iotdb.db.queryengine.plan.relational.metadata.TreeDeviceViewSchema; import org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.DeviceTableScanNode; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.InformationSchemaTableScanNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.IntersectNode; @@ -1164,6 +1165,24 @@ public class RelationPlanner extends AstVisitor<RelationPlan, Void> { intersectNode, analysis.getScope(node), intersectNode.getOutputSymbols(), outerContext); } + @Override + protected RelationPlan visitExcept(Except node, Void context) { + Preconditions.checkArgument( + !node.getRelations().isEmpty(), "No relations specified for except"); + SetOperationPlan setOperationPlan = process(node); + + PlanNode exceptNode = + new ExceptNode( + idAllocator.genPlanNodeId(), + setOperationPlan.getChildren(), + setOperationPlan.getSymbolMapping(), + ImmutableList.copyOf(setOperationPlan.getSymbolMapping().keySet()), + node.isDistinct()); + + return new RelationPlan( + exceptNode, analysis.getScope(node), exceptNode.getOutputSymbols(), outerContext); + } + private SetOperationPlan process(SetOperation node) { RelationType outputFields = analysis.getOutputDescriptor(node); List<Symbol> outputs = @@ -1210,11 +1229,6 @@ public class RelationPlanner extends AstVisitor<RelationPlan, Void> { throw new IllegalStateException("Values is not supported in current version."); } - @Override - protected RelationPlan visitExcept(Except node, Void context) { - throw new IllegalStateException("Except is not supported in current version."); - } - @Override protected RelationPlan visitInsertTablet(InsertTablet node, Void context) { final InsertTabletStatement insertTabletStatement = node.getInnerTreeStatement(); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptAll.java similarity index 51% copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptAll.java index 0a8f8c498ae..9cb2de73511 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptAll.java @@ -19,66 +19,90 @@ package org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule; +import org.apache.iotdb.commons.udf.builtin.relational.TableBuiltinScalarFunction; import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata; import org.apache.iotdb.db.queryengine.plan.relational.planner.Assignments; import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Rule; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode; -import org.apache.iotdb.db.queryengine.plan.relational.planner.node.IntersectNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.Patterns; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ProjectNode; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ArithmeticBinaryExpression; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GenericLiteral; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.QualifiedName; import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Captures; import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Pattern; import com.google.common.collect.ImmutableList; +import org.apache.tsfile.read.common.type.LongType; +import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; -import static org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils.and; -import static org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.node.Patterns.Except.distinct; +import static org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ArithmeticBinaryExpression.Operator.SUBTRACT; -public class ImplementIntersectDistinctAsUnion implements Rule<IntersectNode> { +public class ImplementExceptAll implements Rule<ExceptNode> { - private static final Pattern<IntersectNode> PATTERN = - Patterns.intersect().with(Patterns.Intersect.distinct().equalTo(true)); + private static final Pattern<ExceptNode> PATTERN = + Patterns.except().with(distinct().equalTo(false)); private final Metadata metadata; - @Override - public Pattern<IntersectNode> getPattern() { - return PATTERN; + public ImplementExceptAll(Metadata metadata) { + this.metadata = requireNonNull(metadata, "metadata is null"); } - public ImplementIntersectDistinctAsUnion(Metadata metadata) { - this.metadata = requireNonNull(metadata, "metadata is null"); + @Override + public Pattern<ExceptNode> getPattern() { + return PATTERN; } @Override - public Result apply(IntersectNode node, Captures captures, Context context) { + public Result apply(ExceptNode node, Captures captures, Context context) { SetOperationNodeTranslator translator = new SetOperationNodeTranslator( metadata, context.getSymbolAllocator(), context.getIdAllocator()); - SetOperationNodeTranslator.TranslationResult result = - translator.makeSetContainmentPlanForDistinct(node); - - // add the filterNode above the aggregation node - Expression predicate = - and( - result.getCountSymbols().stream() - .map( - symbol -> - new ComparisonExpression( - GREATER_THAN_OR_EQUAL, - symbol.toSymbolReference(), - new GenericLiteral("INT64", "1"))) - .collect(ImmutableList.toImmutableList())); + // 1. translate the except(all) node to other planNodes + SetOperationNodeTranslator.TranslationResult translationResult = + translator.makeSetContainmentPlanForAll(node); + + checkState( + !translationResult.getCountSymbols().isEmpty(), + "ExceptNode translation result has no count symbols"); + + // 2. add the filter node above the result node from translation process + // filter condition : row_number <= greatest(...greatest((greatest(count1 - count2, 0) - count3, + // 0))....) + Expression minusCount = translationResult.getCountSymbols().get(0).toSymbolReference(); + QualifiedName greatest = + QualifiedName.of(TableBuiltinScalarFunction.GREATEST.getFunctionName()); + for (int i = 1; i < translationResult.getCountSymbols().size(); i++) { + minusCount = + new FunctionCall( + greatest, + ImmutableList.of( + new ArithmeticBinaryExpression( + SUBTRACT, + minusCount, + translationResult.getCountSymbols().get(i).toSymbolReference()), + new GenericLiteral(LongType.INT64.getDisplayName(), "0"))); + } FilterNode filterNode = - new FilterNode(context.getIdAllocator().genPlanNodeId(), result.getPlanNode(), predicate); + new FilterNode( + context.getIdAllocator().genPlanNodeId(), + translationResult.getPlanNode(), + new ComparisonExpression( + ComparisonExpression.Operator.LESS_THAN_OR_EQUAL, + translationResult.getRowNumberSymbol().toSymbolReference(), + minusCount)); + // 3. add the project node to remove the redundant columns return Result.ofPlanNode( new ProjectNode( context.getIdAllocator().genPlanNodeId(), diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptDistinctAsUnion.java similarity index 69% copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptDistinctAsUnion.java index 0a8f8c498ae..7b3d1c00156 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptDistinctAsUnion.java @@ -22,8 +22,8 @@ package org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule; import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata; import org.apache.iotdb.db.queryengine.plan.relational.planner.Assignments; import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Rule; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode; -import org.apache.iotdb.db.queryengine.plan.relational.planner.node.IntersectNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.Patterns; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ProjectNode; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression; @@ -33,29 +33,29 @@ import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Captures; import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Pattern; import com.google.common.collect.ImmutableList; +import org.apache.tsfile.read.common.type.LongType; import static java.util.Objects.requireNonNull; import static org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils.and; import static org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL; -public class ImplementIntersectDistinctAsUnion implements Rule<IntersectNode> { - - private static final Pattern<IntersectNode> PATTERN = - Patterns.intersect().with(Patterns.Intersect.distinct().equalTo(true)); +public class ImplementExceptDistinctAsUnion implements Rule<ExceptNode> { private final Metadata metadata; + private static final Pattern<ExceptNode> PATTERN = + Patterns.except().with(Patterns.Except.distinct().equalTo(true)); - @Override - public Pattern<IntersectNode> getPattern() { - return PATTERN; + public ImplementExceptDistinctAsUnion(Metadata metadata) { + this.metadata = requireNonNull(metadata, "metadata is null"); } - public ImplementIntersectDistinctAsUnion(Metadata metadata) { - this.metadata = requireNonNull(metadata, "metadata is null"); + @Override + public Pattern<ExceptNode> getPattern() { + return PATTERN; } @Override - public Result apply(IntersectNode node, Captures captures, Context context) { + public Result apply(ExceptNode node, Captures captures, Context context) { SetOperationNodeTranslator translator = new SetOperationNodeTranslator( @@ -64,20 +64,26 @@ public class ImplementIntersectDistinctAsUnion implements Rule<IntersectNode> { SetOperationNodeTranslator.TranslationResult result = translator.makeSetContainmentPlanForDistinct(node); - // add the filterNode above the aggregation node - Expression predicate = - and( - result.getCountSymbols().stream() - .map( - symbol -> - new ComparisonExpression( - GREATER_THAN_OR_EQUAL, - symbol.toSymbolReference(), - new GenericLiteral("INT64", "1"))) - .collect(ImmutableList.toImmutableList())); + ImmutableList.Builder<Expression> predicatesBuilder = ImmutableList.builder(); + predicatesBuilder.add( + new ComparisonExpression( + GREATER_THAN_OR_EQUAL, + result.getCountSymbols().get(0).toSymbolReference(), + new GenericLiteral(LongType.INT64.getDisplayName(), "1"))); + + for (int i = 1; i < node.getChildren().size(); i++) { + predicatesBuilder.add( + new ComparisonExpression( + ComparisonExpression.Operator.EQUAL, + result.getCountSymbols().get(i).toSymbolReference(), + new GenericLiteral(LongType.INT64.getDisplayName(), "0"))); + } FilterNode filterNode = - new FilterNode(context.getIdAllocator().genPlanNodeId(), result.getPlanNode(), predicate); + new FilterNode( + context.getIdAllocator().genPlanNodeId(), + result.getPlanNode(), + and(predicatesBuilder.build())); return Result.ofPlanNode( new ProjectNode( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java index 0a8f8c498ae..388631c795b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java @@ -33,6 +33,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Captures; import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Pattern; import com.google.common.collect.ImmutableList; +import org.apache.tsfile.read.common.type.LongType; import static java.util.Objects.requireNonNull; import static org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils.and; @@ -73,7 +74,7 @@ public class ImplementIntersectDistinctAsUnion implements Rule<IntersectNode> { new ComparisonExpression( GREATER_THAN_OR_EQUAL, symbol.toSymbolReference(), - new GenericLiteral("INT64", "1"))) + new GenericLiteral(LongType.INT64.getDisplayName(), "1"))) .collect(ImmutableList.toImmutableList())); FilterNode filterNode = diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/PruneDistinctAggregation.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/PruneDistinctAggregation.java index 26fd0278016..37d604f3dd4 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/PruneDistinctAggregation.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/PruneDistinctAggregation.java @@ -24,6 +24,7 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor; import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Lookup; import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Rule; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.AggregationNode; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.IntersectNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.UnionNode; import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Captures; @@ -109,30 +110,14 @@ public class PruneDistinctAggregation implements Rule<AggregationNode> { return visitPlan(node, context); } - /*@Override - public PlanNode visitUnion(UnionNode node, Boolean context) - { - return rewriteChildren(node, context); - } - @Override - public PlanNode visitIntersect(IntersectNode node, Boolean context) - { - if (node.isDistinct()) { - return rewriteChildren(node, context); - } - return visitPlan(node, context); + public PlanNode visitExcept(ExceptNode node, Boolean context) { + if (node.isDistinct()) { + return rewriteChildren(node, context); + } + return visitPlan(node, context); } - @Override - public PlanNode visitExcept(ExceptNode node, Boolean context) - { - if (node.isDistinct()) { - return rewriteChildren(node, context); - } - return visitPlan(node, context); - }*/ - @Override public PlanNode visitAggregation(AggregationNode node, Boolean context) { boolean distinct = isDistinctOperator(node); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/SetOperationNodeTranslator.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/SetOperationNodeTranslator.java index 3088860564e..509af165b8b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/SetOperationNodeTranslator.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/SetOperationNodeTranslator.java @@ -145,8 +145,8 @@ public class SetOperationNodeTranslator { } /** - * only for transforming the intersection (all) node, add the window node and group node above the - * union node + * for transforming the intersectNode (all) and exceptNode(all), add the window node and group + * node above the union node */ private WindowNode appendCounts( UnionNode union, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ExceptNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ExceptNode.java new file mode 100644 index 00000000000..9eb1df47696 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ExceptNode.java @@ -0,0 +1,99 @@ +/* + * 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.planner.node; + +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor; +import org.apache.iotdb.db.queryengine.plan.relational.planner.Symbol; + +import com.google.common.collect.ListMultimap; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; + +public class ExceptNode extends SetOperationNode { + + private final boolean distinct; + + public ExceptNode( + PlanNodeId id, + List<PlanNode> children, + ListMultimap<Symbol, Symbol> outputToInputs, + List<Symbol> outputs, + boolean distinct) { + + super(id, children, outputToInputs, outputs); + this.distinct = distinct; + } + + private ExceptNode( + PlanNodeId id, + ListMultimap<Symbol, Symbol> outputToInputs, + List<Symbol> outputs, + boolean distinct) { + super(id, outputToInputs, outputs); + this.distinct = distinct; + } + + @Override + public <R, C> R accept(PlanVisitor<R, C> visitor, C context) { + return visitor.visitExcept(this, context); + } + + public boolean isDistinct() { + return distinct; + } + + @Override + public PlanNode clone() { + return new ExceptNode(getPlanNodeId(), getSymbolMapping(), getOutputSymbols(), distinct); + } + + @Override + public List<String> getOutputColumnNames() { + throw new UnsupportedOperationException(); + } + + @Override + protected void serializeAttributes(ByteBuffer byteBuffer) { + throw new UnsupportedOperationException( + "ExceptNode should never be serialized in current version"); + } + + @Override + protected void serializeAttributes(DataOutputStream stream) throws IOException { + throw new UnsupportedOperationException( + "ExceptNode should never be serialized in current version"); + } + + public static ExceptNode deserialize(ByteBuffer byteBuffer) { + throw new UnsupportedOperationException( + "ExceptNode should never be deserialized in current version"); + } + + @Override + public PlanNode replaceChildren(List<PlanNode> newChildren) { + return new ExceptNode( + getPlanNodeId(), newChildren, getSymbolMapping(), getOutputSymbols(), isDistinct()); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/Patterns.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/Patterns.java index d08ac9ecf72..0297add91f4 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/Patterns.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/Patterns.java @@ -193,6 +193,10 @@ public final class Patterns { return typeOf(IntersectNode.class); } + public static Pattern<ExceptNode> except() { + return typeOf(ExceptNode.class); + } + /*public static Pattern<TableWriterNode> tableWriterNode() { return typeOf(TableWriterNode.class); @@ -365,6 +369,12 @@ public final class Patterns { } } + public static final class Except { + public static Property<ExceptNode, Lookup, Boolean> distinct() { + return property("distinct", ExceptNode::isDistinct); + } + } + /*public static final class Sample { public static Property<SampleNode, Lookup, Double> sampleRatio() @@ -421,16 +431,6 @@ public final class Patterns { } }*/ - /* - - public static final class Except - { - public static Property<ExceptNode, Lookup, Boolean> distinct() - { - return property("distinct", ExceptNode::isDistinct); - } - } - */ public static final class PatternRecognition { public static Property<PatternRecognitionNode, Lookup, RowsPerMatch> rowsPerMatch() { return property("rowsPerMatch", PatternRecognitionNode::getRowsPerMatch); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/LogicalOptimizeFactory.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/LogicalOptimizeFactory.java index 65015715945..d7084c3d24e 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/LogicalOptimizeFactory.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/LogicalOptimizeFactory.java @@ -26,6 +26,8 @@ import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Iterati import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Rule; import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.RuleStatsRecorder; import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.CanonicalizeExpressions; +import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.ImplementExceptAll; +import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.ImplementExceptDistinctAsUnion; import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.ImplementIntersectAll; import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.ImplementIntersectDistinctAsUnion; import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.ImplementPatternRecognition; @@ -283,10 +285,9 @@ public class LogicalOptimizeFactory { ImmutableSet.<Rule<?>>builder() .add( new ImplementIntersectDistinctAsUnion(metadata), - // new ImplementExceptDistinctAsUnion(metadata) - new ImplementIntersectAll(metadata) - // new ImplementExceptAll(metadata))), - ) + new ImplementExceptDistinctAsUnion(metadata), + new ImplementIntersectAll(metadata), + new ImplementExceptAll(metadata)) .build()), columnPruningOptimizer, inlineProjectionLimitFiltersOptimizer, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java index ff3a01be849..3fdbec6b0cc 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java @@ -36,6 +36,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.planner.node.AssignUnique import org.apache.iotdb.db.queryengine.plan.relational.planner.node.CorrelatedJoinNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.DeviceTableScanNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.EnforceSingleRowNode; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExplainAnalyzeNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.GapFillNode; @@ -1046,6 +1047,36 @@ public class UnaliasSymbolReferences implements PlanOptimizer { mapping); } + @Override + public PlanAndMappings visitExcept(ExceptNode node, UnaliasContext context) { + + List<PlanAndMappings> rewrittenSources = + node.getChildren().stream() + .map(source -> source.accept(this, context)) + .collect(toImmutableList()); + + List<SymbolMapper> inputMappers = + rewrittenSources.stream() + .map(source -> symbolMapper(new HashMap<>(source.getMappings()))) + .collect(toImmutableList()); + + Map<Symbol, Symbol> mapping = new HashMap<>(context.getCorrelationMapping()); + SymbolMapper outputMapper = symbolMapper(mapping); + + ListMultimap<Symbol, Symbol> newOutputToInputs = + rewriteOutputToInputsMap(node.getSymbolMapping(), outputMapper, inputMappers); + List<Symbol> newOutputs = outputMapper.mapAndDistinct(node.getOutputSymbols()); + + return new PlanAndMappings( + new ExceptNode( + node.getPlanNodeId(), + rewrittenSources.stream().map(PlanAndMappings::getRoot).collect(toImmutableList()), + newOutputToInputs, + newOutputs, + node.isDistinct()), + mapping); + } + private ListMultimap<Symbol, Symbol> rewriteOutputToInputsMap( ListMultimap<Symbol, Symbol> oldMapping, SymbolMapper outputMapper, diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExceptTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExceptTest.java new file mode 100644 index 00000000000..35d35693fce --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExceptTest.java @@ -0,0 +1,132 @@ +/* + * 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.analyzer; + +import org.apache.iotdb.db.queryengine.plan.planner.plan.LogicalQueryPlan; +import org.apache.iotdb.db.queryengine.plan.relational.planner.PlanTester; + +import org.junit.Test; + +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanAssert.assertPlan; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.aggregation; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.filter; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.output; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.project; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.sort; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.tableScan; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.union; +import static org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.window; + +/** tests for except (distinct) and except all */ +public class ExceptTest { + + @Test + public void exceptTest() { + + PlanTester planTester = new PlanTester(); + LogicalQueryPlan actualLogicalQueryPlan = + planTester.createPlan("select tag1 from t1 except select tag1 from t2"); + // Verify Logical plan: `Output - project - filter - aggregation - union - 2*(project - + // tableScan)` + // This matches the ImplementExceptDistinctAsUnion logic + assertPlan( + actualLogicalQueryPlan, + output( + project( + filter( + aggregation( + union( + project(tableScan("testdb.t1")), project(tableScan("testdb.t2")))))))); + } + + @Test + public void exceptAllTest() { + + PlanTester planTester = new PlanTester(); + LogicalQueryPlan actualLogicalQueryPlan = + planTester.createPlan("select tag1 from t1 except all select tag1 from t2"); + // Verify Logical plan: `Output - project - filter - project - window - sort - union - + // 2*(project - tableScan)` + // This matches the ImplementExceptAll logic + assertPlan( + actualLogicalQueryPlan, + output( + project( + filter( + project( + window( + sort( + union( + project(tableScan("testdb.t1")), + project(tableScan("testdb.t2")))))))))); + } + + @Test + public void typeCompatibleTest() { + // use CAST if types of according columns is not compatible + // s1 is INT64, s3 is DOUBLE + + PlanTester planTester = new PlanTester(); + LogicalQueryPlan actualLogicalQueryPlan = + planTester.createPlan("select s1, s3 from table2 except all select s1, s1 from table3 "); + + // The plan structure should be the same as exceptAllTest + assertPlan( + actualLogicalQueryPlan, + output( + project( + filter( + project( + window( + sort( + union( + project(tableScan("testdb.table2")), + project(tableScan("testdb.table3")))))))))); + } + + /** + * The priority of INTERSECT is higher than that of EXCEPT. This query is parsed as: t1 EXCEPT (t2 + * INTERSECT t3) + */ + @Test + public void setOperationPriority() { + + PlanTester planTester = new PlanTester(); + LogicalQueryPlan actualLogicalQueryPlan = + planTester.createPlan( + "select tag1 from t1 except select tag1 from t2 intersect select tag1 from t3"); + + assertPlan( + actualLogicalQueryPlan, + output( + project( + filter( + aggregation( + union( + project(tableScan("testdb.t1")), + project( + project( + filter( + aggregation( + union( + project(tableScan("testdb.t2")), + project(tableScan("testdb.t3"))))))))))))); + } +} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMetadata.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMetadata.java index 6182defaaec..aa9fcdfd1b5 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMetadata.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMetadata.java @@ -40,6 +40,7 @@ import org.apache.iotdb.db.queryengine.plan.function.Split; import org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.model.ModelInferenceDescriptor; import org.apache.iotdb.db.queryengine.plan.relational.function.OperatorType; import org.apache.iotdb.db.queryengine.plan.relational.function.TableBuiltinTableFunction; +import org.apache.iotdb.db.queryengine.plan.relational.function.arithmetic.SubtractionResolver; import org.apache.iotdb.db.queryengine.plan.relational.metadata.AlignedDeviceEntry; import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnMetadata; import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnSchema; @@ -244,8 +245,16 @@ public class TestMetadata implements Metadata { throws OperatorNotFoundException { switch (operatorType) { - case ADD: case SUBTRACT: + Optional<Type> resolvedType = SubtractionResolver.checkConditions(argumentTypes); + return resolvedType.orElseThrow( + () -> + new OperatorNotFoundException( + operatorType, + argumentTypes, + new IllegalArgumentException( + "The combination of argument types is not supported for this operator."))); + case ADD: case MULTIPLY: case DIVIDE: case MODULUS:
