This is an automated email from the ASF dual-hosted git repository.
MartijnVisser 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 33029143730 [FLINK-40449][table] Reject non-temporal streaming sort
during planning
33029143730 is described below
commit 330291437309c829378006f5109c947d1bf4216e
Author: Martijn Visser <[email protected]>
AuthorDate: Fri Aug 21 11:14:36 2026 +0200
[FLINK-40449][table] Reject non-temporal streaming sort during planning
A streaming sort on a non-time attribute is unsupported, but the check
lived in StreamExecSort.translateToPlanInternal, so it only fired at job
(re)submission, not during optimization: COMPILE PLAN succeeded and the
query failed later.
Reject it in StreamPhysicalSortRule.convert() during optimization, with
two targeted messages (non-time-attribute key, and descending time
attribute) built in SortUtil. A sort key that is an expression is
projected into a generated column (EXPR$n from SQL, $fn from the Table
API); the message describes it as "the sort key expression" instead of
leaking that name.
The StreamExecSort check is kept as a backstop for compiled plans loaded
via loadPlan, which bypasses optimization. It uses
LogicalTypeChecks.isTimeAttribute so a TIMESTAMP_LTZ rowtime is
recognized after deserialization, and guards against a plan without sort
keys.
Existing tests whose ORDER BY became invalid in streaming are adapted:
those that sorted only incidentally keep asserting their feature
(TableSinkTest.testDistribution, GroupingSetsTest.testFromBlogspot), the
rest assert the rejection. SortValidationTest covers the rule, the
expression cases, and compile/load round trips for the backstop.
Generated-by: Claude Code (Claude Opus 4.8, Claude Fable 5.1)
---
.../plan/nodes/exec/stream/StreamExecSort.java | 22 ++-
.../physical/stream/StreamPhysicalSortRule.scala | 22 +++
.../flink/table/planner/plan/utils/SortUtil.scala | 28 ++-
.../plan/stream/sql/SortValidationTest.java | 196 +++++++++++++++++++++
.../planner/plan/common/PartialInsertTest.xml | 23 ---
.../table/planner/plan/stream/sql/SortTest.xml | 146 ++-------------
.../planner/plan/stream/sql/TableSinkTest.xml | 11 +-
.../plan/stream/sql/agg/GroupingSetsTest.xml | 79 ++-------
.../planner/plan/common/PartialInsertTest.scala | 13 +-
.../table/planner/plan/stream/sql/SortTest.scala | 35 +---
.../planner/plan/stream/sql/TableSinkTest.scala | 2 +-
.../plan/stream/sql/agg/GroupingSetsTest.scala | 17 +-
.../planner/runtime/stream/sql/SortITCase.scala | 7 +-
13 files changed, 321 insertions(+), 280 deletions(-)
diff --git
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/stream/StreamExecSort.java
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/stream/StreamExecSort.java
index fb9f38ed967..e3a5dd99af9 100644
---
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/stream/StreamExecSort.java
+++
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/stream/StreamExecSort.java
@@ -33,11 +33,14 @@ import
org.apache.flink.table.planner.plan.nodes.exec.ExecNodeMetadata;
import org.apache.flink.table.planner.plan.nodes.exec.InputProperty;
import org.apache.flink.table.planner.plan.nodes.exec.spec.SortSpec;
import org.apache.flink.table.planner.plan.nodes.exec.utils.ExecNodeUtil;
+import org.apache.flink.table.planner.plan.utils.SortUtil;
import org.apache.flink.table.planner.utils.InternalConfigOptions;
import org.apache.flink.table.runtime.generated.GeneratedRecordComparator;
import org.apache.flink.table.runtime.operators.sort.StreamSortOperator;
import org.apache.flink.table.runtime.typeutils.InternalTypeInfo;
+import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.table.types.logical.RowType;
+import org.apache.flink.table.types.logical.utils.LogicalTypeChecks;
import
org.apache.flink.shaded.jackson2.com.fasterxml.jackson.annotation.JsonCreator;
import
org.apache.flink.shaded.jackson2.com.fasterxml.jackson.annotation.JsonProperty;
@@ -100,12 +103,25 @@ public class StreamExecSort extends ExecNodeBase<RowData>
implements StreamExecN
@Override
protected Transformation<RowData> translateToPlanInternal(
PlannerBase planner, ExecNodeConfig config) {
+ ExecEdge inputEdge = getInputEdges().get(0);
+ RowType inputType = (RowType) inputEdge.getOutputType();
if
(!config.get(InternalConfigOptions.TABLE_EXEC_NON_TEMPORAL_SORT_ENABLED)) {
- throw new TableException("Sort on a non-time-attribute field is
not supported.");
+ // Backstop for compiled plans loaded without passing through
StreamPhysicalSortRule.
+ if (sortSpec.getFieldSize() == 0) {
+ throw new TableException(
+ "Compiled plan contains a streaming sort without sort
keys.");
+ }
+ int firstSortField = sortSpec.getFieldIndices()[0];
+ String column = inputType.getFieldNames().get(firstSortField);
+ LogicalType type = inputType.getTypeAt(firstSortField);
+ if (LogicalTypeChecks.isTimeAttribute(type)
+ && !sortSpec.getFieldSpecs()[0].getIsAscendingOrder()) {
+ throw new TableException(
+
SortUtil.sortKeyTimeAttributeMustBeAscendingMessage(column));
+ }
+ throw new
TableException(SortUtil.sortKeyNotTimeAttributeMessage(column, type));
}
- ExecEdge inputEdge = getInputEdges().get(0);
- RowType inputType = (RowType) inputEdge.getOutputType();
// sort code gen
GeneratedRecordComparator rowComparator =
ComparatorCodeGenerator.gen(
diff --git
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/physical/stream/StreamPhysicalSortRule.scala
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/physical/stream/StreamPhysicalSortRule.scala
index 63958f7235c..85ef8544732 100644
---
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/physical/stream/StreamPhysicalSortRule.scala
+++
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/rules/physical/stream/StreamPhysicalSortRule.scala
@@ -17,10 +17,14 @@
*/
package org.apache.flink.table.planner.plan.rules.physical.stream
+import org.apache.flink.table.api.TableException
+import org.apache.flink.table.planner.calcite.FlinkTypeFactory
import org.apache.flink.table.planner.plan.`trait`.FlinkRelDistribution
import org.apache.flink.table.planner.plan.nodes.FlinkConventions
import org.apache.flink.table.planner.plan.nodes.logical.FlinkLogicalSort
import
org.apache.flink.table.planner.plan.nodes.physical.stream.StreamPhysicalSort
+import org.apache.flink.table.planner.plan.utils.SortUtil
+import org.apache.flink.table.planner.utils.{InternalConfigOptions,
ShortcutUtils}
import org.apache.calcite.plan.{RelOptRule, RelOptRuleCall}
import org.apache.calcite.rel.RelNode
@@ -41,6 +45,24 @@ class StreamPhysicalSortRule(config: Config) extends
ConverterRule(config) {
override def convert(rel: RelNode): RelNode = {
val sort: FlinkLogicalSort = rel.asInstanceOf[FlinkLogicalSort]
+ // Reject a non-time-attribute streaming sort here instead of deferring to
StreamExecSort.
+ if (
+ !ShortcutUtils
+ .unwrapTableConfig(sort)
+ .get(InternalConfigOptions.TABLE_EXEC_NON_TEMPORAL_SORT_ENABLED)
+ ) {
+ val field = SortUtil.getFirstSortField(sort.getCollation,
sort.getInput.getRowType)
+ // A time-attribute first key only reaches here when descending;
ascending goes to
+ // StreamPhysicalTemporalSort via matches().
+ val message = if (FlinkTypeFactory.isTimeIndicatorType(field.getType)) {
+ SortUtil.sortKeyTimeAttributeMustBeAscendingMessage(field.getName)
+ } else {
+ SortUtil.sortKeyNotTimeAttributeMessage(
+ field.getName,
+ FlinkTypeFactory.toLogicalType(field.getType))
+ }
+ throw new TableException(message)
+ }
val input = sort.getInput(0)
val requiredTraitSet = input.getTraitSet
.replace(FlinkRelDistribution.SINGLETON)
diff --git
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/utils/SortUtil.scala
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/utils/SortUtil.scala
index da6c1e9fbaf..dc38d99c96c 100644
---
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/utils/SortUtil.scala
+++
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/utils/SortUtil.scala
@@ -23,7 +23,7 @@ import org.apache.flink.table.api.TableException
import org.apache.flink.table.planner.calcite.FlinkPlannerImpl
import org.apache.flink.table.planner.codegen.sort.SortCodeGenerator
import org.apache.flink.table.planner.plan.nodes.exec.spec.SortSpec
-import org.apache.flink.table.types.logical.RowType
+import org.apache.flink.table.types.logical.{LogicalType, RowType}
import org.apache.calcite.rel.`type`._
import org.apache.calcite.rel.{RelCollation, RelFieldCollation}
@@ -74,6 +74,32 @@ object SortUtil {
rowType.getFieldList.get(idx)
}
+ /**
+ * Name pattern of the column a sort expression is projected into: `EXPR$n`
by the SQL converter,
+ * `$fn` by the Table API's RelBuilder. Such names are meaningless to users.
+ */
+ private val GENERATED_SORT_KEY_NAME = "^(EXPR\\$|\\$f)\\d+$".r
+
+ private def describeSortKey(column: String): String =
+ if (GENERATED_SORT_KEY_NAME.findFirstIn(column).isDefined) "the sort key
expression"
+ else s"'$column'"
+
+ /** Error message when the primary streaming sort key is not a time
attribute. */
+ def sortKeyNotTimeAttributeMessage(column: String, tpe: LogicalType): String
=
+ s"Streaming ORDER BY requires the primary sort key to be a time attribute
in ascending " +
+ s"order, but ${describeSortKey(column)} is ${tpe.asSummaryString}. A
time attribute is an " +
+ s"event-time column (a TIMESTAMP or TIMESTAMP_LTZ column with a
WATERMARK) or a " +
+ s"processing-time column. Otherwise use LIMIT for Top-N, sort within a
window, or run in " +
+ s"batch mode."
+
+ /**
+ * Error message when the primary streaming sort key is a time attribute but
sorted descending.
+ */
+ def sortKeyTimeAttributeMustBeAscendingMessage(column: String): String =
+ s"Streaming ORDER BY on time attribute '$column' must be sorted in
ascending order; " +
+ s"descending order is not supported. Otherwise use LIMIT for Top-N, sort
within a window, " +
+ s"or run in batch mode."
+
/** Returns the default null direction if not specified. */
def getNullDefaultOrders(ascendings: Array[Boolean]): Array[Boolean] = {
ascendings.map(asc => FlinkPlannerImpl.defaultNullCollation.last(!asc))
diff --git
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/stream/sql/SortValidationTest.java
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/stream/sql/SortValidationTest.java
new file mode 100644
index 00000000000..1489aff0858
--- /dev/null
+++
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/stream/sql/SortValidationTest.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.table.planner.plan.stream.sql;
+
+import org.apache.flink.table.api.CompiledPlan;
+import org.apache.flink.table.api.PlanReference;
+import org.apache.flink.table.api.Table;
+import org.apache.flink.table.api.TableEnvironment;
+import org.apache.flink.table.api.TableException;
+import org.apache.flink.table.api.internal.CompiledPlanUtils;
+import org.apache.flink.table.planner.utils.InternalConfigOptions;
+import org.apache.flink.table.planner.utils.JavaStreamTableTestUtil;
+import org.apache.flink.table.planner.utils.TableTestBase;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static org.apache.flink.table.api.Expressions.$;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Plan-time validation tests for streaming sort. A non-time-attribute sort is
unsupported and is
+ * rejected by {@code StreamPhysicalSortRule} during optimization, so it
surfaces at {@code COMPILE
+ * PLAN} / planning time. {@code StreamExecSort} keeps the same check as a
backstop for compiled
+ * plans loaded via {@code loadPlan}, which bypasses optimization. Valid
temporal sorts are covered
+ * by {@code SortTest}.
+ */
+class SortValidationTest extends TableTestBase {
+
+ private static final String MESSAGE =
+ "requires the primary sort key to be a time attribute in ascending
order";
+ private static final String MESSAGE_DESC =
+ "must be sorted in ascending order; descending order is not
supported";
+
+ private final JavaStreamTableTestUtil util = javaStreamTestUtil();
+
+ @BeforeEach
+ void setup() {
+ util.addTable(
+ "CREATE TABLE MyTable (\n"
+ + " a INT,\n"
+ + " b STRING,\n"
+ + " c BIGINT,\n"
+ + " proctime AS PROCTIME(),\n"
+ + " rowtime TIMESTAMP(3),\n"
+ + " WATERMARK FOR rowtime AS rowtime\n"
+ + ") WITH ('connector' = 'values')");
+ }
+
+ static Stream<Arguments> nonTemporalSorts() {
+ return Stream.of(
+ // primary sort key is not a time attribute -> message A
+ Arguments.of("SELECT a FROM MyTable ORDER BY c", MESSAGE),
+ Arguments.of("SELECT a FROM MyTable ORDER BY c, proctime",
MESSAGE),
+ Arguments.of("SELECT a FROM MyTable ORDER BY c, rowtime",
MESSAGE),
+ Arguments.of("SELECT a FROM MyTable ORDER BY c, proctime
DESC", MESSAGE),
+ Arguments.of("SELECT a FROM MyTable ORDER BY c, rowtime DESC",
MESSAGE),
+ // primary sort key is a time attribute but sorted descending
-> message B
+ Arguments.of("SELECT a FROM MyTable ORDER BY proctime DESC,
c", MESSAGE_DESC),
+ Arguments.of("SELECT a FROM MyTable ORDER BY rowtime DESC, c",
MESSAGE_DESC),
+ // ordinals and aliases are expanded by the validator and hit
the same rule
+ Arguments.of("SELECT a FROM MyTable ORDER BY 1", MESSAGE),
+ Arguments.of("SELECT c AS x FROM MyTable ORDER BY x", MESSAGE),
+ Arguments.of("SELECT rowtime AS t, a FROM MyTable ORDER BY t
DESC", MESSAGE_DESC),
+ // an expression is projected into a generated column below
the sort
+ Arguments.of("SELECT a FROM MyTable ORDER BY c + 1", MESSAGE));
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("nonTemporalSorts")
+ void testNonTemporalSortRejected(String query, String expectedMessage) {
+ assertThatThrownBy(() -> util.verifyExecPlan(query))
+ .isInstanceOf(TableException.class)
+ .hasMessageContaining(expectedMessage);
+ }
+
+ @Test
+ void testMessageNamesColumnAndType() {
+ assertThatThrownBy(() -> util.verifyExecPlan("SELECT a FROM MyTable
ORDER BY c"))
+ .isInstanceOf(TableException.class)
+ .hasMessageContaining(MESSAGE)
+ .hasMessageContaining("'c' is BIGINT");
+ }
+
+ @Test
+ void testMessageDescribesSqlExpressionKey() {
+ // The SQL converter projects the ORDER BY expression as EXPR$n; the
message must not
+ // leak that alias. The Volcano wrapper prepends the plan (which does
contain the
+ // alias), so assert on the rule's own exception.
+ assertThatThrownBy(() -> util.verifyExecPlan("SELECT a FROM MyTable
ORDER BY c + 1"))
+ .rootCause()
+ .isInstanceOf(TableException.class)
+ .hasMessageContaining("the sort key expression is BIGINT")
+ .hasMessageNotContaining("EXPR$");
+ }
+
+ @Test
+ void testMessageDescribesTableApiExpressionKey() {
+ // The Table API projects the ORDER BY expression as $fn instead of
EXPR$n.
+ Table table =
util.getTableEnv().from("MyTable").orderBy($("c").plus(1));
+ assertThatThrownBy(() -> util.verifyExecPlan(table))
+ .rootCause()
+ .isInstanceOf(TableException.class)
+ .hasMessageContaining("the sort key expression is BIGINT")
+ .hasMessageNotContaining("$f");
+ }
+
+ @Test
+ void testNonTemporalSortAllowedWhenEnabled() {
+ util.getTableEnv()
+ .getConfig()
+
.set(InternalConfigOptions.TABLE_EXEC_NON_TEMPORAL_SORT_ENABLED, true);
+ // With the internal flag enabled, the plan-time check is skipped and
the sort is accepted.
+ assertThatCode(() -> util.getTableEnv().explainSql("SELECT a FROM
MyTable ORDER BY c"))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void testCompilePlanRejectsNonTemporalSort() {
+ util.addTable("CREATE TABLE MySink (a INT) WITH ('connector' =
'values')");
+ assertThatThrownBy(
+ () ->
+ util.getTableEnv()
+ .compilePlanSql(
+ "INSERT INTO MySink SELECT a
FROM MyTable ORDER BY c"))
+ .isInstanceOf(TableException.class)
+ .hasMessageContaining(MESSAGE);
+ }
+
+ @Test
+ void testLoadedPlanRejectsNonTemporalSort() {
+ String json =
+ compileWithNonTemporalSortEnabled(
+ "INSERT INTO MySink SELECT a FROM MyTable ORDER BY c");
+ assertTranslationRejected(json, MESSAGE);
+ }
+
+ @Test
+ void testLoadedPlanRejectsDescendingTimeAttributeSort() {
+ String json =
+ compileWithNonTemporalSortEnabled(
+ "INSERT INTO MySink SELECT a FROM MyTable ORDER BY
rowtime DESC");
+ // The backstop can only tell the two cases apart if the time
attribute survives the
+ // round trip; fail on that premise rather than silently drifting to
the other message.
+ assertThat(json).containsPattern("\"kind\"\\s*:\\s*\"ROWTIME\"");
+ assertTranslationRejected(json, MESSAGE_DESC);
+ }
+
+ /**
+ * Compiles the insert with the internal flag enabled so the sort passes
{@code
+ * StreamPhysicalSortRule}. The flag is not persisted in the plan.
+ */
+ private String compileWithNonTemporalSortEnabled(String insert) {
+ util.addTable("CREATE TABLE MySink (a INT) WITH ('connector' =
'values')");
+ util.getTableEnv()
+ .getConfig()
+
.set(InternalConfigOptions.TABLE_EXEC_NON_TEMPORAL_SORT_ENABLED, true);
+ return util.getTableEnv().compilePlanSql(insert).asJsonString();
+ }
+
+ /**
+ * Loads the plan with the flag disabled and translates it, which reaches
the backstop in {@code
+ * StreamExecSort} without passing through {@code StreamPhysicalSortRule}.
+ */
+ private void assertTranslationRejected(String json, String
expectedMessage) {
+ TableEnvironment tEnv = util.getTableEnv();
+
tEnv.getConfig().set(InternalConfigOptions.TABLE_EXEC_NON_TEMPORAL_SORT_ENABLED,
false);
+ CompiledPlan loaded =
tEnv.loadPlan(PlanReference.fromJsonString(json));
+ assertThatThrownBy(() -> CompiledPlanUtils.toTransformations(tEnv,
loaded))
+ .isInstanceOf(TableException.class)
+ .hasMessageContaining(expectedMessage);
+ }
+}
diff --git
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/common/PartialInsertTest.xml
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/common/PartialInsertTest.xml
index 9f23ebd1a81..d691886784f 100644
---
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/common/PartialInsertTest.xml
+++
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/common/PartialInsertTest.xml
@@ -312,29 +312,6 @@
Sink(table=[default_catalog.default_database.partitioned_sink], targetColumns=[[
+- Exchange(distribution=[hash[a, b, c, d, e]])
+- LocalHashAggregate(groupBy=[a, b, c, d,
e], select=[a, b, c, d, e])
+-
TableSourceScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c, d, e])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testPartialInsertWithOrderBy[isBatch: false]">
- <Resource name="sql">
- <![CDATA[INSERT INTO partitioned_sink (e,a,g,f,c,d) SELECT
e,a,456,123,c,d FROM MyTable ORDER BY a,e,c,d]]>
- </Resource>
- <Resource name="ast">
- <![CDATA[
-LogicalSink(table=[default_catalog.default_database.partitioned_sink],
targetColumns=[[4],[0],[6],[5],[2],[3]], fields=[a, c, d, e, f, g])
-+- LogicalProject(a=[$0], c=[$1], d=[$2], e=[$3], f=[CAST($4):BIGINT],
g=[CAST($5):INTEGER])
- +- LogicalSort(sort0=[$0], sort1=[$3], sort2=[$1], sort3=[$2],
dir0=[ASC-nulls-first], dir1=[ASC-nulls-first], dir2=[ASC-nulls-first],
dir3=[ASC-nulls-first])
- +- LogicalProject(a=[$0], c=[$2], d=[$3], e=[$4], EXPR$4=[123],
EXPR$5=[456])
- +- LogicalTableScan(table=[[default_catalog, default_database,
MyTable]])
-]]>
- </Resource>
- <Resource name="optimized rel plan">
- <![CDATA[
-Sink(table=[default_catalog.default_database.partitioned_sink],
targetColumns=[[4],[0],[6],[5],[2],[3]], fields=[a, c, d, e, f, g])
-+- Calc(select=[a, c, d, e, CAST(123 AS BIGINT) AS f, CAST(456 AS INTEGER) AS
g])
- +- Sort(orderBy=[a ASC, e ASC, c ASC, d ASC])
- +- Exchange(distribution=[single])
- +- TableSourceScan(table=[[default_catalog, default_database,
MyTable]], fields=[a, b, c, d, e])
]]>
</Resource>
</TestCase>
diff --git
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/SortTest.xml
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/SortTest.xml
index 22a2cfc58fc..c92a3bcfa7b 100644
---
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/SortTest.xml
+++
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/SortTest.xml
@@ -16,85 +16,43 @@ See the License for the specific language governing
permissions and
limitations under the License.
-->
<Root>
- <TestCase name="testSortProcessingTime">
+ <TestCase name="testSortOnRowTimeAlias">
<Resource name="sql">
- <![CDATA[SELECT a FROM MyTable ORDER BY proctime, c]]>
+ <![CDATA[SELECT a, rowtime AS t FROM MyTable ORDER BY t, c]]>
</Resource>
<Resource name="ast">
<![CDATA[
-LogicalProject(a=[$0])
+LogicalProject(a=[$0], t=[$1])
+- LogicalSort(sort0=[$1], sort1=[$2], dir0=[ASC-nulls-first],
dir1=[ASC-nulls-first])
- +- LogicalProject(a=[$0], proctime=[$3], c=[$2])
- +- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
-]]>
- </Resource>
- <Resource name="optimized exec plan">
- <![CDATA[
-Calc(select=[a])
-+- TemporalSort(orderBy=[proctime ASC, c ASC])
- +- Exchange(distribution=[single])
- +- DataStreamScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c, proctime, rowtime])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testSortProcessingTimeDesc">
- <Resource name="sql">
- <![CDATA[SELECT a FROM MyTable ORDER BY proctime desc, c]]>
- </Resource>
- <Resource name="ast">
- <![CDATA[
-LogicalProject(a=[$0])
-+- LogicalSort(sort0=[$1], sort1=[$2], dir0=[DESC-nulls-last],
dir1=[ASC-nulls-first])
- +- LogicalProject(a=[$0], proctime=[$3], c=[$2])
+ +- LogicalProject(a=[$0], t=[$4], c=[$2])
+- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
]]>
</Resource>
<Resource name="optimized exec plan">
<![CDATA[
-Calc(select=[a])
-+- Sort(orderBy=[proctime DESC, c ASC])
+Calc(select=[a, rowtime AS t])
++- TemporalSort(orderBy=[rowtime ASC, c ASC])
+- Exchange(distribution=[single])
+- DataStreamScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c, proctime, rowtime])
]]>
</Resource>
</TestCase>
- <TestCase name="testSortProcessingTimeSecond">
+ <TestCase name="testSortProcessingTime">
<Resource name="sql">
- <![CDATA[SELECT a FROM MyTable ORDER BY c, proctime]]>
+ <![CDATA[SELECT a FROM MyTable ORDER BY proctime, c]]>
</Resource>
<Resource name="ast">
<![CDATA[
LogicalProject(a=[$0])
+- LogicalSort(sort0=[$1], sort1=[$2], dir0=[ASC-nulls-first],
dir1=[ASC-nulls-first])
- +- LogicalProject(a=[$0], c=[$2], proctime=[$3])
- +- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
-]]>
- </Resource>
- <Resource name="optimized exec plan">
- <![CDATA[
-Calc(select=[a])
-+- Sort(orderBy=[c ASC, proctime ASC])
- +- Exchange(distribution=[single])
- +- DataStreamScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c, proctime, rowtime])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testSortProcessingTimeSecondDesc">
- <Resource name="sql">
- <![CDATA[SELECT a FROM MyTable ORDER BY c, proctime desc]]>
- </Resource>
- <Resource name="ast">
- <![CDATA[
-LogicalProject(a=[$0])
-+- LogicalSort(sort0=[$1], sort1=[$2], dir0=[ASC-nulls-first],
dir1=[DESC-nulls-last])
- +- LogicalProject(a=[$0], c=[$2], proctime=[$3])
+ +- LogicalProject(a=[$0], proctime=[$3], c=[$2])
+- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
]]>
</Resource>
<Resource name="optimized exec plan">
<![CDATA[
Calc(select=[a])
-+- Sort(orderBy=[c ASC, proctime DESC])
++- TemporalSort(orderBy=[proctime ASC, c ASC])
+- Exchange(distribution=[single])
+- DataStreamScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c, proctime, rowtime])
]]>
@@ -118,90 +76,6 @@ Calc(select=[a])
+- TemporalSort(orderBy=[rowtime ASC, c ASC])
+- Exchange(distribution=[single])
+- DataStreamScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c, proctime, rowtime])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testSortRowTimeDesc">
- <Resource name="sql">
- <![CDATA[SELECT a FROM MyTable ORDER BY rowtime desc, c]]>
- </Resource>
- <Resource name="ast">
- <![CDATA[
-LogicalProject(a=[$0])
-+- LogicalSort(sort0=[$1], sort1=[$2], dir0=[DESC-nulls-last],
dir1=[ASC-nulls-first])
- +- LogicalProject(a=[$0], rowtime=[$4], c=[$2])
- +- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
-]]>
- </Resource>
- <Resource name="optimized exec plan">
- <![CDATA[
-Calc(select=[a])
-+- Sort(orderBy=[rowtime DESC, c ASC])
- +- Exchange(distribution=[single])
- +- DataStreamScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c, proctime, rowtime])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testSortRowTimeSecond">
- <Resource name="sql">
- <![CDATA[SELECT a FROM MyTable ORDER BY c, rowtime]]>
- </Resource>
- <Resource name="ast">
- <![CDATA[
-LogicalProject(a=[$0])
-+- LogicalSort(sort0=[$1], sort1=[$2], dir0=[ASC-nulls-first],
dir1=[ASC-nulls-first])
- +- LogicalProject(a=[$0], c=[$2], rowtime=[$4])
- +- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
-]]>
- </Resource>
- <Resource name="optimized exec plan">
- <![CDATA[
-Calc(select=[a])
-+- Sort(orderBy=[c ASC, rowtime ASC])
- +- Exchange(distribution=[single])
- +- DataStreamScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c, proctime, rowtime])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testSortRowTimeSecondDesc">
- <Resource name="sql">
- <![CDATA[SELECT a FROM MyTable ORDER BY c, rowtime desc]]>
- </Resource>
- <Resource name="ast">
- <![CDATA[
-LogicalProject(a=[$0])
-+- LogicalSort(sort0=[$1], sort1=[$2], dir0=[ASC-nulls-first],
dir1=[DESC-nulls-last])
- +- LogicalProject(a=[$0], c=[$2], rowtime=[$4])
- +- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
-]]>
- </Resource>
- <Resource name="optimized exec plan">
- <![CDATA[
-Calc(select=[a])
-+- Sort(orderBy=[c ASC, rowtime DESC])
- +- Exchange(distribution=[single])
- +- DataStreamScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c, proctime, rowtime])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testSortWithoutTime">
- <Resource name="sql">
- <![CDATA[SELECT a FROM MyTable ORDER BY c]]>
- </Resource>
- <Resource name="ast">
- <![CDATA[
-LogicalProject(a=[$0])
-+- LogicalSort(sort0=[$1], dir0=[ASC-nulls-first])
- +- LogicalProject(a=[$0], c=[$2])
- +- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
-]]>
- </Resource>
- <Resource name="optimized exec plan">
- <![CDATA[
-Calc(select=[a])
-+- Sort(orderBy=[c ASC])
- +- Exchange(distribution=[single])
- +- DataStreamScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c, proctime, rowtime])
]]>
</Resource>
</TestCase>
diff --git
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/TableSinkTest.xml
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/TableSinkTest.xml
index 486e4d045ec..493a6f1f932 100644
---
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/TableSinkTest.xml
+++
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/TableSinkTest.xml
@@ -489,18 +489,15 @@ Sink(table=[default_catalog.default_database.sink],
fields=[id, city_name, ts, r
<Resource name="ast">
<![CDATA[
LogicalSink(table=[default_catalog.default_database.sink], fields=[a, b])
-+- LogicalSort(sort0=[$0], dir0=[ASC-nulls-first])
- +- LogicalProject(a=[$0], b=[$1])
- +- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
++- LogicalProject(a=[$0], b=[$1])
+ +- LogicalTableScan(table=[[default_catalog, default_database, MyTable]])
]]>
</Resource>
<Resource name="optimized exec plan">
<![CDATA[
Sink(table=[default_catalog.default_database.sink], fields=[a, b])
-+- Sort(orderBy=[a ASC])
- +- Exchange(distribution=[single])
- +- Calc(select=[a, b])
- +- DataStreamScan(table=[[default_catalog, default_database,
MyTable]], fields=[a, b, c])
++- Calc(select=[a, b])
+ +- DataStreamScan(table=[[default_catalog, default_database, MyTable]],
fields=[a, b, c])
]]>
</Resource>
</TestCase>
diff --git
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/agg/GroupingSetsTest.xml
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/agg/GroupingSetsTest.xml
index a56cec43528..54d07e3fb38 100644
---
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/agg/GroupingSetsTest.xml
+++
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/stream/sql/agg/GroupingSetsTest.xml
@@ -402,28 +402,24 @@ SELECT deptno, job, empno, ename, SUM(sal) sumsal,
END gr_text
from scott_emp
GROUP BY ROLLUP(deptno, job, (empno,ename))
- ORDER BY deptno, job, empno
]]>
</Resource>
<Resource name="ast">
<![CDATA[
-LogicalSort(sort0=[$0], sort1=[$1], sort2=[$2], dir0=[ASC-nulls-first],
dir1=[ASC-nulls-first], dir2=[ASC-nulls-first])
-+- LogicalProject(deptno=[$0], job=[$1], empno=[$2], ename=[$3], sumsal=[$4],
gr_text=[CASE(=($5, 0), _UTF-16LE'grouped by
deptno,job,empno,ename':VARCHAR(2147483647) CHARACTER SET "UTF-16LE", =($5, 1),
_UTF-16LE'grouped by deptno,job':VARCHAR(2147483647) CHARACTER SET "UTF-16LE",
=($5, 3), _UTF-16LE'grouped by deptno':VARCHAR(2147483647) CHARACTER SET
"UTF-16LE", =($5, 7), _UTF-16LE'grouped by ()':VARCHAR(2147483647) CHARACTER
SET "UTF-16LE", null:VARCHAR(2147483647) CHARACTER SET "UTF- [...]
- +- LogicalAggregate(group=[{0, 1, 2, 3}], groups=[[{0, 1, 2, 3}, {0, 1},
{0}, {}]], sumsal=[SUM($4)], agg#1=[GROUPING_ID($0, $1, $2)])
- +- LogicalProject(deptno=[$7], job=[$2], empno=[$0], ename=[$1],
sal=[$5])
- +- LogicalTableScan(table=[[default_catalog, default_database,
scott_emp]])
+LogicalProject(deptno=[$0], job=[$1], empno=[$2], ename=[$3], sumsal=[$4],
gr_text=[CASE(=($5, 0), _UTF-16LE'grouped by
deptno,job,empno,ename':VARCHAR(2147483647) CHARACTER SET "UTF-16LE", =($5, 1),
_UTF-16LE'grouped by deptno,job':VARCHAR(2147483647) CHARACTER SET "UTF-16LE",
=($5, 3), _UTF-16LE'grouped by deptno':VARCHAR(2147483647) CHARACTER SET
"UTF-16LE", =($5, 7), _UTF-16LE'grouped by ()':VARCHAR(2147483647) CHARACTER
SET "UTF-16LE", null:VARCHAR(2147483647) CHARACTER SET "UTF-16LE")])
++- LogicalAggregate(group=[{0, 1, 2, 3}], groups=[[{0, 1, 2, 3}, {0, 1}, {0},
{}]], sumsal=[SUM($4)], agg#1=[GROUPING_ID($0, $1, $2)])
+ +- LogicalProject(deptno=[$7], job=[$2], empno=[$0], ename=[$1], sal=[$5])
+ +- LogicalTableScan(table=[[default_catalog, default_database,
scott_emp]])
]]>
</Resource>
<Resource name="optimized exec plan">
<![CDATA[
-Sort(orderBy=[deptno ASC, job ASC, empno ASC])
-+- Exchange(distribution=[single])
- +- Calc(select=[deptno, job, empno, ename, sumsal, CASE((CASE(($e = 0), 0,
($e = 3), 1, ($e = 7), 3, 7) = 0), 'grouped by deptno,job,empno,ename',
(CASE(($e = 0), 0, ($e = 3), 1, ($e = 7), 3, 7) = 1), 'grouped by deptno,job',
(CASE(($e = 0), 0, ($e = 3), 1, ($e = 7), 3, 7) = 3), 'grouped by deptno',
(CASE(($e = 0), 0, ($e = 3), 1, ($e = 7), 3, 7) = 7), 'grouped by ()',
null:VARCHAR(2147483647)) AS gr_text])
- +- GroupAggregate(groupBy=[deptno, job, empno, ename, $e],
select=[deptno, job, empno, ename, $e, SUM(sal) AS sumsal])
- +- Exchange(distribution=[hash[deptno, job, empno, ename, $e]])
- +- Expand(projects=[{deptno, job, empno, ename, sal, 0 AS $e},
{deptno, job, null AS empno, null AS ename, sal, 3 AS $e}, {deptno, null AS
job, null AS empno, null AS ename, sal, 7 AS $e}, {null AS deptno, null AS job,
null AS empno, null AS ename, sal, 15 AS $e}])
- +- Calc(select=[deptno, job, empno, ename, sal])
- +- TableSourceScan(table=[[default_catalog,
default_database, scott_emp]], fields=[empno, ename, job, mgr, hiredate, sal,
comm, deptno])
+Calc(select=[deptno, job, empno, ename, sumsal, CASE((CASE(($e = 0), 0, ($e =
3), 1, ($e = 7), 3, 7) = 0), 'grouped by deptno,job,empno,ename', (CASE(($e =
0), 0, ($e = 3), 1, ($e = 7), 3, 7) = 1), 'grouped by deptno,job', (CASE(($e =
0), 0, ($e = 3), 1, ($e = 7), 3, 7) = 3), 'grouped by deptno', (CASE(($e = 0),
0, ($e = 3), 1, ($e = 7), 3, 7) = 7), 'grouped by ()',
null:VARCHAR(2147483647)) AS gr_text])
++- GroupAggregate(groupBy=[deptno, job, empno, ename, $e], select=[deptno,
job, empno, ename, $e, SUM(sal) AS sumsal])
+ +- Exchange(distribution=[hash[deptno, job, empno, ename, $e]])
+ +- Expand(projects=[{deptno, job, empno, ename, sal, 0 AS $e}, {deptno,
job, null AS empno, null AS ename, sal, 3 AS $e}, {deptno, null AS job, null AS
empno, null AS ename, sal, 7 AS $e}, {null AS deptno, null AS job, null AS
empno, null AS ename, sal, 15 AS $e}])
+ +- Calc(select=[deptno, job, empno, ename, sal])
+ +- TableSourceScan(table=[[default_catalog, default_database,
scott_emp]], fields=[empno, ename, job, mgr, hiredate, sal, comm, deptno])
]]>
</Resource>
</TestCase>
@@ -525,36 +521,6 @@ Calc(select=[deptno, gender, CASE(SEARCH($e, Sarg[0, 1]),
0, 1) AS gd, CASE(($e
+- Expand(projects=[{deptno, gender, 0 AS $e}, {deptno, null AS gender,
1 AS $e}, {null AS deptno, gender, 2 AS $e}, {null AS deptno, null AS gender, 3
AS $e}])
+- Calc(select=[deptno, gender])
+- TableSourceScan(table=[[default_catalog, default_database,
emp]], fields=[ename, deptno, gender])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testGroupingInOrderByClause">
- <Resource name="sql">
- <![CDATA[
-SELECT COUNT(*) AS c FROM emp GROUP BY ROLLUP(deptno) ORDER BY
GROUPING(deptno), c
- ]]>
- </Resource>
- <Resource name="ast">
- <![CDATA[
-LogicalProject(c=[$0])
-+- LogicalSort(sort0=[$1], sort1=[$0], dir0=[ASC-nulls-first],
dir1=[ASC-nulls-first])
- +- LogicalProject(c=[$1], EXPR$1=[$2])
- +- LogicalAggregate(group=[{0}], groups=[[{0}, {}]], c=[COUNT()],
agg#1=[GROUPING($0)])
- +- LogicalProject(deptno=[$1])
- +- LogicalTableScan(table=[[default_catalog, default_database,
emp]])
-]]>
- </Resource>
- <Resource name="optimized exec plan">
- <![CDATA[
-Calc(select=[c])
-+- Sort(orderBy=[EXPR$1 ASC, c ASC])
- +- Exchange(distribution=[single])
- +- Calc(select=[c, CASE(($e = 0), 0, 1) AS EXPR$1])
- +- GroupAggregate(groupBy=[deptno, $e], select=[deptno, $e, COUNT(*)
AS c])
- +- Exchange(distribution=[hash[deptno, $e]])
- +- Expand(projects=[{deptno, 0 AS $e}, {null AS deptno, 1 AS
$e}])
- +- Calc(select=[deptno])
- +- TableSourceScan(table=[[default_catalog,
default_database, emp]], fields=[ename, deptno, gender])
]]>
</Resource>
</TestCase>
@@ -979,31 +945,6 @@ Calc(select=[gender, c])
+- Expand(projects=[{gender, 0 AS $e}, {null AS gender, 1 AS $e}])
+- Calc(select=[gender])
+- TableSourceScan(table=[[default_catalog, default_database,
emp]], fields=[ename, deptno, gender])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testRollupPlusOrderBy">
- <Resource name="sql">
- <![CDATA[SELECT gender, COUNT(*) AS c FROM emp GROUP BY ROLLUP(gender)
ORDER BY c DESC]]>
- </Resource>
- <Resource name="ast">
- <![CDATA[
-LogicalSort(sort0=[$1], dir0=[DESC-nulls-last])
-+- LogicalAggregate(group=[{0}], groups=[[{0}, {}]], c=[COUNT()])
- +- LogicalProject(gender=[$2])
- +- LogicalTableScan(table=[[default_catalog, default_database, emp]])
-]]>
- </Resource>
- <Resource name="optimized exec plan">
- <![CDATA[
-Sort(orderBy=[c DESC])
-+- Exchange(distribution=[single])
- +- Calc(select=[gender, c])
- +- GroupAggregate(groupBy=[gender, $e], select=[gender, $e, COUNT(*) AS
c])
- +- Exchange(distribution=[hash[gender, $e]])
- +- Expand(projects=[{gender, 0 AS $e}, {null AS gender, 1 AS $e}])
- +- Calc(select=[gender])
- +- TableSourceScan(table=[[default_catalog,
default_database, emp]], fields=[ename, deptno, gender])
]]>
</Resource>
</TestCase>
diff --git
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/common/PartialInsertTest.scala
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/common/PartialInsertTest.scala
index 574d07b3ecf..4e509882fa7 100644
---
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/common/PartialInsertTest.scala
+++
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/common/PartialInsertTest.scala
@@ -158,9 +158,18 @@ class PartialInsertTest(isBatch: Boolean) extends
TableTestBase {
@TestTemplate
def testPartialInsertWithOrderBy(): Unit = {
- util.verifyRelPlanInsert(
+ val insert =
"INSERT INTO partitioned_sink (e,a,g,f,c,d) " +
- "SELECT e,a,456,123,c,d FROM MyTable ORDER BY a,e,c,d")
+ "SELECT e,a,456,123,c,d FROM MyTable ORDER BY a,e,c,d"
+ // ORDER BY on a non-time attribute is supported in batch but rejected
during streaming
+ // optimization.
+ if (isBatch) {
+ util.verifyRelPlanInsert(insert)
+ } else {
+ assertThatThrownBy(() => util.verifyRelPlanInsert(insert))
+ .hasMessageContaining(
+ "requires the primary sort key to be a time attribute in ascending
order")
+ }
}
@TestTemplate
diff --git
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/SortTest.scala
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/SortTest.scala
index 042842e7d2e..0ec77e0ebe6 100644
---
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/SortTest.scala
+++
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/SortTest.scala
@@ -41,37 +41,10 @@ class SortTest extends TableTestBase {
}
@Test
- def testSortProcessingTimeDesc(): Unit = {
- util.verifyExecPlan("SELECT a FROM MyTable ORDER BY proctime desc, c")
+ def testSortOnRowTimeAlias(): Unit = {
+ util.verifyExecPlan("SELECT a, rowtime AS t FROM MyTable ORDER BY t, c")
}
- @Test
- def testSortRowTimeDesc(): Unit = {
- util.verifyExecPlan("SELECT a FROM MyTable ORDER BY rowtime desc, c")
- }
-
- @Test
- def testSortProcessingTimeSecond(): Unit = {
- util.verifyExecPlan("SELECT a FROM MyTable ORDER BY c, proctime")
- }
-
- @Test
- def testSortRowTimeSecond(): Unit = {
- util.verifyExecPlan("SELECT a FROM MyTable ORDER BY c, rowtime")
- }
-
- @Test
- def testSortProcessingTimeSecondDesc(): Unit = {
- util.verifyExecPlan("SELECT a FROM MyTable ORDER BY c, proctime desc")
- }
-
- @Test
- def testSortRowTimeSecondDesc(): Unit = {
- util.verifyExecPlan("SELECT a FROM MyTable ORDER BY c, rowtime desc")
- }
-
- @Test
- def testSortWithoutTime(): Unit = {
- util.verifyExecPlan("SELECT a FROM MyTable ORDER BY c")
- }
+ // Non-temporal streaming sorts (first sort field is not an ascending time
attribute) are now
+ // rejected during optimization; see SortValidationTest for the
corresponding negative cases.
}
diff --git
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/TableSinkTest.scala
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/TableSinkTest.scala
index a2bad91048f..346db891909 100644
---
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/TableSinkTest.scala
+++
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/TableSinkTest.scala
@@ -800,7 +800,7 @@ class TableSinkTest extends TableTestBase {
|)
|""".stripMargin)
val stmtSet = util.tableEnv.createStatementSet()
- stmtSet.addInsertSql("INSERT INTO sink SELECT a,b FROM MyTable ORDER BY a")
+ stmtSet.addInsertSql("INSERT INTO sink SELECT a,b FROM MyTable")
util.verifyExecPlan(stmtSet)
}
diff --git
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/agg/GroupingSetsTest.scala
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/agg/GroupingSetsTest.scala
index 5e68ef39dd4..ab4c0ffd463 100644
---
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/agg/GroupingSetsTest.scala
+++
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/plan/stream/sql/agg/GroupingSetsTest.scala
@@ -21,7 +21,7 @@ import org.apache.flink.table.api._
import org.apache.flink.table.planner.plan.utils.FlinkRelOptUtil
import org.apache.flink.table.planner.utils.{TableTestBase, TableTestUtil}
-import org.assertj.core.api.Assertions.assertThat
+import org.assertj.core.api.Assertions.{assertThat, assertThatThrownBy}
import org.junit.jupiter.api.Test
import java.sql.Date
@@ -213,8 +213,13 @@ class GroupingSetsTest extends TableTestBase {
@Test
def testRollupPlusOrderBy(): Unit = {
- util.verifyExecPlan(
- "SELECT gender, COUNT(*) AS c FROM emp GROUP BY ROLLUP(gender) ORDER BY
c DESC")
+ // A non-time-attribute streaming sort is rejected during optimization.
+ assertThatThrownBy(
+ () =>
+ util.verifyExecPlan(
+ "SELECT gender, COUNT(*) AS c FROM emp GROUP BY ROLLUP(gender) ORDER
BY c DESC"))
+ .hasMessageContaining(
+ "requires the primary sort key to be a time attribute in ascending
order")
}
@Test
@@ -318,7 +323,10 @@ class GroupingSetsTest extends TableTestBase {
"""
|SELECT COUNT(*) AS c FROM emp GROUP BY ROLLUP(deptno) ORDER BY
GROUPING(deptno), c
""".stripMargin
- util.verifyExecPlan(sqlQuery)
+ // A non-time-attribute streaming sort is rejected during optimization.
+ assertThatThrownBy(() => util.verifyExecPlan(sqlQuery))
+ .hasMessageContaining(
+ "requires the primary sort key to be a time attribute in ascending
order")
}
@Test
@@ -424,7 +432,6 @@ class GroupingSetsTest extends TableTestBase {
| END gr_text
|from scott_emp
| GROUP BY ROLLUP(deptno, job, (empno,ename))
- | ORDER BY deptno, job, empno
""".stripMargin
util.verifyExecPlan(sqlQuery)
}
diff --git
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/stream/sql/SortITCase.scala
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/stream/sql/SortITCase.scala
index 8ba436b52eb..d1d92ce040d 100644
---
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/stream/sql/SortITCase.scala
+++
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/stream/sql/SortITCase.scala
@@ -46,9 +46,12 @@ class SortITCase(mode: StateBackendMode) extends
StreamingWithStateTestBase(mode
val da = StreamingEnvUtil.fromCollection(env, data).toTable(tEnv, 'a1, 'a2)
tEnv.createTemporaryView("a", da)
+ // The rejection is raised during optimization and wrapped by the Volcano
program, so assert
+ // on a contained substring rather than an exact message.
assertThatThrownBy(() => tEnv.sqlQuery(sqlQuery).toRetractStream[Row])
- .hasMessage("Sort on a non-time-attribute field is not supported.")
- .isInstanceOf[TableException]
+ .isExactlyInstanceOf(classOf[TableException])
+ .hasMessageContaining(
+ "requires the primary sort key to be a time attribute in ascending
order")
}
@TestTemplate