This is an automated email from the ASF dual-hosted git repository.

Wei-hao-Li pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new da7056f7777 [IOTDB-17635] Fix window function identity with OVER 
clause (#17645)
da7056f7777 is described below

commit da7056f7777c6fe6a3cd04a487b6d12deb66a52f
Author: 哇塞大嘴好帥 <[email protected]>
AuthorDate: Fri May 15 10:33:10 2026 +0800

    [IOTDB-17635] Fix window function identity with OVER clause (#17645)
---
 .../relational/it/db/it/IoTDBWindowFunction3IT.java   | 19 +++++++++++++++++++
 .../plan/relational/planner/QueryPlanner.java         |  4 +++-
 .../planner/WindowFunctionOptimizationTest.java       | 14 ++++++--------
 .../plan/relational/planner/node/WindowNode.java      |  4 ++--
 .../plan/relational/sql/ast/FunctionCall.java         |  7 ++++++-
 5 files changed, 36 insertions(+), 12 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 d461f3a11fe..0cb67a31ab8 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 class IoTDBWindowFunction3IT {
         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 3fc66970fd3..ea26f63ca0d 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 @@ public class QueryPlanner {
 
     Map<Analysis.ResolvedWindow, List<FunctionCall>> functions =
         scopeAwareDistinct(subPlan, windowFunctions).stream()
-            .collect(Collectors.groupingBy(analysis::getWindow));
+            .collect(
+                Collectors.groupingBy(
+                    analysis::getWindow, LinkedHashMap::new, 
Collectors.toList()));
 
     for (Map.Entry<Analysis.ResolvedWindow, List<FunctionCall>> entry : 
functions.entrySet()) {
       Analysis.ResolvedWindow window = entry.getKey();
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 e31f2f7e580..ebbb0154923 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 class WindowFunctionOptimizationTest {
         "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
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 0fed9c83f64..691d33805f0 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.DataOutputStream;
 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 class WindowNode extends SingleChildProcessNode {
     DataOrganizationSpecification specification = 
DataOrganizationSpecification.deserialize(buffer);
     int preSortedOrderPrefix = ReadWriteIOUtils.readInt(buffer);
     size = ReadWriteIOUtils.readInt(buffer);
-    Map<Symbol, Function> windowFunctions = new HashMap<>(size);
+    Map<Symbol, Function> 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 68a6aa3a50c..ebb04a2a81f 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 class FunctionCall extends Expression {
   public List<Node> getChildren() {
     ImmutableList.Builder<Node> nodes = ImmutableList.builder();
     nodes.addAll(arguments);
+    window.ifPresent(window -> nodes.add((Node) window));
     return nodes.build();
   }
 
@@ -190,6 +191,8 @@ public class FunctionCall extends Expression {
     }
     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 class FunctionCall extends Expression {
 
   @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 class FunctionCall extends Expression {
     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);
   }

Reply via email to