@ -332,9 +332,6 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
bool reversed ;
bool reversed ;
bool unique_ified ;
bool unique_ified ;
bool must_be_leftjoin ;
bool must_be_leftjoin ;
bool lateral_fwd ;
bool lateral_rev ;
Relids join_lateral_rels ;
ListCell * l ;
ListCell * l ;
/*
/*
@ -527,75 +524,126 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
/*
/*
* We also have to check for constraints imposed by LATERAL references .
* We also have to check for constraints imposed by LATERAL references .
* The proposed rels could each contain lateral references to the other ,
* in which case the join is impossible . If there are lateral references
* in just one direction , then the join has to be done with a nestloop
* with the lateral referencer on the inside . If the join matches an SJ
* that cannot be implemented by such a nestloop , the join is impossible .
*/
*/
lateral_fwd = lateral_rev = false ;
if ( root - > hasLateralRTEs )
foreach ( l , root - > lateral_info_list )
{
{
LateralJoinInfo * ljinfo = ( LateralJoinInfo * ) lfirst ( l ) ;
bool lateral_fwd ;
bool lateral_rev ;
Relids join_lateral_rels ;
if ( bms_is_subset ( ljinfo - > lateral_rhs , rel2 - > relids ) & &
/*
bms_overlap ( ljinfo - > lateral_lhs , rel1 - > relids ) )
* The proposed rels could each contain lateral references to the
* other , in which case the join is impossible . If there are lateral
* references in just one direction , then the join has to be done with
* a nestloop with the lateral referencer on the inside . If the join
* matches an SJ that cannot be implemented by such a nestloop , the
* join is impossible .
*
* Also , if the lateral reference is only indirect , we should reject
* the join ; whatever rel ( s ) the reference chain goes through must be
* joined to first .
*
* Another case that might keep us from building a valid plan is the
* implementation restriction described by have_dangerous_phv ( ) .
*/
lateral_fwd = bms_overlap ( rel1 - > relids , rel2 - > lateral_relids ) ;
lateral_rev = bms_overlap ( rel2 - > relids , rel1 - > lateral_relids ) ;
if ( lateral_fwd & & lateral_rev )
return false ; /* have lateral refs in both directions */
if ( lateral_fwd )
{
{
/* has to be implemented as nestloop with rel1 on left */
/* has to be implemented as nestloop with rel1 on left */
if ( lateral_rev )
return false ; /* have lateral refs in both directions */
lateral_fwd = true ;
if ( ! bms_is_subset ( ljinfo - > lateral_lhs , rel1 - > relids ) )
return false ; /* rel1 can't compute the required parameter */
if ( match_sjinfo & &
if ( match_sjinfo & &
( reversed | |
( reversed | |
unique_ified | |
unique_ified | |
match_sjinfo - > jointype = = JOIN_FULL ) )
match_sjinfo - > jointype = = JOIN_FULL ) )
return false ; /* not implementable as nestloop */
return false ; /* not implementable as nestloop */
/* check there is a direct reference from rel2 to rel1 */
foreach ( l , root - > lateral_info_list )
{
LateralJoinInfo * ljinfo = ( LateralJoinInfo * ) lfirst ( l ) ;
if ( bms_is_subset ( ljinfo - > lateral_rhs , rel2 - > relids ) & &
bms_is_subset ( ljinfo - > lateral_lhs , rel1 - > relids ) )
break ;
}
if ( l = = NULL )
return false ; /* only indirect refs, so reject */
/* check we won't have a dangerous PHV */
if ( have_dangerous_phv ( root , rel1 - > relids , rel2 - > lateral_relids ) )
return false ; /* might be unable to handle required PHV */
}
}
if ( bms_is_subset ( ljinfo - > lateral_rhs , rel1 - > relids ) & &
else if ( lateral_rev )
bms_overlap ( ljinfo - > lateral_lhs , rel2 - > relids ) )
{
{
/* has to be implemented as nestloop with rel2 on left */
/* has to be implemented as nestloop with rel2 on left */
if ( lateral_fwd )
return false ; /* have lateral refs in both directions */
lateral_rev = true ;
if ( ! bms_is_subset ( ljinfo - > lateral_lhs , rel2 - > relids ) )
return false ; /* rel2 can't compute the required parameter */
if ( match_sjinfo & &
if ( match_sjinfo & &
( ! reversed | |
( ! reversed | |
unique_ified | |
unique_ified | |
match_sjinfo - > jointype = = JOIN_FULL ) )
match_sjinfo - > jointype = = JOIN_FULL ) )
return false ; /* not implementable as nestloop */
return false ; /* not implementable as nestloop */
/* check there is a direct reference from rel1 to rel2 */
foreach ( l , root - > lateral_info_list )
{
LateralJoinInfo * ljinfo = ( LateralJoinInfo * ) lfirst ( l ) ;
if ( bms_is_subset ( ljinfo - > lateral_rhs , rel1 - > relids ) & &
bms_is_subset ( ljinfo - > lateral_lhs , rel2 - > relids ) )
break ;
}
if ( l = = NULL )
return false ; /* only indirect refs, so reject */
/* check we won't have a dangerous PHV */
if ( have_dangerous_phv ( root , rel2 - > relids , rel1 - > lateral_relids ) )
return false ; /* might be unable to handle required PHV */
}
}
}
/*
/*
* LATERAL references could also cause problems later on if we accept this
* LATERAL references could also cause problems later on if we accept
* join : if the join ' s minimum parameterization includes any rels that
* this join : if the join ' s minimum parameterization includes any rels
* would have to be on the inside of an outer join with this join rel ,
* that would have to be on the inside of an outer join with this join
* then it ' s never going to be possible to build the complete query using
* rel , then it ' s never going to be possible to build the complete
* this join . We should reject this join not only because it ' ll save
* query using this join . We should reject this join not only because
* work , but because if we don ' t , the clauseless - join heuristics might
* it ' ll save work , but because if we don ' t , the clauseless - join
* think that legality of this join means that some other join rel need
* heuristics might think that legality of this join means that some
* not be formed , and that could lead to failure to find any plan at all .
* other join rel need not be formed , and that could lead to failure
* It seems best not to merge this check into the main loop above , because
* to find any plan at all . We have to consider not only rels that
* it is concerned with SJs that are not otherwise relevant to this join .
* are directly on the inner side of an OJ with the joinrel , but also
*/
* ones that are indirectly so , so search to find all such rels .
join_lateral_rels = min_join_parameterization ( root , joinrelids ) ;
*/
if ( join_lateral_rels )
join_lateral_rels = min_join_parameterization ( root , joinrelids ,
{
rel1 , rel2 ) ;
foreach ( l , root - > join_info_list )
if ( join_lateral_rels )
{
{
SpecialJoinInfo * sjinfo = ( SpecialJoinInfo * ) lfirst ( l ) ;
Relids join_plus_rhs = bms_copy ( joinrelids ) ;
bool more ;
if ( bms_overlap ( sjinfo - > min_righthand , join_lateral_rels ) & &
bms_overlap ( sjinfo - > min_lefthand , joinrelids ) )
do
return false ; /* will not be able to join to min_righthand */
{
if ( sjinfo - > jointype = = JOIN_FULL & &
more = false ;
bms_overlap ( sjinfo - > min_lefthand , join_lateral_rels ) & &
foreach ( l , root - > join_info_list )
bms_overlap ( sjinfo - > min_righthand , joinrelids ) )
{
return false ; /* will not be able to join to min_lefthand */
SpecialJoinInfo * sjinfo = ( SpecialJoinInfo * ) lfirst ( l ) ;
if ( bms_overlap ( sjinfo - > min_lefthand , join_plus_rhs ) & &
! bms_is_subset ( sjinfo - > min_righthand , join_plus_rhs ) )
{
join_plus_rhs = bms_add_members ( join_plus_rhs ,
sjinfo - > min_righthand ) ;
more = true ;
}
/* full joins constrain both sides symmetrically */
if ( sjinfo - > jointype = = JOIN_FULL & &
bms_overlap ( sjinfo - > min_righthand , join_plus_rhs ) & &
! bms_is_subset ( sjinfo - > min_lefthand , join_plus_rhs ) )
{
join_plus_rhs = bms_add_members ( join_plus_rhs ,
sjinfo - > min_lefthand ) ;
more = true ;
}
}
} while ( more ) ;
if ( bms_overlap ( join_plus_rhs , join_lateral_rels ) )
return false ; /* will not be able to join to some RHS rel */
}
}
}
}
@ -852,8 +900,9 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
* could be merged with that function , but it seems clearer to separate the
* could be merged with that function , but it seems clearer to separate the
* two concerns . We need this test because there are degenerate cases where
* two concerns . We need this test because there are degenerate cases where
* a clauseless join must be performed to satisfy join - order restrictions .
* a clauseless join must be performed to satisfy join - order restrictions .
* Also , if one rel has a lateral reference to the other , we should consider
* Also , if one rel has a lateral reference to the other , or both are needed
* joining them even if the join would be clauseless .
* to compute some PHV , we should consider joining them even if the join would
* be clauseless .
*
*
* Note : this is only a problem if one side of a degenerate outer join
* Note : this is only a problem if one side of a degenerate outer join
* contains multiple rels , or a clauseless join is required within an
* contains multiple rels , or a clauseless join is required within an
@ -870,8 +919,8 @@ have_join_order_restriction(PlannerInfo *root,
ListCell * l ;
ListCell * l ;
/*
/*
* If either side has a lateral reference to the other , attempt the join
* If either side has a direct lateral reference to the other , attempt the
* regardless of outer - join considerations .
* join regardless of outer - join considerations .
*/
*/
foreach ( l , root - > lateral_info_list )
foreach ( l , root - > lateral_info_list )
{
{
@ -885,6 +934,22 @@ have_join_order_restriction(PlannerInfo *root,
return true ;
return true ;
}
}
/*
* Likewise , if both rels are needed to compute some PlaceHolderVar ,
* attempt the join regardless of outer - join considerations . ( This is not
* very desirable , because a PHV with a large eval_at set will cause a lot
* of probably - useless joins to be considered , but failing to do this can
* cause us to fail to construct a plan at all . )
*/
foreach ( l , root - > placeholder_list )
{
PlaceHolderInfo * phinfo = ( PlaceHolderInfo * ) lfirst ( l ) ;
if ( bms_is_subset ( rel1 - > relids , phinfo - > ph_eval_at ) & &
bms_is_subset ( rel2 - > relids , phinfo - > ph_eval_at ) )
return true ;
}
/*
/*
* It ' s possible that the rels correspond to the left and right sides of a
* It ' s possible that the rels correspond to the left and right sides of a
* degenerate outer join , that is , one with no joinclause mentioning the
* degenerate outer join , that is , one with no joinclause mentioning the
@ -960,7 +1025,7 @@ have_join_order_restriction(PlannerInfo *root,
* has_join_restriction
* has_join_restriction
* Detect whether the specified relation has join - order restrictions ,
* Detect whether the specified relation has join - order restrictions ,
* due to being inside an outer join or an IN ( sub - SELECT ) ,
* due to being inside an outer join or an IN ( sub - SELECT ) ,
* or participating in any LATERAL references .
* or participating in any LATERAL references or multi - rel PHVs .
*
*
* Essentially , this tests whether have_join_order_restriction ( ) could
* Essentially , this tests whether have_join_order_restriction ( ) could
* succeed with this rel and some other one . It ' s OK if we sometimes
* succeed with this rel and some other one . It ' s OK if we sometimes
@ -972,12 +1037,15 @@ has_join_restriction(PlannerInfo *root, RelOptInfo *rel)
{
{
ListCell * l ;
ListCell * l ;
foreach ( l , root - > lateral_info_list )
if ( rel - > lateral_relids ! = NULL | | rel - > lateral_referencers ! = NULL )
return true ;
foreach ( l , root - > placeholder_list )
{
{
LateralJoinInfo * ljinfo = ( LateralJoinInfo * ) lfirst ( l ) ;
PlaceHolderInfo * phinfo = ( PlaceHolder Info * ) lfirst ( l ) ;
if ( bms_is_subset ( ljinfo - > lateral_rhs , rel - > relids ) | |
if ( bms_is_subset ( re l- > relids , phinfo - > ph_eval_at ) & &
bms_overlap ( ljinfo - > lateral_lhs , rel - > relids ) )
! bms_equal ( rel - > relids , phinfo - > ph_eval_at ) )
return true ;
return true ;
}
}
@ -1058,6 +1126,57 @@ has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel)
}
}
/*
* There ' s a pitfall for creating parameterized nestloops : suppose the inner
* rel ( call it A ) has a parameter that is a PlaceHolderVar , and that PHV ' s
* minimum eval_at set includes the outer rel ( B ) and some third rel ( C ) .
* We might think we could create a B / A nestloop join that ' s parameterized by
* C . But we would end up with a plan in which the PHV ' s expression has to be
* evaluated as a nestloop parameter at the B / A join ; and the executor is only
* set up to handle simple Vars as NestLoopParams . Rather than add complexity
* and overhead to the executor for such corner cases , it seems better to
* forbid the join . ( Note that we can still make use of A ' s parameterized
* path with pre - joined B + C as the outer rel . have_join_order_restriction ( )
* ensures that we will consider making such a join even if there are not
* other reasons to do so . )
*
* So we check whether any PHVs used in the query could pose such a hazard .
* We don ' t have any simple way of checking whether a risky PHV would actually
* be used in the inner plan , and the case is so unusual that it doesn ' t seem
* worth working very hard on it .
*
* This needs to be checked in two places . If the inner rel ' s minimum
* parameterization would trigger the restriction , then join_is_legal ( ) should
* reject the join altogether , because there will be no workable paths for it .
* But joinpath . c has to check again for every proposed nestloop path , because
* the inner path might have more than the minimum parameterization , causing
* some PHV to be dangerous for it that otherwise wouldn ' t be .
*/
bool
have_dangerous_phv ( PlannerInfo * root ,
Relids outer_relids , Relids inner_params )
{
ListCell * lc ;
foreach ( lc , root - > placeholder_list )
{
PlaceHolderInfo * phinfo = ( PlaceHolderInfo * ) lfirst ( lc ) ;
if ( ! bms_is_subset ( phinfo - > ph_eval_at , inner_params ) )
continue ; /* ignore, could not be a nestloop param */
if ( ! bms_overlap ( phinfo - > ph_eval_at , outer_relids ) )
continue ; /* ignore, not relevant to this join */
if ( bms_is_subset ( phinfo - > ph_eval_at , outer_relids ) )
continue ; /* safe, it can be eval'd within outerrel */
/* Otherwise, it's potentially unsafe, so reject the join */
return true ;
}
/* OK to perform the join */
return false ;
}
/*
/*
* is_dummy_rel - - - has relation been proven empty ?
* is_dummy_rel - - - has relation been proven empty ?
*/
*/