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);
}