diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 207acea9472b05d57b9aa33f37d564d9e9045e94..7467e1a00e1feb1ed83d467b878c6603901c1af1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.49 2003/05/28 22:32:49 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.50 2003/06/15 22:51:45 tgl Exp $
  *
  * NOTES
  *	  XXX a few of the following functions are duplicated to handle
@@ -358,6 +358,21 @@ llast(List *l)
 	return lfirst(l);
 }
 
+/*
+ *	llastnode
+ *
+ *	Get the last node of l ... NIL if empty list
+ */
+List *
+llastnode(List *l)
+{
+	if (l == NIL)
+		return NIL;
+	while (lnext(l) != NIL)
+		l = lnext(l);
+	return l;
+}
+
 /*
  *	freeList
  *
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index cec7f09f0a91d9127249c8fe531e3c4082c7022f..f042e8a8d210333c4fcc05b7acb7be385a7f2957 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.207 2003/06/06 15:04:02 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.208 2003/06/15 22:51:45 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -933,6 +933,7 @@ _outIndexPath(StringInfo str, IndexPath *node)
 
 	WRITE_NODE_FIELD(indexinfo);
 	WRITE_NODE_FIELD(indexqual);
+	WRITE_NODE_FIELD(indexjoinclauses);
 	WRITE_ENUM_FIELD(indexscandir, ScanDirection);
 	WRITE_FLOAT_FIELD(rows, "%.2f");
 }
@@ -1034,6 +1035,7 @@ _outRestrictInfo(StringInfo str, RestrictInfo *node)
 {
 	WRITE_NODE_TYPE("RESTRICTINFO");
 
+	/* NB: this isn't a complete set of fields */
 	WRITE_NODE_FIELD(clause);
 	WRITE_BOOL_FIELD(ispusheddown);
 	WRITE_NODE_FIELD(subclauseindices);
@@ -1042,6 +1044,8 @@ _outRestrictInfo(StringInfo str, RestrictInfo *node)
 	WRITE_OID_FIELD(mergejoinoperator);
 	WRITE_OID_FIELD(left_sortop);
 	WRITE_OID_FIELD(right_sortop);
+	WRITE_NODE_FIELD(left_pathkey);
+	WRITE_NODE_FIELD(right_pathkey);
 	WRITE_OID_FIELD(hashjoinoperator);
 }
 
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index ace494ba20bad254c566dc177c09671599a5675d..fbbf983869cbb026257f5ae648220e19aed52360 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.143 2003/05/28 22:32:49 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.144 2003/06/15 22:51:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -59,10 +59,10 @@ static bool match_or_subclause_to_indexkey(RelOptInfo *rel,
 							   IndexOptInfo *index,
 							   Expr *clause);
 static List *group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index);
-static List *group_clauses_by_indexkey_for_join(RelOptInfo *rel,
-								IndexOptInfo *index,
-								Relids outer_relids,
-								bool isouterjoin);
+static List *group_clauses_by_indexkey_for_join(Query *root,
+								   RelOptInfo *rel, IndexOptInfo *index,
+								   Relids outer_relids,
+								   JoinType jointype, bool isouterjoin);
 static bool match_clause_to_indexcol(RelOptInfo *rel, IndexOptInfo *index,
 									 int indexcol, Oid opclass, Expr *clause);
 static bool match_join_clause_to_indexcol(RelOptInfo *rel, IndexOptInfo *index,
@@ -583,8 +583,10 @@ group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index)
  * will already have been generated for it.)
  */
 static List *
-group_clauses_by_indexkey_for_join(RelOptInfo *rel, IndexOptInfo *index,
-								   Relids outer_relids, bool isouterjoin)
+group_clauses_by_indexkey_for_join(Query *root,
+								   RelOptInfo *rel, IndexOptInfo *index,
+								   Relids outer_relids,
+								   JoinType jointype, bool isouterjoin)
 {
 	FastList	clausegroup_list;
 	bool		jfound = false;
@@ -629,6 +631,24 @@ group_clauses_by_indexkey_for_join(RelOptInfo *rel, IndexOptInfo *index,
 			}
 		}
 
+		/*
+		 * If we found join clauses in more than one joininfo list, we may
+		 * now have clauses that are known redundant.  Get rid of 'em.
+		 * (There is no point in looking at restriction clauses, because
+		 * remove_redundant_join_clauses will never think they are
+		 * redundant, so we do this before adding restriction clauses to
+		 * the clause group.)
+		 */
+		if (FastListValue(&clausegroup) != NIL)
+		{
+			List *nl;
+
+			nl = remove_redundant_join_clauses(root,
+											   FastListValue(&clausegroup),
+											   jointype);
+			FastListFromList(&clausegroup, nl);
+		}
+
 		/* We can also use plain restriction clauses for the rel */
 		foreach(i, rel->baserestrictinfo)
 		{
@@ -1461,9 +1481,11 @@ best_inner_indexscan(Query *root, RelOptInfo *rel,
 			List	   *clausegroups;
 
 			/* find useful clauses for this index and outerjoin set */
-			clausegroups = group_clauses_by_indexkey_for_join(rel,
+			clausegroups = group_clauses_by_indexkey_for_join(root,
+															  rel,
 															  index,
 															  index_outer_relids,
+															  jointype,
 															  isouterjoin);
 			if (clausegroups)
 			{
@@ -1520,7 +1542,7 @@ make_innerjoin_index_path(Query *root,
 			   *allclauses,
 			   *l;
 
-	/* XXX this code ought to be merged with create_index_path? */
+	/* XXX perhaps this code should be merged with create_index_path? */
 
 	pathnode->path.pathtype = T_IndexScan;
 	pathnode->path.parent = rel;
@@ -1535,12 +1557,25 @@ make_innerjoin_index_path(Query *root,
 	/* Convert RestrictInfo nodes to indexquals the executor can handle */
 	indexquals = expand_indexqual_conditions(index, clausegroups);
 
+	/*
+	 * Also make a flattened list of the RestrictInfo nodes; createplan.c
+	 * will need this later.  We assume here that we can destructively
+	 * modify the passed-in clausegroups list structure.
+	 */
+	allclauses = NIL;
+	foreach(l, clausegroups)
+	{
+		/* nconc okay here since same clause couldn't be in two sublists */
+		allclauses = nconc(allclauses, (List *) lfirst(l));
+	}
+
 	/*
 	 * Note that we are making a pathnode for a single-scan indexscan;
-	 * therefore, both indexinfo and indexqual should be single-element lists.
+	 * therefore, indexinfo and indexqual should be single-element lists.
 	 */
 	pathnode->indexinfo = makeList1(index);
 	pathnode->indexqual = makeList1(indexquals);
+	pathnode->indexjoinclauses = makeList1(allclauses);
 
 	/* We don't actually care what order the index scans in ... */
 	pathnode->indexscandir = NoMovementScanDirection;
@@ -1558,17 +1593,9 @@ make_innerjoin_index_path(Query *root,
 	 * linking them into different lists, it should be sufficient to use
 	 * pointer comparison to remove duplicates.)
 	 *
-	 * We assume we can destructively modify the input sublists.
-	 *
 	 * Always assume the join type is JOIN_INNER; even if some of the
 	 * join clauses come from other contexts, that's not our problem.
 	 */
-	allclauses = NIL;
-	foreach(l, clausegroups)
-	{
-		/* nconc okay here since same clause couldn't be in two sublists */
-		allclauses = nconc(allclauses, (List *) lfirst(l));
-	}
 	allclauses = set_ptrUnion(rel->baserestrictinfo, allclauses);
 	pathnode->rows = rel->tuples *
 		restrictlist_selectivity(root,
diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c
index 10eb050f3a3193dba8da301b5a9b54c76c3d770a..a078b3f5a93064a021453e95fd4ba7818b26bbf1 100644
--- a/src/backend/optimizer/path/orindxpath.c
+++ b/src/backend/optimizer/path/orindxpath.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.50 2003/05/28 22:32:49 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.51 2003/06/15 22:51:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -89,6 +89,9 @@ create_or_index_paths(Query *root, RelOptInfo *rel)
 				 */
 				pathnode->path.pathkeys = NIL;
 
+				/* It's not an innerjoin path. */
+				pathnode->indexjoinclauses = NIL;
+
 				/* We don't actually care what order the index scans in. */
 				pathnode->indexscandir = NoMovementScanDirection;
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 08b5944a1dd39916ecded9d45a5fdb2c024eff23..273a80129eb2e52a241508ac743b1c24367e84c6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.144 2003/05/28 23:06:16 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.145 2003/06/15 22:51:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,17 +51,11 @@ static SubqueryScan *create_subqueryscan_plan(Path *best_path,
 						 List *tlist, List *scan_clauses);
 static FunctionScan *create_functionscan_plan(Path *best_path,
 						 List *tlist, List *scan_clauses);
-static NestLoop *create_nestloop_plan(Query *root,
-					 NestPath *best_path, List *tlist,
-					 List *joinclauses, List *otherclauses,
+static NestLoop *create_nestloop_plan(Query *root, NestPath *best_path,
 					 Plan *outer_plan, Plan *inner_plan);
-static MergeJoin *create_mergejoin_plan(Query *root,
-					  MergePath *best_path, List *tlist,
-					  List *joinclauses, List *otherclauses,
+static MergeJoin *create_mergejoin_plan(Query *root, MergePath *best_path,
 					  Plan *outer_plan, Plan *inner_plan);
-static HashJoin *create_hashjoin_plan(Query *root,
-					 HashPath *best_path, List *tlist,
-					 List *joinclauses, List *otherclauses,
+static HashJoin *create_hashjoin_plan(Query *root, HashPath *best_path,
 					 Plan *outer_plan, Plan *inner_plan);
 static void fix_indxqual_references(List *indexquals, IndexPath *index_path,
 						List **fixed_indexquals,
@@ -356,59 +350,35 @@ disuse_physical_tlist(Plan *plan, Path *path)
 static Join *
 create_join_plan(Query *root, JoinPath *best_path)
 {
-	List	   *join_tlist = best_path->path.parent->targetlist;
 	Plan	   *outer_plan;
 	Plan	   *inner_plan;
-	List	   *joinclauses;
-	List	   *otherclauses;
 	Join	   *plan;
 
 	outer_plan = create_plan(root, best_path->outerjoinpath);
 	inner_plan = create_plan(root, best_path->innerjoinpath);
 
-	if (IS_OUTER_JOIN(best_path->jointype))
-	{
-		get_actual_join_clauses(best_path->joinrestrictinfo,
-								&joinclauses, &otherclauses);
-	}
-	else
-	{
-		/* We can treat all clauses alike for an inner join */
-		joinclauses = get_actual_clauses(best_path->joinrestrictinfo);
-		otherclauses = NIL;
-	}
-
 	switch (best_path->path.pathtype)
 	{
 		case T_MergeJoin:
 			plan = (Join *) create_mergejoin_plan(root,
 												  (MergePath *) best_path,
-												  join_tlist,
-												  joinclauses,
-												  otherclauses,
 												  outer_plan,
 												  inner_plan);
 			break;
 		case T_HashJoin:
 			plan = (Join *) create_hashjoin_plan(root,
 												 (HashPath *) best_path,
-												 join_tlist,
-												 joinclauses,
-												 otherclauses,
 												 outer_plan,
 												 inner_plan);
 			break;
 		case T_NestLoop:
 			plan = (Join *) create_nestloop_plan(root,
 												 (NestPath *) best_path,
-												 join_tlist,
-												 joinclauses,
-												 otherclauses,
 												 outer_plan,
 												 inner_plan);
 			break;
 		default:
-			elog(ERROR, "create_join_plan: unknown node type: %d",
+			elog(ERROR, "unsupported node type %d",
 				 best_path->path.pathtype);
 			plan = NULL;		/* keep compiler quiet */
 			break;
@@ -869,15 +839,16 @@ create_functionscan_plan(Path *best_path, List *tlist, List *scan_clauses)
 static NestLoop *
 create_nestloop_plan(Query *root,
 					 NestPath *best_path,
-					 List *tlist,
-					 List *joinclauses,
-					 List *otherclauses,
 					 Plan *outer_plan,
 					 Plan *inner_plan)
 {
+	List	   *tlist = best_path->path.parent->targetlist;
+	List	   *joinrestrictclauses = best_path->joinrestrictinfo;
+	List	   *joinclauses;
+	List	   *otherclauses;
 	NestLoop   *join_plan;
 
-	if (IsA(inner_plan, IndexScan))
+	if (IsA(best_path->innerjoinpath, IndexPath))
 	{
 		/*
 		 * An index is being used to reduce the number of tuples scanned
@@ -888,22 +859,41 @@ create_nestloop_plan(Query *root,
 		 * (otherwise, several different sets of clauses are being ORed
 		 * together).
 		 *
-		 * Note we must compare against indxqualorig not the "fixed" indxqual
-		 * (which has index attnos instead of relation attnos, and may have
-		 * been commuted as well).
+		 * We can also remove any join clauses that are redundant with those
+		 * being used in the index scan; prior redundancy checks will not
+		 * have caught this case because the join clauses would never have
+		 * been put in the same joininfo list.
+		 *
+		 * This would be a waste of time if the indexpath was an ordinary
+		 * indexpath and not a special innerjoin path.  We will skip it in
+		 * that case since indexjoinclauses is NIL in an ordinary indexpath.
 		 */
-		IndexScan  *innerscan = (IndexScan *) inner_plan;
-		List	   *indxqualorig = innerscan->indxqualorig;
+		IndexPath  *innerpath = (IndexPath *) best_path->innerjoinpath;
+		List	   *indexjoinclauses = innerpath->indexjoinclauses;
 
-		if (length(indxqualorig) == 1) /* single indexscan? */
+		if (length(indexjoinclauses) == 1) /* single indexscan? */
 		{
-			/* No work needed if indxqual refers only to its own relation... */
-			if (NumRelids((Node *) indxqualorig) > 1)
-				joinclauses = set_difference(joinclauses,
-											 lfirst(indxqualorig));
+			joinrestrictclauses =
+				select_nonredundant_join_clauses(root,
+												 joinrestrictclauses,
+												 lfirst(indexjoinclauses),
+												 best_path->jointype);
 		}
 	}
 
+	/* Get the join qual clauses (in plain expression form) */
+	if (IS_OUTER_JOIN(best_path->jointype))
+	{
+		get_actual_join_clauses(joinrestrictclauses,
+								&joinclauses, &otherclauses);
+	}
+	else
+	{
+		/* We can treat all clauses alike for an inner join */
+		joinclauses = get_actual_clauses(joinrestrictclauses);
+		otherclauses = NIL;
+	}
+
 	join_plan = make_nestloop(tlist,
 							  joinclauses,
 							  otherclauses,
@@ -919,15 +909,28 @@ create_nestloop_plan(Query *root,
 static MergeJoin *
 create_mergejoin_plan(Query *root,
 					  MergePath *best_path,
-					  List *tlist,
-					  List *joinclauses,
-					  List *otherclauses,
 					  Plan *outer_plan,
 					  Plan *inner_plan)
 {
+	List	   *tlist = best_path->jpath.path.parent->targetlist;
+	List	   *joinclauses;
+	List	   *otherclauses;
 	List	   *mergeclauses;
 	MergeJoin  *join_plan;
 
+	/* Get the join qual clauses (in plain expression form) */
+	if (IS_OUTER_JOIN(best_path->jpath.jointype))
+	{
+		get_actual_join_clauses(best_path->jpath.joinrestrictinfo,
+								&joinclauses, &otherclauses);
+	}
+	else
+	{
+		/* We can treat all clauses alike for an inner join */
+		joinclauses = get_actual_clauses(best_path->jpath.joinrestrictinfo);
+		otherclauses = NIL;
+	}
+
 	/*
 	 * Remove the mergeclauses from the list of join qual clauses, leaving
 	 * the list of quals that must be checked as qpquals.
@@ -986,18 +989,31 @@ create_mergejoin_plan(Query *root,
 static HashJoin *
 create_hashjoin_plan(Query *root,
 					 HashPath *best_path,
-					 List *tlist,
-					 List *joinclauses,
-					 List *otherclauses,
 					 Plan *outer_plan,
 					 Plan *inner_plan)
 {
+	List	   *tlist = best_path->jpath.path.parent->targetlist;
+	List	   *joinclauses;
+	List	   *otherclauses;
 	List	   *hashclauses;
 	HashJoin   *join_plan;
 	Hash	   *hash_plan;
 	List	   *innerhashkeys;
 	List	   *hcl;
 
+	/* Get the join qual clauses (in plain expression form) */
+	if (IS_OUTER_JOIN(best_path->jpath.jointype))
+	{
+		get_actual_join_clauses(best_path->jpath.joinrestrictinfo,
+								&joinclauses, &otherclauses);
+	}
+	else
+	{
+		/* We can treat all clauses alike for an inner join */
+		joinclauses = get_actual_clauses(best_path->jpath.joinrestrictinfo);
+		otherclauses = NIL;
+	}
+
 	/*
 	 * Remove the hashclauses from the list of join qual clauses, leaving
 	 * the list of quals that must be checked as qpquals.
@@ -1056,13 +1072,9 @@ create_hashjoin_plan(Query *root,
  *	  Adjust indexqual clauses to the form the executor's indexqual
  *	  machinery needs, and check for recheckable (lossy) index conditions.
  *
- * We have four tasks here:
+ * We have three tasks here:
  *	* Index keys must be represented by Var nodes with varattno set to the
  *	  index's attribute number, not the attribute number in the original rel.
- *	* indxpath.c may have selected an index that is binary-compatible with
- *	  the actual expression operator, but not exactly the same datatype.
- *	  We must replace the expression's operator with the binary-compatible
- *	  equivalent operator that the index will recognize.
  *	* If the index key is on the right, commute the clause to put it on the
  *	  left.  (Someday the executor might not need this, but for now it does.)
  *	* If the indexable operator is marked 'amopreqcheck' in pg_amop, then
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 25648beed19dd465be50a76610ffe3e5e02bd070..da9497d58a4c5691320392230da12284acf51bdb 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.89 2003/05/26 00:11:27 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.90 2003/06/15 22:51:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -362,6 +362,9 @@ create_index_path(Query *root,
 	pathnode->indexinfo = makeList1(index);
 	pathnode->indexqual = makeList1(indexquals);
 
+	/* It's not an innerjoin path. */
+	pathnode->indexjoinclauses = NIL;
+
 	pathnode->indexscandir = indexscandir;
 
 	/*
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index bdcc338d609a22fa50f24fb409cdcc896a52b9e1..334fc5784cf435f2e51048bd2e354b1207053485 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.16 2003/01/24 03:58:43 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.17 2003/06/15 22:51:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,6 +20,12 @@
 #include "optimizer/var.h"
 
 
+static bool join_clause_is_redundant(Query *root,
+									 RestrictInfo *rinfo,
+									 List *reference_list,
+									 JoinType jointype);
+
+
 /*
  * restriction_is_or_clause
  *
@@ -94,6 +100,76 @@ get_actual_join_clauses(List *restrictinfo_list,
  * discussion). We detect that case and omit the redundant clause from the
  * result list.
  *
+ * The result is a fresh List, but it points to the same member nodes
+ * as were in the input.
+ */
+List *
+remove_redundant_join_clauses(Query *root, List *restrictinfo_list,
+							  JoinType jointype)
+{
+	List	   *result = NIL;
+	List	   *item;
+
+	foreach(item, restrictinfo_list)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(item);
+
+		/* drop it if redundant with any prior clause */
+		if (join_clause_is_redundant(root, rinfo, result, jointype))
+			continue;
+
+		/* otherwise, add it to result list */
+		result = lappend(result, rinfo);
+	}
+
+	return result;
+}
+
+/*
+ * select_nonredundant_join_clauses
+ *
+ * Given a list of RestrictInfo clauses that are to be applied in a join,
+ * select the ones that are not redundant with any clause in the
+ * reference_list.
+ *
+ * This is similar to remove_redundant_join_clauses, but we are looking for
+ * redundancies with a separate list of clauses (i.e., clauses that have
+ * already been applied below the join itself).
+ *
+ * Note that we assume the given restrictinfo_list has already been checked
+ * for local redundancies, so we don't check again.
+ */
+List *
+select_nonredundant_join_clauses(Query *root,
+								 List *restrictinfo_list,
+								 List *reference_list,
+								 JoinType jointype)
+{
+	List	   *result = NIL;
+	List	   *item;
+
+	foreach(item, restrictinfo_list)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(item);
+
+		/* drop it if redundant with any reference clause */
+		if (join_clause_is_redundant(root, rinfo, reference_list, jointype))
+			continue;
+
+		/* otherwise, add it to result list */
+		result = lappend(result, rinfo);
+	}
+
+	return result;
+}
+
+/*
+ * join_clause_is_redundant
+ *		Returns true if rinfo is redundant with any clause in reference_list.
+ *
+ * This is the guts of both remove_redundant_join_clauses and
+ * select_nonredundant_join_clauses.  See the docs above for motivation.
+ *
  * We can detect redundant mergejoinable clauses very cheaply by using their
  * left and right pathkeys, which uniquely identify the sets of equijoined
  * variables in question.  All the members of a pathkey set that are in the
@@ -113,69 +189,59 @@ get_actual_join_clauses(List *restrictinfo_list,
  * except one is pushed down into an outer join and the other isn't,
  * then they're not really redundant, because one constrains the
  * joined rows after addition of null fill rows, and the other doesn't.
- *
- * The result is a fresh List, but it points to the same member nodes
- * as were in the input.
  */
-List *
-remove_redundant_join_clauses(Query *root, List *restrictinfo_list,
-							  JoinType jointype)
+static bool
+join_clause_is_redundant(Query *root,
+						 RestrictInfo *rinfo,
+						 List *reference_list,
+						 JoinType jointype)
 {
-	List	   *result = NIL;
-	List	   *item;
+	/* always consider exact duplicates redundant */
+	/* XXX would it be sufficient to use ptrMember here? */
+	if (member(rinfo, reference_list))
+		return true;
 
-	foreach(item, restrictinfo_list)
+	/* check for redundant merge clauses */
+	if (rinfo->mergejoinoperator != InvalidOid)
 	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(item);
+		bool		redundant = false;
+		List	   *refitem;
 
-		/* always eliminate duplicates */
-		if (member(rinfo, result))
-			continue;
+		cache_mergeclause_pathkeys(root, rinfo);
 
-		/* check for redundant merge clauses */
-		if (rinfo->mergejoinoperator != InvalidOid)
+		/* do the cheap tests first */
+		foreach(refitem, reference_list)
 		{
-			bool		redundant = false;
-			List	   *olditem;
+			RestrictInfo *refrinfo = (RestrictInfo *) lfirst(refitem);
 
-			cache_mergeclause_pathkeys(root, rinfo);
-
-			/* do the cheap tests first */
-			foreach(olditem, result)
+			if (refrinfo->mergejoinoperator != InvalidOid &&
+				rinfo->left_pathkey == refrinfo->left_pathkey &&
+				rinfo->right_pathkey == refrinfo->right_pathkey &&
+				(rinfo->ispusheddown == refrinfo->ispusheddown ||
+				 !IS_OUTER_JOIN(jointype)))
 			{
-				RestrictInfo *oldrinfo = (RestrictInfo *) lfirst(olditem);
-
-				if (oldrinfo->mergejoinoperator != InvalidOid &&
-					rinfo->left_pathkey == oldrinfo->left_pathkey &&
-					rinfo->right_pathkey == oldrinfo->right_pathkey &&
-					(rinfo->ispusheddown == oldrinfo->ispusheddown ||
-					 !IS_OUTER_JOIN(jointype)))
-				{
-					redundant = true;
-					break;
-				}
-			}
-
-			if (redundant)
-			{
-				/*
-				 * It looks redundant, now check for "var = const" case.
-				 * If left_relids/right_relids are set, then there are
-				 * definitely vars on both sides; else we must check the
-				 * hard way.
-				 */
-				if (rinfo->left_relids)
-					continue;	/* var = var, so redundant */
-				if (contain_var_clause(get_leftop(rinfo->clause)) &&
-					contain_var_clause(get_rightop(rinfo->clause)))
-					continue;	/* var = var, so redundant */
-				/* else var = const, not redundant */
+				redundant = true;
+				break;
 			}
 		}
 
-		/* otherwise, add it to result list */
-		result = lappend(result, rinfo);
+		if (redundant)
+		{
+			/*
+			 * It looks redundant, now check for "var = const" case.
+			 * If left_relids/right_relids are set, then there are
+			 * definitely vars on both sides; else we must check the
+			 * hard way.
+			 */
+			if (rinfo->left_relids)
+				return true;	/* var = var, so redundant */
+			if (contain_var_clause(get_leftop(rinfo->clause)) &&
+				contain_var_clause(get_rightop(rinfo->clause)))
+				return true;	/* var = var, so redundant */
+			/* else var = const, not redundant */
+		}
 	}
 
-	return result;
+	/* otherwise, not redundant */
+	return false;
 }
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index bc42917a355eea361a63b616c18f4b6771afd37b..5bf17ceb14ed66d5be012fda8d23ab2d5da901f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_list.h,v 1.36 2003/05/28 22:32:50 tgl Exp $
+ * $Id: pg_list.h,v 1.37 2003/06/15 22:51:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -140,6 +140,8 @@ typedef struct FastList
 } FastList;
 
 #define FastListInit(fl)	( (fl)->head = (fl)->tail = NIL )
+#define FastListFromList(fl, l)  \
+	( (fl)->head = (l), (fl)->tail = llastnode((fl)->head) )
 #define FastListValue(fl)	( (fl)->head )
 
 
@@ -166,6 +168,7 @@ extern void FastConcFast(FastList *fl, FastList *fl2);
 extern void *nth(int n, List *l);
 extern int	length(List *list);
 extern void *llast(List *list);
+extern List *llastnode(List *list);
 extern bool member(void *datum, List *list);
 extern bool ptrMember(void *datum, List *list);
 extern bool intMember(int datum, List *list);
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 4f770b7ca10cfc09eec6d7410247e1e1ad37a863..8392ab505bd66789c88a1d8aa4ff680cf265a40f 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: relation.h,v 1.80 2003/05/28 16:04:02 tgl Exp $
+ * $Id: relation.h,v 1.81 2003/06/15 22:51:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -340,6 +340,15 @@ typedef struct Path
  * Also note that indexquals lists do not contain RestrictInfo nodes,
  * just bare clause expressions.
  *
+ * 'indexjoinclauses' is NIL for an ordinary indexpath (one that does not
+ * use any join clauses in the index conditions).  For an innerjoin indexpath,
+ * it has the same structure as 'indexqual', but references the RestrictInfo
+ * nodes from which the indexqual was built, rather than the bare clause
+ * expressions.  (Note: there isn't necessarily a one-to-one correspondence
+ * between RestrictInfos and expressions, because of expansion of special
+ * indexable operators.)  We need this so that we can eliminate redundant
+ * join clauses when plans are built.
+ *
  * 'indexscandir' is one of:
  *		ForwardScanDirection: forward scan of an ordered index
  *		BackwardScanDirection: backward scan of an ordered index
@@ -360,6 +369,7 @@ typedef struct IndexPath
 	Path		path;
 	List	   *indexinfo;
 	List	   *indexqual;
+	List	   *indexjoinclauses;
 	ScanDirection indexscandir;
 	double		rows;			/* estimated number of result tuples */
 } IndexPath;
diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h
index 997d4ab8c52dbf8326731e2ba28f2595658b8573..19c3435c3e55754f975e23f6032426b9d7041a54 100644
--- a/src/include/optimizer/restrictinfo.h
+++ b/src/include/optimizer/restrictinfo.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: restrictinfo.h,v 1.16 2002/11/24 21:52:15 tgl Exp $
+ * $Id: restrictinfo.h,v 1.17 2003/06/15 22:51:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,5 +23,9 @@ extern void get_actual_join_clauses(List *restrictinfo_list,
 extern List *remove_redundant_join_clauses(Query *root,
 										   List *restrictinfo_list,
 										   JoinType jointype);
+extern List *select_nonredundant_join_clauses(Query *root,
+											  List *restrictinfo_list,
+											  List *reference_list,
+											  JoinType jointype);
 
 #endif   /* RESTRICTINFO_H */