diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index e39c374f48..f783a49eba 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2137,6 +2137,16 @@ _outIndexOptInfo(StringInfo str, const IndexOptInfo *node) /* 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 _outEquivalenceClass(StringInfo str, const EquivalenceClass *node) { @@ -3607,6 +3617,9 @@ _outNode(StringInfo str, const void *obj) case T_IndexOptInfo: _outIndexOptInfo(str, obj); break; + case T_ForeignKeyOptInfo: + _outForeignKeyOptInfo(str, obj); + break; case T_EquivalenceClass: _outEquivalenceClass(str, obj); break; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 5bdeac0df6..0f291275dc 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -28,6 +28,7 @@ #include "catalog/dependency.h" #include "catalog/heap.h" #include "catalog/pg_am.h" +#include "catalog/pg_constraint.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -41,6 +42,7 @@ #include "rewrite/rewriteManip.h" #include "storage/bufmgr.h" #include "utils/lsyscache.h" +#include "utils/syscache.h" #include "utils/rel.h" #include "utils/snapmgr.h" @@ -94,6 +96,9 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, Relation relation; bool hasindex; List *indexinfos = NIL; + List *fkinfos = NIL; + List *fkoidlist; + ListCell *l; /* * We need not lock the relation since it was already locked, either by @@ -141,7 +146,6 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, if (hasindex) { List *indexoidlist; - ListCell *l; LOCKMODE lmode; indexoidlist = RelationGetIndexList(relation); @@ -388,6 +392,85 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, 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) + { + int i; + ArrayType *arr; + Datum adatum; + bool isnull; + int numkeys; + Oid fkoid = lfirst_oid(l); + + HeapTuple htup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fkoid)); + Form_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(htup); + + ForeignKeyOptInfo *info; + + 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*)palloc0(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); + numkeys = ARR_DIMS(arr)[0]; + info->confkeys = (int*)palloc0(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); + numkeys = ARR_DIMS(arr)[0]; + info->conpfeqop = (Oid*)palloc0(numkeys * sizeof(Oid)); + + for (i = 0; i < numkeys; i++) + info->conpfeqop[i] = ((Oid *) ARR_DATA_PTR(arr))[i]; + + info->nkeys = numkeys; + + ReleaseSysCache(htup); + + fkinfos = lcons(info, fkinfos); + } + + list_free(fkoidlist); + + rel->fkeylist = fkinfos; + /* Grab foreign-table info using the relcache, while we have it */ if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) { diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 130c06d81c..432feefa60 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -2031,6 +2031,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) FreeTupleDesc(relation->rd_att); } list_free(relation->rd_indexlist); + list_free(relation->rd_fkeylist); bms_free(relation->rd_indexattr); bms_free(relation->rd_keyattr); bms_free(relation->rd_idattr); @@ -3956,6 +3957,79 @@ RelationGetIndexList(Relation relation) 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 a new Oid into a sorted list of Oids, preserving ordering @@ -4920,6 +4994,7 @@ load_relcache_init_file(bool shared) rel->rd_indexattr = NULL; rel->rd_keyattr = NULL; rel->rd_idattr = NULL; + rel->rd_fkeylist = NIL; rel->rd_createSubid = InvalidSubTransactionId; rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; rel->rd_amcache = NULL; diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 3f22bdb5a8..84efa8e886 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -223,6 +223,7 @@ typedef enum NodeTag T_PlannerGlobal, T_RelOptInfo, T_IndexOptInfo, + T_ForeignKeyOptInfo, T_ParamPathInfo, T_Path, T_IndexPath, diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 5264d3cc76..d430f6e9fd 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -516,6 +516,7 @@ typedef struct RelOptInfo List *lateral_vars; /* LATERAL Vars and PHVs referenced by rel */ Relids lateral_referencers; /* rels that reference me laterally */ List *indexlist; /* list of IndexOptInfo */ + List *fkeylist; /* list of ForeignKeyOptInfo */ BlockNumber pages; /* size estimates derived from pg_class */ double tuples; double allvisfrac; @@ -621,6 +622,27 @@ typedef struct IndexOptInfo void (*amcostestimate) (); /* AM's cost estimator */ } 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 diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index f2bebf2c3d..51eb27a381 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -94,6 +94,9 @@ typedef struct RelationData Oid rd_oidindex; /* OID of unique index on OID, 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: */ Bitmapset *rd_indexattr; /* identifies columns used in indexes */ Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */ diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index 1b4830462d..7f07c26914 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -38,6 +38,7 @@ extern void RelationClose(Relation relation); * Routines to compute/retrieve additional cached information */ extern List *RelationGetIndexList(Relation relation); +extern List *RelationGetFKeyList(Relation relation); extern Oid RelationGetOidIndex(Relation relation); extern Oid RelationGetReplicaIndex(Relation relation); extern List *RelationGetIndexExpressions(Relation relation);