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

JackieTien97 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 db22016e160 Fix order in subquery when meets diff function (#17629)
db22016e160 is described below

commit db22016e160108b66e3d1351386c06e82c13e578
Author: Weihao Li <[email protected]>
AuthorDate: Mon May 11 14:38:11 2026 +0800

    Fix order in subquery when meets diff function (#17629)
---
 .../scalar/IoTDBDiffFunctionTableIT.java           | 80 ++++++++++++++++++++++
 .../relational/analyzer/StatementAnalyzer.java     | 52 +++++++++++++-
 .../distribute/TableDistributedPlanGenerator.java  | 34 +++++++--
 .../optimizations/PushPredicateIntoTableScan.java  |  2 +-
 .../plan/relational/analyzer/AnalyzerTest.java     | 50 ++++++++++++++
 5 files changed, 210 insertions(+), 8 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/builtinfunction/scalar/IoTDBDiffFunctionTableIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/builtinfunction/scalar/IoTDBDiffFunctionTableIT.java
index 6bf1ef8db3e..2eaa96e5b85 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/builtinfunction/scalar/IoTDBDiffFunctionTableIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/builtinfunction/scalar/IoTDBDiffFunctionTableIT.java
@@ -146,4 +146,84 @@ public class IoTDBDiffFunctionTableIT {
         retArray,
         DATABASE_NAME);
   }
+
+  @Test
+  public void testDiffWithOrderBySubquery() {
+    String[] expectedHeader = new String[] {"time", "device_id", "s1", 
"_col3"};
+    String[] retArray =
+        new String[] {
+          "1970-02-27T20:53:20.001Z,d1,8,3.0,",
+          "1970-02-27T20:53:20.000Z,d1,null,null,",
+          "1970-01-01T00:00:00.006Z,d1,null,null,",
+          "1970-01-01T00:00:00.005Z,d1,5,1.0,",
+          "1970-01-01T00:00:00.004Z,d1,4,2.0,",
+          "1970-01-01T00:00:00.003Z,d1,null,null,",
+          "1970-01-01T00:00:00.002Z,d1,2,1.0,",
+          "1970-01-01T00:00:00.001Z,d1,1,null,"
+        };
+    tableResultSetEqualTest(
+        "SELECT time, device_id, s1, diff(s1) FROM ("
+            + "select * "
+            + "from table1 where device_id='d1' ORDER by time"
+            + ") "
+            + "ORDER by time DESC",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+
+  @Test
+  public void testDiffInOuterWhereHavingOrderBy() {
+    String[] expectedHeader = new String[] {"time", "device_id", "s1"};
+    String[] expectedRowsFilteredByDiff =
+        new String[] {
+          "1970-02-27T20:53:20.001Z,d1,8,",
+          "1970-01-01T00:00:00.005Z,d1,5,",
+          "1970-01-01T00:00:00.004Z,d1,4,",
+          "1970-01-01T00:00:00.002Z,d1,2,"
+        };
+    tableResultSetEqualTest(
+        "SELECT time, device_id, s1 FROM ("
+            + "select * "
+            + "from table1 where device_id='d1' ORDER by time"
+            + ") "
+            + "WHERE diff(s1) IS NOT NULL "
+            + "ORDER by time DESC",
+        expectedHeader,
+        expectedRowsFilteredByDiff,
+        DATABASE_NAME);
+
+    tableResultSetEqualTest(
+        "SELECT time, device_id, s1 FROM ("
+            + "select * "
+            + "from table1 where device_id='d1' ORDER by time"
+            + ") "
+            + "GROUP BY time, device_id, s1 "
+            + "HAVING diff(s1) IS NOT NULL "
+            + "ORDER by time DESC",
+        expectedHeader,
+        expectedRowsFilteredByDiff,
+        DATABASE_NAME);
+
+    String[] expectedRowsOrderedByDiff =
+        new String[] {
+          "1970-02-27T20:53:20.001Z,d1,8,",
+          "1970-01-01T00:00:00.004Z,d1,4,",
+          "1970-01-01T00:00:00.005Z,d1,5,",
+          "1970-01-01T00:00:00.002Z,d1,2,",
+          "1970-02-27T20:53:20.000Z,d1,null,",
+          "1970-01-01T00:00:00.006Z,d1,null,",
+          "1970-01-01T00:00:00.003Z,d1,null,",
+          "1970-01-01T00:00:00.001Z,d1,1,"
+        };
+    tableResultSetEqualTest(
+        "SELECT time, device_id, s1 FROM ("
+            + "select * "
+            + "from table1 where device_id='d1' ORDER by time"
+            + ") "
+            + "ORDER BY coalesce(diff(s1), -1000.0) DESC, time DESC",
+        expectedHeader,
+        expectedRowsOrderedByDiff,
+        DATABASE_NAME);
+  }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
index 6d3326dd402..12656f0ce5e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
@@ -292,6 +292,7 @@ import static 
org.apache.iotdb.db.queryengine.plan.relational.analyzer.Expressio
 import static 
org.apache.iotdb.db.queryengine.plan.relational.analyzer.Scope.BasisType.TABLE;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.metadata.MetadataUtil.createQualifiedObjectName;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.IrExpressionInterpreter.evaluateConstantExpression;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.optimizations.PushPredicateIntoTableScan.containsDiffFunction;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.sql.util.AstUtil.preOrder;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.utils.NodeUtils.getSortItemsFromOrderBy;
 import static org.apache.tsfile.read.common.type.BooleanType.BOOLEAN;
@@ -303,6 +304,7 @@ public class StatementAnalyzer {
   private final Analysis analysis;
 
   private boolean hasFillInParentScope = false;
+  private boolean hasDiffFunctionInParentScope = false;
   private final MPPQueryContext queryContext;
 
   private final AccessControl accessControl;
@@ -870,9 +872,13 @@ public class StatementAnalyzer {
 
     @Override
     public Scope visitQuery(Query node, Optional<Scope> context) {
+      boolean originalHasFillInParentScope = hasFillInParentScope;
+      boolean originalHasDiffFunctionInParentScope = 
hasDiffFunctionInParentScope;
       analysis.setQuery(true);
       Scope withScope = analyzeWith(node, context);
       hasFillInParentScope = node.getFill().isPresent() || 
hasFillInParentScope;
+      hasDiffFunctionInParentScope =
+          containsDiffFunctionInQuery(node) || hasDiffFunctionInParentScope;
       Scope queryBodyScope = process(node.getQueryBody(), withScope);
 
       if (node.getFill().isPresent()) {
@@ -887,7 +893,8 @@ public class StatementAnalyzer {
         if ((queryBodyScope.getOuterQueryParent().isPresent() || !isTopLevel)
             && !node.getLimit().isPresent()
             && !node.getOffset().isPresent()
-            && !hasFillInParentScope) {
+            && !hasFillInParentScope
+            && !hasDiffFunctionInParentScope) {
           // not the root scope and ORDER BY is ineffective
           analysis.markRedundantOrderBy(node.getOrderBy().get());
           warningCollector.add(
@@ -922,6 +929,8 @@ public class StatementAnalyzer {
               .build();
 
       analysis.setScope(node, queryScope);
+      hasFillInParentScope = originalHasFillInParentScope;
+      hasDiffFunctionInParentScope = originalHasDiffFunctionInParentScope;
       return queryScope;
     }
 
@@ -1144,6 +1153,7 @@ public class StatementAnalyzer {
           statementAnalyzerFactory.createStatementAnalyzer(
               analysis, queryContext, sessionContext, warningCollector, 
CorrelationSupport.ALLOWED);
       analyzer.hasFillInParentScope = hasFillInParentScope;
+      analyzer.hasDiffFunctionInParentScope = hasDiffFunctionInParentScope;
       Scope queryScope =
           analyzer.analyze(
               node.getQuery(),
@@ -1153,9 +1163,13 @@ public class StatementAnalyzer {
 
     @Override
     public Scope visitQuerySpecification(QuerySpecification node, 
Optional<Scope> scope) {
+      boolean originalHasFillInParentScope = hasFillInParentScope;
+      boolean originalHasDiffFunctionInParentScope = 
hasDiffFunctionInParentScope;
       // TODO: extract candidate names from SELECT, WHERE, HAVING, GROUP BY 
and ORDER BY expressions
       // to pass down to analyzeFrom
       hasFillInParentScope = node.getFill().isPresent() || 
hasFillInParentScope;
+      hasDiffFunctionInParentScope =
+          containsDiffFunctionInQuerySpecification(node) || 
hasDiffFunctionInParentScope;
 
       Scope sourceScope = analyzeFrom(node, scope);
       analyzeWindowDefinitions(node, sourceScope);
@@ -1188,7 +1202,8 @@ public class StatementAnalyzer {
         if ((sourceScope.getOuterQueryParent().isPresent() || !isTopLevel)
             && !node.getLimit().isPresent()
             && !node.getOffset().isPresent()
-            && !hasFillInParentScope) {
+            && !hasFillInParentScope
+            && !hasDiffFunctionInParentScope) {
           // not the root scope and ORDER BY is ineffective
           analysis.markRedundantOrderBy(orderBy);
           warningCollector.add(
@@ -1249,6 +1264,8 @@ public class StatementAnalyzer {
             orderByScope.orElseThrow(() -> new NoSuchElementException("No 
value present")));
       }
 
+      hasFillInParentScope = originalHasFillInParentScope;
+      hasDiffFunctionInParentScope = originalHasDiffFunctionInParentScope;
       return outputScope;
     }
 
@@ -1301,6 +1318,37 @@ public class StatementAnalyzer {
       return windowFunctions;
     }
 
+    // cover case: (query1) UNION (query2) order by ...
+    private boolean containsDiffFunctionInQuery(Query node) {
+      return getSortItemsFromOrderBy(node.getOrderBy()).stream()
+          .map(SortItem::getSortKey)
+          .anyMatch(expression -> containsDiffFunction(expression));
+    }
+
+    private boolean 
containsDiffFunctionInQuerySpecification(QuerySpecification node) {
+      for (SelectItem selectItem : node.getSelect().getSelectItems()) {
+        if (selectItem instanceof AllColumns) {
+          Optional<Expression> target = ((AllColumns) selectItem).getTarget();
+          if (target.isPresent() && containsDiffFunction(target.get())) {
+            return true;
+          }
+        } else if (selectItem instanceof SingleColumn
+            && containsDiffFunction(((SingleColumn) 
selectItem).getExpression())) {
+          return true;
+        }
+      }
+
+      if (node.getWhere().isPresent() && 
containsDiffFunction(node.getWhere().get())) {
+        return true;
+      }
+      if (node.getHaving().isPresent() && 
containsDiffFunction(node.getHaving().get())) {
+        return true;
+      }
+      return getSortItemsFromOrderBy(node.getOrderBy()).stream()
+          .map(SortItem::getSortKey)
+          .anyMatch(expression -> containsDiffFunction(expression));
+    }
+
     private void resolveFunctionCallAndMeasureWindows(QuerySpecification 
querySpecification) {
       ImmutableList.Builder<Expression> expressions = ImmutableList.builder();
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/distribute/TableDistributedPlanGenerator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/distribute/TableDistributedPlanGenerator.java
index eb967534e17..fbd1d4cc24d 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/distribute/TableDistributedPlanGenerator.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/distribute/TableDistributedPlanGenerator.java
@@ -107,7 +107,6 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.Table
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.TableDeviceQueryCountNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.TableDeviceQueryScanNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.optimizations.DataNodeLocationSupplierFactory;
-import 
org.apache.iotdb.db.queryengine.plan.relational.planner.optimizations.PushPredicateIntoTableScan;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Insert;
 import org.apache.iotdb.db.queryengine.plan.statement.component.Ordering;
 import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache;
@@ -321,7 +320,22 @@ public class TableDistributedPlanGenerator
 
   @Override
   public List<PlanNode> visitProject(ProjectNode node, PlanContext context) {
+    boolean containsDiff =
+        node.getAssignments().getMap().values().stream()
+            .anyMatch(expression -> containsDiffFunction(expression));
+    OrderingScheme originalExpectedOrdering = context.expectedOrderingScheme;
+    boolean originalHasSortProperty = context.hasSortProperty;
+    if (containsDiff) {
+      context.clearExpectedOrderingScheme();
+    }
     List<PlanNode> childrenNodes = node.getChild().accept(this, context);
+    if (containsDiff) {
+      if (originalHasSortProperty) {
+        context.setExpectedOrderingScheme(originalExpectedOrdering);
+      } else {
+        context.clearExpectedOrderingScheme();
+      }
+    }
     OrderingScheme childOrdering = 
nodeOrderingMap.get(childrenNodes.get(0).getPlanNodeId());
     boolean containAllSortItem = false;
     if (childOrdering != null) {
@@ -378,9 +392,6 @@ public class TableDistributedPlanGenerator
       return Collections.singletonList(node);
     }
 
-    boolean containsDiff =
-        node.getAssignments().getMap().values().stream()
-            .anyMatch(PushPredicateIntoTableScan::containsDiffFunction);
     if (containsDiff) {
       if (containAllSortItem) {
         nodeOrderingMap.put(node.getPlanNodeId(), childOrdering);
@@ -583,7 +594,20 @@ public class TableDistributedPlanGenerator
 
   @Override
   public List<PlanNode> visitFilter(FilterNode node, PlanContext context) {
+    boolean containsDiff = containsDiffFunction(node.getPredicate());
+    OrderingScheme originalExpectedOrdering = context.expectedOrderingScheme;
+    boolean originalHasSortProperty = context.hasSortProperty;
+    if (containsDiff) {
+      context.clearExpectedOrderingScheme();
+    }
     List<PlanNode> childrenNodes = node.getChild().accept(this, context);
+    if (containsDiff) {
+      if (originalHasSortProperty) {
+        context.setExpectedOrderingScheme(originalExpectedOrdering);
+      } else {
+        context.clearExpectedOrderingScheme();
+      }
+    }
     OrderingScheme childOrdering = 
nodeOrderingMap.get(childrenNodes.get(0).getPlanNodeId());
     if (childOrdering != null) {
       nodeOrderingMap.put(node.getPlanNodeId(), childOrdering);
@@ -594,7 +618,7 @@ public class TableDistributedPlanGenerator
       return Collections.singletonList(node);
     }
 
-    if (containsDiffFunction(node.getPredicate())) {
+    if (containsDiff) {
       node.setChild(mergeChildrenViaCollectOrMergeSort(childOrdering, 
childrenNodes));
       return Collections.singletonList(node);
     }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/PushPredicateIntoTableScan.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/PushPredicateIntoTableScan.java
index 5827993a062..61245da7a34 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/PushPredicateIntoTableScan.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/PushPredicateIntoTableScan.java
@@ -1453,7 +1453,7 @@ public class PushPredicateIntoTableScan implements 
PlanOptimizer {
 
     if (!expression.getChildren().isEmpty()) {
       for (Node node : expression.getChildren()) {
-        if (containsDiffFunction((Expression) node)) {
+        if (node instanceof Expression && containsDiffFunction((Expression) 
node)) {
           return true;
         }
       }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java
index e8626735374..45599c8a981 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java
@@ -65,6 +65,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
 import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.OperatorNotFoundException;
 import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.QualifiedObjectName;
 import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.TableHeaderSchemaValidator;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.PlanTester;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.SymbolAllocator;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.TableLogicalPlanner;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.distribute.TableDistributedPlanner;
@@ -108,6 +109,15 @@ import static 
org.apache.iotdb.db.queryengine.plan.relational.analyzer.TestUtils
 import static 
org.apache.iotdb.db.queryengine.plan.relational.analyzer.TestUtils.TEST_MATADATA;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.analyzer.TestUtils.assertTableScanWithoutEntryOrder;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.analyzer.TestUtils.getChildrenNode;
+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.exchange;
+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.mergeSort;
+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.statement.component.Ordering.ASC;
 import static org.apache.tsfile.read.common.type.BooleanType.BOOLEAN;
 import static org.apache.tsfile.read.common.type.DoubleType.DOUBLE;
@@ -854,6 +864,46 @@ public class AnalyzerTest {
         filterNode.getPredicate().toString());
   }
 
+  @Test
+  public void diffWithSubqueryOrderByTest() {
+    PlanTester planTester = new PlanTester();
+    String sqlWithOuterWhere =
+        "SELECT time, tag1, s1 FROM ("
+            + "select * from table1 ORDER by time"
+            + ") WHERE diff(s1) IS NOT NULL ORDER by time DESC";
+    planTester.createPlan(sqlWithOuterWhere);
+    assertPlan(
+        planTester.getFragmentPlan(0),
+        output(sort(filter(mergeSort(exchange(), exchange(), exchange())))));
+    assertPlan(planTester.getFragmentPlan(1), 
sort(tableScan("testdb.table1")));
+    assertPlan(planTester.getFragmentPlan(2), 
sort(tableScan("testdb.table1")));
+    assertPlan(planTester.getFragmentPlan(3), 
sort(tableScan("testdb.table1")));
+
+    String sqlWithOuterHaving =
+        "SELECT time, tag1, s1 FROM ("
+            + "select * from table1 ORDER by time"
+            + ") GROUP BY time, tag1, s1 HAVING diff(s1) IS NOT NULL ORDER by 
time DESC";
+    planTester.createPlan(sqlWithOuterHaving);
+    assertPlan(
+        planTester.getFragmentPlan(0),
+        output(sort(filter(aggregation(mergeSort(exchange(), exchange(), 
exchange()))))));
+    assertPlan(planTester.getFragmentPlan(1), 
sort(tableScan("testdb.table1")));
+    assertPlan(planTester.getFragmentPlan(2), 
sort(tableScan("testdb.table1")));
+    assertPlan(planTester.getFragmentPlan(3), 
sort(tableScan("testdb.table1")));
+
+    String sqlWithOuterOrderBy =
+        "SELECT time, tag1, s1 FROM ("
+            + "select * from table1 ORDER by time"
+            + ") ORDER by coalesce(diff(s1), -1000.0) DESC, time DESC";
+    planTester.createPlan(sqlWithOuterOrderBy);
+    assertPlan(
+        planTester.getFragmentPlan(0),
+        output(project(sort(project(mergeSort(exchange(), exchange(), 
exchange()))))));
+    assertPlan(planTester.getFragmentPlan(1), 
sort(tableScan("testdb.table1")));
+    assertPlan(planTester.getFragmentPlan(2), 
sort(tableScan("testdb.table1")));
+    assertPlan(planTester.getFragmentPlan(3), 
sort(tableScan("testdb.table1")));
+  }
+
   @Test
   public void predicatePushDownTest() {
     sql =

Reply via email to