Revert "Use Foreign Key relationships to infer multi-column join selectivity".
This commit reverts137805f89
as well as the associated commits015e88942
,5306df283
, and68d704edb
. We found multiple bugs in this feature, and there was concern about possible planner slowdown (though to be fair, exhibiting a very large slowdown proved difficult). The way forward requires a considerable rewrite, which may or may not be possible to accomplish in time for beta2. In my judgment reviewing the rewrite will be easier to accomplish starting from a clean slate, so let's temporarily revert what's there now. This also leaves us in a safe state if it turns out to be necessary to postpone the rewrite to the next development cycle. Discussion: <20160429102531.GA13701@huehner.biz>
This commit is contained in:
parent
5c6d2a5e7c
commit
77ba610805
|
@ -2137,16 +2137,6 @@ _outIndexOptInfo(StringInfo str, const IndexOptInfo *node)
|
||||||
/* we don't bother with fields copied from the index AM's API struct */
|
/* we don't bother with fields copied from the index AM's API struct */
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
_outForeignKeyOptInfo(StringInfo str, const ForeignKeyOptInfo *node)
|
|
||||||
{
|
|
||||||
WRITE_NODE_TYPE("FOREIGNKEYOPTINFO");
|
|
||||||
|
|
||||||
WRITE_OID_FIELD(conrelid);
|
|
||||||
WRITE_OID_FIELD(confrelid);
|
|
||||||
WRITE_INT_FIELD(nkeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
|
_outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
|
||||||
{
|
{
|
||||||
|
@ -3617,9 +3607,6 @@ outNode(StringInfo str, const void *obj)
|
||||||
case T_IndexOptInfo:
|
case T_IndexOptInfo:
|
||||||
_outIndexOptInfo(str, obj);
|
_outIndexOptInfo(str, obj);
|
||||||
break;
|
break;
|
||||||
case T_ForeignKeyOptInfo:
|
|
||||||
_outForeignKeyOptInfo(str, obj);
|
|
||||||
break;
|
|
||||||
case T_EquivalenceClass:
|
case T_EquivalenceClass:
|
||||||
_outEquivalenceClass(str, obj);
|
_outEquivalenceClass(str, obj);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -126,7 +126,6 @@ bool enable_nestloop = true;
|
||||||
bool enable_material = true;
|
bool enable_material = true;
|
||||||
bool enable_mergejoin = true;
|
bool enable_mergejoin = true;
|
||||||
bool enable_hashjoin = true;
|
bool enable_hashjoin = true;
|
||||||
bool enable_fkey_estimates = true;
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
|
@ -3888,361 +3887,6 @@ get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
|
||||||
return nrows;
|
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
|
* calc_joinrel_size_estimate
|
||||||
* Workhorse for set_joinrel_size_estimates and
|
* Workhorse for set_joinrel_size_estimates and
|
||||||
|
@ -4289,11 +3933,11 @@ calc_joinrel_size_estimate(PlannerInfo *root,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get the separate selectivities */
|
/* Get the separate selectivities */
|
||||||
jselec = clauselist_join_selectivity(root,
|
jselec = clauselist_selectivity(root,
|
||||||
joinquals,
|
joinquals,
|
||||||
|
0,
|
||||||
jointype,
|
jointype,
|
||||||
sjinfo);
|
sjinfo);
|
||||||
|
|
||||||
pselec = clauselist_selectivity(root,
|
pselec = clauselist_selectivity(root,
|
||||||
pushedquals,
|
pushedquals,
|
||||||
0,
|
0,
|
||||||
|
@ -4306,8 +3950,9 @@ calc_joinrel_size_estimate(PlannerInfo *root,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
jselec = clauselist_join_selectivity(root,
|
jselec = clauselist_selectivity(root,
|
||||||
restrictlist,
|
restrictlist,
|
||||||
|
0,
|
||||||
jointype,
|
jointype,
|
||||||
sjinfo);
|
sjinfo);
|
||||||
pselec = 0.0; /* not used, keep compiler quiet */
|
pselec = 0.0; /* not used, keep compiler quiet */
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
#include "catalog/dependency.h"
|
#include "catalog/dependency.h"
|
||||||
#include "catalog/heap.h"
|
#include "catalog/heap.h"
|
||||||
#include "catalog/pg_am.h"
|
#include "catalog/pg_am.h"
|
||||||
#include "catalog/pg_constraint.h"
|
|
||||||
#include "foreign/fdwapi.h"
|
#include "foreign/fdwapi.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
|
@ -42,7 +41,6 @@
|
||||||
#include "rewrite/rewriteManip.h"
|
#include "rewrite/rewriteManip.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/syscache.h"
|
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
#include "utils/snapmgr.h"
|
#include "utils/snapmgr.h"
|
||||||
|
|
||||||
|
@ -96,9 +94,6 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
||||||
Relation relation;
|
Relation relation;
|
||||||
bool hasindex;
|
bool hasindex;
|
||||||
List *indexinfos = NIL;
|
List *indexinfos = NIL;
|
||||||
List *fkinfos = NIL;
|
|
||||||
List *fkoidlist;
|
|
||||||
ListCell *l;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We need not lock the relation since it was already locked, either by
|
* We need not lock the relation since it was already locked, either by
|
||||||
|
@ -149,6 +144,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
||||||
if (hasindex)
|
if (hasindex)
|
||||||
{
|
{
|
||||||
List *indexoidlist;
|
List *indexoidlist;
|
||||||
|
ListCell *l;
|
||||||
LOCKMODE lmode;
|
LOCKMODE lmode;
|
||||||
|
|
||||||
indexoidlist = RelationGetIndexList(relation);
|
indexoidlist = RelationGetIndexList(relation);
|
||||||
|
@ -395,85 +391,6 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
||||||
|
|
||||||
rel->indexlist = indexinfos;
|
rel->indexlist = indexinfos;
|
||||||
|
|
||||||
/*
|
|
||||||
* Load foreign key data. Note this is the definitional data from the
|
|
||||||
* catalog only and does not lock the referenced tables here. The
|
|
||||||
* precise definition of the FK is important and may affect the usage
|
|
||||||
* elsewhere in the planner, e.g. if the constraint is deferred or
|
|
||||||
* if the constraint is not valid then relying upon this in the executor
|
|
||||||
* may not be accurate, though might be considered a useful estimate for
|
|
||||||
* planning purposes.
|
|
||||||
*/
|
|
||||||
fkoidlist = RelationGetFKeyList(relation);
|
|
||||||
|
|
||||||
foreach(l, fkoidlist)
|
|
||||||
{
|
|
||||||
Oid fkoid = lfirst_oid(l);
|
|
||||||
HeapTuple htup;
|
|
||||||
Form_pg_constraint constraint;
|
|
||||||
ForeignKeyOptInfo *info;
|
|
||||||
Datum adatum;
|
|
||||||
bool isnull;
|
|
||||||
ArrayType *arr;
|
|
||||||
int numkeys;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
htup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fkoid));
|
|
||||||
if (!HeapTupleIsValid(htup)) /* should not happen */
|
|
||||||
elog(ERROR, "cache lookup failed for constraint %u", fkoid);
|
|
||||||
constraint = (Form_pg_constraint) GETSTRUCT(htup);
|
|
||||||
|
|
||||||
Assert(constraint->contype == CONSTRAINT_FOREIGN);
|
|
||||||
|
|
||||||
info = makeNode(ForeignKeyOptInfo);
|
|
||||||
|
|
||||||
info->conrelid = constraint->conrelid;
|
|
||||||
info->confrelid = constraint->confrelid;
|
|
||||||
|
|
||||||
/* conkey */
|
|
||||||
adatum = SysCacheGetAttr(CONSTROID, htup,
|
|
||||||
Anum_pg_constraint_conkey, &isnull);
|
|
||||||
Assert(!isnull);
|
|
||||||
|
|
||||||
arr = DatumGetArrayTypeP(adatum);
|
|
||||||
numkeys = ARR_DIMS(arr)[0];
|
|
||||||
info->conkeys = (int*)palloc(numkeys * sizeof(int));
|
|
||||||
for (i = 0; i < numkeys; i++)
|
|
||||||
info->conkeys[i] = ((int16 *) ARR_DATA_PTR(arr))[i];
|
|
||||||
|
|
||||||
/* confkey */
|
|
||||||
adatum = SysCacheGetAttr(CONSTROID, htup,
|
|
||||||
Anum_pg_constraint_confkey, &isnull);
|
|
||||||
Assert(!isnull);
|
|
||||||
|
|
||||||
arr = DatumGetArrayTypeP(adatum);
|
|
||||||
Assert(numkeys == ARR_DIMS(arr)[0]);
|
|
||||||
info->confkeys = (int*)palloc(numkeys * sizeof(int));
|
|
||||||
for (i = 0; i < numkeys; i++)
|
|
||||||
info->confkeys[i] = ((int16 *) ARR_DATA_PTR(arr))[i];
|
|
||||||
|
|
||||||
/* conpfeqop */
|
|
||||||
adatum = SysCacheGetAttr(CONSTROID, htup,
|
|
||||||
Anum_pg_constraint_conpfeqop, &isnull);
|
|
||||||
Assert(!isnull);
|
|
||||||
|
|
||||||
arr = DatumGetArrayTypeP(adatum);
|
|
||||||
Assert(numkeys == ARR_DIMS(arr)[0]);
|
|
||||||
info->conpfeqop = (Oid*)palloc(numkeys * sizeof(Oid));
|
|
||||||
for (i = 0; i < numkeys; i++)
|
|
||||||
info->conpfeqop[i] = ((Oid *) ARR_DATA_PTR(arr))[i];
|
|
||||||
|
|
||||||
info->nkeys = numkeys;
|
|
||||||
|
|
||||||
ReleaseSysCache(htup);
|
|
||||||
|
|
||||||
fkinfos = lappend(fkinfos, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
list_free(fkoidlist);
|
|
||||||
|
|
||||||
rel->fkeylist = fkinfos;
|
|
||||||
|
|
||||||
/* Grab foreign-table info using the relcache, while we have it */
|
/* Grab foreign-table info using the relcache, while we have it */
|
||||||
if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
|
if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2031,7 +2031,6 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
|
||||||
FreeTupleDesc(relation->rd_att);
|
FreeTupleDesc(relation->rd_att);
|
||||||
}
|
}
|
||||||
list_free(relation->rd_indexlist);
|
list_free(relation->rd_indexlist);
|
||||||
list_free(relation->rd_fkeylist);
|
|
||||||
bms_free(relation->rd_indexattr);
|
bms_free(relation->rd_indexattr);
|
||||||
bms_free(relation->rd_keyattr);
|
bms_free(relation->rd_keyattr);
|
||||||
bms_free(relation->rd_idattr);
|
bms_free(relation->rd_idattr);
|
||||||
|
@ -3957,79 +3956,6 @@ RelationGetIndexList(Relation relation)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* RelationGetFKeyList -- get a list of foreign key oids
|
|
||||||
*
|
|
||||||
* Use an index scan on pg_constraint to load in FK definitions,
|
|
||||||
* intended for use within the planner, not for enforcing FKs.
|
|
||||||
*
|
|
||||||
* Data is ordered by Oid, though this is not critical at this point
|
|
||||||
* since we do not lock the referenced relations.
|
|
||||||
*/
|
|
||||||
List *
|
|
||||||
RelationGetFKeyList(Relation relation)
|
|
||||||
{
|
|
||||||
Relation conrel;
|
|
||||||
SysScanDesc conscan;
|
|
||||||
ScanKeyData skey;
|
|
||||||
HeapTuple htup;
|
|
||||||
List *result;
|
|
||||||
List *oldlist;
|
|
||||||
MemoryContext oldcxt;
|
|
||||||
|
|
||||||
/* Quick exit if we already computed the list. */
|
|
||||||
if (relation->rd_fkeylist)
|
|
||||||
return list_copy(relation->rd_fkeylist);
|
|
||||||
|
|
||||||
/* Fast path if no FKs... if it doesn't have a trigger, it can't have a FK */
|
|
||||||
if (!relation->rd_rel->relhastriggers)
|
|
||||||
return NIL;
|
|
||||||
/*
|
|
||||||
* We build the list we intend to return (in the caller's context) while
|
|
||||||
* doing the scan. After successfully completing the scan, we copy that
|
|
||||||
* list into the relcache entry. This avoids cache-context memory leakage
|
|
||||||
* if we get some sort of error partway through.
|
|
||||||
*/
|
|
||||||
result = NIL;
|
|
||||||
|
|
||||||
/* Prepare to scan pg_constraint for entries having conrelid = this rel. */
|
|
||||||
ScanKeyInit(&skey,
|
|
||||||
Anum_pg_constraint_conrelid,
|
|
||||||
BTEqualStrategyNumber, F_OIDEQ,
|
|
||||||
ObjectIdGetDatum(RelationGetRelid(relation)));
|
|
||||||
|
|
||||||
conrel = heap_open(ConstraintRelationId, AccessShareLock);
|
|
||||||
conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
|
|
||||||
NULL, 1, &skey);
|
|
||||||
|
|
||||||
while (HeapTupleIsValid(htup = systable_getnext(conscan)))
|
|
||||||
{
|
|
||||||
Form_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(htup);
|
|
||||||
|
|
||||||
/* return only foreign keys */
|
|
||||||
if (constraint->contype != CONSTRAINT_FOREIGN)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Add FK's OID to result list in the proper order */
|
|
||||||
result = insert_ordered_oid(result, HeapTupleGetOid(htup));
|
|
||||||
}
|
|
||||||
|
|
||||||
systable_endscan(conscan);
|
|
||||||
|
|
||||||
heap_close(conrel, AccessShareLock);
|
|
||||||
|
|
||||||
/* Now save a copy of the completed list in the relcache entry. */
|
|
||||||
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
|
|
||||||
oldlist = relation->rd_fkeylist;
|
|
||||||
relation->rd_fkeylist = list_copy(result);
|
|
||||||
MemoryContextSwitchTo(oldcxt);
|
|
||||||
|
|
||||||
/* Don't leak the old list, if there is one */
|
|
||||||
list_free(oldlist);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* insert_ordered_oid
|
* insert_ordered_oid
|
||||||
* Insert a new Oid into a sorted list of Oids, preserving ordering
|
* Insert a new Oid into a sorted list of Oids, preserving ordering
|
||||||
|
@ -4994,7 +4920,6 @@ load_relcache_init_file(bool shared)
|
||||||
rel->rd_indexattr = NULL;
|
rel->rd_indexattr = NULL;
|
||||||
rel->rd_keyattr = NULL;
|
rel->rd_keyattr = NULL;
|
||||||
rel->rd_idattr = NULL;
|
rel->rd_idattr = NULL;
|
||||||
rel->rd_fkeylist = NIL;
|
|
||||||
rel->rd_createSubid = InvalidSubTransactionId;
|
rel->rd_createSubid = InvalidSubTransactionId;
|
||||||
rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
|
rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
|
||||||
rel->rd_amcache = NULL;
|
rel->rd_amcache = NULL;
|
||||||
|
|
|
@ -877,15 +877,6 @@ static struct config_bool ConfigureNamesBool[] =
|
||||||
true,
|
true,
|
||||||
NULL, NULL, NULL
|
NULL, NULL, NULL
|
||||||
},
|
},
|
||||||
{
|
|
||||||
{"enable_fkey_estimates", PGC_USERSET, QUERY_TUNING_METHOD,
|
|
||||||
gettext_noop("Enables use of foreign keys for estimating joins."),
|
|
||||||
NULL
|
|
||||||
},
|
|
||||||
&enable_fkey_estimates,
|
|
||||||
true,
|
|
||||||
NULL, NULL, NULL
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
|
{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
|
||||||
|
|
|
@ -223,7 +223,6 @@ typedef enum NodeTag
|
||||||
T_PlannerGlobal,
|
T_PlannerGlobal,
|
||||||
T_RelOptInfo,
|
T_RelOptInfo,
|
||||||
T_IndexOptInfo,
|
T_IndexOptInfo,
|
||||||
T_ForeignKeyOptInfo,
|
|
||||||
T_ParamPathInfo,
|
T_ParamPathInfo,
|
||||||
T_Path,
|
T_Path,
|
||||||
T_IndexPath,
|
T_IndexPath,
|
||||||
|
|
|
@ -516,7 +516,6 @@ typedef struct RelOptInfo
|
||||||
List *lateral_vars; /* LATERAL Vars and PHVs referenced by rel */
|
List *lateral_vars; /* LATERAL Vars and PHVs referenced by rel */
|
||||||
Relids lateral_referencers; /* rels that reference me laterally */
|
Relids lateral_referencers; /* rels that reference me laterally */
|
||||||
List *indexlist; /* list of IndexOptInfo */
|
List *indexlist; /* list of IndexOptInfo */
|
||||||
List *fkeylist; /* list of ForeignKeyOptInfo */
|
|
||||||
BlockNumber pages; /* size estimates derived from pg_class */
|
BlockNumber pages; /* size estimates derived from pg_class */
|
||||||
double tuples;
|
double tuples;
|
||||||
double allvisfrac;
|
double allvisfrac;
|
||||||
|
@ -623,27 +622,6 @@ typedef struct IndexOptInfo
|
||||||
void (*amcostestimate) (); /* AM's cost estimator */
|
void (*amcostestimate) (); /* AM's cost estimator */
|
||||||
} IndexOptInfo;
|
} IndexOptInfo;
|
||||||
|
|
||||||
/*
|
|
||||||
* ForeignKeyOptInfo
|
|
||||||
* Per-foreign-key information for planning/optimization
|
|
||||||
*
|
|
||||||
* Only includes columns from pg_constraint related to foreign keys.
|
|
||||||
*
|
|
||||||
* conkeys[], confkeys[] and conpfeqop[] each have nkeys entries.
|
|
||||||
*/
|
|
||||||
typedef struct ForeignKeyOptInfo
|
|
||||||
{
|
|
||||||
NodeTag type;
|
|
||||||
|
|
||||||
Oid conrelid; /* relation constrained by the foreign key */
|
|
||||||
Oid confrelid; /* relation referenced by the foreign key */
|
|
||||||
|
|
||||||
int nkeys; /* number of columns in the foreign key */
|
|
||||||
int *conkeys; /* attnums of columns in the constrained table */
|
|
||||||
int *confkeys; /* attnums of columns in the referenced table */
|
|
||||||
Oid *conpfeqop; /* OIDs of equality operators used by the FK */
|
|
||||||
|
|
||||||
} ForeignKeyOptInfo;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* EquivalenceClasses
|
* EquivalenceClasses
|
||||||
|
|
|
@ -66,7 +66,6 @@ extern bool enable_nestloop;
|
||||||
extern bool enable_material;
|
extern bool enable_material;
|
||||||
extern bool enable_mergejoin;
|
extern bool enable_mergejoin;
|
||||||
extern bool enable_hashjoin;
|
extern bool enable_hashjoin;
|
||||||
extern bool enable_fkey_estimates;
|
|
||||||
extern int constraint_exclusion;
|
extern int constraint_exclusion;
|
||||||
|
|
||||||
extern double clamp_row_est(double nrows);
|
extern double clamp_row_est(double nrows);
|
||||||
|
|
|
@ -76,8 +76,6 @@ extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause,
|
||||||
int indexcol,
|
int indexcol,
|
||||||
List **indexcolnos,
|
List **indexcolnos,
|
||||||
bool *var_on_left_p);
|
bool *var_on_left_p);
|
||||||
extern bool has_matching_fkey(RelOptInfo *rel, RelOptInfo *frel, List *clauses,
|
|
||||||
bool reverse);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* tidpath.h
|
* tidpath.h
|
||||||
|
|
|
@ -95,9 +95,6 @@ typedef struct RelationData
|
||||||
Oid rd_oidindex; /* OID of unique index on OID, if any */
|
Oid rd_oidindex; /* OID of unique index on OID, if any */
|
||||||
Oid rd_replidindex; /* OID of replica identity index, if any */
|
Oid rd_replidindex; /* OID of replica identity index, if any */
|
||||||
|
|
||||||
/* data managed by RelationGetFKList: */
|
|
||||||
List *rd_fkeylist; /* OIDs of foreign keys */
|
|
||||||
|
|
||||||
/* data managed by RelationGetIndexAttrBitmap: */
|
/* data managed by RelationGetIndexAttrBitmap: */
|
||||||
Bitmapset *rd_indexattr; /* identifies columns used in indexes */
|
Bitmapset *rd_indexattr; /* identifies columns used in indexes */
|
||||||
Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */
|
Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */
|
||||||
|
|
|
@ -38,7 +38,6 @@ extern void RelationClose(Relation relation);
|
||||||
* Routines to compute/retrieve additional cached information
|
* Routines to compute/retrieve additional cached information
|
||||||
*/
|
*/
|
||||||
extern List *RelationGetIndexList(Relation relation);
|
extern List *RelationGetIndexList(Relation relation);
|
||||||
extern List *RelationGetFKeyList(Relation relation);
|
|
||||||
extern Oid RelationGetOidIndex(Relation relation);
|
extern Oid RelationGetOidIndex(Relation relation);
|
||||||
extern Oid RelationGetReplicaIndex(Relation relation);
|
extern Oid RelationGetReplicaIndex(Relation relation);
|
||||||
extern List *RelationGetIndexExpressions(Relation relation);
|
extern List *RelationGetIndexExpressions(Relation relation);
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
|
SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
|
||||||
name | setting
|
name | setting
|
||||||
-----------------------+---------
|
----------------------+---------
|
||||||
enable_bitmapscan | on
|
enable_bitmapscan | on
|
||||||
enable_fkey_estimates | on
|
|
||||||
enable_hashagg | on
|
enable_hashagg | on
|
||||||
enable_hashjoin | on
|
enable_hashjoin | on
|
||||||
enable_indexonlyscan | on
|
enable_indexonlyscan | on
|
||||||
|
@ -13,7 +12,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
|
||||||
enable_seqscan | on
|
enable_seqscan | on
|
||||||
enable_sort | on
|
enable_sort | on
|
||||||
enable_tidscan | on
|
enable_tidscan | on
|
||||||
(12 rows)
|
(11 rows)
|
||||||
|
|
||||||
CREATE TABLE foo2(fooid int, f2 int);
|
CREATE TABLE foo2(fooid int, f2 int);
|
||||||
INSERT INTO foo2 VALUES(1, 11);
|
INSERT INTO foo2 VALUES(1, 11);
|
||||||
|
|
Loading…
Reference in New Issue