@@ -1364,6 +1364,7 @@ where
13641364 self . binder . bind_table_ref_sql ( from, self . arena ) ?,
13651365 JoinCondition :: None ,
13661366 JoinType :: Cross ,
1367+ self . binder . force_nested_loop ,
13671368 )
13681369 }
13691370 plan
@@ -1424,7 +1425,6 @@ where
14241425 group_by : & GroupByExpr ,
14251426 having : Option < & Expr > ,
14261427 orderby : Option < & [ OrderByExpr ] > ,
1427- force_spill : bool ,
14281428 ) -> Result < BindPlanAggregated < ' s , ' a , ' b , ' arena , T , A > , DatabaseError > {
14291429 let group_by = with_query_bind_step ! ( self . binder, QueryBindStep :: Agg , {
14301430 match group_by {
@@ -1453,22 +1453,16 @@ where
14531453 } )
14541454 } )
14551455 . transpose ( ) ?;
1456- self . aggregate (
1457- group_by,
1458- having,
1459- orderby,
1460- force_spill,
1461- |binder, arena, orderby| {
1462- let OrderByExpr { expr, options, .. } = orderby;
1463- with_query_bind_step ! ( binder, QueryBindStep :: Sort , {
1464- SortField :: new(
1465- binder. bind_expr( expr, arena) ?,
1466- options. asc. is_none_or( |asc| asc) ,
1467- options. nulls_first. unwrap_or( false ) ,
1468- )
1469- } )
1470- } ,
1471- )
1456+ self . aggregate ( group_by, having, orderby, |binder, arena, orderby| {
1457+ let OrderByExpr { expr, options, .. } = orderby;
1458+ with_query_bind_step ! ( binder, QueryBindStep :: Sort , {
1459+ SortField :: new(
1460+ binder. bind_expr( expr, arena) ?,
1461+ options. asc. is_none_or( |asc| asc) ,
1462+ options. nulls_first. unwrap_or( false ) ,
1463+ )
1464+ } )
1465+ } )
14721466 }
14731467}
14741468
@@ -1480,9 +1474,8 @@ where
14801474 pub ( crate ) fn distinct_sql (
14811475 self ,
14821476 distinct : Option < & Distinct > ,
1483- force_spill : bool ,
14841477 ) -> Result < BindPlanDistinct < ' s , ' a , ' b , ' arena , T , A > , DatabaseError > {
1485- self . distinct ( matches ! ( distinct, Some ( Distinct :: Distinct ) ) , force_spill )
1478+ self . distinct ( matches ! ( distinct, Some ( Distinct :: Distinct ) ) )
14861479 }
14871480}
14881481
@@ -2624,27 +2617,40 @@ impl<'a, 'parent, T: Transaction, A: AsRef<[(&'static str, DataValue)]>> Binder<
26242617 "QUALIFY is not supported" . to_string ( ) ,
26252618 ) ) ;
26262619 }
2627- let force_spill = optimizer_hint
2628- . as_ref ( )
2629- . is_some_and ( |hint| hint. text . trim ( ) . eq_ignore_ascii_case ( "FORCE_AGG_SPILL" ) ) ;
2620+ let has_hint = |expected : & str | {
2621+ optimizer_hint. as_ref ( ) . is_some_and ( |hint| {
2622+ hint. text
2623+ . split ( |char : char | char. is_ascii_whitespace ( ) || char == ',' )
2624+ . any ( |hint| hint. eq_ignore_ascii_case ( expected) )
2625+ } )
2626+ } ;
2627+ let force_spill = has_hint ( "FORCE_AGG_SPILL" ) ;
2628+ let force_nested_loop = has_hint ( "FORCE_NEST_LOOP_JOIN" ) ;
26302629 if force_spill && !cfg ! ( feature = "spill" ) {
26312630 return Err ( DatabaseError :: UnsupportedStmt (
26322631 "FORCE_AGG_SPILL requires the `spill` feature" . to_string ( ) ,
26332632 ) ) ;
26342633 }
2635- Ok ( self
2636- . build_plan ( arena)
2637- . from_sql ( from) ?
2638- . select_list_from_sql ( projection) ?
2639- . where_sql ( selection. as_ref ( ) ) ?
2640- . aggregate_sql ( group_by, having. as_ref ( ) , orderby, force_spill) ?
2641- . having ( ) ?
2642- . window ( ) ?
2643- . distinct_sql ( distinct. as_ref ( ) , force_spill) ?
2644- . order_by ( ) ?
2645- . project ( ) ?
2646- . select_into_sql ( into. as_ref ( ) ) ?
2647- . finish ( ) )
2634+ let previous_options = ( self . force_spill , self . force_nested_loop ) ;
2635+ self . force_spill = force_spill;
2636+ self . force_nested_loop = force_nested_loop;
2637+ let result = ( || {
2638+ Ok ( self
2639+ . build_plan ( arena)
2640+ . from_sql ( from) ?
2641+ . select_list_from_sql ( projection) ?
2642+ . where_sql ( selection. as_ref ( ) ) ?
2643+ . aggregate_sql ( group_by, having. as_ref ( ) , orderby) ?
2644+ . having ( ) ?
2645+ . window ( ) ?
2646+ . distinct_sql ( distinct. as_ref ( ) ) ?
2647+ . order_by ( ) ?
2648+ . project ( ) ?
2649+ . select_into_sql ( into. as_ref ( ) ) ?
2650+ . finish ( ) )
2651+ } ) ( ) ;
2652+ ( self . force_spill , self . force_nested_loop ) = previous_options;
2653+ result
26482654 }
26492655
26502656 /// FIXME: temp values need to register BindContext.bind_table
@@ -3154,6 +3160,54 @@ mod tests {
31543160 Ok ( ( ) )
31553161 }
31563162
3163+ #[ test]
3164+ fn force_nest_loop_join_marks_join_operator ( ) -> Result < ( ) , DatabaseError > {
3165+ let tables = build_t1_table ( ) ?;
3166+ let plan =
3167+ tables. plan ( "select /*+ FORCE_NEST_LOOP_JOIN */ c1, c3 from t1 join t2 on c1 = c3" ) ?;
3168+ let join = plan
3169+ . childrens
3170+ . iter ( )
3171+ . find_map ( |plan| match & plan. operator {
3172+ Operator :: Join ( operator) => Some ( operator) ,
3173+ _ => None ,
3174+ } )
3175+ . expect ( "query should contain a join" ) ;
3176+
3177+ assert ! ( join. force_nested_loop) ;
3178+ Ok ( ( ) )
3179+ }
3180+
3181+ #[ cfg( feature = "spill" ) ]
3182+ #[ test]
3183+ fn optimizer_hints_can_be_combined ( ) -> Result < ( ) , DatabaseError > {
3184+ let tables = build_t1_table ( ) ?;
3185+ let plan = tables. plan (
3186+ "select /*+ FORCE_AGG_SPILL, FORCE_NEST_LOOP_JOIN */ c1, count(c3) \
3187+ from t1 join t2 on c1 = c3 group by c1",
3188+ ) ?;
3189+ let aggregate_plan = plan
3190+ . childrens
3191+ . iter ( )
3192+ . find ( |plan| matches ! ( plan. operator, Operator :: Aggregate ( _) ) )
3193+ . expect ( "query should contain an aggregate" ) ;
3194+ let Operator :: Aggregate ( aggregate) = & aggregate_plan. operator else {
3195+ unreachable ! ( )
3196+ } ;
3197+ let join = aggregate_plan
3198+ . childrens
3199+ . iter ( )
3200+ . find_map ( |plan| match & plan. operator {
3201+ Operator :: Join ( operator) => Some ( operator) ,
3202+ _ => None ,
3203+ } )
3204+ . expect ( "aggregate input should contain a join" ) ;
3205+
3206+ assert ! ( aggregate. force_spill) ;
3207+ assert ! ( join. force_nested_loop) ;
3208+ Ok ( ( ) )
3209+ }
3210+
31573211 #[ cfg( feature = "copy" ) ]
31583212 #[ test]
31593213 fn test_copy_file_format_options ( ) -> Result < ( ) , DatabaseError > {
0 commit comments