@@ -1536,15 +1536,10 @@ fn test_collect_left_join_keeps_hash_partitioned_build_side_coalesce() -> Result
15361536// Limits with a `skip`
15371537// ========================================================================
15381538
1539- /// A sort pushed below `GlobalLimitExec` with a non-zero `skip` must ask its
1540- /// input for `skip + fetch` rows; asking for only `fetch` rows used to leave
1541- /// `LIMIT 10 OFFSET 5` with 5 result rows.
1542- ///
1543- /// This checks a single pass only: the sort is inserted by `pushdown_sorts`,
1544- /// which runs after `parallelize_sorts`, so a second pass would additionally
1545- /// parallelize it into `SortPreservingMergeExec` + partitioned `SortExec`.
1539+ /// Keep sorting above an unordered limit so its input can stop early and only
1540+ /// the rows retained after the offset need sorting.
15461541#[ test]
1547- fn test_sort_pushed_below_limit_with_skip_keeps_skip_rows ( ) -> Result < ( ) > {
1542+ fn test_keep_sort_above_unordered_limit_with_skip ( ) -> Result < ( ) > {
15481543 let source = Arc :: new ( MockMultiPartitionExec :: new ( 4 ) ) ;
15491544 let coalesce = Arc :: new ( CoalescePartitionsExec :: new ( source) ) ;
15501545 let limit = Arc :: new ( GlobalLimitExec :: new ( coalesce, 5 , Some ( 10 ) ) ) ;
@@ -1553,10 +1548,36 @@ fn test_sort_pushed_below_limit_with_skip_keeps_skip_rows() -> Result<()> {
15531548
15541549 let optimized = optimize_and_sanity_check ( sort) ?;
15551550 assert_snapshot ! ( plan_string( & optimized) , @r"
1556- GlobalLimitExec: skip=5, fetch=10
1557- SortExec: TopK(fetch=15), expr=[a@0 DESC], preserve_partitioning=[false]
1551+ SortExec: expr=[a@0 DESC], preserve_partitioning=[false]
1552+ GlobalLimitExec: skip=5, fetch=10
15581553 CoalescePartitionsExec
15591554 MockMultiPartitionExec
15601555 " ) ;
15611556 Ok ( ( ) )
15621557}
1558+
1559+ /// Refining an existing ordering can still push a TopK below the limit. It must
1560+ /// retain `skip + fetch` rows so the offset does not reduce the result count.
1561+ #[ test]
1562+ fn test_sort_pushed_below_ordered_limit_with_skip_keeps_skip_rows ( ) -> Result < ( ) > {
1563+ let source = Arc :: new ( MockMultiPartitionExec :: new ( 4 ) ) ;
1564+ let mut ordering = sort_expr_on ( "a" , 0 , false , false ) ;
1565+ let ordered_input = Arc :: new ( MockReqExec :: new (
1566+ source,
1567+ Distribution :: SinglePartition ,
1568+ Some ( ordering. clone ( ) ) ,
1569+ ) ) ;
1570+ let limit = Arc :: new ( GlobalLimitExec :: new ( ordered_input, 5 , Some ( 10 ) ) ) ;
1571+ ordering. extend ( sort_expr_on ( "b" , 1 , false , false ) ) ;
1572+ let sort: Arc < dyn ExecutionPlan > = Arc :: new ( SortExec :: new ( ordering, limit) ) ;
1573+
1574+ let optimized = optimize_and_sanity_check ( sort) ?;
1575+ assert_snapshot ! ( plan_string( & optimized) , @r"
1576+ GlobalLimitExec: skip=5, fetch=10
1577+ SortExec: TopK(fetch=15), expr=[a@0 ASC NULLS LAST, b@1 ASC NULLS LAST], preserve_partitioning=[false], sort_prefix=[a@0 ASC NULLS LAST]
1578+ MockReqExec
1579+ SortPreservingMergeExec: [a@0 ASC NULLS LAST]
1580+ MockMultiPartitionExec
1581+ " ) ;
1582+ Ok ( ( ) )
1583+ }
0 commit comments