@@ -488,8 +488,12 @@ fn build_ranking_topn_plan(
488488 Ok ( filter)
489489}
490490
491- /// Build a RANK plan with NO ORDER BY: every row ties at rank 1 — degenerate.
492- fn build_rank_no_order_by_plan ( limit_value : i64 ) -> Result < Arc < dyn ExecutionPlan > > {
491+ /// Build a RANK / DENSE_RANK plan with NO ORDER BY: every row ties at rank 1 — degenerate.
492+ fn build_no_order_by_plan (
493+ udwf_factory : fn ( ) -> Arc < datafusion_expr:: WindowUDF > ,
494+ udwf_name : & str ,
495+ limit_value : i64 ,
496+ ) -> Result < Arc < dyn ExecutionPlan > > {
493497 let s = schema ( ) ;
494498 let input: Arc < dyn ExecutionPlan > = Arc :: new ( PlaceholderRowExec :: new ( Arc :: clone ( & s) ) ) ;
495499
@@ -503,7 +507,7 @@ fn build_rank_no_order_by_plan(limit_value: i64) -> Result<Arc<dyn ExecutionPlan
503507 let partition_by = vec ! [ col( "pk" , & s) ?] ;
504508
505509 let window_expr = Arc :: new ( StandardWindowExpr :: new (
506- create_udwf_window_expr ( & rank_udwf ( ) , & [ ] , & s, "rank" . to_string ( ) , false ) ?,
510+ create_udwf_window_expr ( & udwf_factory ( ) , & [ ] , & s, udwf_name . to_string ( ) , false ) ?,
507511 & partition_by,
508512 & [ ] , // empty ORDER BY
509513 Arc :: new ( WindowFrame :: new_bounds (
@@ -520,7 +524,7 @@ fn build_rank_no_order_by_plan(limit_value: i64) -> Result<Arc<dyn ExecutionPlan
520524 true ,
521525 ) ?) ;
522526
523- let rk_col = Arc :: new ( Column :: new ( "rank" , 2 ) ) ;
527+ let rk_col = Arc :: new ( Column :: new ( udwf_name , 2 ) ) ;
524528 let limit_lit = lit ( ScalarValue :: UInt64 ( Some ( limit_value as u64 ) ) ) ;
525529 let predicate = Arc :: new ( BinaryExpr :: new ( rk_col, Operator :: LtEq , limit_lit) ) ;
526530 let filter: Arc < dyn ExecutionPlan > =
@@ -582,7 +586,7 @@ fn rank_no_order_by_no_change() -> Result<()> {
582586 // Without ORDER BY, every row ties at rank 1 — the optimization is
583587 // degenerate (entire input would be retained, ties storage unbounded).
584588 // The rule must skip.
585- let plan = build_rank_no_order_by_plan ( 3 ) ?;
589+ let plan = build_no_order_by_plan ( rank_udwf , "rank" , 3 ) ?;
586590 let before = plan_str ( plan. as_ref ( ) ) ;
587591 let optimized = optimize ( plan) ?;
588592 let after = plan_str ( optimized. as_ref ( ) ) ;
@@ -593,17 +597,98 @@ fn rank_no_order_by_no_change() -> Result<()> {
593597 Ok ( ( ) )
594598}
595599
600+ // ----------------------------------------------------------------------
601+ // DENSE_RANK rule tests
602+ // ----------------------------------------------------------------------
603+
596604#[ test]
597- fn dense_rank_no_change ( ) -> Result < ( ) > {
598- // DENSE_RANK is not yet supported by the rule. The plan must pass
599- // through unchanged.
605+ fn basic_dense_rank_dr_lteq_3 ( ) -> Result < ( ) > {
600606 let plan = build_ranking_topn_plan ( dense_rank_udwf, "dense_rank" , 3 , Operator :: LtEq ) ?;
607+ let optimized = optimize ( plan) ?;
608+ assert_snapshot ! ( plan_str( optimized. as_ref( ) ) , @r#"
609+ BoundedWindowAggExec: wdw=[dense_rank: Field { "dense_rank": UInt64 }, frame: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW], mode=[Sorted]
610+ PartitionedTopKExec: fn=dense_rank, fetch=3, partition=[pk@0], order=[val@1 ASC]
611+ PlaceholderRowExec
612+ "# ) ;
613+ Ok ( ( ) )
614+ }
615+
616+ #[ test]
617+ fn dense_rank_dr_lt_4_becomes_fetch_3 ( ) -> Result < ( ) > {
618+ let plan = build_ranking_topn_plan ( dense_rank_udwf, "dense_rank" , 4 , Operator :: Lt ) ?;
619+ let optimized = optimize ( plan) ?;
620+ assert_snapshot ! ( plan_str( optimized. as_ref( ) ) , @r#"
621+ BoundedWindowAggExec: wdw=[dense_rank: Field { "dense_rank": UInt64 }, frame: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW], mode=[Sorted]
622+ PartitionedTopKExec: fn=dense_rank, fetch=3, partition=[pk@0], order=[val@1 ASC]
623+ PlaceholderRowExec
624+ "# ) ;
625+ Ok ( ( ) )
626+ }
627+
628+ #[ test]
629+ fn dense_rank_flipped_3_gteq_dr ( ) -> Result < ( ) > {
630+ let plan = build_ranking_topn_plan ( dense_rank_udwf, "dense_rank" , 3 , Operator :: GtEq ) ?;
631+ let optimized = optimize ( plan) ?;
632+ assert_snapshot ! ( plan_str( optimized. as_ref( ) ) , @r#"
633+ BoundedWindowAggExec: wdw=[dense_rank: Field { "dense_rank": UInt64 }, frame: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW], mode=[Sorted]
634+ PartitionedTopKExec: fn=dense_rank, fetch=3, partition=[pk@0], order=[val@1 ASC]
635+ PlaceholderRowExec
636+ "# ) ;
637+ Ok ( ( ) )
638+ }
639+
640+ #[ test]
641+ fn dense_rank_flipped_4_gt_dr_becomes_fetch_3 ( ) -> Result < ( ) > {
642+ let plan = build_ranking_topn_plan ( dense_rank_udwf, "dense_rank" , 4 , Operator :: Gt ) ?;
643+ let optimized = optimize ( plan) ?;
644+ assert_snapshot ! ( plan_str( optimized. as_ref( ) ) , @r#"
645+ BoundedWindowAggExec: wdw=[dense_rank: Field { "dense_rank": UInt64 }, frame: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW], mode=[Sorted]
646+ PartitionedTopKExec: fn=dense_rank, fetch=3, partition=[pk@0], order=[val@1 ASC]
647+ PlaceholderRowExec
648+ "# ) ;
649+ Ok ( ( ) )
650+ }
651+
652+ #[ test]
653+ fn dense_rank_no_order_by_no_change ( ) -> Result < ( ) > {
654+ // Without ORDER BY, every row ties at dense_rank 1 — the optimization
655+ // is degenerate (entire input would be retained). The rule must skip.
656+ let plan = build_no_order_by_plan ( dense_rank_udwf, "dense_rank" , 3 ) ?;
601657 let before = plan_str ( plan. as_ref ( ) ) ;
602658 let optimized = optimize ( plan) ?;
603659 let after = plan_str ( optimized. as_ref ( ) ) ;
604660 assert_eq ! (
605661 before, after,
606- "DENSE_RANK is unsupported and must not be rewritten"
662+ "DENSE_RANK with empty ORDER BY must not be rewritten"
607663 ) ;
608664 Ok ( ( ) )
609665}
666+
667+ // ----------------------------------------------------------------------
668+ // Shared guard: `fn < 1` keeps nothing
669+ // ----------------------------------------------------------------------
670+
671+ #[ test]
672+ fn predicate_lt_1_no_change ( ) -> Result < ( ) > {
673+ // `fn < 1` (and the flipped `1 > fn`) yields limit_n = 0. Since
674+ // ROW_NUMBER / RANK / DENSE_RANK are always >= 1, the predicate keeps
675+ // nothing and the rule must skip — a fetch=0 PartitionedTopK* would
676+ // otherwise panic on its `k > 0` assertion at execution time.
677+ type UdwfFactory = fn ( ) -> Arc < datafusion_expr:: WindowUDF > ;
678+ let cases: [ ( UdwfFactory , & str ) ; 3 ] = [
679+ ( row_number_udwf, "row_number" ) ,
680+ ( rank_udwf, "rank" ) ,
681+ ( dense_rank_udwf, "dense_rank" ) ,
682+ ] ;
683+ for ( factory, name) in cases {
684+ let plan = build_ranking_topn_plan ( factory, name, 1 , Operator :: Lt ) ?;
685+ let before = plan_str ( plan. as_ref ( ) ) ;
686+ let optimized = optimize ( plan) ?;
687+ let after = plan_str ( optimized. as_ref ( ) ) ;
688+ assert_eq ! (
689+ before, after,
690+ "`{name} < 1` (limit 0) must not be rewritten"
691+ ) ;
692+ }
693+ Ok ( ( ) )
694+ }
0 commit comments