From fbf6fa4b8bfe57d8b6c0491680ffa1ec84ca77bd Mon Sep 17 00:00:00 2001 From: DaZuiZui Date: Tue, 12 May 2026 15:25:04 +0800 Subject: [PATCH 1/2] [IOTDB-17635] Fix window function identity with OVER clause --- .../it/db/it/IoTDBWindowFunction3IT.java | 19 +++++++++++++++++++ .../plan/relational/planner/QueryPlanner.java | 4 +++- .../relational/planner/node/WindowNode.java | 4 ++-- .../plan/relational/sql/ast/FunctionCall.java | 7 ++++++- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBWindowFunction3IT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBWindowFunction3IT.java index d461f3a11fee6..0cb67a31ab876 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBWindowFunction3IT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBWindowFunction3IT.java @@ -116,6 +116,25 @@ public void testSwapWindowFunctions() { DATABASE_NAME); } + @Test + public void testSameWindowFunctionWithDifferentOrdering() { + String[] expectedHeader = new String[] {"time", "device", "value", "rank_time", "rank_value"}; + String[] retArray = + new String[] { + "2021-01-01T09:05:00.000Z,d1,3.0,1,2,", + "2021-01-01T09:07:00.000Z,d1,5.0,2,4,", + "2021-01-01T09:09:00.000Z,d1,3.0,3,2,", + "2021-01-01T09:10:00.000Z,d1,1.0,4,1,", + "2021-01-01T09:08:00.000Z,d2,2.0,1,1,", + "2021-01-01T09:15:00.000Z,d2,4.0,2,2,", + }; + tableResultSetEqualTest( + "SELECT *, rank() OVER (PARTITION BY device ORDER BY \"time\") AS rank_time, rank() OVER (PARTITION BY device ORDER BY value) AS rank_value FROM demo ORDER BY device, \"time\"", + expectedHeader, + retArray, + DATABASE_NAME); + } + @Test public void testPushDownFilterIntoWindow() { String[] expectedHeader = new String[] {"time", "device", "value", "rn"}; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java index 3fc66970fd327..ea26f63ca0ddd 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java @@ -334,7 +334,9 @@ private PlanBuilder planWindowFunctions( Map> functions = scopeAwareDistinct(subPlan, windowFunctions).stream() - .collect(Collectors.groupingBy(analysis::getWindow)); + .collect( + Collectors.groupingBy( + analysis::getWindow, LinkedHashMap::new, Collectors.toList())); for (Map.Entry> entry : functions.entrySet()) { Analysis.ResolvedWindow window = entry.getKey(); diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/planner/node/WindowNode.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/planner/node/WindowNode.java index 0fed9c83f640b..691d33805f04d 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/planner/node/WindowNode.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/planner/node/WindowNode.java @@ -44,8 +44,8 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -212,7 +212,7 @@ public static WindowNode deserialize(ByteBuffer buffer) { DataOrganizationSpecification specification = DataOrganizationSpecification.deserialize(buffer); int preSortedOrderPrefix = ReadWriteIOUtils.readInt(buffer); size = ReadWriteIOUtils.readInt(buffer); - Map windowFunctions = new HashMap<>(size); + Map windowFunctions = new LinkedHashMap<>(size); for (int i = 0; i < size; i++) { Symbol symbol = Symbol.deserialize(buffer); Function function = new Function(buffer); diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/ast/FunctionCall.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/ast/FunctionCall.java index 68a6aa3a50c4b..ebb04a2a81f8b 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/ast/FunctionCall.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/ast/FunctionCall.java @@ -177,6 +177,7 @@ public R accept(IAstVisitor visitor, C context) { public List getChildren() { ImmutableList.Builder nodes = ImmutableList.builder(); nodes.addAll(arguments); + window.ifPresent(window -> nodes.add((Node) window)); return nodes.build(); } @@ -190,6 +191,8 @@ public boolean equals(Object obj) { } FunctionCall o = (FunctionCall) obj; return Objects.equals(name, o.name) + && Objects.equals(window, o.window) + && Objects.equals(nullTreatment, o.nullTreatment) && Objects.equals(distinct, o.distinct) && Objects.equals(processingMode, o.processingMode) && Objects.equals(arguments, o.arguments); @@ -197,7 +200,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return Objects.hash(name, distinct, processingMode, arguments); + return Objects.hash(name, window, nullTreatment, distinct, processingMode, arguments); } public enum NullTreatment { @@ -214,6 +217,8 @@ public boolean shallowEquals(Node other) { FunctionCall otherFunction = (FunctionCall) other; return name.equals(otherFunction.name) + && window.isPresent() == otherFunction.window.isPresent() + && nullTreatment.equals(otherFunction.nullTreatment) && distinct == otherFunction.distinct && processingMode.equals(otherFunction.processingMode); } From e50949783cbc19432496d73a4097ef9f1dc3487c Mon Sep 17 00:00:00 2001 From: DaZuiZui Date: Wed, 13 May 2026 10:01:26 +0800 Subject: [PATCH 2/2] [IOTDB-17635] Fix window optimization test expectation --- .../planner/WindowFunctionOptimizationTest.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/WindowFunctionOptimizationTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/WindowFunctionOptimizationTest.java index e31f2f7e58065..ebbb015492352 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/WindowFunctionOptimizationTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/WindowFunctionOptimizationTest.java @@ -97,24 +97,22 @@ public void testSwapWindowFunctions() { "SELECT sum(s1) OVER (PARTITION BY tag1, s1), min(s1) OVER (PARTITION BY tag1) FROM table1"; LogicalQueryPlan logicalQueryPlan2 = planTester.createPlan(sql2); - // Two window function has swapped, but the query plan remains the same + // Two window functions have swapped. Since the initial sort by (tag1, s1) satisfies both + // windows, no extra sort is needed between them. /* * └──OutputNode * └──ProjectNode * └──WindowNode(PARTITION BY tag1, s1) - * └──SortNode - * └──WindowNode(PARTITION BY tag1) - * └──SortNode - * └──TableScanNode + * └──WindowNode(PARTITION BY tag1) + * └──SortNode + * └──TableScanNode */ assertPlan( logicalQueryPlan2, output( project( window( - ImmutableList.of("tag1", "s1"), - ImmutableList.of(), - sort(window(sort(tableScan))))))); + ImmutableList.of("tag1", "s1"), ImmutableList.of(), window(sort(tableScan)))))); } @Test