Hi,

attached is a first version of a patch that aims to improve cardinality estimates of joins by matching foreign keys between the tables (which was ignored by the planner until now).

This significantly improves estimates when joining two tables using multi-column conditions, matching a foreign key between the tables.

Consider for example this simple case

CREATE TABLE dim (a int, b int, primary key (a,b));
CREATE TABLE fact (a int, b int, foreign key (a,b) references dim(a,b));

INSERT INTO dim SELECT i,i FROM generate_series(1,1000000) s(i);
INSERT INTO fact SELECT i,i FROM generate_series(1,1000000) s(i);

ANALYZE dim;
ANALYZE fact;

EXPLAIN SELECT * FROM fact f JOIN dim d USING (a,b);

                           QUERY PLAN
---------------------------------------------------------------------------
Hash Join  (cost=29425.00..51350.01 rows=1 width=16)
  Hash Cond: ((f.a = d.a) AND (f.b = d.b))
  ->  Seq Scan on fact f (cost=0.00..14425.00 rows=1000000 width=8)
  ->  Hash  (cost=14425.00..14425.00 rows=1000000 width=8)
      ->  Seq Scan on dim d (cost=0.00..14425.00 rows=1000000 width=8)
(5 rows)

which is of course completely off, because the query produces 1M rows.

In practice, underestimates like this often cause much more serious issues in the subsequent steps - for example the next join would probably be executed as nested loop, which makes sense with a single row but is an awful choice with 1M rows.

With the patch, the planner realizes there is a matching foreign key, and tweaks the selectivities in calc_joinrel_size_estimate().

                             QUERY PLAN
-------------------------------------------------------------------------
 Hash Join  (cost=29426.25..250323877.62 rows=1000050 width=8)
   Hash Cond: ((fact.a = dim.a) AND (fact.b = dim.b))
   ->  Seq Scan on fact  (cost=0.00..14425.50 rows=1000050 width=8)
   ->  Hash  (cost=14425.50..14425.50 rows=1000050 width=8)
         ->  Seq Scan on dim  (cost=0.00..14425.50 rows=1000050 width=8)
(5 rows)


There are a few unsolved questions/issues:

(1) The current patch only does the trick when the FK matches the
    conditions perfectly - when there are no missing columns (present
    in the FK, not covered by a condition).

    I believe this might be relaxed in both directions. When the
    conditions don't cover all the FK columns, we know there's at least
    one matching row (and we can estimate the number of matches). In
    the other direction, we can estimate just the 'extra' conditions.

(2) Adding further conditions may further break the estimates, for
    example after adding "WHERE d.a = d.b" this happens

                               QUERY PLAN
------------------------------------------------------------------------
 Hash Join  (cost=16987.50..33931.50 rows=25 width=8)
   Hash Cond: (f.a = d.a)
   ->  Seq Scan on fact f  (cost=0.00..16925.00 rows=5000 width=8)
         Filter: (a = b)
   ->  Hash  (cost=16925.00..16925.00 rows=5000 width=8)
         ->  Seq Scan on dim d  (cost=0.00..16925.00 rows=5000 width=8)
               Filter: (a = b)
(7 rows)

    One issue is that "a=b" condition is poorly estimated due to
    correlation (which might be improved by multi-variate stats). It
    however removes one of the conditions from the join restrict list,
    so it only contains "f.a = d.a" and thus only covers one of the FK
    columns, and thus the patch fails to detect the FK :-(

    Not sure how to fix this - one way might be performing the check
    sooner, before the second join clause is removed (not sure where
    that happens). Another option is reconstructing clauses somehow.


regards

--
Tomas Vondra                  http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>From 5450be4c3af455ddae37b8949b293f8c01fc5bdd Mon Sep 17 00:00:00 2001
From: Tomas Vondra <t...@fuzzy.cz>
Date: Wed, 1 Apr 2015 22:26:46 +0200
Subject: [PATCH] improve cardinality estimation of joins on foreign keys

When estimating joins with multi-column join clauses, matching
FK constraints, the cardinality estimate is incorrect because
it multiplies selectivities of the clauses.

This may be significant issue for example with models involving
junction tables with multi-column primary keys, or other models
using multi-column primary keys.

CREATE TABLE dimension (a INT, b INT, PRIMARY KEY (a,b));

CREATE TABLE fact (a INT, b INT, FOREIGN KEY (a,b)
                              REFERENCES dimension (a,b));

INSERT INTO dimension SELECT i,i
                        FROM generate_series(1,1000) s(i);

INSERT INTO fact SELECT mod(i,1000)+1, mod(i,1000)+1
                   FROM generate_series(1,1000000) s(i);

ANALYZE;

EXPLAIN SELECT * FROM fact JOIN dimension USING (a,b);

This should estimate the join cardinality as 1.000.000, but it
the actual estimate is 1.000 (because of the multiplication).
The patch fixes this by matching the join clauses and foreign
key constraints in calc_joinrel_size_estimate().
---
 src/backend/nodes/outfuncs.c              |  13 ++
 src/backend/optimizer/path/costsize.c     |  14 ++
 src/backend/optimizer/plan/analyzejoins.c | 249 ++++++++++++++++++++++++++++++
 src/backend/optimizer/util/plancat.c      |  80 ++++++++++
 src/backend/utils/cache/relcache.c        |  69 +++++++++
 src/include/nodes/nodes.h                 |   1 +
 src/include/nodes/relation.h              |  15 ++
 src/include/optimizer/paths.h             |   2 +
 src/include/optimizer/planmain.h          |   3 +
 src/include/utils/rel.h                   |   4 +
 src/include/utils/relcache.h              |   1 +
 11 files changed, 451 insertions(+)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 385b289..2fec460 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1837,6 +1837,16 @@ _outIndexOptInfo(StringInfo str, const IndexOptInfo *node)
 }
 
 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)
 {
 	/*
@@ -3178,6 +3188,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/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1a0d358..f4c353a 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3647,6 +3647,7 @@ calc_joinrel_size_estimate(PlannerInfo *root,
 	Selectivity jselec;
 	Selectivity pselec;
 	double		nrows;
+	int		fkey_join;
 
 	/*
 	 * Compute joinclause selectivity.  Note that we are only considering
@@ -3688,12 +3689,17 @@ calc_joinrel_size_estimate(PlannerInfo *root,
 										jointype,
 										sjinfo);
 
+		/* we only need join quals to determine this */
+		fkey_join = join_matches_fkey(root, sjinfo, joinquals);
+
 		/* Avoid leaking a lot of ListCells */
 		list_free(joinquals);
 		list_free(pushedquals);
 	}
 	else
 	{
+		fkey_join = join_matches_fkey(root, sjinfo, restrictlist);
+
 		jselec = clauselist_selectivity(root,
 										restrictlist,
 										0,
@@ -3703,6 +3709,14 @@ calc_joinrel_size_estimate(PlannerInfo *root,
 	}
 
 	/*
+	 * If it's a join on foreign key, tweak the selectivity accordingly. If there's
+	 * outer table references inner (PK on inner), use 1/inner_rows, otherwise use
+	 * 1/outer_rows.
+	 */
+	if (fkey_join)
+		jselec = (fkey_join == 1) ? (1.0 / inner_rows) : (1.0 / outer_rows);
+
+	/*
 	 * Basically, we multiply size of Cartesian product by selectivity.
 	 *
 	 * If we are doing an outer join, take that into account: the joinqual
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 11d3933..3f601cc 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -564,6 +564,31 @@ remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved)
 	return result;
 }
 
+/*
+ * rel_has_unique_index
+ *		Returns true if rel has at least one unique index.
+ *
+ * Fast pre-check for specialjoin_is_fk_join(), quickly eliminating joins that
+ * can't be FK joins.
+ */
+bool
+rel_has_unique_index(PlannerInfo *root, RelOptInfo *rel)
+{
+	ListCell *lc;
+
+	if (rel->rtekind != RTE_RELATION)
+		return false;
+
+	foreach (lc, rel->indexlist)
+	{
+		IndexOptInfo *iinfo = (IndexOptInfo *)lfirst(lc);
+
+		if (iinfo->unique)
+			return true;
+	}
+
+	return false;
+}
 
 /*
  * query_supports_distinctness - could the query possibly be proven distinct
@@ -749,3 +774,227 @@ distinct_col_search(int colno, List *colnos, List *opids)
 	}
 	return InvalidOid;
 }
+
+bool
+has_matching_fkey(RelOptInfo *rel, Oid freloid, List *clauses,
+				  bool reverse)
+{
+	ListCell   *lc;
+	bool		match = false;
+
+	foreach (lc, rel->fkeylist)
+	{
+		int					i;
+		bool			   *matches;
+		ForeignKeyOptInfo  *fkinfo = (ForeignKeyOptInfo *)lfirst(lc);
+		int				   *conkey = fkinfo->conkeys,
+						   *confkey = fkinfo->confkeys;
+		Oid				   *conpfeqop = fkinfo->conpfeqop;
+		ListCell		   *lc2;
+
+		if (fkinfo->confrelid != freloid)
+			continue;
+
+		/* one flag for each key of the foreign key constraint */
+		matches = (bool*)palloc0(fkinfo->nkeys);
+
+		foreach (lc2, clauses)
+		{
+			RestrictInfo *rinfo = (RestrictInfo *)lfirst(lc2);
+			OpExpr *clause;
+			bool	clause_match = false;
+
+			Var *outer_var, *inner_var;
+
+			/* FIXME should result in 'false' answer */
+			if (! IsA(rinfo, RestrictInfo))
+				break;
+
+			if (! IsA(rinfo->clause, OpExpr))
+				break;
+
+			clause = (OpExpr*)rinfo->clause;
+
+			if (list_length(clause->args) != 2)
+				break;
+
+			if ((rinfo->outer_is_left && (! reverse)) || (!rinfo->outer_is_left && reverse))
+			{
+				outer_var = list_nth(clause->args, 0);
+				inner_var = list_nth(clause->args, 1);
+			}
+			else
+			{
+				outer_var = list_nth(clause->args, 1);
+				inner_var = list_nth(clause->args, 0);
+			}
+
+			if ((! IsA(outer_var, Var)) || (! IsA(inner_var, Var)))
+				break;
+
+			for (i = 0; i < fkinfo->nkeys; i++)
+			{
+				if ((inner_var->varattno == confkey[i]) &&
+					(outer_var->varattno == conkey[i]) &&
+					(clause->opno == conpfeqop[i]))
+				{
+					clause_match = true;
+					matches[i] = true;
+				}
+			}
+
+			if (! clause_match)
+			{
+				/* reset the matches */
+				elog(WARNING, "clause does not match");
+				memset(matches, 0, fkinfo->nkeys);
+				break;
+			}
+		}
+
+		match = true;
+
+		for (i = 0; i < fkinfo->nkeys; i++)
+			match &= matches[i];
+
+		break;
+	}
+
+	return match;
+}
+
+
+/*
+ * join_is_on_fk
+ *
+ * blah blah blah
+ */
+bool
+join_matches_fkey(PlannerInfo *root, SpecialJoinInfo *sjinfo,
+			  List * restrictlist)
+{
+	int			innerrelid;
+	int			outerrelid;
+	RelOptInfo *innerrel;
+	RelOptInfo *outerrel;
+	Relids		joinrelids;
+
+	ListCell   *lc;
+
+	bool		inner_singleton;
+	bool		outer_singleton;
+
+	Relids		varnos = NULL;
+
+	/* if there's more than 1 inner relation involved then punt */
+	inner_singleton
+		= bms_get_singleton_member(sjinfo->min_righthand, &innerrelid);
+
+	outer_singleton
+		= bms_get_singleton_member(sjinfo->min_lefthand, &outerrelid);
+
+	/* we need at least one singleton (otherwise we can't get PK) */
+	if ((! inner_singleton) && (! outer_singleton))
+		return false;
+
+	if ((! inner_singleton) || (! outer_singleton))
+	{
+		/* collect attnos from the clauses */
+		foreach (lc, restrictlist)
+		{
+			RestrictInfo *rinfo = (RestrictInfo*)lfirst(lc);
+			OpExpr *clause;
+			ListCell *lc2;
+
+			Assert(IsA(rinfo, RestrictInfo));
+
+			/* get the clause, check that it has proper structure */
+			clause = (OpExpr*)rinfo->clause;
+
+			if (! IsA(clause, OpExpr))
+				return false;
+			else if (list_length(clause->args) != 2)
+				return false;
+
+			foreach (lc2, clause->args)
+			{
+				Var *var = (Var*)lfirst(lc2);
+				if (! IsA(var, Var))
+					return false;
+
+				varnos = bms_add_member(varnos, var->varno);
+			}
+		}
+
+		if (bms_num_members(varnos) != 2)
+			return false;
+
+		if (inner_singleton)
+		{
+			varnos = bms_del_member(varnos, innerrelid);
+			outerrelid = bms_singleton_member(varnos);
+		}
+
+		if (outer_singleton)
+		{
+			varnos = bms_del_member(varnos, outerrelid);
+			innerrelid = bms_singleton_member(varnos);
+		}
+	}
+
+	innerrel = find_base_rel(root, innerrelid);
+
+	if (innerrel->reloptkind != RELOPT_BASEREL)
+		return false;
+
+	outerrel = find_base_rel(root, outerrelid);
+
+	if (outerrel->reloptkind != RELOPT_BASEREL)
+		return false;
+
+	/*
+	 * Before we go to the effort of pulling out the join condition's columns,
+	 * make a quick check to eliminate cases in which we will surely be unable
+	 * to prove the join is a FK joinuniqueness of the innerrel.
+	 */
+	if ((! rel_has_unique_index(root, innerrel)) &&
+		(! rel_has_unique_index(root, outerrel)))
+		return false;
+
+	joinrelids = NULL;
+	joinrelids = bms_add_member(joinrelids, outerrelid);
+	joinrelids = bms_add_member(joinrelids, innerrelid);
+
+	/*
+	 * Search for clauses that constrain the inner rel against either the
+	 * outer rel.
+	 */
+	foreach(lc, restrictlist)
+	{
+		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc);
+
+		/* needed to set the outer_is_left in the RestrictInfo */
+		clause_sides_match_join(restrictinfo,
+								outerrel->relids, innerrel->relids);
+	}
+
+	/*
+	 * SELECT * FROM pg_constraint
+	 *  WHERE contype = 'f' AND conrelid = (outer) AND confrelid = (inner)
+	 *
+	 * conkey = confkey (using conpfeqop)
+	 */
+
+	if (rel_has_unique_index(root, innerrel) &&
+		has_matching_fkey(root->simple_rel_array[outerrelid],
+							 root->simple_rte_array[innerrelid]->relid,
+							 restrictlist, false))
+		return 1;
+	else if (rel_has_unique_index(root, outerrel) &&
+		has_matching_fkey(root->simple_rel_array[innerrelid],
+							 root->simple_rte_array[outerrelid]->relid,
+							 restrictlist, true))
+		return 2;
+
+	return 0;
+}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8abed2a..fd31274 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -25,6 +25,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
+#include "catalog/pg_constraint.h"
 #include "catalog/heap.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -39,6 +40,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"
 
@@ -89,6 +91,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	Relation	relation;
 	bool		hasindex;
 	List	   *indexinfos = NIL;
+	List	   *fkinfos = NIL;
 
 	/*
 	 * We need not lock the relation since it was already locked, either by
@@ -377,6 +380,83 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 	rel->indexlist = indexinfos;
 
+	/* TODO Can we do something like (hasindex) here? Is it necessary? */
+	if (true)
+	{
+		List	   *fkoidlist;
+		ListCell   *l;
+
+		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 the fdwroutine info using the relcache, while we have it */
 	if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 		rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 4ea01d1..1149aab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3907,6 +3907,73 @@ RelationGetIndexList(Relation relation)
 }
 
 /*
+ * RelationGetFKeyList -- get a list of foreign key oids
+ *
+ * TODO blah blah blah
+ */
+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_fkeyvalid)
+		return list_copy(relation->rd_fkeylist);
+
+	/*
+	 * 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_index for entries having indrelid = 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 index'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);
+	relation->rd_fkeyvalid = true;
+	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
  *
@@ -4875,6 +4942,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_indexattr = NULL;
 		rel->rd_keyattr = NULL;
 		rel->rd_idattr = NULL;
+		rel->rd_fkeylist = NIL;
+		rel->rd_fkeyvalid = false;
 		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 38469ef..696d043 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -215,6 +215,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 401a686..d7b6f1c 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -452,6 +452,7 @@ typedef struct RelOptInfo
 	Relids		lateral_relids; /* minimum parameterization of 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;
@@ -543,6 +544,20 @@ typedef struct IndexOptInfo
 	bool		amhasgetbitmap; /* does AM have amgetbitmap interface? */
 } IndexOptInfo;
 
+/* TODO add info*/
+typedef struct ForeignKeyOptInfo
+{
+	NodeTag		type;
+
+	Oid			conrelid;
+	Oid			confrelid;
+
+	int			nkeys;
+	int		   *conkeys;
+	int		   *confkeys;
+	Oid		   *conpfeqop;
+
+} ForeignKeyOptInfo;
 
 /*
  * EquivalenceClasses
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 6cad92e..7aae736 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -64,6 +64,8 @@ extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause,
 							int indexcol,
 							List **indexcolnos,
 							bool *var_on_left_p);
+extern bool has_matching_fkey(RelOptInfo *rel, Oid freloid, List *clauses,
+							  bool reverse);
 
 /*
  * tidpath.h
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index fa72918..66d909a 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -123,6 +123,9 @@ extern RestrictInfo *build_implied_join_equality(Oid opno,
  * prototypes for plan/analyzejoins.c
  */
 extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
+extern bool rel_has_unique_index(PlannerInfo *root, RelOptInfo *rel);
+extern bool join_matches_fkey(PlannerInfo *root, SpecialJoinInfo *sjinfo,
+						  List *restrictlist);
 extern bool query_supports_distinctness(Query *query);
 extern bool query_is_distinct_for(Query *query, List *colnos, List *opids);
 
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 9e17d87..d58bfa6 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -80,6 +80,7 @@ typedef struct RelationData
 	bool		rd_isvalid;		/* relcache entry is valid */
 	char		rd_indexvalid;	/* state of rd_indexlist: 0 = not valid, 1 =
 								 * valid, 2 = temporarily forced */
+	bool		rd_fkeyvalid;	/* state of rd_fkeylist: 0 = not valid, 1 = valid */
 
 	/*
 	 * rd_createSubid is the ID of the highest subtransaction the rel has
@@ -113,6 +114,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 6953281..8878478 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);
-- 
1.9.3

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to