@ -126,7 +126,6 @@ bool enable_nestloop = true;
bool enable_material = true ;
bool enable_mergejoin = true ;
bool enable_hashjoin = true ;
bool enable_fkey_estimates = true ;
typedef struct
{
@ -3888,361 +3887,6 @@ get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
return nrows ;
}
/*
* quals_match_foreign_key
* Determines if the foreign key is matched by joinquals .
*
* Checks that there are conditions on all columns of the foreign key , matching
* the operator used by the foreign key etc . If such complete match is found ,
* the function returns bitmap identifying the matching quals ( 0 - based ) .
*
* Otherwise ( no match at all or incomplete match ) , NULL is returned .
*
* XXX It seems possible in the future to do something useful when a
* partial match occurs between join and FK , but that is less common
* and that part isn ' t worked out yet .
*/
static Bitmapset *
quals_match_foreign_key ( PlannerInfo * root , ForeignKeyOptInfo * fkinfo ,
RelOptInfo * fkrel , RelOptInfo * foreignrel ,
List * joinquals )
{
int i ;
int nkeys = fkinfo - > nkeys ;
Bitmapset * qualmatches = NULL ;
Bitmapset * fkmatches = NULL ;
/*
* Loop over each column of the foreign key and build a bitmapset
* of each joinqual which matches . Note that we don ' t stop when we find
* the first match , as the expression could be duplicated in the
* joinquals , and we want to generate a bitmapset which has bits set for
* every matching join qual .
*/
for ( i = 0 ; i < nkeys ; i + + )
{
ListCell * lc ;
int quallstidx = - 1 ;
foreach ( lc , joinquals )
{
RestrictInfo * rinfo ;
OpExpr * clause ;
Var * leftvar ;
Var * rightvar ;
quallstidx + + ;
/*
* Technically we don ' t need to , but here we skip this qual if
* we ' ve matched it to part of the foreign key already . This
* should prove to be a useful optimization when the quals appear
* in the same order as the foreign key ' s keys . We need only bother
* doing this when the foreign key is made up of more than 1 set
* of columns , and we ' re not testing the first column .
*/
if ( i > 0 & & bms_is_member ( quallstidx , qualmatches ) )
continue ;
rinfo = ( RestrictInfo * ) lfirst ( lc ) ;
clause = ( OpExpr * ) rinfo - > clause ;
/* only OpExprs are useful for consideration */
if ( ! IsA ( clause , OpExpr ) )
continue ;
/*
* If the operator does not match then there ' s little point in
* checking the operands .
*/
if ( clause - > opno ! = fkinfo - > conpfeqop [ i ] )
continue ;
leftvar = ( Var * ) get_leftop ( ( Expr * ) clause ) ;
rightvar = ( Var * ) get_rightop ( ( Expr * ) clause ) ;
/* Foreign keys only support Vars, so ignore anything more complex */
if ( ! IsA ( leftvar , Var ) | | ! IsA ( rightvar , Var ) )
continue ;
/*
* For RestrictInfos built from an eclass we must consider each
* member of the eclass as rinfo ' s operands may not belong to the
* foreign key . For efficient tracking of which Vars we ' ve found ,
* since we ' re only tracking 2 Vars , we use a bitmask . We can
* safely finish searching when both of the least significant bits
* are set .
*/
if ( rinfo - > parent_ec )
{
EquivalenceClass * ec = rinfo - > parent_ec ;
ListCell * lc2 ;
int foundvarmask = 0 ;
foreach ( lc2 , ec - > ec_members )
{
EquivalenceMember * em = ( EquivalenceMember * ) lfirst ( lc2 ) ;
Var * var = ( Var * ) em - > em_expr ;
if ( ! IsA ( var , Var ) )
continue ;
if ( foreignrel - > relid = = var - > varno & &
fkinfo - > confkeys [ i ] = = var - > varattno )
foundvarmask | = 1 ;
else if ( fkrel - > relid = = var - > varno & &
fkinfo - > conkeys [ i ] = = var - > varattno )
foundvarmask | = 2 ;
/*
* Check if we ' ve found both matches . If found we add
* this qual to the matched list and mark this key as
* matched too .
*/
if ( foundvarmask = = 3 )
{
qualmatches = bms_add_member ( qualmatches , quallstidx ) ;
fkmatches = bms_add_member ( fkmatches , i ) ;
break ;
}
}
}
else
{
/*
* In this non eclass RestrictInfo case we ' ll check if the left
* and right Vars match to this part of the foreign key .
* Remember that this could be written with the Vars in either
* order , so we test both permutations of the expression .
*/
if ( ( foreignrel - > relid = = leftvar - > varno ) & &
( fkrel - > relid = = rightvar - > varno ) & &
( fkinfo - > confkeys [ i ] = = leftvar - > varattno ) & &
( fkinfo - > conkeys [ i ] = = rightvar - > varattno ) )
{
qualmatches = bms_add_member ( qualmatches , quallstidx ) ;
fkmatches = bms_add_member ( fkmatches , i ) ;
}
else if ( ( foreignrel - > relid = = rightvar - > varno ) & &
( fkrel - > relid = = leftvar - > varno ) & &
( fkinfo - > confkeys [ i ] = = rightvar - > varattno ) & &
( fkinfo - > conkeys [ i ] = = leftvar - > varattno ) )
{
qualmatches = bms_add_member ( qualmatches , quallstidx ) ;
fkmatches = bms_add_member ( fkmatches , i ) ;
}
}
}
}
/* can't find more matches than columns in the foreign key */
Assert ( bms_num_members ( fkmatches ) < = nkeys ) ;
/* Only return the matches if the foreign key is matched fully. */
if ( bms_num_members ( fkmatches ) = = nkeys )
{
bms_free ( fkmatches ) ;
return qualmatches ;
}
bms_free ( fkmatches ) ;
bms_free ( qualmatches ) ;
return NULL ;
}
/*
* find_best_foreign_key_quals
* Finds the foreign key best matching the joinquals .
*
* Analyzes joinquals to determine if any quals match foreign keys defined the
* two relations ( fkrel referencing foreignrel ) . When multiple foreign keys
* match , we choose the one with the most keys as the best one because of the
* way estimation occurs in clauselist_join_selectivity ( ) . We could choose
* the FK matching the most quals , however we assume the quals may be duplicated .
*
* We also track which joinquals match the current foreign key , so that we can
* easily skip then when computing the selectivity .
*
* When no matching foreign key is found we return 0 , otherwise we return the
* number of keys in the foreign key .
*
* Foreign keys matched only partially are currently ignored .
*/
static int
find_best_foreign_key_quals ( PlannerInfo * root , RelOptInfo * fkrel ,
RelOptInfo * foreignrel , List * joinquals ,
Bitmapset * * joinqualsbitmap )
{
Bitmapset * qualbestmatch ;
ListCell * lc ;
int bestmatchnkeys ;
/*
* fast path out when there ' s no foreign keys on fkrel , or when use of
* foreign keys for estimation is disabled by GUC
*/
if ( ( fkrel - > fkeylist = = NIL ) | | ( ! enable_fkey_estimates ) )
{
* joinqualsbitmap = NULL ;
return 0 ;
}
qualbestmatch = NULL ;
bestmatchnkeys = 0 ;
/* now check the matches for each foreign key defined on the fkrel */
foreach ( lc , fkrel - > fkeylist )
{
ForeignKeyOptInfo * fkinfo = ( ForeignKeyOptInfo * ) lfirst ( lc ) ;
Bitmapset * qualsmatched ;
/*
* We make no attempt in checking that this foreign key actually
* references ' foreignrel ' , the reasoning here is that we may be able
* to match the foreign key to an eclass member Var of a RestrictInfo
* that ' s in qualslist , this Var may belong to some other relation .
*
* XXX Is this assumption safe in all cases ? Maybe not , but does
* it lead to a worse estimate than the previous approach ? Doubt it .
*/
qualsmatched = quals_match_foreign_key ( root , fkinfo , fkrel , foreignrel ,
joinquals ) ;
/* Did we get a match? And is that match better than a previous one? */
if ( qualsmatched ! = NULL & & fkinfo - > nkeys > bestmatchnkeys )
{
/* save the new best match */
bms_free ( qualbestmatch ) ;
qualbestmatch = qualsmatched ;
bestmatchnkeys = fkinfo - > nkeys ;
}
}
* joinqualsbitmap = qualbestmatch ;
return bestmatchnkeys ;
}
/*
* clauselist_join_selectivity
* Estimate selectivity of join clauses either by using foreign key info
* or by using the regular clauselist_selectivity ( ) .
*
* Since selectivity estimates for each joinqual are multiplied together , this
* can cause significant underestimates on the number of join tuples in cases
* where there ' s more than 1 clause in the join condition . To help ease the
* pain here we make use of foreign keys , and we assume that 1 row will match
* when * all * of the foreign key columns are present in the join condition , any
* additional clauses are estimated using clauselist_selectivity ( ) .
*
* Note this ignores whether the FK is invalid or currently deferred ; we don ' t
* rely on this assumption for correctness of the query , so it is a reasonable
* and safe assumption for planning purposes .
*/
static Selectivity
clauselist_join_selectivity ( PlannerInfo * root , List * joinquals ,
JoinType jointype , SpecialJoinInfo * sjinfo )
{
int outerid ;
int innerid ;
Selectivity sel = 1.0 ;
Bitmapset * foundfkquals = NULL ;
innerid = - 1 ;
while ( ( innerid = bms_next_member ( sjinfo - > min_righthand , innerid ) ) > = 0 )
{
RelOptInfo * innerrel = find_base_rel ( root , innerid ) ;
outerid = - 1 ;
while ( ( outerid = bms_next_member ( sjinfo - > min_lefthand , outerid ) ) > = 0 )
{
RelOptInfo * outerrel = find_base_rel ( root , outerid ) ;
Bitmapset * outer2inner ;
Bitmapset * inner2outer ;
int innermatches ;
int outermatches ;
/*
* check which quals are matched by a foreign key referencing the
* innerrel .
*/
outermatches = find_best_foreign_key_quals ( root , outerrel ,
innerrel , joinquals , & outer2inner ) ;
/* do the same, but with relations swapped */
innermatches = find_best_foreign_key_quals ( root , innerrel ,
outerrel , joinquals , & inner2outer ) ;
/*
* did we find any matches at all ? If so we need to see which one is
* the best / longest match
*/
if ( outermatches ! = 0 | | innermatches ! = 0 )
{
double referenced_tuples ;
bool overlap ;
/* either could be zero, but not both. */
if ( outermatches < innermatches )
{
overlap = bms_overlap ( foundfkquals , inner2outer ) ;
foundfkquals = bms_add_members ( foundfkquals , inner2outer ) ;
referenced_tuples = Max ( outerrel - > tuples , 1.0 ) ;
}
else
{
overlap = bms_overlap ( foundfkquals , outer2inner ) ;
foundfkquals = bms_add_members ( foundfkquals , outer2inner ) ;
referenced_tuples = Max ( innerrel - > tuples , 1.0 ) ;
}
/*
* XXX should we ignore these overlapping matches ?
* Or perhaps take the Max ( ) or Min ( ) ?
*/
if ( overlap )
{
if ( jointype = = JOIN_SEMI | | jointype = = JOIN_ANTI )
sel = Min ( sel , Min ( 1.0 / ( outerrel - > tuples / Max ( innerrel - > tuples , 1.0 ) ) , 1.0 ) ) ;
else
sel = Min ( sel , 1.0 / referenced_tuples ) ;
}
else
{
if ( jointype = = JOIN_SEMI | | jointype = = JOIN_ANTI )
sel * = Min ( 1.0 / ( outerrel - > tuples / Max ( innerrel - > tuples , 1.0 ) ) , 1.0 ) ;
else
sel * = 1.0 / referenced_tuples ;
}
}
}
}
/*
* If any non matched quals exist then we build a list of the non - matches
* and use clauselist_selectivity ( ) to estimate the selectivity of these .
*/
if ( bms_num_members ( foundfkquals ) < list_length ( joinquals ) )
{
ListCell * lc ;
int lstidx = 0 ;
List * nonfkeyclauses = NIL ;
foreach ( lc , joinquals )
{
if ( ! bms_is_member ( lstidx , foundfkquals ) )
nonfkeyclauses = lappend ( nonfkeyclauses , lfirst ( lc ) ) ;
lstidx + + ;
}
sel * = clauselist_selectivity ( root , nonfkeyclauses , 0 , jointype , sjinfo ) ;
}
return sel ;
}
/*
* calc_joinrel_size_estimate
* Workhorse for set_joinrel_size_estimates and
@ -4289,11 +3933,11 @@ calc_joinrel_size_estimate(PlannerInfo *root,
}
/* Get the separate selectivities */
jselec = clauselist_join_ selectivity ( root ,
joinquals ,
jointype ,
sjinfo ) ;
jselec = clauselist_selectivity ( root ,
joinquals ,
0 ,
jointype ,
sjinfo ) ;
pselec = clauselist_selectivity ( root ,
pushedquals ,
0 ,
@ -4306,10 +3950,11 @@ calc_joinrel_size_estimate(PlannerInfo *root,
}
else
{
jselec = clauselist_join_selectivity ( root ,
restrictlist ,
jointype ,
sjinfo ) ;
jselec = clauselist_selectivity ( root ,
restrictlist ,
0 ,
jointype ,
sjinfo ) ;
pselec = 0.0 ; /* not used, keep compiler quiet */
}