@ -42,6 +42,17 @@
# include "rewrite/rewriteManip.h"
typedef struct nullingrel_info
{
/*
* For each leaf RTE , nullingrels [ rti ] is the set of relids of outer joins
* that potentially null that RTE .
*/
Relids * nullingrels ;
/* Length of range table (maximum index in nullingrels[]) */
int rtlength ; /* used only for assertion checks */
} nullingrel_info ;
typedef struct pullup_replace_vars_context
{
PlannerInfo * root ;
@ -49,6 +60,8 @@ typedef struct pullup_replace_vars_context
RangeTblEntry * target_rte ; /* RTE of subquery */
Relids relids ; /* relids within subquery, as numbered after
* pullup ( set only if target_rte - > lateral ) */
nullingrel_info * nullinfo ; /* per-RTE nullingrel info (set only if
* target_rte - > lateral ) */
bool * outer_hasSubLinks ; /* -> outer query's hasSubLinks */
int varno ; /* varno of subquery */
bool wrap_non_vars ; /* do we need all non-Var outputs to be PHVs? */
@ -142,6 +155,9 @@ static void substitute_phv_relids(Node *node,
static void fix_append_rel_relids ( PlannerInfo * root , int varno ,
Relids subrelids ) ;
static Node * find_jointree_node_for_rel ( Node * jtnode , int relid ) ;
static nullingrel_info * get_nullingrels ( Query * parse ) ;
static void get_nullingrels_recurse ( Node * jtnode , Relids upper_nullingrels ,
nullingrel_info * info ) ;
/*
@ -1115,10 +1131,16 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
rvcontext . targetlist = subquery - > targetList ;
rvcontext . target_rte = rte ;
if ( rte - > lateral )
{
rvcontext . relids = get_relids_in_jointree ( ( Node * ) subquery - > jointree ,
true , true ) ;
else /* won't need relids */
rvcontext . nullinfo = get_nullingrels ( parse ) ;
}
else /* won't need these values */
{
rvcontext . relids = NULL ;
rvcontext . nullinfo = NULL ;
}
rvcontext . outer_hasSubLinks = & parse - > hasSubLinks ;
rvcontext . varno = varno ;
/* this flag will be set below, if needed */
@ -1580,6 +1602,9 @@ is_simple_subquery(PlannerInfo *root, Query *subquery, RangeTblEntry *rte,
* such refs to be wrapped in PlaceHolderVars , even when they ' re below
* the nearest outer join ? But it ' s a pretty hokey usage , so not
* clear this is worth sweating over . )
*
* If you change this , see also the comments about lateral references
* in pullup_replace_vars_callback ( ) .
*/
if ( lowest_outer_join ! = NULL )
{
@ -1664,7 +1689,8 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
rvcontext . root = root ;
rvcontext . targetlist = tlist ;
rvcontext . target_rte = rte ;
rvcontext . relids = NULL ;
rvcontext . relids = NULL ; /* can't be any lateral references here */
rvcontext . nullinfo = NULL ;
rvcontext . outer_hasSubLinks = & parse - > hasSubLinks ;
rvcontext . varno = varno ;
rvcontext . wrap_non_vars = false ;
@ -1826,9 +1852,10 @@ pull_up_constant_function(PlannerInfo *root, Node *jtnode,
/*
* Since this function was reduced to a Const , it doesn ' t contain any
* lateral references , even if it ' s marked as LATERAL . This means we
* don ' t need to fill relids .
* don ' t need to fill relids or nullinfo .
*/
rvcontext . relids = NULL ;
rvcontext . nullinfo = NULL ;
rvcontext . outer_hasSubLinks = & parse - > hasSubLinks ;
rvcontext . varno = ( ( RangeTblRef * ) jtnode ) - > rtindex ;
@ -2543,9 +2570,52 @@ pullup_replace_vars_callback(Var *var,
{
/*
* There should be Vars / PHVs within the expression that we can
* modify . Per above discussion , modify only Vars / PHVs of the
* subquery , not lateral references .
* modify . Vars / PHVs of the subquery should have the full
* var - > varnullingrels added to them , but if there are lateral
* references within the expression , those must be marked with
* only the nullingrels that potentially apply to them . ( This
* corresponds to the fact that the expression will now be
* evaluated at the join level of the Var that we are replacing :
* the lateral references may have bubbled up through fewer outer
* joins than the subquery ' s Vars have . Per the discussion above ,
* we ' ll still get the right answers . ) That relid set could be
* different for different lateral relations , so we have to do
* this work for each one .
*
* ( Currently , the restrictions in is_simple_subquery ( ) mean that
* at most we have to remove the lowest outer join ' s relid from
* the nullingrels of a lateral reference . However , we might
* relax those restrictions someday , so let ' s do this right . )
*/
if ( rcon - > target_rte - > lateral )
{
nullingrel_info * nullinfo = rcon - > nullinfo ;
Relids lvarnos ;
int lvarno ;
/*
* Identify lateral varnos used within newnode . We must do
* this before injecting var - > varnullingrels into the tree .
*/
lvarnos = pull_varnos ( rcon - > root , newnode ) ;
lvarnos = bms_del_members ( lvarnos , rcon - > relids ) ;
/* For each one, add relevant nullingrels if any */
lvarno = - 1 ;
while ( ( lvarno = bms_next_member ( lvarnos , lvarno ) ) > = 0 )
{
Relids lnullingrels ;
Assert ( lvarno > 0 & & lvarno < = nullinfo - > rtlength ) ;
lnullingrels = bms_intersect ( var - > varnullingrels ,
nullinfo - > nullingrels [ lvarno ] ) ;
if ( ! bms_is_empty ( lnullingrels ) )
newnode = add_nulling_relids ( newnode ,
bms_make_singleton ( lvarno ) ,
lnullingrels ) ;
}
}
/* Finally, deal with Vars/PHVs of the subquery itself */
newnode = add_nulling_relids ( newnode ,
rcon - > relids ,
var - > varnullingrels ) ;
@ -3983,3 +4053,94 @@ find_jointree_node_for_rel(Node *jtnode, int relid)
( int ) nodeTag ( jtnode ) ) ;
return NULL ;
}
/*
* get_nullingrels : collect info about which outer joins null which relations
*
* The result struct contains , for each leaf relation used in the query ,
* the set of relids of outer joins that potentially null that rel .
*/
static nullingrel_info *
get_nullingrels ( Query * parse )
{
nullingrel_info * result = palloc_object ( nullingrel_info ) ;
result - > rtlength = list_length ( parse - > rtable ) ;
result - > nullingrels = palloc0_array ( Relids , result - > rtlength + 1 ) ;
get_nullingrels_recurse ( ( Node * ) parse - > jointree , NULL , result ) ;
return result ;
}
/*
* Recursive guts of get_nullingrels ( ) .
*
* Note : at any recursion level , the passed - down upper_nullingrels must be
* treated as a constant , but it can be stored directly into * info
* if we ' re at leaf level . Upper recursion levels do not free their mutated
* copies of the nullingrels , because those are probably referenced by
* at least one leaf rel .
*/
static void
get_nullingrels_recurse ( Node * jtnode , Relids upper_nullingrels ,
nullingrel_info * info )
{
if ( jtnode = = NULL )
return ;
if ( IsA ( jtnode , RangeTblRef ) )
{
int varno = ( ( RangeTblRef * ) jtnode ) - > rtindex ;
Assert ( varno > 0 & & varno < = info - > rtlength ) ;
info - > nullingrels [ varno ] = upper_nullingrels ;
}
else if ( IsA ( jtnode , FromExpr ) )
{
FromExpr * f = ( FromExpr * ) jtnode ;
ListCell * l ;
foreach ( l , f - > fromlist )
{
get_nullingrels_recurse ( lfirst ( l ) , upper_nullingrels , info ) ;
}
}
else if ( IsA ( jtnode , JoinExpr ) )
{
JoinExpr * j = ( JoinExpr * ) jtnode ;
Relids local_nullingrels ;
switch ( j - > jointype )
{
case JOIN_INNER :
get_nullingrels_recurse ( j - > larg , upper_nullingrels , info ) ;
get_nullingrels_recurse ( j - > rarg , upper_nullingrels , info ) ;
break ;
case JOIN_LEFT :
case JOIN_SEMI :
case JOIN_ANTI :
local_nullingrels = bms_add_member ( bms_copy ( upper_nullingrels ) ,
j - > rtindex ) ;
get_nullingrels_recurse ( j - > larg , upper_nullingrels , info ) ;
get_nullingrels_recurse ( j - > rarg , local_nullingrels , info ) ;
break ;
case JOIN_FULL :
local_nullingrels = bms_add_member ( bms_copy ( upper_nullingrels ) ,
j - > rtindex ) ;
get_nullingrels_recurse ( j - > larg , local_nullingrels , info ) ;
get_nullingrels_recurse ( j - > rarg , local_nullingrels , info ) ;
break ;
case JOIN_RIGHT :
local_nullingrels = bms_add_member ( bms_copy ( upper_nullingrels ) ,
j - > rtindex ) ;
get_nullingrels_recurse ( j - > larg , local_nullingrels , info ) ;
get_nullingrels_recurse ( j - > rarg , upper_nullingrels , info ) ;
break ;
default :
elog ( ERROR , " unrecognized join type: %d " ,
( int ) j - > jointype ) ;
break ;
}
}
else
elog ( ERROR , " unrecognized node type: %d " ,
( int ) nodeTag ( jtnode ) ) ;
}