Commit 100340e2 authored by Tom Lane's avatar Tom Lane

Restore foreign-key-aware estimation of join relation sizes.

This patch provides a new implementation of the logic added by commit
137805f8 and later removed by 77ba6108.  It differs from the original
primarily in expending much less effort per joinrel in large queries,
which it accomplishes by doing most of the matching work once per query not
once per joinrel.  Hopefully, it's also less buggy and better commented.
The never-documented enable_fkey_estimates GUC remains gone.

There remains work to be done to make the selectivity estimates account
for nulls in FK referencing columns; but that was true of the original
patch as well.  We may be able to address this point later in beta.
In the meantime, any error should be in the direction of overestimating
rather than underestimating joinrel sizes, which seems like the direction
we want to err in.

Tomas Vondra and Tom Lane

Discussion: <31041.1465069446@sss.pgh.pa.us>
parent 598aa194
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "nodes/plannodes.h" #include "nodes/plannodes.h"
#include "nodes/relation.h" #include "nodes/relation.h"
#include "utils/datum.h" #include "utils/datum.h"
#include "utils/rel.h"
/* /*
...@@ -4254,6 +4255,24 @@ _copyValue(const Value *from) ...@@ -4254,6 +4255,24 @@ _copyValue(const Value *from)
return newnode; return newnode;
} }
static ForeignKeyCacheInfo *
_copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
{
ForeignKeyCacheInfo *newnode = makeNode(ForeignKeyCacheInfo);
COPY_SCALAR_FIELD(conrelid);
COPY_SCALAR_FIELD(confrelid);
COPY_SCALAR_FIELD(nkeys);
/* COPY_SCALAR_FIELD might work for these, but let's not assume that */
memcpy(newnode->conkey, from->conkey, sizeof(newnode->conkey));
memcpy(newnode->confkey, from->confkey, sizeof(newnode->confkey));
memcpy(newnode->conpfeqop, from->conpfeqop, sizeof(newnode->conpfeqop));
return newnode;
}
/* /*
* copyObject * copyObject
* *
...@@ -5052,6 +5071,13 @@ copyObject(const void *from) ...@@ -5052,6 +5071,13 @@ copyObject(const void *from)
retval = _copyRoleSpec(from); retval = _copyRoleSpec(from);
break; break;
/*
* MISCELLANEOUS NODES
*/
case T_ForeignKeyCacheInfo:
retval = _copyForeignKeyCacheInfo(from);
break;
default: default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from)); elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
retval = 0; /* keep compiler quiet */ retval = 0; /* keep compiler quiet */
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "nodes/plannodes.h" #include "nodes/plannodes.h"
#include "nodes/relation.h" #include "nodes/relation.h"
#include "utils/datum.h" #include "utils/datum.h"
#include "utils/rel.h"
/* /*
...@@ -2050,6 +2051,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) ...@@ -2050,6 +2051,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_NODE_FIELD(append_rel_list); WRITE_NODE_FIELD(append_rel_list);
WRITE_NODE_FIELD(rowMarks); WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(placeholder_list); WRITE_NODE_FIELD(placeholder_list);
WRITE_NODE_FIELD(fkey_list);
WRITE_NODE_FIELD(query_pathkeys); WRITE_NODE_FIELD(query_pathkeys);
WRITE_NODE_FIELD(group_pathkeys); WRITE_NODE_FIELD(group_pathkeys);
WRITE_NODE_FIELD(window_pathkeys); WRITE_NODE_FIELD(window_pathkeys);
...@@ -2139,6 +2141,37 @@ _outIndexOptInfo(StringInfo str, const IndexOptInfo *node) ...@@ -2139,6 +2141,37 @@ _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)
{
int i;
WRITE_NODE_TYPE("FOREIGNKEYOPTINFO");
WRITE_UINT_FIELD(con_relid);
WRITE_UINT_FIELD(ref_relid);
WRITE_INT_FIELD(nkeys);
appendStringInfoString(str, " :conkey");
for (i = 0; i < node->nkeys; i++)
appendStringInfo(str, " %d", node->conkey[i]);
appendStringInfoString(str, " :confkey");
for (i = 0; i < node->nkeys; i++)
appendStringInfo(str, " %d", node->confkey[i]);
appendStringInfoString(str, " :conpfeqop");
for (i = 0; i < node->nkeys; i++)
appendStringInfo(str, " %u", node->conpfeqop[i]);
WRITE_INT_FIELD(nmatched_ec);
WRITE_INT_FIELD(nmatched_rcols);
WRITE_INT_FIELD(nmatched_ri);
/* for compactness, just print the number of matches per column: */
appendStringInfoString(str, " :eclass");
for (i = 0; i < node->nkeys; i++)
appendStringInfo(str, " %d", (node->eclass[i] != NULL));
appendStringInfoString(str, " :rinfos");
for (i = 0; i < node->nkeys; i++)
appendStringInfo(str, " %d", list_length(node->rinfos[i]));
}
static void static void
_outEquivalenceClass(StringInfo str, const EquivalenceClass *node) _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
{ {
...@@ -3209,6 +3242,27 @@ _outConstraint(StringInfo str, const Constraint *node) ...@@ -3209,6 +3242,27 @@ _outConstraint(StringInfo str, const Constraint *node)
} }
} }
static void
_outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
{
int i;
WRITE_NODE_TYPE("FOREIGNKEYCACHEINFO");
WRITE_OID_FIELD(conrelid);
WRITE_OID_FIELD(confrelid);
WRITE_INT_FIELD(nkeys);
appendStringInfoString(str, " :conkey");
for (i = 0; i < node->nkeys; i++)
appendStringInfo(str, " %d", node->conkey[i]);
appendStringInfoString(str, " :confkey");
for (i = 0; i < node->nkeys; i++)
appendStringInfo(str, " %d", node->confkey[i]);
appendStringInfoString(str, " :conpfeqop");
for (i = 0; i < node->nkeys; i++)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
/* /*
* outNode - * outNode -
...@@ -3609,6 +3663,9 @@ outNode(StringInfo str, const void *obj) ...@@ -3609,6 +3663,9 @@ 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;
...@@ -3785,6 +3842,9 @@ outNode(StringInfo str, const void *obj) ...@@ -3785,6 +3842,9 @@ outNode(StringInfo str, const void *obj)
case T_XmlSerialize: case T_XmlSerialize:
_outXmlSerialize(str, obj); _outXmlSerialize(str, obj);
break; break;
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
break;
default: default:
......
This diff is collapsed.
...@@ -1925,6 +1925,85 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2) ...@@ -1925,6 +1925,85 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2)
} }
/*
* match_eclasses_to_foreign_key_col
* See whether a foreign key column match is proven by any eclass.
*
* If the referenced and referencing Vars of the fkey's colno'th column are
* known equal due to any eclass, return that eclass; otherwise return NULL.
* (In principle there might be more than one matching eclass if multiple
* collations are involved, but since collation doesn't matter for equality,
* we ignore that fine point here.) This is much like exprs_known_equal,
* except that we insist on the comparison operator matching the eclass, so
* that the result is definite not approximate.
*/
EquivalenceClass *
match_eclasses_to_foreign_key_col(PlannerInfo *root,
ForeignKeyOptInfo *fkinfo,
int colno)
{
Index var1varno = fkinfo->con_relid;
AttrNumber var1attno = fkinfo->conkey[colno];
Index var2varno = fkinfo->ref_relid;
AttrNumber var2attno = fkinfo->confkey[colno];
Oid eqop = fkinfo->conpfeqop[colno];
List *opfamilies = NIL; /* compute only if needed */
ListCell *lc1;
foreach(lc1, root->eq_classes)
{
EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc1);
bool item1member = false;
bool item2member = false;
ListCell *lc2;
/* Never match to a volatile EC */
if (ec->ec_has_volatile)
continue;
/* Note: it seems okay to match to "broken" eclasses here */
foreach(lc2, ec->ec_members)
{
EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2);
Var *var;
if (em->em_is_child)
continue; /* ignore children here */
/* EM must be a Var, possibly with RelabelType */
var = (Var *) em->em_expr;
while (var && IsA(var, RelabelType))
var = (Var *) ((RelabelType *) var)->arg;
if (!(var && IsA(var, Var)))
continue;
/* Match? */
if (var->varno == var1varno && var->varattno == var1attno)
item1member = true;
else if (var->varno == var2varno && var->varattno == var2attno)
item2member = true;
/* Have we found both PK and FK column in this EC? */
if (item1member && item2member)
{
/*
* Succeed if eqop matches EC's opfamilies. We could test
* this before scanning the members, but it's probably cheaper
* to test for member matches first.
*/
if (opfamilies == NIL) /* compute if we didn't already */
opfamilies = get_mergejoin_opfamilies(eqop);
if (equal(opfamilies, ec->ec_opfamilies))
return ec;
/* Otherwise, done with this EC, move on to the next */
break;
}
}
}
return NULL;
}
/* /*
* add_child_rel_equivalences * add_child_rel_equivalences
* Search for EC members that reference the parent_rel, and * Search for EC members that reference the parent_rel, and
......
...@@ -433,6 +433,11 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids) ...@@ -433,6 +433,11 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
distribute_restrictinfo_to_rels(root, rinfo); distribute_restrictinfo_to_rels(root, rinfo);
} }
} }
/*
* There may be references to the rel in root->fkey_list, but if so,
* match_foreign_keys_to_quals() will get rid of them.
*/
} }
/* /*
......
...@@ -2306,6 +2306,159 @@ build_implied_join_equality(Oid opno, ...@@ -2306,6 +2306,159 @@ build_implied_join_equality(Oid opno,
} }
/*
* match_foreign_keys_to_quals
* Match foreign-key constraints to equivalence classes and join quals
*
* The idea here is to see which query join conditions match equality
* constraints of a foreign-key relationship. For such join conditions,
* we can use the FK semantics to make selectivity estimates that are more
* reliable than estimating from statistics, especially for multiple-column
* FKs, where the normal assumption of independent conditions tends to fail.
*
* In this function we annotate the ForeignKeyOptInfos in root->fkey_list
* with info about which eclasses and join qual clauses they match, and
* discard any ForeignKeyOptInfos that are irrelevant for the query.
*/
void
match_foreign_keys_to_quals(PlannerInfo *root)
{
List *newlist = NIL;
ListCell *lc;
foreach(lc, root->fkey_list)
{
ForeignKeyOptInfo *fkinfo = (ForeignKeyOptInfo *) lfirst(lc);
RelOptInfo *con_rel = find_base_rel(root, fkinfo->con_relid);
RelOptInfo *ref_rel = find_base_rel(root, fkinfo->ref_relid);
int colno;
/*
* Ignore FK unless both rels are baserels. This gets rid of FKs that
* link to inheritance child rels (otherrels) and those that link to
* rels removed by join removal (dead rels).
*/
if (con_rel->reloptkind != RELOPT_BASEREL ||
ref_rel->reloptkind != RELOPT_BASEREL)
continue;
/*
* Scan the columns and try to match them to eclasses and quals.
*
* Note: for simple inner joins, any match should be in an eclass.
* "Loose" quals that syntactically match an FK equality must have
* been rejected for EC status because they are outer-join quals or
* similar. We can still consider them to match the FK if they are
* not outerjoin_delayed.
*/
for (colno = 0; colno < fkinfo->nkeys; colno++)
{
AttrNumber con_attno,
ref_attno;
Oid fpeqop;
ListCell *lc2;
fkinfo->eclass[colno] = match_eclasses_to_foreign_key_col(root,
fkinfo,
colno);
/* Don't bother looking for loose quals if we got an EC match */
if (fkinfo->eclass[colno] != NULL)
{
fkinfo->nmatched_ec++;
continue;
}
/*
* Scan joininfo list for relevant clauses. Either rel's joininfo
* list would do equally well; we use con_rel's.
*/
con_attno = fkinfo->conkey[colno];
ref_attno = fkinfo->confkey[colno];
fpeqop = InvalidOid; /* we'll look this up only if needed */
foreach(lc2, con_rel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2);
OpExpr *clause = (OpExpr *) rinfo->clause;
Var *leftvar;
Var *rightvar;
/* Ignore outerjoin-delayed clauses */
if (rinfo->outerjoin_delayed)
continue;
/* Only binary OpExprs are useful for consideration */
if (!IsA(clause, OpExpr) ||
list_length(clause->args) != 2)
continue;
leftvar = (Var *) get_leftop((Expr *) clause);
rightvar = (Var *) get_rightop((Expr *) clause);
/* Operands must be Vars, possibly with RelabelType */
while (leftvar && IsA(leftvar, RelabelType))
leftvar = (Var *) ((RelabelType *) leftvar)->arg;
if (!(leftvar && IsA(leftvar, Var)))
continue;
while (rightvar && IsA(rightvar, RelabelType))
rightvar = (Var *) ((RelabelType *) rightvar)->arg;
if (!(rightvar && IsA(rightvar, Var)))
continue;
/* Now try to match the vars to the current foreign key cols */
if (fkinfo->ref_relid == leftvar->varno &&
ref_attno == leftvar->varattno &&
fkinfo->con_relid == rightvar->varno &&
con_attno == rightvar->varattno)
{
/* Vars match, but is it the right operator? */
if (clause->opno == fkinfo->conpfeqop[colno])
{
fkinfo->rinfos[colno] = lappend(fkinfo->rinfos[colno],
rinfo);
fkinfo->nmatched_ri++;
}
}
else if (fkinfo->ref_relid == rightvar->varno &&
ref_attno == rightvar->varattno &&
fkinfo->con_relid == leftvar->varno &&
con_attno == leftvar->varattno)
{
/*
* Reverse match, must check commutator operator. Look it
* up if we didn't already. (In the worst case we might
* do multiple lookups here, but that would require an FK
* equality operator without commutator, which is
* unlikely.)
*/
if (!OidIsValid(fpeqop))
fpeqop = get_commutator(fkinfo->conpfeqop[colno]);
if (clause->opno == fpeqop)
{
fkinfo->rinfos[colno] = lappend(fkinfo->rinfos[colno],
rinfo);
fkinfo->nmatched_ri++;
}
}
}
/* If we found any matching loose quals, count col as matched */
if (fkinfo->rinfos[colno])
fkinfo->nmatched_rcols++;
}
/*
* Currently, we drop multicolumn FKs that aren't fully matched to the
* query. Later we might figure out how to derive some sort of
* estimate from them, in which case this test should be weakened to
* "if ((fkinfo->nmatched_ec + fkinfo->nmatched_rcols) > 0)".
*/
if ((fkinfo->nmatched_ec + fkinfo->nmatched_rcols) == fkinfo->nkeys)
newlist = lappend(newlist, fkinfo);
}
/* Replace fkey_list, thereby discarding any useless entries */
root->fkey_list = newlist;
}
/***************************************************************************** /*****************************************************************************
* *
* CHECKS FOR MERGEJOINABLE AND HASHJOINABLE CLAUSES * CHECKS FOR MERGEJOINABLE AND HASHJOINABLE CLAUSES
......
...@@ -115,6 +115,7 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -115,6 +115,7 @@ query_planner(PlannerInfo *root, List *tlist,
root->full_join_clauses = NIL; root->full_join_clauses = NIL;
root->join_info_list = NIL; root->join_info_list = NIL;
root->placeholder_list = NIL; root->placeholder_list = NIL;
root->fkey_list = NIL;
root->initial_rels = NIL; root->initial_rels = NIL;
/* /*
...@@ -205,6 +206,14 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -205,6 +206,14 @@ query_planner(PlannerInfo *root, List *tlist,
*/ */
create_lateral_join_info(root); create_lateral_join_info(root);
/*
* Match foreign keys to equivalence classes and join quals. This must be
* done after finalizing equivalence classes, and it's useful to wait till
* after join removal so that we can skip processing foreign keys
* involving removed relations.
*/
match_foreign_keys_to_quals(root);
/* /*
* Look for join OR clauses that we can extract single-relation * Look for join OR clauses that we can extract single-relation
* restriction OR clauses from. * restriction OR clauses from.
......
...@@ -52,6 +52,8 @@ int constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION; ...@@ -52,6 +52,8 @@ int constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
get_relation_info_hook_type get_relation_info_hook = NULL; get_relation_info_hook_type get_relation_info_hook = NULL;
static void get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
Relation relation);
static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel, static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
List *idxExprs); List *idxExprs);
static int32 get_rel_data_width(Relation rel, int32 *attr_widths); static int32 get_rel_data_width(Relation rel, int32 *attr_widths);
...@@ -77,6 +79,8 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index, ...@@ -77,6 +79,8 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
* pages number of pages * pages number of pages
* tuples number of tuples * tuples number of tuples
* *
* Also, add information about the relation's foreign keys to root->fkey_list.
*
* Also, initialize the attr_needed[] and attr_widths[] arrays. In most * Also, initialize the attr_needed[] and attr_widths[] arrays. In most
* cases these are left as zeroes, but sometimes we need to compute attr * cases these are left as zeroes, but sometimes we need to compute attr
* widths here, and we may as well cache the results for costsize.c. * widths here, and we may as well cache the results for costsize.c.
...@@ -403,6 +407,9 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, ...@@ -403,6 +407,9 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
rel->fdwroutine = NULL; rel->fdwroutine = NULL;
} }
/* Collect info about relation's foreign keys, if relevant */
get_relation_foreign_keys(root, rel, relation);
heap_close(relation, NoLock); heap_close(relation, NoLock);
/* /*
...@@ -414,6 +421,97 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, ...@@ -414,6 +421,97 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
(*get_relation_info_hook) (root, relationObjectId, inhparent, rel); (*get_relation_info_hook) (root, relationObjectId, inhparent, rel);
} }
/*
* get_relation_foreign_keys -
* Retrieves foreign key information for a given relation.
*
* ForeignKeyOptInfos for relevant foreign keys are created and added to
* root->fkey_list. We do this now while we have the relcache entry open.
* We could sometimes avoid making useless ForeignKeyOptInfos if we waited
* until all RelOptInfos have been built, but the cost of re-opening the
* relcache entries would probably exceed any savings.
*/
static void
get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
Relation relation)
{
List *rtable = root->parse->rtable;
List *cachedfkeys;
ListCell *lc;
/*
* If it's not a baserel, we don't care about its FKs. Also, if the query
* references only a single relation, we can skip the lookup since no FKs
* could satisfy the requirements below.
*/
if (rel->reloptkind != RELOPT_BASEREL ||
list_length(rtable) < 2)
return;
/*
* Extract data about relation's FKs from the relcache. Note that this
* list belongs to the relcache and might disappear in a cache flush, so
* we must not do any further catalog access within this function.
*/
cachedfkeys = RelationGetFKeyList(relation);
/*
* Figure out which FKs are of interest for this query, and create
* ForeignKeyOptInfos for them. We want only FKs that reference some
* other RTE of the current query. In queries containing self-joins,
* there might be more than one other RTE for a referenced table, and we
* should make a ForeignKeyOptInfo for each occurrence.
*
* Ideally, we would ignore RTEs that correspond to non-baserels, but it's
* too hard to identify those here, so we might end up making some useless
* ForeignKeyOptInfos. If so, match_foreign_keys_to_quals() will remove
* them again.
*/
foreach(lc, cachedfkeys)
{
ForeignKeyCacheInfo *cachedfk = (ForeignKeyCacheInfo *) lfirst(lc);
Index rti;
ListCell *lc2;
/* conrelid should always be that of the table we're considering */
Assert(cachedfk->conrelid == RelationGetRelid(relation));
/* Scan to find other RTEs matching confrelid */
rti = 0;
foreach(lc2, rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
ForeignKeyOptInfo *info;
rti++;
/* Ignore if not the correct table */
if (rte->rtekind != RTE_RELATION ||
rte->relid != cachedfk->confrelid)
continue;
/* Ignore self-referential FKs; we only care about joins */
if (rti == rel->relid)
continue;
/* OK, let's make an entry */
info = makeNode(ForeignKeyOptInfo);
info->con_relid = rel->relid;
info->ref_relid = rti;
info->nkeys = cachedfk->nkeys;
memcpy(info->conkey, cachedfk->conkey, sizeof(info->conkey));
memcpy(info->confkey, cachedfk->confkey, sizeof(info->confkey));
memcpy(info->conpfeqop, cachedfk->conpfeqop, sizeof(info->conpfeqop));
/* zero out fields to be filled by match_foreign_keys_to_quals */
info->nmatched_ec = 0;
info->nmatched_rcols = 0;
info->nmatched_ri = 0;
memset(info->eclass, 0, sizeof(info->eclass));
memset(info->rinfos, 0, sizeof(info->rinfos));
root->fkey_list = lappend(root->fkey_list, info);
}
}
}
/* /*
* infer_arbiter_indexes - * infer_arbiter_indexes -
* Determine the unique indexes used to arbitrate speculative insertion. * Determine the unique indexes used to arbitrate speculative insertion.
......
...@@ -1264,8 +1264,8 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, ...@@ -1264,8 +1264,8 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel,
/* Estimate the number of rows returned by the parameterized join */ /* Estimate the number of rows returned by the parameterized join */
rows = get_parameterized_joinrel_size(root, joinrel, rows = get_parameterized_joinrel_size(root, joinrel,
outer_path->rows, outer_path,
inner_path->rows, inner_path,
sjinfo, sjinfo,
*restrict_clauses); *restrict_clauses);
......
...@@ -1049,6 +1049,10 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) ...@@ -1049,6 +1049,10 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else else
relation->rd_rsdesc = NULL; relation->rd_rsdesc = NULL;
/* foreign key data is not loaded till asked for */
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
/* /*
* if it's an index, initialize index-related information * if it's an index, initialize index-related information
*/ */
...@@ -2030,11 +2034,12 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) ...@@ -2030,11 +2034,12 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
else else
FreeTupleDesc(relation->rd_att); FreeTupleDesc(relation->rd_att);
} }
FreeTriggerDesc(relation->trigdesc);
list_free_deep(relation->rd_fkeylist);
list_free(relation->rd_indexlist); list_free(relation->rd_indexlist);
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);
FreeTriggerDesc(relation->trigdesc);
if (relation->rd_options) if (relation->rd_options)
pfree(relation->rd_options); pfree(relation->rd_options);
if (relation->rd_indextuple) if (relation->rd_indextuple)
...@@ -3803,6 +3808,147 @@ CheckConstraintCmp(const void *a, const void *b) ...@@ -3803,6 +3808,147 @@ CheckConstraintCmp(const void *a, const void *b)
return strcmp(ca->ccname, cb->ccname); return strcmp(ca->ccname, cb->ccname);
} }
/*
* RelationGetFKeyList -- get a list of foreign key info for the relation
*
* Returns a list of ForeignKeyCacheInfo structs, one per FK constraining
* the given relation. This data is a direct copy of relevant fields from
* pg_constraint. The list items are in no particular order.
*
* CAUTION: the returned list is part of the relcache's data, and could
* vanish in a relcache entry reset. Callers must inspect or copy it
* before doing anything that might trigger a cache flush, such as
* system catalog accesses. copyObject() can be used if desired.
* (We define it this way because current callers want to filter and
* modify the list entries anyway, so copying would be a waste of time.)
*/
List *
RelationGetFKeyList(Relation relation)
{
List *result;
Relation conrel;
SysScanDesc conscan;
ScanKeyData skey;
HeapTuple htup;
List *oldlist;
MemoryContext oldcxt;
/* Quick exit if we already computed the list. */
if (relation->rd_fkeyvalid)
return relation->rd_fkeylist;
/* Fast path: if it doesn't have any triggers, it can't have FKs */
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);
ForeignKeyCacheInfo *info;
Datum adatum;
bool isnull;
ArrayType *arr;
int nelem;
/* consider only foreign keys */
if (constraint->contype != CONSTRAINT_FOREIGN)
continue;
info = makeNode(ForeignKeyCacheInfo);
info->conrelid = constraint->conrelid;
info->confrelid = constraint->confrelid;
/* Extract data from conkey field */
adatum = fastgetattr(htup, Anum_pg_constraint_conkey,
conrel->rd_att, &isnull);
if (isnull)
elog(ERROR, "null conkey for rel %s",
RelationGetRelationName(relation));
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem < 1 ||
nelem > INDEX_MAX_KEYS ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != INT2OID)
elog(ERROR, "conkey is not a 1-D smallint array");
info->nkeys = nelem;
memcpy(info->conkey, ARR_DATA_PTR(arr), nelem * sizeof(AttrNumber));
/* Likewise for confkey */
adatum = fastgetattr(htup, Anum_pg_constraint_confkey,
conrel->rd_att, &isnull);
if (isnull)
elog(ERROR, "null confkey for rel %s",
RelationGetRelationName(relation));
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem != info->nkeys ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != INT2OID)
elog(ERROR, "confkey is not a 1-D smallint array");
memcpy(info->confkey, ARR_DATA_PTR(arr), nelem * sizeof(AttrNumber));
/* Likewise for conpfeqop */
adatum = fastgetattr(htup, Anum_pg_constraint_conpfeqop,
conrel->rd_att, &isnull);
if (isnull)
elog(ERROR, "null conpfeqop for rel %s",
RelationGetRelationName(relation));
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem != info->nkeys ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "conpfeqop is not a 1-D OID array");
memcpy(info->conpfeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid));
/* Add FK's node to the result list */
result = lappend(result, info);
}
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 = copyObject(result);
relation->rd_fkeyvalid = true;
MemoryContextSwitchTo(oldcxt);
/* Don't leak the old list, if there is one */
list_free_deep(oldlist);
return result;
}
/* /*
* RelationGetIndexList -- get a list of OIDs of indexes on this relation * RelationGetIndexList -- get a list of OIDs of indexes on this relation
* *
...@@ -4892,7 +5038,8 @@ load_relcache_init_file(bool shared) ...@@ -4892,7 +5038,8 @@ load_relcache_init_file(bool shared)
* format is complex and subject to change). They must be rebuilt if * format is complex and subject to change). They must be rebuilt if
* needed by RelationCacheInitializePhase3. This is not expected to * needed by RelationCacheInitializePhase3. This is not expected to
* be a big performance hit since few system catalogs have such. Ditto * be a big performance hit since few system catalogs have such. Ditto
* for index expressions, predicates, exclusion info, and FDW info. * for RLS policy data, index expressions, predicates, exclusion info,
* and FDW info.
*/ */
rel->rd_rules = NULL; rel->rd_rules = NULL;
rel->rd_rulescxt = NULL; rel->rd_rulescxt = NULL;
...@@ -4914,6 +5061,8 @@ load_relcache_init_file(bool shared) ...@@ -4914,6 +5061,8 @@ load_relcache_init_file(bool shared)
else else
rel->rd_refcnt = 0; rel->rd_refcnt = 0;
rel->rd_indexvalid = 0; rel->rd_indexvalid = 0;
rel->rd_fkeylist = NIL;
rel->rd_fkeyvalid = false;
rel->rd_indexlist = NIL; rel->rd_indexlist = NIL;
rel->rd_oidindex = InvalidOid; rel->rd_oidindex = InvalidOid;
rel->rd_replidindex = InvalidOid; rel->rd_replidindex = InvalidOid;
......
...@@ -223,6 +223,7 @@ typedef enum NodeTag ...@@ -223,6 +223,7 @@ 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,
...@@ -478,7 +479,8 @@ typedef enum NodeTag ...@@ -478,7 +479,8 @@ typedef enum NodeTag
T_InlineCodeBlock, /* in nodes/parsenodes.h */ T_InlineCodeBlock, /* in nodes/parsenodes.h */
T_FdwRoutine, /* in foreign/fdwapi.h */ T_FdwRoutine, /* in foreign/fdwapi.h */
T_IndexAmRoutine, /* in access/amapi.h */ T_IndexAmRoutine, /* in access/amapi.h */
T_TsmRoutine /* in access/tsmapi.h */ T_TsmRoutine, /* in access/tsmapi.h */
T_ForeignKeyCacheInfo /* in utils/rel.h */
} NodeTag; } NodeTag;
/* /*
......
...@@ -251,6 +251,8 @@ typedef struct PlannerInfo ...@@ -251,6 +251,8 @@ typedef struct PlannerInfo
List *placeholder_list; /* list of PlaceHolderInfos */ List *placeholder_list; /* list of PlaceHolderInfos */
List *fkey_list; /* list of ForeignKeyOptInfos */
List *query_pathkeys; /* desired pathkeys for query_planner() */ List *query_pathkeys; /* desired pathkeys for query_planner() */
List *group_pathkeys; /* groupClause pathkeys, if any */ List *group_pathkeys; /* groupClause pathkeys, if any */
...@@ -622,6 +624,36 @@ typedef struct IndexOptInfo ...@@ -622,6 +624,36 @@ 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
*
* The per-FK-column arrays can be fixed-size because we allow at most
* INDEX_MAX_KEYS columns in a foreign key constraint. Each array has
* nkeys valid entries.
*/
typedef struct ForeignKeyOptInfo
{
NodeTag type;
/* Basic data about the foreign key (fetched from catalogs): */
Index con_relid; /* RT index of the referencing table */
Index ref_relid; /* RT index of the referenced table */
int nkeys; /* number of columns in the foreign key */
AttrNumber conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
AttrNumber confkey[INDEX_MAX_KEYS]; /* cols in referenced table */
Oid conpfeqop[INDEX_MAX_KEYS]; /* PK = FK operator OIDs */
/* Derived info about whether FK's equality conditions match the query: */
int nmatched_ec; /* # of FK cols matched by ECs */
int nmatched_rcols; /* # of FK cols matched by non-EC rinfos */
int nmatched_ri; /* total # of non-EC rinfos matched to FK */
/* Pointer to eclass matching each column's condition, if there is one */
struct EquivalenceClass *eclass[INDEX_MAX_KEYS];
/* List of non-EC RestrictInfos matching each column's condition */
List *rinfos[INDEX_MAX_KEYS];
} ForeignKeyOptInfo;
/* /*
* EquivalenceClasses * EquivalenceClasses
......
...@@ -167,8 +167,8 @@ extern double get_parameterized_baserel_size(PlannerInfo *root, ...@@ -167,8 +167,8 @@ extern double get_parameterized_baserel_size(PlannerInfo *root,
List *param_clauses); List *param_clauses);
extern double get_parameterized_joinrel_size(PlannerInfo *root, extern double get_parameterized_joinrel_size(PlannerInfo *root,
RelOptInfo *rel, RelOptInfo *rel,
double outer_rows, Path *outer_path,
double inner_rows, Path *inner_path,
SpecialJoinInfo *sjinfo, SpecialJoinInfo *sjinfo,
List *restrict_clauses); List *restrict_clauses);
extern void set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel, extern void set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
......
...@@ -140,6 +140,9 @@ extern List *generate_join_implied_equalities_for_ecs(PlannerInfo *root, ...@@ -140,6 +140,9 @@ extern List *generate_join_implied_equalities_for_ecs(PlannerInfo *root,
Relids outer_relids, Relids outer_relids,
RelOptInfo *inner_rel); RelOptInfo *inner_rel);
extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2); extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2);
extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root,
ForeignKeyOptInfo *fkinfo,
int colno);
extern void add_child_rel_equivalences(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root,
AppendRelInfo *appinfo, AppendRelInfo *appinfo,
RelOptInfo *parent_rel, RelOptInfo *parent_rel,
......
...@@ -95,6 +95,7 @@ extern RestrictInfo *build_implied_join_equality(Oid opno, ...@@ -95,6 +95,7 @@ extern RestrictInfo *build_implied_join_equality(Oid opno,
Expr *item2, Expr *item2,
Relids qualscope, Relids qualscope,
Relids nullable_relids); Relids nullable_relids);
extern void match_foreign_keys_to_quals(PlannerInfo *root);
/* /*
* prototypes for plan/analyzejoins.c * prototypes for plan/analyzejoins.c
......
...@@ -90,6 +90,10 @@ typedef struct RelationData ...@@ -90,6 +90,10 @@ typedef struct RelationData
/* use "struct" here to avoid needing to include rowsecurity.h: */ /* use "struct" here to avoid needing to include rowsecurity.h: */
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */ struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
/* data managed by RelationGetFKeyList: */
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
/* data managed by RelationGetIndexList: */ /* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */ List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */ Oid rd_oidindex; /* OID of unique index on OID, if any */
...@@ -170,6 +174,34 @@ typedef struct RelationData ...@@ -170,6 +174,34 @@ typedef struct RelationData
struct PgStat_TableStatus *pgstat_info; /* statistics collection area */ struct PgStat_TableStatus *pgstat_info; /* statistics collection area */
} RelationData; } RelationData;
/*
* ForeignKeyCacheInfo
* Information the relcache can cache about foreign key constraints
*
* This is basically just an image of relevant columns from pg_constraint.
* We make it a subclass of Node so that copyObject() can be used on a list
* of these, but we also ensure it is a "flat" object without substructure,
* so that list_free_deep() is sufficient to free such a list.
* The per-FK-column arrays can be fixed-size because we allow at most
* INDEX_MAX_KEYS columns in a foreign key constraint.
*
* Currently, we only cache fields of interest to the planner, but the
* set of fields could be expanded in future.
*/
typedef struct ForeignKeyCacheInfo
{
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 */
/* these arrays each have nkeys valid entries: */
AttrNumber conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
AttrNumber confkey[INDEX_MAX_KEYS]; /* cols in referenced table */
Oid conpfeqop[INDEX_MAX_KEYS]; /* PK = FK operator OIDs */
} ForeignKeyCacheInfo;
/* /*
* StdRdOptions * StdRdOptions
* Standard contents of rd_options for heaps and generic indexes. * Standard contents of rd_options for heaps and generic indexes.
......
...@@ -37,6 +37,7 @@ extern void RelationClose(Relation relation); ...@@ -37,6 +37,7 @@ extern void RelationClose(Relation relation);
/* /*
* Routines to compute/retrieve additional cached information * Routines to compute/retrieve additional cached information
*/ */
extern List *RelationGetFKeyList(Relation relation);
extern List *RelationGetIndexList(Relation relation); extern List *RelationGetIndexList(Relation relation);
extern Oid RelationGetOidIndex(Relation relation); extern Oid RelationGetOidIndex(Relation relation);
extern Oid RelationGetReplicaIndex(Relation relation); extern Oid RelationGetReplicaIndex(Relation relation);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment