@@ -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
@@ -2594,6 +2595,7 @@ impl<'a, 'parent, T: Transaction, A: AsRef<[(&'static str, DataValue)]>> Binder<
25942595 arena : & mut PlanArena ,
25952596 ) -> Result < LogicalPlan , DatabaseError > {
25962597 let Select {
2598+ optimizer_hint,
25972599 projection,
25982600 from,
25992601 selection,
@@ -2615,19 +2617,40 @@ impl<'a, 'parent, T: Transaction, A: AsRef<[(&'static str, DataValue)]>> Binder<
26152617 "QUALIFY is not supported" . to_string ( ) ,
26162618 ) ) ;
26172619 }
2618- Ok ( self
2619- . build_plan ( arena)
2620- . from_sql ( from) ?
2621- . select_list_from_sql ( projection) ?
2622- . where_sql ( selection. as_ref ( ) ) ?
2623- . aggregate_sql ( group_by, having. as_ref ( ) , orderby) ?
2624- . having ( ) ?
2625- . window ( ) ?
2626- . distinct_sql ( distinct. as_ref ( ) ) ?
2627- . order_by ( ) ?
2628- . project ( ) ?
2629- . select_into_sql ( into. as_ref ( ) ) ?
2630- . finish ( ) )
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" ) ;
2629+ if force_spill && !cfg ! ( feature = "spill" ) {
2630+ return Err ( DatabaseError :: UnsupportedStmt (
2631+ "FORCE_AGG_SPILL requires the `spill` feature" . to_string ( ) ,
2632+ ) ) ;
2633+ }
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
26312654 }
26322655
26332656 /// FIXME: temp values need to register BindContext.bind_table
@@ -3137,6 +3160,54 @@ mod tests {
31373160 Ok ( ( ) )
31383161 }
31393162
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+
31403211 #[ cfg( feature = "copy" ) ]
31413212 #[ test]
31423213 fn test_copy_file_format_options ( ) -> Result < ( ) , DatabaseError > {
0 commit comments