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

snuyanzin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/master by this push:
     new 20216e41992 [FLINK-39715][table] Sync SQL and table api behavior for 
queries with  sort
20216e41992 is described below

commit 20216e41992090ca3ee08f129335bde86c9c7c2d
Author: Sergey Nuyanzin <[email protected]>
AuthorDate: Tue Jul 7 22:23:17 2026 +0200

    [FLINK-39715][table] Sync SQL and table api behavior for queries with  sort
    
    
    ---------
    
    Co-authored-by: Arvind Kandpal <[email protected]>
---
 .../table/planner/calcite/FlinkRelBuilder.java     | 12 ++-
 .../planner/plan/QueryOperationConverter.java      | 29 ++++++-
 .../table/api/QueryOperationSqlSemanticTest.java   |  3 +
 .../api/QueryOperationSqlSerializationTest.java    |  3 +
 .../table/api/QueryOperationTestPrograms.java      | 96 ++++++++++++++++++++++
 .../table/planner/plan/batch/sql/SortTest.xml      | 72 ++++++++++++++++
 .../planner/plan/batch/table/AggregateTest.xml     | 80 ++++++++++++++++++
 .../planner/plan/batch/table/SetOperatorsTest.xml  | 25 ++++++
 .../table/planner/plan/batch/sql/SortTest.scala    | 17 ++++
 .../planner/plan/batch/table/AggregateTest.scala   | 38 +++++++++
 .../plan/batch/table/SetOperatorsTest.scala        | 13 +++
 11 files changed, 385 insertions(+), 3 deletions(-)

diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/calcite/FlinkRelBuilder.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/calcite/FlinkRelBuilder.java
index 9045b7d9e38..c68cdfed8f8 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/calcite/FlinkRelBuilder.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/calcite/FlinkRelBuilder.java
@@ -242,8 +242,16 @@ public final class FlinkRelBuilder extends RelBuilder {
     }
 
     public RelBuilder queryOperation(QueryOperation queryOperation) {
-        final RelNode relNode = queryOperation.accept(toRelNodeConverter);
-        return push(relNode);
+        // Only the outermost call has a root (its top-level ORDER BY is 
kept); subquery
+        // re-entries have none, so their fetch-less sorts are dropped. 
Restore afterwards.
+        final QueryOperation previousRoot = 
toRelNodeConverter.getRootOperation();
+        toRelNodeConverter.setRootOperation(previousRoot == null ? 
queryOperation : null);
+        try {
+            final RelNode relNode = queryOperation.accept(toRelNodeConverter);
+            return push(relNode);
+        } finally {
+            toRelNodeConverter.setRootOperation(previousRoot);
+        }
     }
 
     @Override
diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/QueryOperationConverter.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/QueryOperationConverter.java
index 2fb2b5b71fb..41b5ebe632a 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/QueryOperationConverter.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/QueryOperationConverter.java
@@ -108,6 +108,7 @@ import org.apache.calcite.plan.ViewExpanders;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.CorrelationId;
 import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.Sort;
 import org.apache.calcite.rel.logical.LogicalTableFunctionScan;
 import org.apache.calcite.rel.logical.LogicalTableScan;
 import org.apache.calcite.rel.logical.LogicalValues;
@@ -151,12 +152,24 @@ public class QueryOperationConverter extends 
QueryOperationDefaultVisitor<RelNod
     private final JoinExpressionVisitor joinExpressionVisitor = new 
JoinExpressionVisitor();
     private final boolean isBatchMode;
 
+    // Root of the operation tree currently being converted; used to keep a 
top-level ORDER BY
+    // while dropping fetch-less sorts elsewhere (mirrors 
SqlToRelConverter#removeSortInSubQuery).
+    private QueryOperation rootOperation;
+
     public QueryOperationConverter(FlinkRelBuilder relBuilder, boolean 
isBatchMode) {
         this.relBuilder = relBuilder;
         this.expressionConverter = new ExpressionConverter(relBuilder);
         this.isBatchMode = isBatchMode;
     }
 
+    public void setRootOperation(QueryOperation rootOperation) {
+        this.rootOperation = rootOperation;
+    }
+
+    public QueryOperation getRootOperation() {
+        return rootOperation;
+    }
+
     @Override
     public RelNode defaultMethod(QueryOperation other) {
         other.getChildren().forEach(child -> 
relBuilder.push(child.accept(this)));
@@ -176,6 +189,14 @@ public class QueryOperationConverter extends 
QueryOperationDefaultVisitor<RelNod
 
         @Override
         public RelNode visit(AggregateQueryOperation aggregate) {
+            // Project the input off a sort (as SqlToRelConverter does) so the 
sort's collation is
+            // not required on the aggregate output, avoiding an IOOBE in 
rules like e.g.
+            // FlinkExpandConversionRule.
+            final RelNode input = relBuilder.peek();
+            if (input instanceof Sort) {
+                relBuilder.project(relBuilder.fields(), 
input.getRowType().getFieldNames(), true);
+            }
+
             List<AggCall> aggregations =
                     aggregate.getAggregateExpressions().stream()
                             .map(this::getAggCall)
@@ -291,7 +312,13 @@ public class QueryOperationConverter extends 
QueryOperationDefaultVisitor<RelNod
 
         @Override
         public RelNode visit(SortQueryOperation sort) {
-            List<RexNode> rexNodes = convertToRexNodes(sort.getOrder());
+            // A non-root sort with neither FETCH nor a non-zero OFFSET has no 
observable effect,
+            // so drop it (mirrors SqlToRelConverter#removeSortInSubQuery; -1 
means "unset").
+            final boolean isRoot = sort == rootOperation;
+            if (!isRoot && sort.getFetch() < 0 && sort.getOffset() <= 0) {
+                return relBuilder.build();
+            }
+            final List<RexNode> rexNodes = convertToRexNodes(sort.getOrder());
             return relBuilder.sortLimit(sort.getOffset(), sort.getFetch(), 
rexNodes).build();
         }
 
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationSqlSemanticTest.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationSqlSemanticTest.java
index 354ae8cd08f..26735396b58 100644
--- 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationSqlSemanticTest.java
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationSqlSemanticTest.java
@@ -44,6 +44,9 @@ public class QueryOperationSqlSemanticTest extends 
SemanticTestBase {
                 QueryOperationTestPrograms.DISTINCT_QUERY_OPERATION,
                 QueryOperationTestPrograms.JOIN_QUERY_OPERATION,
                 QueryOperationTestPrograms.ORDER_BY_QUERY_OPERATION,
+                QueryOperationTestPrograms.ORDER_BY_AGGREGATE_QUERY_OPERATION,
+                
QueryOperationTestPrograms.ORDER_BY_NO_FETCH_AGGREGATE_QUERY_OPERATION,
+                QueryOperationTestPrograms.AGGREGATE_HAVING_QUERY_OPERATION,
                 QueryOperationTestPrograms.LIMIT_QUERY_OPERATION,
                 QueryOperationTestPrograms.WINDOW_AGGREGATE_QUERY_OPERATION,
                 QueryOperationTestPrograms.UNION_ALL_QUERY_OPERATION,
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationSqlSerializationTest.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationSqlSerializationTest.java
index e9525eae79e..c513c277cd5 100644
--- 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationSqlSerializationTest.java
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationSqlSerializationTest.java
@@ -59,6 +59,9 @@ public class QueryOperationSqlSerializationTest implements 
TableTestProgramRunne
                 QueryOperationTestPrograms.DISTINCT_QUERY_OPERATION,
                 QueryOperationTestPrograms.JOIN_QUERY_OPERATION,
                 QueryOperationTestPrograms.ORDER_BY_QUERY_OPERATION,
+                QueryOperationTestPrograms.ORDER_BY_AGGREGATE_QUERY_OPERATION,
+                
QueryOperationTestPrograms.ORDER_BY_NO_FETCH_AGGREGATE_QUERY_OPERATION,
+                QueryOperationTestPrograms.AGGREGATE_HAVING_QUERY_OPERATION,
                 QueryOperationTestPrograms.LIMIT_QUERY_OPERATION,
                 QueryOperationTestPrograms.WINDOW_AGGREGATE_QUERY_OPERATION,
                 QueryOperationTestPrograms.UNION_ALL_QUERY_OPERATION,
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationTestPrograms.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationTestPrograms.java
index e8671f0da73..6436d5ef854 100644
--- 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationTestPrograms.java
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/api/QueryOperationTestPrograms.java
@@ -427,6 +427,102 @@ public class QueryOperationTestPrograms {
                                     + " OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY")
                     .build();
 
+    static final TableTestProgram ORDER_BY_AGGREGATE_QUERY_OPERATION =
+            TableTestProgram.of("order-by-aggregate-query-operation", 
"verifies sql serialization")
+                    .setupTableSource(
+                            SourceTestStep.newBuilder("s")
+                                    .addSchema("a bigint", "b string")
+                                    .producedValues(
+                                            Row.of(1L, "a"), Row.of(2L, "b"), 
Row.of(3L, "c"))
+                                    .build())
+                    .setupTableSink(
+                            SinkTestStep.newBuilder("sink")
+                                    .addSchema("a bigint")
+                                    .consumedValues(
+                                            Row.ofKind(RowKind.INSERT, 1L),
+                                            Row.ofKind(RowKind.UPDATE_BEFORE, 
1L),
+                                            Row.ofKind(RowKind.UPDATE_AFTER, 
2L))
+                                    .build())
+                    .runTableApi(
+                            t -> 
t.from("s").orderBy($("b")).fetch(2).select($("a").max()), "sink")
+                    .runSql(
+                            "SELECT `$$T_PROJECT`.`EXPR$0` FROM (\n"
+                                    + "    SELECT (MAX(`$$T_AGG`.`a`)) AS 
`EXPR$0` FROM (\n"
+                                    + "        SELECT `$$T_SORT`.`a`, 
`$$T_SORT`.`b` FROM (\n"
+                                    + "            SELECT `$$T_SOURCE`.`a`, 
`$$T_SOURCE`.`b` FROM "
+                                    + 
"`default_catalog`.`default_database`.`s` $$T_SOURCE\n"
+                                    + "        ) $$T_SORT ORDER BY 
`$$T_SORT`.`b` ASC OFFSET 0 ROWS"
+                                    + " FETCH NEXT 2 ROWS ONLY\n"
+                                    + "    ) $$T_AGG\n"
+                                    + "    GROUP BY 1\n"
+                                    + ") $$T_PROJECT")
+                    .build();
+
+    static final TableTestProgram ORDER_BY_NO_FETCH_AGGREGATE_QUERY_OPERATION =
+            TableTestProgram.of(
+                            "order-by-no-fetch-aggregate-query-operation",
+                            "verifies sql serialization")
+                    .setupTableSource(
+                            SourceTestStep.newBuilder("s")
+                                    .addSchema("a bigint", "b string")
+                                    .producedValues(
+                                            Row.of(1L, "a"), Row.of(2L, "b"), 
Row.of(3L, "c"))
+                                    .build())
+                    .setupTableSink(
+                            SinkTestStep.newBuilder("sink")
+                                    .addSchema("a bigint")
+                                    .consumedValues(
+                                            Row.ofKind(RowKind.INSERT, 1L),
+                                            Row.ofKind(RowKind.UPDATE_BEFORE, 
1L),
+                                            Row.ofKind(RowKind.UPDATE_AFTER, 
2L),
+                                            Row.ofKind(RowKind.UPDATE_BEFORE, 
2L),
+                                            Row.ofKind(RowKind.UPDATE_AFTER, 
3L))
+                                    .build())
+                    .runTableApi(t -> 
t.from("s").orderBy($("b")).select($("a").max()), "sink")
+                    .runSql(
+                            "SELECT `$$T_PROJECT`.`EXPR$0` FROM (\n"
+                                    + "    SELECT (MAX(`$$T_AGG`.`a`)) AS 
`EXPR$0` FROM (\n"
+                                    + "        SELECT `$$T_SORT`.`a`, 
`$$T_SORT`.`b` FROM (\n"
+                                    + "            SELECT `$$T_SOURCE`.`a`, 
`$$T_SOURCE`.`b` FROM "
+                                    + 
"`default_catalog`.`default_database`.`s` $$T_SOURCE\n"
+                                    + "        ) $$T_SORT ORDER BY 
`$$T_SORT`.`b` ASC\n"
+                                    + "    ) $$T_AGG\n"
+                                    + "    GROUP BY 1\n"
+                                    + ") $$T_PROJECT")
+                    .build();
+
+    static final TableTestProgram AGGREGATE_HAVING_QUERY_OPERATION =
+            TableTestProgram.of("aggregate-having-query-operation", "verifies 
sql serialization")
+                    .setupTableSource(
+                            SourceTestStep.newBuilder("s")
+                                    .addSchema("a bigint", "b string")
+                                    .producedValues(
+                                            Row.of(1L, "a"), Row.of(2L, "b"), 
Row.of(3L, "b"))
+                                    .build())
+                    .setupTableSink(
+                            SinkTestStep.newBuilder("sink")
+                                    .addSchema("b string", "s bigint")
+                                    
.consumedValues(Row.ofKind(RowKind.UPDATE_AFTER, "b", 5L))
+                                    .build())
+                    .runTableApi(
+                            t ->
+                                    t.from("s")
+                                            .groupBy($("b"))
+                                            .select($("b"), 
$("a").sum().as("s"))
+                                            .where($("s").isGreater(3L)),
+                            "sink")
+                    .runSql(
+                            "SELECT `$$T_FILTER`.`b`, `$$T_FILTER`.`s` FROM 
(\n"
+                                    + "    SELECT `$$T_PROJECT`.`b`, 
`$$T_PROJECT`.`EXPR$0` AS `s` FROM (\n"
+                                    + "        SELECT `$$T_AGG`.`b`, 
(SUM(`$$T_AGG`.`a`)) AS `EXPR$0` FROM (\n"
+                                    + "            SELECT `$$T_SOURCE`.`a`, 
`$$T_SOURCE`.`b` FROM "
+                                    + 
"`default_catalog`.`default_database`.`s` $$T_SOURCE\n"
+                                    + "        ) $$T_AGG\n"
+                                    + "        GROUP BY `$$T_AGG`.`b`\n"
+                                    + "    ) $$T_PROJECT\n"
+                                    + ") $$T_FILTER WHERE `$$T_FILTER`.`s` > 
CAST(3 AS BIGINT)")
+                    .build();
+
     static final TableTestProgram LIMIT_QUERY_OPERATION =
             TableTestProgram.of("limit-query-operation", "verifies sql 
serialization")
                     .setupTableSource(
diff --git 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/SortTest.xml
 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/SortTest.xml
index 1a6ee5d248c..5adc2c8422a 100644
--- 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/SortTest.xml
+++ 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/sql/SortTest.xml
@@ -71,6 +71,78 @@ SortLimit(orderBy=[a DESC], offset=[0], fetch=[200], 
global=[true])
 +- Exchange(distribution=[single])
    +- SortLimit(orderBy=[a DESC], offset=[0], fetch=[200], global=[false])
       +- TableSourceScan(table=[[default_catalog, default_database, MyTable]], 
fields=[a, b, c])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testOrderByExprWithFetchThenGlobalAggregate">
+    <Resource name="sql">
+      <![CDATA[SELECT MAX(a) FROM (SELECT a, b FROM MyTable2 ORDER BY a + 1 
LIMIT 10)]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalAggregate(group=[{}], EXPR$0=[MAX($0)])
++- LogicalProject(a=[$0])
+   +- LogicalProject(a=[$0], b=[$1])
+      +- LogicalSort(sort0=[$2], dir0=[ASC-nulls-first], fetch=[10])
+         +- LogicalProject(a=[$0], b=[$1], EXPR$2=[+($0, 1)])
+            +- LogicalTableScan(table=[[default_catalog, default_database, 
MyTable2]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+HashAggregate(isMerge=[false], select=[MAX(a) AS EXPR$0])
++- Calc(select=[a])
+   +- SortLimit(orderBy=[EXPR$2 ASC], offset=[0], fetch=[10], global=[true])
+      +- Exchange(distribution=[single])
+         +- SortLimit(orderBy=[EXPR$2 ASC], offset=[0], fetch=[10], 
global=[false])
+            +- Calc(select=[a, b, +(a, 1) AS EXPR$2])
+               +- TableSourceScan(table=[[default_catalog, default_database, 
MyTable2]], fields=[a, b])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testOrderByThenGlobalAggregate">
+    <Resource name="sql">
+      <![CDATA[SELECT MAX(a) FROM (SELECT a, b FROM MyTable2 ORDER BY b)]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalAggregate(group=[{}], EXPR$0=[MAX($0)])
++- LogicalProject(a=[$0])
+   +- LogicalProject(a=[$0], b=[$1])
+      +- LogicalTableScan(table=[[default_catalog, default_database, 
MyTable2]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+HashAggregate(isMerge=[true], select=[Final_MAX(max$0) AS EXPR$0])
++- Exchange(distribution=[single])
+   +- LocalHashAggregate(select=[Partial_MAX(a) AS max$0])
+      +- Calc(select=[a])
+         +- TableSourceScan(table=[[default_catalog, default_database, 
MyTable2]], fields=[a, b])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testOrderByWithFetchThenGlobalAggregate">
+    <Resource name="sql">
+      <![CDATA[SELECT MAX(a) FROM (SELECT a, b FROM MyTable2 ORDER BY b LIMIT 
10)]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalAggregate(group=[{}], EXPR$0=[MAX($0)])
++- LogicalProject(a=[$0])
+   +- LogicalSort(sort0=[$1], dir0=[ASC-nulls-first], fetch=[10])
+      +- LogicalProject(a=[$0], b=[$1])
+         +- LogicalTableScan(table=[[default_catalog, default_database, 
MyTable2]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+HashAggregate(isMerge=[false], select=[MAX(a) AS EXPR$0])
++- Calc(select=[a])
+   +- SortLimit(orderBy=[b ASC], offset=[0], fetch=[10], global=[true])
+      +- Exchange(distribution=[single])
+         +- SortLimit(orderBy=[b ASC], offset=[0], fetch=[10], global=[false])
+            +- TableSourceScan(table=[[default_catalog, default_database, 
MyTable2]], fields=[a, b])
 ]]>
     </Resource>
   </TestCase>
diff --git 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/table/AggregateTest.xml
 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/table/AggregateTest.xml
index 6f6ce9785d0..0a5581d6540 100644
--- 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/table/AggregateTest.xml
+++ 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/table/AggregateTest.xml
@@ -91,6 +91,86 @@ Calc(select=[CAST(1 AS INTEGER) AS a, EXPR$0, EXPR$1, 
EXPR$2])
       +- LocalHashAggregate(groupBy=[a], select=[a, Partial_AVG(a) AS (sum$0, 
count$1), Partial_SUM(b) AS sum$2, Partial_COUNT(c) AS count$3])
          +- Calc(select=[a, b, c], where=[(a = 1)])
             +- TableSourceScan(table=[[default_catalog, default_database, 
MyTable]], fields=[a, b, c])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testGroupAggregateWithHaving">
+    <Resource name="ast">
+      <![CDATA[
+LogicalFilter(condition=[>($1, 3)])
++- LogicalProject(b=[$0], s=[$1])
+   +- LogicalAggregate(group=[{1}], EXPR$0=[SUM($0)])
+      +- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+Calc(select=[b, EXPR$0], where=[>(EXPR$0, 3)])
++- HashAggregate(isMerge=[true], groupBy=[b], select=[b, Final_SUM(sum$0) AS 
EXPR$0])
+   +- Exchange(distribution=[hash[b]])
+      +- LocalHashAggregate(groupBy=[b], select=[b, Partial_SUM(a) AS sum$0])
+         +- TableSourceScan(table=[[default_catalog, default_database, 
MyTable]], fields=[a, b])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testOrderByExprWithFetchThenGlobalAggregate">
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(EXPR$0=[$0])
++- LogicalAggregate(group=[{}], EXPR$0=[MAX($0)])
+   +- LogicalProject(a=[$0])
+      +- LogicalSort(sort0=[$2], dir0=[ASC], fetch=[10])
+         +- LogicalProject(a=[$0], b=[$1], $f2=[+($0, 1)])
+            +- LogicalTableScan(table=[[default_catalog, default_database, 
MyTable]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+HashAggregate(isMerge=[false], select=[MAX(a) AS EXPR$0])
++- Calc(select=[a])
+   +- SortLimit(orderBy=[$f2 ASC], offset=[0], fetch=[10], global=[true])
+      +- Exchange(distribution=[single])
+         +- SortLimit(orderBy=[$f2 ASC], offset=[0], fetch=[10], 
global=[false])
+            +- Calc(select=[a, b, +(a, 1) AS $f2])
+               +- TableSourceScan(table=[[default_catalog, default_database, 
MyTable]], fields=[a, b])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testOrderByThenGlobalAggregate">
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(EXPR$0=[$0])
++- LogicalAggregate(group=[{}], EXPR$0=[MAX($0)])
+   +- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+HashAggregate(isMerge=[true], select=[Final_MAX(max$0) AS EXPR$0])
++- Exchange(distribution=[single])
+   +- LocalHashAggregate(select=[Partial_MAX(a) AS max$0])
+      +- TableSourceScan(table=[[default_catalog, default_database, MyTable]], 
fields=[a, b])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testOrderByWithFetchThenGlobalAggregate">
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(EXPR$0=[$0])
++- LogicalAggregate(group=[{}], EXPR$0=[MAX($0)])
+   +- LogicalProject(a=[$0])
+      +- LogicalSort(sort0=[$1], dir0=[ASC], fetch=[10])
+         +- LogicalTableScan(table=[[default_catalog, default_database, 
MyTable]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+HashAggregate(isMerge=[false], select=[MAX(a) AS EXPR$0])
++- Calc(select=[a])
+   +- SortLimit(orderBy=[b ASC], offset=[0], fetch=[10], global=[true])
+      +- Exchange(distribution=[single])
+         +- SortLimit(orderBy=[b ASC], offset=[0], fetch=[10], global=[false])
+            +- TableSourceScan(table=[[default_catalog, default_database, 
MyTable]], fields=[a, b])
 ]]>
     </Resource>
   </TestCase>
diff --git 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/table/SetOperatorsTest.xml
 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/table/SetOperatorsTest.xml
index 4fb765038f1..f531cda7fe6 100644
--- 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/table/SetOperatorsTest.xml
+++ 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/batch/table/SetOperatorsTest.xml
@@ -69,6 +69,31 @@ Calc(select=[EXPR$0 AS a, b, EXPR$1 AS c])
             :  +- TableSourceScan(table=[[default_catalog, default_database, 
left]], fields=[a, b, c])
             +- Calc(select=[a, b, c], where=[(a > 0)])
                +- TableSourceScan(table=[[default_catalog, default_database, 
right]], fields=[a, b, c])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testInSubQueryKeepsOuterOrderBy">
+    <Resource name="ast">
+      <![CDATA[
+LogicalSort(sort0=[$1], dir0=[ASC])
++- LogicalFilter(condition=[IN($0, {
+LogicalProject(a=[$0])
+  LogicalFilter(condition=[>($1, 10)])
+    LogicalTableScan(table=[[default_catalog, default_database, A]])
+})])
+   +- LogicalTableScan(table=[[default_catalog, default_database, A]])
+]]>
+    </Resource>
+    <Resource name="optimized exec plan">
+      <![CDATA[
+Sort(orderBy=[b ASC])
++- Exchange(distribution=[single])
+   +- HashJoin(joinType=[LeftSemiJoin], where=[(a = a0)], select=[a, b, c], 
build=[right])
+      :- Exchange(distribution=[hash[a]])
+      :  +- TableSourceScan(table=[[default_catalog, default_database, A]], 
fields=[a, b, c])(reuse_id=[1])
+      +- Exchange(distribution=[hash[a]])
+         +- Calc(select=[a], where=[(b > 10)])
+            +- Reused(reference_id=[1])
 ]]>
     </Resource>
   </TestCase>
diff --git 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/sql/SortTest.scala
 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/sql/SortTest.scala
index 518d86882b9..0a68d57c8ae 100644
--- 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/sql/SortTest.scala
+++ 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/sql/SortTest.scala
@@ -27,6 +27,8 @@ class SortTest extends TableTestBase {
 
   private val util = batchTestUtil()
   util.addTableSource[(Int, Long, String)]("MyTable", 'a, 'b, 'c)
+  // 2-column table so the plans below match the Table API AggregateTest 
exactly.
+  util.addTableSource[(Int, String)]("MyTable2", 'a, 'b)
 
   @Test
   def testNonRangeSortOnSingleFieldWithoutForceLimit(): Unit = {
@@ -64,4 +66,19 @@ class SortTest extends TableTestBase {
     // exec node does not support range sort yet, so we verify rel plan here
     util.verifyRelPlan("SELECT * FROM MyTable ORDER BY a DESC")
   }
+
+  @Test
+  def testOrderByThenGlobalAggregate(): Unit = {
+    util.verifyRelPlan("SELECT MAX(a) FROM (SELECT a, b FROM MyTable2 ORDER BY 
b)")
+  }
+
+  @Test
+  def testOrderByWithFetchThenGlobalAggregate(): Unit = {
+    util.verifyRelPlan("SELECT MAX(a) FROM (SELECT a, b FROM MyTable2 ORDER BY 
b LIMIT 10)")
+  }
+
+  @Test
+  def testOrderByExprWithFetchThenGlobalAggregate(): Unit = {
+    util.verifyRelPlan("SELECT MAX(a) FROM (SELECT a, b FROM MyTable2 ORDER BY 
a + 1 LIMIT 10)")
+  }
 }
diff --git 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/table/AggregateTest.scala
 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/table/AggregateTest.scala
index 561eb11e1ba..7e02c411025 100644
--- 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/table/AggregateTest.scala
+++ 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/table/AggregateTest.scala
@@ -73,4 +73,42 @@ class AggregateTest extends TableTestBase {
 
     util.verifyExecPlan(resultTable)
   }
+
+  @Test
+  def testOrderByThenGlobalAggregate(): Unit = {
+    val util = batchTestUtil()
+    val sourceTable = util.addTableSource[(Int, String)]("MyTable", 'a, 'b)
+    val resultTable = sourceTable.orderBy('b.asc).select('a.max)
+
+    util.verifyRelPlan(resultTable)
+  }
+
+  @Test
+  def testOrderByWithFetchThenGlobalAggregate(): Unit = {
+    val util = batchTestUtil()
+    val sourceTable = util.addTableSource[(Int, String)]("MyTable", 'a, 'b)
+    val resultTable = sourceTable.orderBy('b.asc).fetch(10).select('a.max)
+
+    util.verifyRelPlan(resultTable)
+  }
+
+  @Test
+  def testOrderByExprWithFetchThenGlobalAggregate(): Unit = {
+    val util = batchTestUtil()
+    val sourceTable = util.addTableSource[(Int, String)]("MyTable", 'a, 'b)
+    val resultTable = sourceTable.orderBy(('a + 
1).asc).fetch(10).select('a.max)
+
+    util.verifyRelPlan(resultTable)
+  }
+
+  @Test
+  def testGroupAggregateWithHaving(): Unit = {
+    // Filter on an aggregate result (HAVING semantics).
+    val util = batchTestUtil()
+    val sourceTable = util.addTableSource[(Int, String)]("MyTable", 'a, 'b)
+    val resultTable = sourceTable.groupBy('b).select('b, 
'a.sum.as("s")).where('s > 3)
+
+    util.verifyRelPlan(resultTable)
+  }
+
 }
diff --git 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/table/SetOperatorsTest.scala
 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/table/SetOperatorsTest.scala
index 1ec48783dc4..b1d8f8d57c2 100644
--- 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/table/SetOperatorsTest.scala
+++ 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/batch/table/SetOperatorsTest.scala
@@ -133,4 +133,17 @@ class SetOperatorsTest extends TableTestBase {
     util.verifyExecPlan(result)
 
   }
+
+  @Test
+  def testInSubQueryKeepsOuterOrderBy(): Unit = {
+    val util = batchTestUtil()
+    val t = util.addTableSource[(Int, Long, String)]("A", 'a, 'b, 'c)
+
+    // The IN subquery re-enters QueryOperationConverter, so the outer ORDER 
BY (the actual
+    // root) must not be dropped as if it were a fetch-less subquery sort. See 
FLINK-39715.
+    val elements = t.where('b > 10L).select('a)
+    val result = t.where('a.in(elements)).orderBy('b)
+
+    util.verifyExecPlan(result)
+  }
 }

Reply via email to