@@ -356,19 +356,18 @@ fn merges_local_limit_with_global_limit() -> Result<()> {
356356}
357357
358358#[ test]
359- fn preserves_nested_global_limit_through_projection_inside_join ( ) -> Result < ( ) > {
360- // GlobalLimitExec nodes with skip > 0 that appear inside join subtrees
361- // (wrapped in projections) should be preserved .
359+ fn preserves_nested_global_limit ( ) -> Result < ( ) > {
360+ // If there are multiple limits in an execution plan, they all need to be
361+ // preserved in the optimized plan .
362362 //
363363 // Plan structure:
364364 // GlobalLimitExec: skip=1, fetch=1
365365 // NestedLoopJoinExec (Left)
366366 // EmptyExec (left side)
367- // ProjectionExec <-- wraps the inner limit, so outer join doesn't see it
368- // GlobalLimitExec: skip=2, fetch=1
369- // NestedLoopJoinExec (Right)
370- // EmptyExec (left side)
371- // EmptyExec (right side)
367+ // GlobalLimitExec: skip=2, fetch=1
368+ // NestedLoopJoinExec (Right)
369+ // EmptyExec (left side)
370+ // EmptyExec (right side)
372371 let schema = create_schema ( ) ;
373372
374373 // Build inner join: NestedLoopJoin(Empty, Empty)
@@ -379,13 +378,9 @@ fn preserves_nested_global_limit_through_projection_inside_join() -> Result<()>
379378 // Add inner limit: GlobalLimitExec: skip=2, fetch=1
380379 let inner_limit = global_limit_exec ( inner_join, 2 , Some ( 1 ) ) ;
381380
382- // Wrap in projection - this is key! The outer join won't see the inner
383- // GlobalLimitExec as a direct child, so `satisfied` will be set to true.
384- let projection = projection_exec ( Arc :: clone ( & schema) , inner_limit) ?;
385-
386- // Build outer join: NestedLoopJoin(Empty, projection)
381+ // Build outer join: NestedLoopJoin(Empty, GlobalLimit)
387382 let outer_left = empty_exec ( Arc :: clone ( & schema) ) ;
388- let outer_join = nested_loop_join_exec ( outer_left, projection , JoinType :: Left ) ?;
383+ let outer_join = nested_loop_join_exec ( outer_left, inner_limit , JoinType :: Left ) ?;
389384
390385 // Add outer limit: GlobalLimitExec: skip=1, fetch=1
391386 let outer_limit = global_limit_exec ( outer_join, 1 , Some ( 1 ) ) ;
@@ -395,30 +390,23 @@ fn preserves_nested_global_limit_through_projection_inside_join() -> Result<()>
395390 "GlobalLimitExec: skip=1, fetch=1" ,
396391 " NestedLoopJoinExec: join_type=Left" ,
397392 " EmptyExec" ,
398- " ProjectionExec: expr=[c1@0 as c1, c2@1 as c2, c3@2 as c3]" ,
399- " GlobalLimitExec: skip=2, fetch=1" ,
400- " NestedLoopJoinExec: join_type=Right" ,
401- " EmptyExec" ,
402- " EmptyExec" ,
393+ " GlobalLimitExec: skip=2, fetch=1" ,
394+ " NestedLoopJoinExec: join_type=Right" ,
395+ " EmptyExec" ,
396+ " EmptyExec" ,
403397 ] ;
404398 assert_eq ! ( initial, expected_initial) ;
405399
406400 let after_optimize =
407401 LimitPushdown :: new ( ) . optimize ( outer_limit, & ConfigOptions :: new ( ) ) ?;
408-
409- // Both GlobalLimitExec nodes should be preserved because:
410- // 1. Joins don't support limit pushdown
411- // 2. The limits have skip > 0, so they must remain as GlobalLimitExec
412- // Before the fix, the inner GlobalLimitExec was incorrectly removed.
413402 let expected = [
414403 "GlobalLimitExec: skip=1, fetch=1" ,
415404 " NestedLoopJoinExec: join_type=Left" ,
416405 " EmptyExec" ,
417- " ProjectionExec: expr=[c1@0 as c1, c2@1 as c2, c3@2 as c3]" ,
418- " GlobalLimitExec: skip=2, fetch=1" ,
419- " NestedLoopJoinExec: join_type=Right" ,
420- " EmptyExec" ,
421- " EmptyExec" ,
406+ " GlobalLimitExec: skip=2, fetch=1" ,
407+ " NestedLoopJoinExec: join_type=Right" ,
408+ " EmptyExec" ,
409+ " EmptyExec" ,
422410 ] ;
423411 assert_eq ! ( get_plan_string( & after_optimize) , expected) ;
424412
0 commit comments