[FLINK-40449][table] Reject non-temporal streaming sort during planning - #29003
[FLINK-40449][table] Reject non-temporal streaming sort during planning#29003MartijnVisser wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Thanks for the fix @MartijnVisser!
The changes look good.
Left just two minor comments.
Thanks, Fabian
| * 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; DESC is " + |
There was a problem hiding this comment.
| s"Streaming ORDER BY on time attribute '$column' must be sorted in ascending order; DESC is " + | |
| s"Streaming ORDER BY on time attribute '$column' must be sorted in ascending order; descending order is " + |
Spell out descending as ascending is spelled out as well?
There was a problem hiding this comment.
Yes, good one. Fixed!
| * be rejected during optimization (so it surfaces at {@code COMPILE PLAN} / planning time), not | ||
| * deferred to execution-plan translation. Valid temporal sorts are covered by {@code SortTest}. | ||
| */ | ||
| class SortValidationTest extends TableTestBase { |
There was a problem hiding this comment.
add a test asserting that the plan check are only done if the TABLE_EXEC_NON_TEMPORAL_SORT_ENABLED flag isn't set?
There was a problem hiding this comment.
Makes sense, I've added that too
74fb43b to
c2d671a
Compare
| * be rejected during optimization (so it surfaces at {@code COMPILE PLAN} / planning time), not | ||
| * deferred to execution-plan translation. Valid temporal sorts are covered by {@code SortTest}. | ||
| */ | ||
| class SortValidationTest extends TableTestBase { |
There was a problem hiding this comment.
How about using new way semantic tests instead of old fashion approach ?
There was a problem hiding this comment.
Good question — I looked into migrating this to the semantic test framework (SemanticTestBase / TableTestProgram), but it does not fit this particular test:
runFailingSqlonly acceptsValidationExceptionorTableRuntimeException(hardPreconditionscheck inFailingSqlTestStep), while this check throwsTableException— the same type the previous execution-time check threw, so I would rather not change the user-facing exception type in this PR.- Three of the assertions here have no equivalent test step: the
table.exec.non-temporal-sort.enabledescape hatch (asserting no exception), theCOMPILE PLANcheck viacompilePlanSql, and asserting the message names the column and its type.
Since *ValidationTest classes extending TableTestBase are still the established pattern for planning-time rejections (e.g. the batch SortValidationTest, AggregateValidationTest), I would prefer to keep this as-is. Happy to revisit in a follow-up if we relax the exception-type restriction in FailingSqlTestStep.
| 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), |
There was a problem hiding this comment.
Do we have tests with sorting by ordinals?
Probably same for sorting by alias?
Based on FlinkSqlConformance they should be valid
There was a problem hiding this comment.
Good point. Ordinals and aliases are expanded by the SQL validator before optimization, so they hit the same rule — but explicit coverage is cheap. I have added negative cases for ORDER BY <ordinal>, ORDER BY <alias>, and ORDER BY <time-attribute alias> DESC, plus a positive case in SortTest showing that an alias of rowtime still routes to the temporal sort.
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. The StreamExecSort check is kept as a backstop for compiled plans loaded via loadPlan, which bypasses optimization. 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, and SortValidationTest covers the new behavior. Generated-by: Claude Code (Claude Opus 4.8)
c2d671a to
3e939ff
Compare
What is the purpose of the change
A streaming query that sorts on a non-time attribute (e.g.
SELECT a FROM t ORDER BY c, where c is not an ascending time attribute) is not supported. Today that rejection is thrown fromStreamExecSort.translateToPlanInternal, i.e. during execution-plan translation, so it only surfaces when the job is (re)submitted rather than during optimization. As a result COMPILE PLAN FOR '' succeeds and the statement fails only later; in SQL-gateway/REST flows this can lead to a restart loop before the deterministic error reaches the user.This change rejects the sort during optimization instead, so it is reported by
optimize()/COMPILE PLANbefore job submission. It also replaces the previous message (which was inaccurate for a descending time attribute, it claimed "non-time-attribute" even when the column was a time attribute) with two targeted messages.Brief change log
StreamPhysicalSortRule.convert()rejects a non-temporal streaming sort during the physical optimization phase (consistent with e.g.StreamPhysicalOverAggregateRule), reusingSortUtil.getFirstSortField.SortUtiland are shared by the rule and the exec node: one for a non-time-attribute primary key, one for a time attribute sorted DESC.StreamExecSort.translateToPlanInternalcheck is kept as a backstop for compiled plans loaded via loadPlan, which bypassesoptimize().Verifying this change
This change added tests and can be verified as follows:
SortValidationTest(parameterized over the non-temporal sort shapes, plus aCOMPILE PLANcase) asserting the rejection now happens at planning time.TableSinkTest.testDistribution,GroupingSetsTest.testFromBlogspot); the rest assert the rejection (GroupingSetsTest.testRollupPlusOrderBy/testGroupingInOrderByClause, streamingPartialInsertTest.testPartialInsertWithOrderBy,SortITCase.testDisableSortNonTemporalField).mvn verifypasses forflink-table-planner,includingSortITCaseend-to-end.Note: because the exception is thrown from a rule during the Volcano phase, the top-level message is wrapped by
FlinkVolcanoProgram("Sql optimization: …") with the original text as the root cause, that's the same shape as other rule-phase rejections (GROUPING SETS, OVER aggregates).Does this pull request potentially affect one of the following parts:
Documentation
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Claude Opus 4.8)