diff --git a/doc/src/sgml/xoper.sgml b/doc/src/sgml/xoper.sgml
index 395306bbd60e3234d1a257e97775f1466fac9352..24c74cd8b60c96e179c5f2cfb3bcee0f9a6e0f96 100644
--- a/doc/src/sgml/xoper.sgml
+++ b/doc/src/sgml/xoper.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/xoper.sgml,v 1.21 2003/01/06 01:20:40 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/xoper.sgml,v 1.22 2003/01/15 19:35:35 tgl Exp $
 -->
 
  <Chapter Id="xoper">
@@ -375,6 +375,27 @@ table1.column1 OP table2.column2
      equality operators that are (or could be) implemented by <function>memcmp()</function>.
     </para>
 
+    <note>
+    <para>
+     The function underlying a hashjoinable operator must be marked
+     immutable or stable.  If it is volatile, the system will never
+     attempt to use the operator for a hash join.
+    </para>
+    </note>
+
+    <note>
+    <para>
+     If a hashjoinable operator has an underlying function that is marked
+     strict, the
+     function must also be complete: that is, it should return TRUE or
+     FALSE, never NULL, for any two non-NULL inputs.  If this rule is
+     not followed, hash-optimization of <literal>IN</> operations may
+     generate wrong results.  (Specifically, <literal>IN</> might return
+     FALSE where the correct answer per spec would be NULL; or it might
+     yield an error complaining that it wasn't prepared for a NULL result.)
+    </para>
+    </note>
+
    </sect2>
 
    <sect2>
@@ -472,6 +493,14 @@ table1.column1 OP table2.column2
      </itemizedlist>
     </para>
 
+    <note>
+    <para>
+     The function underlying a mergejoinable operator must be marked
+     immutable or stable.  If it is volatile, the system will never
+     attempt to use the operator for a merge join.
+    </para>
+    </note>
+
     <note>
     <para>
      <literal>GROUP BY</> and <literal>DISTINCT</> operations require each
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e260bc6595077d21a6e21841aeae90412ec94a5a..8663c6c4a147faa47d1ad653b65d6bd09c537a12 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.235 2003/01/10 21:08:10 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.236 2003/01/15 19:35:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1059,6 +1059,8 @@ _copyRestrictInfo(RestrictInfo *from)
 	COPY_NODE_FIELD(subclauseindices); /* XXX probably bad */
 	COPY_SCALAR_FIELD(eval_cost);
 	COPY_SCALAR_FIELD(this_selec);
+	COPY_INTLIST_FIELD(left_relids);
+	COPY_INTLIST_FIELD(right_relids);
 	COPY_SCALAR_FIELD(mergejoinoperator);
 	COPY_SCALAR_FIELD(left_sortop);
 	COPY_SCALAR_FIELD(right_sortop);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0ef9b3fa2209a20c8d1afae7770c8208ec098a30..a4e9e1092d804781dab5daa978f31d37eeb9c7a3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.179 2003/01/10 21:08:10 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.180 2003/01/15 19:35:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -464,10 +464,10 @@ _equalRestrictInfo(RestrictInfo *a, RestrictInfo *b)
 	COMPARE_NODE_FIELD(clause);
 	COMPARE_SCALAR_FIELD(ispusheddown);
 	/*
-	 * We ignore subclauseindices, eval_cost, this_selec, left/right_pathkey,
-	 * and left/right_bucketsize, since they may not be set yet, and should be
-	 * derivable from the clause anyway.  Probably it's not really necessary
-	 * to compare any of these remaining fields ...
+	 * We ignore subclauseindices, eval_cost, this_selec, left/right_relids,
+	 * left/right_pathkey, and left/right_bucketsize, since they may not be
+	 * set yet, and should be derivable from the clause anyway.  Probably it's
+	 * not really necessary to compare any of these remaining fields ...
 	 */
 	COMPARE_SCALAR_FIELD(mergejoinoperator);
 	COMPARE_SCALAR_FIELD(left_sortop);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e7d8fa71ed78ea0b41c30affb6dce7ee9d13ca14..e72b52570e56b22fa1ec343a5b1b294b02036f05 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.192 2003/01/10 21:08:11 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.193 2003/01/15 19:35:39 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -952,6 +952,8 @@ _outRestrictInfo(StringInfo str, RestrictInfo *node)
 	WRITE_NODE_FIELD(clause);
 	WRITE_BOOL_FIELD(ispusheddown);
 	WRITE_NODE_FIELD(subclauseindices);
+	WRITE_INTLIST_FIELD(left_relids);
+	WRITE_INTLIST_FIELD(right_relids);
 	WRITE_OID_FIELD(mergejoinoperator);
 	WRITE_OID_FIELD(left_sortop);
 	WRITE_OID_FIELD(right_sortop);
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index dd5186860bfe33fb37ed3427e60904ef24ec3177..43b8e99893cef3db4a24d28e5807ffc00e29025e 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.58 2002/12/12 15:49:28 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.59 2003/01/15 19:35:39 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -370,10 +370,10 @@ print_expr(Node *expr, List *rtable)
 		{
 			char	   *opname;
 
-			print_expr((Node *) get_leftop(e), rtable);
+			print_expr(get_leftop(e), rtable);
 			opname = get_opname(((OpExpr *) e)->opno);
 			printf(" %s ", ((opname != NULL) ? opname : "(invalid operator)"));
-			print_expr((Node *) get_rightop(e), rtable);
+			print_expr(get_rightop(e), rtable);
 		}
 		else
 			printf("an expr");
diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README
index f4d64ebbcadfc5fddcf8cc0b6df359e4faba5b78..955e022d8f6d5bd0b34c838c2b1825c9f9098af1 100644
--- a/src/backend/optimizer/README
+++ b/src/backend/optimizer/README
@@ -251,8 +251,10 @@ Optimizer Data Structures
 
 RelOptInfo      - a relation or joined relations
 
- RestrictInfo   - restriction clauses, like "x = 3"
- JoinInfo       - join clauses, including the relids needed for the join
+ RestrictInfo   - WHERE clauses, like "x = 3" or "y = z"
+                  (note the same structure is used for restriction and
+                   join clauses)
+ JoinInfo       - join clauses associated with a particular pair of relations
 
  Path           - every way to generate a RelOptInfo(sequential,index,joins)
   SeqScan       - a plain Path node with pathtype = T_SeqScan
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 0294c828124e4ea9ee07a1337264e3105c8450bc..84041a566d18b3fc7fed3e7d8985143e87475842 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/clausesel.c,v 1.54 2002/12/12 15:49:28 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/clausesel.c,v 1.55 2003/01/15 19:35:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -266,12 +266,12 @@ addRangeClause(RangeQueryClause **rqlist, Node *clause,
 
 	if (varonleft)
 	{
-		var = (Node *) get_leftop((Expr *) clause);
+		var = get_leftop((Expr *) clause);
 		is_lobound = !isLTsel;	/* x < something is high bound */
 	}
 	else
 	{
-		var = (Node *) get_rightop((Expr *) clause);
+		var = get_rightop((Expr *) clause);
 		is_lobound = isLTsel;	/* something < x is low bound */
 	}
 
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1d736b34b91102c615c3924a58e3b13148e732d7..efd80dff1ed7a6ffc46b59dee1164ce3198a1ede 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -42,7 +42,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.99 2003/01/12 22:35:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.100 2003/01/15 19:35:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -752,7 +752,6 @@ cost_mergejoin(Path *path, Query *root,
 	Cost		cpu_per_tuple;
 	QualCost	restrict_qual_cost;
 	RestrictInfo *firstclause;
-	Var		   *leftvar;
 	double		outer_rows,
 				inner_rows;
 	double		ntuples;
@@ -779,9 +778,7 @@ cost_mergejoin(Path *path, Query *root,
 						 &firstclause->left_mergescansel,
 						 &firstclause->right_mergescansel);
 
-	leftvar = get_leftop(firstclause->clause);
-	Assert(IsA(leftvar, Var));
-	if (VARISRELMEMBER(leftvar->varno, outer_path->parent))
+	if (is_subseti(firstclause->left_relids, outer_path->parent->relids))
 	{
 		/* left side of clause is outer */
 		outerscansel = firstclause->left_mergescansel;
@@ -935,14 +932,9 @@ cost_hashjoin(Path *path, Query *root,
 	foreach(hcl, hashclauses)
 	{
 		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(hcl);
-		Var		   *left,
-				   *right;
 		Selectivity thisbucketsize;
 
 		Assert(IsA(restrictinfo, RestrictInfo));
-		/* these must be OK, since check_hashjoinable accepted the clause */
-		left = get_leftop(restrictinfo->clause);
-		right = get_rightop(restrictinfo->clause);
 
 		/*
 		 * First we have to figure out which side of the hashjoin clause is the
@@ -952,27 +944,30 @@ cost_hashjoin(Path *path, Query *root,
 		 * a large query, we cache the bucketsize estimate in the RestrictInfo
 		 * node to avoid repeated lookups of statistics.
 		 */
-		if (VARISRELMEMBER(right->varno, inner_path->parent))
+		if (is_subseti(restrictinfo->right_relids, inner_path->parent->relids))
 		{
 			/* righthand side is inner */
 			thisbucketsize = restrictinfo->right_bucketsize;
 			if (thisbucketsize < 0)
 			{
 				/* not cached yet */
-				thisbucketsize = estimate_hash_bucketsize(root, right,
+				thisbucketsize = estimate_hash_bucketsize(root,
+									(Var *) get_rightop(restrictinfo->clause),
 														  virtualbuckets);
 				restrictinfo->right_bucketsize = thisbucketsize;
 			}
 		}
 		else
 		{
-			Assert(VARISRELMEMBER(left->varno, inner_path->parent));
+			Assert(is_subseti(restrictinfo->left_relids,
+							  inner_path->parent->relids));
 			/* lefthand side is inner */
 			thisbucketsize = restrictinfo->left_bucketsize;
 			if (thisbucketsize < 0)
 			{
 				/* not cached yet */
-				thisbucketsize = estimate_hash_bucketsize(root, left,
+				thisbucketsize = estimate_hash_bucketsize(root,
+									(Var *) get_leftop(restrictinfo->clause),
 														  virtualbuckets);
 				restrictinfo->left_bucketsize = thisbucketsize;
 			}
@@ -1088,7 +1083,7 @@ estimate_hash_bucketsize(Query *root, Var *var, int nbuckets)
 	 * Lookup info about var's relation and attribute; if none available,
 	 * return default estimate.
 	 */
-	if (!IsA(var, Var))
+	if (var == NULL || !IsA(var, Var))
 		return 0.1;
 
 	relid = getrelid(var->varno, root->rtable);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 228a876f71adebd3a88582a0a07c062abab185ec..7e68c41ef37ae5f345461f1e4676457940e0d856 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.130 2002/12/16 21:30:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.131 2003/01/15 19:35:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -85,15 +85,15 @@ static Relids indexable_outerrelids(RelOptInfo *rel, IndexOptInfo *index);
 static Path *make_innerjoin_index_path(Query *root,
 									   RelOptInfo *rel, IndexOptInfo *index,
 									   List *clausegroup);
-static bool match_index_to_operand(int indexkey, Var *operand,
+static bool match_index_to_operand(int indexkey, Node *operand,
 					   RelOptInfo *rel, IndexOptInfo *index);
 static bool function_index_operand(Expr *funcOpnd, RelOptInfo *rel,
 					   IndexOptInfo *index);
 static bool match_special_index_operator(Expr *clause, Oid opclass,
 							 bool indexkey_on_left);
-static List *prefix_quals(Var *leftop, Oid expr_op,
+static List *prefix_quals(Node *leftop, Oid expr_op,
 			 Const *prefix, Pattern_Prefix_Status pstatus);
-static List *network_prefix_quals(Var *leftop, Oid expr_op, Datum rightop);
+static List *network_prefix_quals(Node *leftop, Oid expr_op, Datum rightop);
 static Oid	find_operator(const char *opname, Oid datatype);
 static Datum string_to_datum(const char *str, Oid datatype);
 static Const *string_to_const(const char *str, Oid datatype);
@@ -713,7 +713,7 @@ match_clause_to_indexkey(RelOptInfo *rel,
 						 Oid opclass,
 						 Expr *clause)
 {
-	Var		   *leftop,
+	Node	   *leftop,
 			   *rightop;
 
 	/* Clause must be a binary opclause. */
@@ -730,7 +730,7 @@ match_clause_to_indexkey(RelOptInfo *rel,
 	 * Anything that is a "pseudo constant" expression will do.
 	 */
 	if (match_index_to_operand(indexkey, leftop, rel, index) &&
-		is_pseudo_constant_clause((Node *) rightop))
+		is_pseudo_constant_clause(rightop))
 	{
 		if (is_indexable_operator(clause, opclass, true))
 			return true;
@@ -745,7 +745,7 @@ match_clause_to_indexkey(RelOptInfo *rel,
 	}
 
 	if (match_index_to_operand(indexkey, rightop, rel, index) &&
-		is_pseudo_constant_clause((Node *) leftop))
+		is_pseudo_constant_clause(leftop))
 	{
 		if (is_indexable_operator(clause, opclass, false))
 			return true;
@@ -801,7 +801,7 @@ match_join_clause_to_indexkey(RelOptInfo *rel,
 							  Oid opclass,
 							  Expr *clause)
 {
-	Var		   *leftop,
+	Node	   *leftop,
 			   *rightop;
 
 	/* Clause must be a binary opclause. */
@@ -820,12 +820,12 @@ match_join_clause_to_indexkey(RelOptInfo *rel,
 	 */
 	if (match_index_to_operand(indexkey, leftop, rel, index))
 	{
-		List	   *othervarnos = pull_varnos((Node *) rightop);
+		List	   *othervarnos = pull_varnos(rightop);
 		bool		isIndexable;
 
 		isIndexable =
 			!intMember(lfirsti(rel->relids), othervarnos) &&
-			!contain_volatile_functions((Node *) rightop) &&
+			!contain_volatile_functions(rightop) &&
 			is_indexable_operator(clause, opclass, true);
 		freeList(othervarnos);
 		return isIndexable;
@@ -833,12 +833,12 @@ match_join_clause_to_indexkey(RelOptInfo *rel,
 
 	if (match_index_to_operand(indexkey, rightop, rel, index))
 	{
-		List	   *othervarnos = pull_varnos((Node *) leftop);
+		List	   *othervarnos = pull_varnos(leftop);
 		bool		isIndexable;
 
 		isIndexable =
 			!intMember(lfirsti(rel->relids), othervarnos) &&
-			!contain_volatile_functions((Node *) leftop) &&
+			!contain_volatile_functions(leftop) &&
 			is_indexable_operator(clause, opclass, false);
 		freeList(othervarnos);
 		return isIndexable;
@@ -1622,7 +1622,7 @@ make_innerjoin_index_path(Query *root,
  */
 static bool
 match_index_to_operand(int indexkey,
-					   Var *operand,
+					   Node *operand,
 					   RelOptInfo *rel,
 					   IndexOptInfo *index)
 {
@@ -1633,7 +1633,7 @@ match_index_to_operand(int indexkey,
 	 * eval_const_expressions() will have simplified if more than one.
 	 */
 	if (operand && IsA(operand, RelabelType))
-		operand = (Var *) ((RelabelType *) operand)->arg;
+		operand = (Node *) ((RelabelType *) operand)->arg;
 
 	if (index->indproc == InvalidOid)
 	{
@@ -1641,8 +1641,8 @@ match_index_to_operand(int indexkey,
 		 * Simple index.
 		 */
 		if (operand && IsA(operand, Var) &&
-			lfirsti(rel->relids) == operand->varno &&
-			indexkey == operand->varattno)
+			lfirsti(rel->relids) == ((Var *) operand)->varno &&
+			indexkey == ((Var *) operand)->varattno)
 			return true;
 		else
 			return false;
@@ -1764,7 +1764,7 @@ match_special_index_operator(Expr *clause, Oid opclass,
 							 bool indexkey_on_left)
 {
 	bool		isIndexable = false;
-	Var		   *leftop,
+	Node	   *leftop,
 			   *rightop;
 	Oid			expr_op;
 	Const	   *patt = NULL;
@@ -1944,8 +1944,8 @@ expand_indexqual_conditions(List *indexquals)
 		Expr	   *clause = (Expr *) lfirst(q);
 
 		/* we know these will succeed */
-		Var		   *leftop = get_leftop(clause);
-		Var		   *rightop = get_rightop(clause);
+		Node	   *leftop = get_leftop(clause);
+		Node	   *rightop = get_rightop(clause);
 		Oid			expr_op = ((OpExpr *) clause)->opno;
 		Const	   *patt = (Const *) rightop;
 		Const	   *prefix = NULL;
@@ -2033,7 +2033,7 @@ expand_indexqual_conditions(List *indexquals)
  * operators.
  */
 static List *
-prefix_quals(Var *leftop, Oid expr_op,
+prefix_quals(Node *leftop, Oid expr_op,
 			 Const *prefix_const, Pattern_Prefix_Status pstatus)
 {
 	List	   *result;
@@ -2143,7 +2143,7 @@ prefix_quals(Var *leftop, Oid expr_op,
  * operator.
  */
 static List *
-network_prefix_quals(Var *leftop, Oid expr_op, Datum rightop)
+network_prefix_quals(Node *leftop, Oid expr_op, Datum rightop)
 {
 	bool		is_eq;
 	char	   *opr1name;
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 65d0d8fa3581216a3a8fe4124fab6183348c5318..8a6fcd3f060215a6a4394e6dbe1447f8122faef6 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.74 2002/11/30 05:21:02 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.75 2003/01/15 19:35:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -774,10 +774,9 @@ hash_inner_and_outer(Query *root,
 	foreach(i, restrictlist)
 	{
 		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(i);
-		Var		   *left,
-				   *right;
 
-		if (restrictinfo->hashjoinoperator == InvalidOid)
+		if (restrictinfo->left_relids == NIL ||
+			restrictinfo->hashjoinoperator == InvalidOid)
 			continue;			/* not hashjoinable */
 
 		/*
@@ -787,26 +786,16 @@ hash_inner_and_outer(Query *root,
 		if (isouterjoin && restrictinfo->ispusheddown)
 			continue;
 
-		/* these must be OK, since check_hashjoinable accepted the clause */
-		left = get_leftop(restrictinfo->clause);
-		right = get_rightop(restrictinfo->clause);
-
 		/*
 		 * Check if clause is usable with these input rels.
-		 *
-		 * Since we currently accept only var-op-var clauses as hashjoinable,
-		 * we need only check the membership of the vars to determine whether
-		 * a particular clause can be used with this pair of sub-relations.
-		 * This code would need to be upgraded if we wanted to allow
-		 * more-complex expressions in hash joins.
 		 */
-		if (VARISRELMEMBER(left->varno, outerrel) &&
-			VARISRELMEMBER(right->varno, innerrel))
+		if (is_subseti(restrictinfo->left_relids, outerrel->relids) &&
+			is_subseti(restrictinfo->right_relids, innerrel->relids))
 		{
 			/* righthand side is inner */
 		}
-		else if (VARISRELMEMBER(left->varno, innerrel) &&
-				 VARISRELMEMBER(right->varno, outerrel))
+		else if (is_subseti(restrictinfo->left_relids, innerrel->relids) &&
+				 is_subseti(restrictinfo->right_relids, outerrel->relids))
 		{
 			/* lefthand side is inner */
 		}
@@ -874,9 +863,6 @@ select_mergejoin_clauses(RelOptInfo *joinrel,
 	foreach(i, restrictlist)
 	{
 		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(i);
-		Expr	   *clause;
-		Var		   *left,
-				   *right;
 
 		/*
 		 * If processing an outer join, only use its own join clauses in
@@ -896,11 +882,13 @@ select_mergejoin_clauses(RelOptInfo *joinrel,
 			switch (jointype)
 			{
 				case JOIN_RIGHT:
-					if (restrictinfo->mergejoinoperator == InvalidOid)
+					if (restrictinfo->left_relids == NIL ||
+						restrictinfo->mergejoinoperator == InvalidOid)
 						return NIL;		/* not mergejoinable */
 					break;
 				case JOIN_FULL:
-					if (restrictinfo->mergejoinoperator == InvalidOid)
+					if (restrictinfo->left_relids == NIL ||
+						restrictinfo->mergejoinoperator == InvalidOid)
 						elog(ERROR, "FULL JOIN is only supported with mergejoinable join conditions");
 					break;
 				default:
@@ -909,19 +897,27 @@ select_mergejoin_clauses(RelOptInfo *joinrel,
 			}
 		}
 
-		if (restrictinfo->mergejoinoperator == InvalidOid)
+		if (restrictinfo->left_relids == NIL ||
+			restrictinfo->mergejoinoperator == InvalidOid)
 			continue;			/* not mergejoinable */
 
-		clause = restrictinfo->clause;
-		/* these must be OK, since check_mergejoinable accepted the clause */
-		left = get_leftop(clause);
-		right = get_rightop(clause);
+		/*
+		 * Check if clause is usable with these input rels.
+		 */
+		if (is_subseti(restrictinfo->left_relids, outerrel->relids) &&
+			is_subseti(restrictinfo->right_relids, innerrel->relids))
+		{
+			/* righthand side is inner */
+		}
+		else if (is_subseti(restrictinfo->left_relids, innerrel->relids) &&
+				 is_subseti(restrictinfo->right_relids, outerrel->relids))
+		{
+			/* lefthand side is inner */
+		}
+		else
+			continue;			/* no good for these input relations */
 
-		if ((VARISRELMEMBER(left->varno, outerrel) &&
-			 VARISRELMEMBER(right->varno, innerrel)) ||
-			(VARISRELMEMBER(left->varno, innerrel) &&
-			 VARISRELMEMBER(right->varno, outerrel)))
-			result_list = lcons(restrictinfo, result_list);
+		result_list = lcons(restrictinfo, result_list);
 	}
 
 	return result_list;
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 9c6ed4d1bd35422c2ffb69ca16f9a46f03e41eae..194bdddc2f09c1b151b3f64053331e9c3b0e7157 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.43 2002/12/17 01:18:22 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.44 2003/01/15 19:35:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,27 +53,23 @@ makePathKeyItem(Node *key, Oid sortop)
  *	  The given clause has a mergejoinable operator, so its two sides
  *	  can be considered equal after restriction clause application; in
  *	  particular, any pathkey mentioning one side (with the correct sortop)
- *	  can be expanded to include the other as well.  Record the vars and
+ *	  can be expanded to include the other as well.  Record the exprs and
  *	  associated sortops in the query's equi_key_list for future use.
  *
  * The query's equi_key_list field points to a list of sublists of PathKeyItem
- * nodes, where each sublist is a set of two or more vars+sortops that have
+ * nodes, where each sublist is a set of two or more exprs+sortops that have
  * been identified as logically equivalent (and, therefore, we may consider
  * any two in a set to be equal).  As described above, we will subsequently
  * use direct pointers to one of these sublists to represent any pathkey
  * that involves an equijoined variable.
- *
- * This code would actually work fine with expressions more complex than
- * a single Var, but currently it won't see any because check_mergejoinable
- * won't accept such clauses as mergejoinable.
  */
 void
 add_equijoined_keys(Query *root, RestrictInfo *restrictinfo)
 {
 	Expr	   *clause = restrictinfo->clause;
-	PathKeyItem *item1 = makePathKeyItem((Node *) get_leftop(clause),
+	PathKeyItem *item1 = makePathKeyItem(get_leftop(clause),
 										 restrictinfo->left_sortop);
-	PathKeyItem *item2 = makePathKeyItem((Node *) get_rightop(clause),
+	PathKeyItem *item2 = makePathKeyItem(get_rightop(clause),
 										 restrictinfo->right_sortop);
 	List	   *newset,
 			   *cursetlink;
@@ -717,13 +713,13 @@ cache_mergeclause_pathkeys(Query *root, RestrictInfo *restrictinfo)
 
 	if (restrictinfo->left_pathkey == NIL)
 	{
-		key = (Node *) get_leftop(restrictinfo->clause);
+		key = get_leftop(restrictinfo->clause);
 		item = makePathKeyItem(key, restrictinfo->left_sortop);
 		restrictinfo->left_pathkey = make_canonical_pathkey(root, item);
 	}
 	if (restrictinfo->right_pathkey == NIL)
 	{
-		key = (Node *) get_rightop(restrictinfo->clause);
+		key = get_rightop(restrictinfo->clause);
 		item = makePathKeyItem(key, restrictinfo->right_sortop);
 		restrictinfo->right_pathkey = make_canonical_pathkey(root, item);
 	}
@@ -852,32 +848,24 @@ make_pathkeys_for_mergeclauses(Query *root,
 	foreach(i, mergeclauses)
 	{
 		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(i);
-		Node	   *key;
 		List	   *pathkey;
 
 		cache_mergeclause_pathkeys(root, restrictinfo);
 
-		key = (Node *) get_leftop(restrictinfo->clause);
-		if (IsA(key, Var) &&
-			VARISRELMEMBER(((Var *) key)->varno, rel))
+		if (is_subseti(restrictinfo->left_relids, rel->relids))
 		{
 			/* Rel is left side of mergeclause */
 			pathkey = restrictinfo->left_pathkey;
 		}
+		else if (is_subseti(restrictinfo->right_relids, rel->relids))
+		{
+			/* Rel is right side of mergeclause */
+			pathkey = restrictinfo->right_pathkey;
+		}
 		else
 		{
-			key = (Node *) get_rightop(restrictinfo->clause);
-			if (IsA(key, Var) &&
-				VARISRELMEMBER(((Var *) key)->varno, rel))
-			{
-				/* Rel is right side of mergeclause */
-				pathkey = restrictinfo->right_pathkey;
-			}
-			else
-			{
-				elog(ERROR, "make_pathkeys_for_mergeclauses: can't identify which side of mergeclause to use");
-				pathkey = NIL;	/* keep compiler quiet */
-			}
+			elog(ERROR, "make_pathkeys_for_mergeclauses: can't identify which side of mergeclause to use");
+			pathkey = NIL;	/* keep compiler quiet */
 		}
 
 		/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 3d9ee6e4f4f6313c9166c36dcf59880f2acd4c2f..03ec35384797f41dfb0a5618f3dbfaf146b6ee93 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.129 2003/01/13 00:29:25 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.130 2003/01/15 19:35:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,18 +49,15 @@ static FunctionScan *create_functionscan_plan(Path *best_path,
 static NestLoop *create_nestloop_plan(Query *root,
 					 NestPath *best_path, List *tlist,
 					 List *joinclauses, List *otherclauses,
-					 Plan *outer_plan, List *outer_tlist,
-					 Plan *inner_plan, List *inner_tlist);
+					 Plan *outer_plan, Plan *inner_plan);
 static MergeJoin *create_mergejoin_plan(Query *root,
 					  MergePath *best_path, List *tlist,
 					  List *joinclauses, List *otherclauses,
-					  Plan *outer_plan, List *outer_tlist,
-					  Plan *inner_plan, List *inner_tlist);
+					  Plan *outer_plan, Plan *inner_plan);
 static HashJoin *create_hashjoin_plan(Query *root,
 					 HashPath *best_path, List *tlist,
 					 List *joinclauses, List *otherclauses,
-					 Plan *outer_plan, List *outer_tlist,
-					 Plan *inner_plan, List *inner_tlist);
+					 Plan *outer_plan, Plan *inner_plan);
 static void fix_indxqual_references(List *indexquals, IndexPath *index_path,
 						List **fixed_indexquals,
 						List **recheck_indexquals);
@@ -70,7 +67,7 @@ static void fix_indxqual_sublist(List *indexqual, int baserelid,
 static Node *fix_indxqual_operand(Node *node, int baserelid,
 					 IndexOptInfo *index,
 					 Oid *opclass);
-static List *switch_outer(List *clauses);
+static List *get_switched_clauses(List *clauses, List *outerrelids);
 static List *order_qual_clauses(Query *root, List *clauses);
 static void copy_path_costsize(Plan *dest, Path *src);
 static void copy_plan_costsize(Plan *dest, Plan *src);
@@ -98,6 +95,9 @@ static MergeJoin *make_mergejoin(List *tlist,
 			   List *mergeclauses,
 			   Plan *lefttree, Plan *righttree,
 			   JoinType jointype);
+static Sort *make_sort_from_pathkeys(Query *root, Plan *lefttree,
+									 List *relids, List *pathkeys);
+
 
 /*
  * create_plan
@@ -246,18 +246,13 @@ create_join_plan(Query *root, JoinPath *best_path)
 {
 	List	   *join_tlist = best_path->path.parent->targetlist;
 	Plan	   *outer_plan;
-	List	   *outer_tlist;
 	Plan	   *inner_plan;
-	List	   *inner_tlist;
 	List	   *joinclauses;
 	List	   *otherclauses;
 	Join	   *plan;
 
 	outer_plan = create_plan(root, best_path->outerjoinpath);
-	outer_tlist = outer_plan->targetlist;
-
 	inner_plan = create_plan(root, best_path->innerjoinpath);
-	inner_tlist = inner_plan->targetlist;
 
 	if (IS_OUTER_JOIN(best_path->jointype))
 	{
@@ -280,9 +275,7 @@ create_join_plan(Query *root, JoinPath *best_path)
 												  joinclauses,
 												  otherclauses,
 												  outer_plan,
-												  outer_tlist,
-												  inner_plan,
-												  inner_tlist);
+												  inner_plan);
 			break;
 		case T_HashJoin:
 			plan = (Join *) create_hashjoin_plan(root,
@@ -291,9 +284,7 @@ create_join_plan(Query *root, JoinPath *best_path)
 												 joinclauses,
 												 otherclauses,
 												 outer_plan,
-												 outer_tlist,
-												 inner_plan,
-												 inner_tlist);
+												 inner_plan);
 			break;
 		case T_NestLoop:
 			plan = (Join *) create_nestloop_plan(root,
@@ -302,9 +293,7 @@ create_join_plan(Query *root, JoinPath *best_path)
 												 joinclauses,
 												 otherclauses,
 												 outer_plan,
-												 outer_tlist,
-												 inner_plan,
-												 inner_tlist);
+												 inner_plan);
 			break;
 		default:
 			elog(ERROR, "create_join_plan: unknown node type: %d",
@@ -672,10 +661,9 @@ create_functionscan_plan(Path *best_path, List *tlist, List *scan_clauses)
  *
  * A cleaner solution would be to not call join_references() here at all,
  * but leave it for setrefs.c to do at the end of plan tree construction.
- * But that would make switch_outer() much more complicated, and some care
- * would be needed to get setrefs.c to do the right thing with nestloop
- * inner indexscan quals.  So, we do subplan reference adjustment here for
- * quals of join nodes (and *only* for quals of join nodes).
+ * But some care would be needed to get setrefs.c to do the right thing with
+ * nestloop inner indexscan quals.  So, we do subplan reference adjustment
+ * here for quals of join nodes (and *only* for quals of join nodes).
  *
  *****************************************************************************/
 
@@ -686,10 +674,10 @@ create_nestloop_plan(Query *root,
 					 List *joinclauses,
 					 List *otherclauses,
 					 Plan *outer_plan,
-					 List *outer_tlist,
-					 Plan *inner_plan,
-					 List *inner_tlist)
+					 Plan *inner_plan)
 {
+	List	   *outer_tlist = outer_plan->targetlist;
+	List	   *inner_tlist = inner_plan->targetlist;
 	NestLoop   *join_plan;
 
 	if (IsA(inner_plan, IndexScan))
@@ -797,44 +785,45 @@ create_mergejoin_plan(Query *root,
 					  List *joinclauses,
 					  List *otherclauses,
 					  Plan *outer_plan,
-					  List *outer_tlist,
-					  Plan *inner_plan,
-					  List *inner_tlist)
+					  Plan *inner_plan)
 {
+	List	   *outer_tlist = outer_plan->targetlist;
+	List	   *inner_tlist = inner_plan->targetlist;
 	List	   *mergeclauses;
 	MergeJoin  *join_plan;
 
+	/*
+	 * Remove the mergeclauses from the list of join qual clauses, leaving
+	 * the list of quals that must be checked as qpquals.
+	 */
 	mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
+	joinclauses = set_difference(joinclauses, mergeclauses);
 
 	/*
-	 * Remove the mergeclauses from the list of join qual clauses, leaving
-	 * the list of quals that must be checked as qpquals. Set those
-	 * clauses to contain INNER/OUTER var references.
+	 * Rearrange mergeclauses, if needed, so that the outer variable
+	 * is always on the left.
 	 */
-	joinclauses = join_references(set_difference(joinclauses, mergeclauses),
+	mergeclauses = get_switched_clauses(best_path->path_mergeclauses,
+										best_path->jpath.outerjoinpath->parent->relids);
+
+	/*
+	 * Fix all the join clauses to contain INNER/OUTER var references.
+	 */
+	joinclauses = join_references(joinclauses,
 								  root->rtable,
 								  outer_tlist,
 								  inner_tlist,
 								  (Index) 0);
-
-	/*
-	 * Fix the additional qpquals too.
-	 */
 	otherclauses = join_references(otherclauses,
 								   root->rtable,
 								   outer_tlist,
 								   inner_tlist,
 								   (Index) 0);
-
-	/*
-	 * Now set the references in the mergeclauses and rearrange them so
-	 * that the outer variable is always on the left.
-	 */
-	mergeclauses = switch_outer(join_references(mergeclauses,
-												root->rtable,
-												outer_tlist,
-												inner_tlist,
-												(Index) 0));
+	mergeclauses = join_references(mergeclauses,
+								   root->rtable,
+								   outer_tlist,
+								   inner_tlist,
+								   (Index) 0);
 
 	/*
 	 * Create explicit sort nodes for the outer and inner join paths if
@@ -843,15 +832,15 @@ create_mergejoin_plan(Query *root,
 	if (best_path->outersortkeys)
 		outer_plan = (Plan *)
 			make_sort_from_pathkeys(root,
-									outer_tlist,
 									outer_plan,
+									best_path->jpath.outerjoinpath->parent->relids,
 									best_path->outersortkeys);
 
 	if (best_path->innersortkeys)
 		inner_plan = (Plan *)
 			make_sort_from_pathkeys(root,
-									inner_tlist,
 									inner_plan,
+									best_path->jpath.innerjoinpath->parent->relids,
 									best_path->innersortkeys);
 
 	/*
@@ -877,47 +866,48 @@ create_hashjoin_plan(Query *root,
 					 List *joinclauses,
 					 List *otherclauses,
 					 Plan *outer_plan,
-					 List *outer_tlist,
-					 Plan *inner_plan,
-					 List *inner_tlist)
+					 Plan *inner_plan)
 {
+	List	   *outer_tlist = outer_plan->targetlist;
+	List	   *inner_tlist = inner_plan->targetlist;
 	List	   *hashclauses;
 	HashJoin   *join_plan;
 	Hash	   *hash_plan;
 	List	   *innerhashkeys;
 	List	   *hcl;
 
+	/*
+	 * Remove the hashclauses from the list of join qual clauses, leaving
+	 * the list of quals that must be checked as qpquals.
+	 */
 	hashclauses = get_actual_clauses(best_path->path_hashclauses);
+	joinclauses = set_difference(joinclauses, hashclauses);
 
 	/*
-	 * Remove the hashclauses from the list of join qual clauses, leaving
-	 * the list of quals that must be checked as qpquals. Set those
-	 * clauses to contain INNER/OUTER var references.
+	 * Rearrange hashclauses, if needed, so that the outer variable
+	 * is always on the left.
+	 */
+	hashclauses = get_switched_clauses(best_path->path_hashclauses,
+									   best_path->jpath.outerjoinpath->parent->relids);
+
+	/*
+	 * Fix all the join clauses to contain INNER/OUTER var references.
 	 */
-	joinclauses = join_references(set_difference(joinclauses, hashclauses),
+	joinclauses = join_references(joinclauses,
 								  root->rtable,
 								  outer_tlist,
 								  inner_tlist,
 								  (Index) 0);
-
-	/*
-	 * Fix the additional qpquals too.
-	 */
 	otherclauses = join_references(otherclauses,
 								   root->rtable,
 								   outer_tlist,
 								   inner_tlist,
 								   (Index) 0);
-
-	/*
-	 * Now set the references in the hashclauses and rearrange them so
-	 * that the outer variable is always on the left.
-	 */
-	hashclauses = switch_outer(join_references(hashclauses,
-											   root->rtable,
-											   outer_tlist,
-											   inner_tlist,
-											   (Index) 0));
+	hashclauses = join_references(hashclauses,
+								  root->rtable,
+								  outer_tlist,
+								  inner_tlist,
+								  (Index) 0);
 
 	/*
 	 * Extract the inner hash keys (right-hand operands of the hashclauses)
@@ -1154,27 +1144,26 @@ fix_indxqual_operand(Node *node, int baserelid, IndexOptInfo *index,
 }
 
 /*
- * switch_outer
- *	  Given a list of merge or hash joinclauses, rearrange the elements within
- *	  the clauses so the outer join variable is on the left and the inner is
- *	  on the right.  The original list is not touched; a modified list
- *	  is returned.
+ * get_switched_clauses
+ *	  Given a list of merge or hash joinclauses (as RestrictInfo nodes),
+ *	  extract the bare clauses, and rearrange the elements within the
+ *	  clauses, if needed, so the outer join variable is on the left and
+ *	  the inner is on the right.  The original data structure is not touched;
+ *	  a modified list is returned.
  */
 static List *
-switch_outer(List *clauses)
+get_switched_clauses(List *clauses, List *outerrelids)
 {
 	List	   *t_list = NIL;
 	List	   *i;
 
 	foreach(i, clauses)
 	{
-		OpExpr	   *clause = (OpExpr *) lfirst(i);
-		Var		   *op;
+		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(i);
+		OpExpr	   *clause = (OpExpr *) restrictinfo->clause;
 
 		Assert(is_opclause(clause));
-		op = get_rightop((Expr *) clause);
-		Assert(op && IsA(op, Var));
-		if (var_is_outer(op))
+		if (is_subseti(restrictinfo->right_relids, outerrelids))
 		{
 			/*
 			 * Duplicate just enough of the structure to allow commuting
@@ -1554,17 +1543,24 @@ make_sort(Query *root, List *tlist, Plan *lefttree, int keycount)
  * make_sort_from_pathkeys
  *	  Create sort plan to sort according to given pathkeys
  *
- *	  'tlist' is the target list of the input plan
  *	  'lefttree' is the node which yields input tuples
+ *	  'relids' is the set of relids represented by the input node
  *	  'pathkeys' is the list of pathkeys by which the result is to be sorted
  *
  * We must convert the pathkey information into reskey and reskeyop fields
  * of resdom nodes in the sort plan's target list.
+ *
+ * If the pathkeys include expressions that aren't simple Vars, we will
+ * usually need to add resjunk items to the input plan's targetlist to
+ * compute these expressions (since the Sort node itself won't do it).
+ * If the input plan type isn't one that can do projections, this means
+ * adding a Result node just to do the projection.
  */
-Sort *
-make_sort_from_pathkeys(Query *root, List *tlist,
-						Plan *lefttree, List *pathkeys)
+static Sort *
+make_sort_from_pathkeys(Query *root, Plan *lefttree,
+						List *relids, List *pathkeys)
 {
+	List	   *tlist = lefttree->targetlist;
 	List	   *sort_tlist;
 	List	   *i;
 	int			numsortkeys = 0;
@@ -1582,7 +1578,8 @@ make_sort_from_pathkeys(Query *root, List *tlist,
 		/*
 		 * We can sort by any one of the sort key items listed in this
 		 * sublist.  For now, we take the first one that corresponds to an
-		 * available Var in the sort_tlist.
+		 * available Var in the sort_tlist.  If there isn't any, use the
+		 * first one that is an expression in the input's vars.
 		 *
 		 * XXX if we have a choice, is there any way of figuring out which
 		 * might be cheapest to execute?  (For example, int4lt is likely
@@ -1599,8 +1596,52 @@ make_sort_from_pathkeys(Query *root, List *tlist,
 				break;
 		}
 		if (!resdom)
-			elog(ERROR, "make_sort_from_pathkeys: cannot find tlist item to sort");
-
+		{
+			/* No matching Var; look for an expression */
+			foreach(j, keysublist)
+			{
+				pathkey = lfirst(j);
+				if (is_subseti(pull_varnos(pathkey->key), relids))
+					break;
+			}
+			if (!j)
+				elog(ERROR, "make_sort_from_pathkeys: cannot find pathkey item to sort");
+			/*
+			 * Do we need to insert a Result node?
+			 *
+			 * Currently, the only non-projection-capable plan type
+			 * we can see here is Append.
+			 */
+			if (IsA(lefttree, Append))
+			{
+				tlist = new_unsorted_tlist(tlist);
+				lefttree = (Plan *) make_result(tlist, NULL, lefttree);
+			}
+			/*
+			 * Add resjunk entry to input's tlist
+			 */
+			resdom = makeResdom(length(tlist) + 1,
+								exprType(pathkey->key),
+								exprTypmod(pathkey->key),
+								NULL,
+								true);
+			tlist = lappend(tlist,
+							makeTargetEntry(resdom,
+											(Expr *) pathkey->key));
+			lefttree->targetlist = tlist; /* just in case NIL before */
+			/*
+			 * Add one to sort node's tlist too.  This will be identical
+			 * except we are going to set the sort key info in it.
+			 */
+			resdom = makeResdom(length(sort_tlist) + 1,
+								exprType(pathkey->key),
+								exprTypmod(pathkey->key),
+								NULL,
+								true);
+			sort_tlist = lappend(sort_tlist,
+								 makeTargetEntry(resdom,
+												 (Expr *) pathkey->key));
+		}
 		/*
 		 * The resdom might be already marked as a sort key, if the
 		 * pathkeys contain duplicate entries.	(This can happen in
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 1c1a848ea72dcc8be4c7b6ec0264f0d596a0352a..87c77e52fc37e2f063adfacbf261c89dcf42a47f 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.80 2003/01/12 22:35:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.81 2003/01/15 19:35:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -60,32 +60,24 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * add_base_rels_to_query
  *
  *	  Scan the query's jointree and create baserel RelOptInfos for all
- *	  the base relations (ie, table and subquery RTEs) appearing in the
- *	  jointree.  Also, create otherrel RelOptInfos for join RTEs.
- *
- * The return value is a list of all the baserel indexes (but not join RTE
- * indexes) included in the scanned jointree.  This is actually just an
- * internal convenience for marking join otherrels properly; no outside
- * caller uses the result.
+ *	  the base relations (ie, table, subquery, and function RTEs)
+ *	  appearing in the jointree.
  *
  * At the end of this process, there should be one baserel RelOptInfo for
  * every non-join RTE that is used in the query.  Therefore, this routine
  * is the only place that should call build_base_rel.  But build_other_rel
- * will be used again later to build rels for inheritance children.
+ * will be used later to build rels for inheritance children.
  */
-List *
+void
 add_base_rels_to_query(Query *root, Node *jtnode)
 {
-	List	   *result = NIL;
-
 	if (jtnode == NULL)
-		return NIL;
+		return;
 	if (IsA(jtnode, RangeTblRef))
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
 		build_base_rel(root, varno);
-		result = makeListi1(varno);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -94,29 +86,15 @@ add_base_rels_to_query(Query *root, Node *jtnode)
 
 		foreach(l, f->fromlist)
 		{
-			result = nconc(result,
-						   add_base_rels_to_query(root, lfirst(l)));
+			add_base_rels_to_query(root, lfirst(l));
 		}
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
-		RelOptInfo *jrel;
-
-		result = add_base_rels_to_query(root, j->larg);
-		result = nconc(result,
-					   add_base_rels_to_query(root, j->rarg));
-		/* the join's own rtindex is NOT added to result */
-		jrel = build_other_rel(root, j->rtindex);
-
-		/*
-		 * Mark the join's otherrel with outerjoinset = list of baserel
-		 * ids included in the join.  Note we must copy here because
-		 * result list is destructively modified by nconcs at higher
-		 * levels.
-		 */
-		jrel->outerjoinset = listCopy(result);
 
+		add_base_rels_to_query(root, j->larg);
+		add_base_rels_to_query(root, j->rarg);
 		/*
 		 * Safety check: join RTEs should not be SELECT FOR UPDATE targets
 		 */
@@ -126,7 +104,6 @@ add_base_rels_to_query(Query *root, Node *jtnode)
 	else
 		elog(ERROR, "add_base_rels_to_query: unexpected node type %d",
 			 nodeTag(jtnode));
-	return result;
 }
 
 
@@ -154,11 +131,6 @@ build_base_rel_tlists(Query *root, List *tlist)
  * add_vars_to_targetlist
  *	  For each variable appearing in the list, add it to the owning
  *	  relation's targetlist if not already present.
- *
- * Note that join alias variables will be attached to the otherrel for
- * the join RTE.  They will later be transferred to the tlist of
- * the corresponding joinrel.  We will also cause entries to be made
- * for the Vars that the alias will eventually depend on.
  */
 static void
 add_vars_to_targetlist(Query *root, List *vars)
@@ -171,19 +143,6 @@ add_vars_to_targetlist(Query *root, List *vars)
 		RelOptInfo *rel = find_base_rel(root, var->varno);
 
 		add_var_to_tlist(rel, var);
-
-		if (rel->reloptkind == RELOPT_OTHER_JOIN_REL)
-		{
-			/* Var is an alias */
-			Node	   *expansion;
-			List	   *varsused;
-
-			expansion = flatten_join_alias_vars((Node *) var,
-												root->rtable, true);
-			varsused = pull_var_clause(expansion, false);
-			add_vars_to_targetlist(root, varsused);
-			freeList(varsused);
-		}
 	}
 }
 
@@ -398,6 +357,8 @@ distribute_qual_to_rels(Query *root, Node *clause,
 	restrictinfo->subclauseindices = NIL;
 	restrictinfo->eval_cost.startup = -1; /* not computed until needed */
 	restrictinfo->this_selec = -1;		/* not computed until needed */
+	restrictinfo->left_relids = NIL; /* set below, if join clause */
+	restrictinfo->right_relids = NIL;
 	restrictinfo->mergejoinoperator = InvalidOid;
 	restrictinfo->left_sortop = InvalidOid;
 	restrictinfo->right_sortop = InvalidOid;
@@ -416,41 +377,11 @@ distribute_qual_to_rels(Query *root, Node *clause,
 	clause_get_relids_vars(clause, &relids, &vars);
 
 	/*
-	 * The clause might contain some join alias vars; if so, we want to
-	 * remove the join otherrelids from relids and add the referent joins'
-	 * scope lists instead (thus ensuring that the clause can be evaluated
-	 * no lower than that join node).  We rely here on the marking done
-	 * earlier by add_base_rels_to_query.
-	 *
-	 * We can combine this step with a cross-check that the clause contains
-	 * no relids not within its scope.	If the first crosscheck succeeds,
-	 * the clause contains no aliases and we needn't look more closely.
+	 * Cross-check: clause should contain no relids not within its scope.
+	 * Otherwise the parser messed up.
 	 */
 	if (!is_subseti(relids, qualscope))
-	{
-		Relids		newrelids = NIL;
-		List	   *relid;
-
-		foreach(relid, relids)
-		{
-			RelOptInfo *rel = find_other_rel(root, lfirsti(relid));
-
-			if (rel && rel->outerjoinset)
-			{
-				/* this relid is for a join RTE */
-				newrelids = set_unioni(newrelids, rel->outerjoinset);
-			}
-			else
-			{
-				/* this relid is for a true baserel */
-				newrelids = lappendi(newrelids, lfirsti(relid));
-			}
-		}
-		relids = newrelids;
-		/* Now repeat the crosscheck */
-		if (!is_subseti(relids, qualscope))
-			elog(ERROR, "JOIN qualification may not refer to other relations");
-	}
+		elog(ERROR, "JOIN qualification may not refer to other relations");
 
 	/*
 	 * If the clause is variable-free, we force it to be evaluated at its
@@ -575,7 +506,27 @@ distribute_qual_to_rels(Query *root, Node *clause,
 		/*
 		 * 'clause' is a join clause, since there is more than one rel in
 		 * the relid list.	Set additional RestrictInfo fields for
-		 * joining.
+		 * joining.  First, does it look like a normal join clause, i.e.,
+		 * a binary operator relating expressions that come from distinct
+		 * relations?  If so we might be able to use it in a join algorithm.
+		 */
+		if (is_opclause(clause) && length(((OpExpr *) clause)->args) == 2)
+		{
+			List	   *left_relids;
+			List	   *right_relids;
+
+			left_relids = pull_varnos(get_leftop((Expr *) clause));
+			right_relids = pull_varnos(get_rightop((Expr *) clause));
+			if (left_relids && right_relids &&
+				nonoverlap_setsi(left_relids, right_relids))
+			{
+				restrictinfo->left_relids = left_relids;
+				restrictinfo->right_relids = right_relids;
+			}
+		}
+
+		/*
+		 * Now check for hash or mergejoinable operators.
 		 *
 		 * We don't bother setting the hashjoin info if we're not going
 		 * to need it.	We do want to know about mergejoinable ops in all
@@ -675,11 +626,6 @@ void
 process_implied_equality(Query *root, Node *item1, Node *item2,
 						 Oid sortop1, Oid sortop2)
 {
-	Index		irel1;
-	Index		irel2;
-	RelOptInfo *rel1;
-	List	   *restrictlist;
-	List	   *itm;
 	Oid			ltype,
 				rtype;
 	Operator	eq_operator;
@@ -687,50 +633,14 @@ process_implied_equality(Query *root, Node *item1, Node *item2,
 	Expr	   *clause;
 
 	/*
-	 * Currently, since check_mergejoinable only accepts Var = Var
-	 * clauses, we should only see Var nodes here.	Would have to work a
-	 * little harder to locate the right rel(s) if more-general mergejoin
-	 * clauses were accepted.
-	 */
-	Assert(IsA(item1, Var));
-	irel1 = ((Var *) item1)->varno;
-	Assert(IsA(item2, Var));
-	irel2 = ((Var *) item2)->varno;
-
-	/*
-	 * If both vars belong to same rel, we need to look at that rel's
-	 * baserestrictinfo list.  If different rels, each will have a
-	 * joininfo node for the other, and we can scan either list.
-	 */
-	rel1 = find_base_rel(root, irel1);
-	if (irel1 == irel2)
-		restrictlist = rel1->baserestrictinfo;
-	else
-	{
-		JoinInfo   *joininfo = find_joininfo_node(rel1,
-												  makeListi1(irel2));
-
-		restrictlist = joininfo->jinfo_restrictinfo;
-	}
-
-	/*
-	 * Scan to see if equality is already known.
+	 * Forget it if this equality is already recorded.
+	 *
+	 * Note: if only a single relation is involved, we may fall through
+	 * here and end up rejecting the equality later on in qual_is_redundant.
+	 * This is a tad slow but should be okay.
 	 */
-	foreach(itm, restrictlist)
-	{
-		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(itm);
-		Node	   *left,
-				   *right;
-
-		if (restrictinfo->mergejoinoperator == InvalidOid)
-			continue;			/* ignore non-mergejoinable clauses */
-		/* We now know the restrictinfo clause is a binary opclause */
-		left = (Node *) get_leftop(restrictinfo->clause);
-		right = (Node *) get_rightop(restrictinfo->clause);
-		if ((equal(item1, left) && equal(item2, right)) ||
-			(equal(item2, left) && equal(item1, right)))
-			return;				/* found a matching clause */
-	}
+	if (exprs_known_equal(root, item1, item2))
+		return;
 
 	/*
 	 * This equality is new information, so construct a clause
@@ -770,6 +680,8 @@ process_implied_equality(Query *root, Node *item1, Node *item2,
 	ReleaseSysCache(eq_operator);
 
 	/*
+	 * Push the new clause into all the appropriate restrictinfo lists.
+	 *
 	 * Note: we mark the qual "pushed down" to ensure that it can never be
 	 * taken for an original JOIN/ON clause.
 	 */
@@ -779,44 +691,45 @@ process_implied_equality(Query *root, Node *item1, Node *item2,
 }
 
 /*
- * vars_known_equal
- *	  Detect whether two Vars are known equal due to equijoin clauses.
+ * exprs_known_equal
+ *	  Detect whether two expressions are known equal due to equijoin clauses.
  *
  * This is not completely accurate since we avoid adding redundant restriction
  * clauses to individual base rels (see qual_is_redundant).  However, after
- * the implied-equality-deduction phase, it is complete for Vars of different
- * rels; that's sufficient for planned uses.
+ * the implied-equality-deduction phase, it is complete for expressions
+ * involving Vars of multiple rels; that's sufficient for planned uses.
  */
 bool
-vars_known_equal(Query *root, Var *var1, Var *var2)
+exprs_known_equal(Query *root, Node *item1, Node *item2)
 {
-	Index		irel1;
-	Index		irel2;
+	List	   *relids;
 	RelOptInfo *rel1;
 	List	   *restrictlist;
 	List	   *itm;
 
+	/* Get list of relids referenced in the two expressions */
+	relids = set_unioni(pull_varnos(item1), pull_varnos(item2));
+
 	/*
-	 * Would need more work here if we wanted to check for known equality
-	 * of general clauses: there might be multiple base rels involved.
+	 * If there are no Vars at all, say "true".  This prevents
+	 * process_implied_equality from trying to store "const = const"
+	 * deductions.
 	 */
-	Assert(IsA(var1, Var));
-	irel1 = var1->varno;
-	Assert(IsA(var2, Var));
-	irel2 = var2->varno;
+	if (relids == NIL)
+		return true;
 
 	/*
-	 * If both vars belong to same rel, we need to look at that rel's
-	 * baserestrictinfo list.  If different rels, each will have a
-	 * joininfo node for the other, and we can scan either list.
+	 * If the exprs involve a single rel, we need to look at that rel's
+	 * baserestrictinfo list.  If multiple rels, any one will have a
+	 * joininfo node for the rest, and we can scan any of 'em.
 	 */
-	rel1 = find_base_rel(root, irel1);
-	if (irel1 == irel2)
+	rel1 = find_base_rel(root, lfirsti(relids));
+	relids = lnext(relids);
+	if (relids == NIL)
 		restrictlist = rel1->baserestrictinfo;
 	else
 	{
-		JoinInfo   *joininfo = find_joininfo_node(rel1,
-												  makeListi1(irel2));
+		JoinInfo   *joininfo = find_joininfo_node(rel1, relids);
 
 		restrictlist = joininfo->jinfo_restrictinfo;
 	}
@@ -833,10 +746,10 @@ vars_known_equal(Query *root, Var *var1, Var *var2)
 		if (restrictinfo->mergejoinoperator == InvalidOid)
 			continue;			/* ignore non-mergejoinable clauses */
 		/* We now know the restrictinfo clause is a binary opclause */
-		left = (Node *) get_leftop(restrictinfo->clause);
-		right = (Node *) get_rightop(restrictinfo->clause);
-		if ((equal(var1, left) && equal(var2, right)) ||
-			(equal(var2, left) && equal(var1, right)))
+		left = get_leftop(restrictinfo->clause);
+		right = get_rightop(restrictinfo->clause);
+		if ((equal(item1, left) && equal(item2, right)) ||
+			(equal(item2, left) && equal(item1, right)))
 			return true;		/* found a matching clause */
 	}
 
@@ -862,7 +775,7 @@ qual_is_redundant(Query *root,
 	List	   *olditem;
 	Node	   *newleft;
 	Node	   *newright;
-	List	   *equalvars;
+	List	   *equalexprs;
 	bool		someadded;
 
 	/*
@@ -898,15 +811,15 @@ qual_is_redundant(Query *root,
 		return false;
 
 	/*
-	 * Now, we want to develop a list of Vars that are known equal to the
+	 * Now, we want to develop a list of exprs that are known equal to the
 	 * left side of the new qual.  We traverse the old-quals list
-	 * repeatedly to transitively expand the Vars list.  If at any point
-	 * we find we can reach the right-side Var of the new qual, we are
-	 * done.  We give up when we can't expand the equalvars list any more.
+	 * repeatedly to transitively expand the exprs list.  If at any point
+	 * we find we can reach the right-side expr of the new qual, we are
+	 * done.  We give up when we can't expand the equalexprs list any more.
 	 */
-	newleft = (Node *) get_leftop(restrictinfo->clause);
-	newright = (Node *) get_rightop(restrictinfo->clause);
-	equalvars = makeList1(newleft);
+	newleft = get_leftop(restrictinfo->clause);
+	newright = get_rightop(restrictinfo->clause);
+	equalexprs = makeList1(newleft);
 	do
 	{
 		someadded = false;
@@ -915,22 +828,22 @@ qual_is_redundant(Query *root,
 		while (olditem)
 		{
 			RestrictInfo *oldrinfo = (RestrictInfo *) lfirst(olditem);
-			Node	   *oldleft = (Node *) get_leftop(oldrinfo->clause);
-			Node	   *oldright = (Node *) get_rightop(oldrinfo->clause);
+			Node	   *oldleft = get_leftop(oldrinfo->clause);
+			Node	   *oldright = get_rightop(oldrinfo->clause);
 			Node	   *newguy = NULL;
 
 			/* must advance olditem before lremove possibly pfree's it */
 			olditem = lnext(olditem);
 
-			if (member(oldleft, equalvars))
+			if (member(oldleft, equalexprs))
 				newguy = oldright;
-			else if (member(oldright, equalvars))
+			else if (member(oldright, equalexprs))
 				newguy = oldleft;
 			else
 				continue;
 			if (equal(newguy, newright))
 				return true;	/* we proved new clause is redundant */
-			equalvars = lcons(newguy, equalvars);
+			equalexprs = lcons(newguy, equalexprs);
 			someadded = true;
 
 			/*
@@ -956,39 +869,28 @@ qual_is_redundant(Query *root,
  *	  info fields in the restrictinfo.
  *
  *	  Currently, we support mergejoin for binary opclauses where
- *	  both operands are simple Vars and the operator is a mergejoinable
- *	  operator.
+ *	  the operator is a mergejoinable operator.  The arguments can be
+ *	  anything --- as long as there are no volatile functions in them.
  */
 static void
 check_mergejoinable(RestrictInfo *restrictinfo)
 {
 	Expr	   *clause = restrictinfo->clause;
-	Var		   *left,
-			   *right;
 	Oid			opno,
 				leftOp,
 				rightOp;
 
 	if (!is_opclause(clause))
 		return;
-
-	left = get_leftop(clause);
-	right = get_rightop(clause);
-
-	/* caution: is_opclause accepts more than I do, so check it */
-	if (!right)
-		return;					/* unary opclauses need not apply */
-	if (!IsA(left, Var) ||
-		!IsA(right, Var))
+	if (length(((OpExpr *) clause)->args) != 2)
 		return;
 
 	opno = ((OpExpr *) clause)->opno;
 
 	if (op_mergejoinable(opno,
-						 left->vartype,
-						 right->vartype,
 						 &leftOp,
-						 &rightOp))
+						 &rightOp) &&
+		!contain_volatile_functions((Node *) clause))
 	{
 		restrictinfo->mergejoinoperator = opno;
 		restrictinfo->left_sortop = leftOp;
@@ -1002,34 +904,23 @@ check_mergejoinable(RestrictInfo *restrictinfo)
  *	  info fields in the restrictinfo.
  *
  *	  Currently, we support hashjoin for binary opclauses where
- *	  both operands are simple Vars and the operator is a hashjoinable
- *	  operator.
+ *	  the operator is a hashjoinable operator.  The arguments can be
+ *	  anything --- as long as there are no volatile functions in them.
  */
 static void
 check_hashjoinable(RestrictInfo *restrictinfo)
 {
 	Expr	   *clause = restrictinfo->clause;
-	Var		   *left,
-			   *right;
 	Oid			opno;
 
 	if (!is_opclause(clause))
 		return;
-
-	left = get_leftop(clause);
-	right = get_rightop(clause);
-
-	/* caution: is_opclause accepts more than I do, so check it */
-	if (!right)
-		return;					/* unary opclauses need not apply */
-	if (!IsA(left, Var) ||
-		!IsA(right, Var))
+	if (length(((OpExpr *) clause)->args) != 2)
 		return;
 
 	opno = ((OpExpr *) clause)->opno;
 
-	if (op_hashjoinable(opno,
-						left->vartype,
-						right->vartype))
+	if (op_hashjoinable(opno) &&
+		!contain_volatile_functions((Node *) clause))
 		restrictinfo->hashjoinoperator = opno;
 }
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 5d2634bf82d883d55d87da2bb7c6cebdc447c4d2..6e265931eb226edf60ac26d06f93019fa32ed1aa 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.72 2002/11/21 00:42:19 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.73 2003/01/15 19:35:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -117,7 +117,7 @@ query_planner(Query *root, List *tlist, double tuple_fraction,
 	/*
 	 * Construct RelOptInfo nodes for all base relations in query.
 	 */
-	(void) add_base_rels_to_query(root, (Node *) root->jointree);
+	add_base_rels_to_query(root, (Node *) root->jointree);
 
 	/*
 	 * Examine the targetlist and qualifications, adding entries to
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 76a16cd833d64cf7ac1198b4e4861c5a1f747120..4cf6a09acfecbcc1b117195d871ba66aaffaf187 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.138 2003/01/13 18:10:53 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.139 2003/01/15 19:35:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -559,20 +559,6 @@ is_simple_subquery(Query *subquery)
 	if (expression_returns_set((Node *) subquery->targetList))
 		return false;
 
-	/*
-	 * Don't pull up a subquery that has any sublinks in its targetlist,
-	 * either.  As of PG 7.3 this creates problems because the pulled-up
-	 * expressions may go into join alias lists, and the sublinks would
-	 * not get fixed because we do flatten_join_alias_vars() too late.
-	 * Eventually we should do a complete flatten_join_alias_vars as the
-	 * first step of preprocess_expression, and then we could probably
-	 * support this.  (BUT: it might be a bad idea anyway, due to possibly
-	 * causing multiple evaluations of an expensive sublink.)
-	 */
-	if (subquery->hasSubLinks &&
-		contain_subplans((Node *) subquery->targetList))
-		return false;
-
 	/*
 	 * Hack: don't try to pull up a subquery with an empty jointree.
 	 * query_planner() will correctly generate a Result plan for a
@@ -750,6 +736,14 @@ preprocess_jointree(Query *parse, Node *jtnode)
 static Node *
 preprocess_expression(Query *parse, Node *expr, int kind)
 {
+	/*
+	 * If the query has any join RTEs, replace join alias variables with
+	 * base-relation variables. We must do this before sublink processing,
+	 * else sublinks expanded out from join aliases wouldn't get processed.
+	 */
+	if (parse->hasJoinRTEs)
+		expr = flatten_join_alias_vars(expr, parse->rtable);
+
 	/*
 	 * Simplify constant expressions.
 	 *
@@ -783,15 +777,6 @@ preprocess_expression(Query *parse, Node *expr, int kind)
 	if (PlannerQueryLevel > 1)
 		expr = SS_replace_correlation_vars(expr);
 
-	/*
-	 * If the query has any join RTEs, try to replace join alias variables
-	 * with base-relation variables, to allow quals to be pushed down. We
-	 * must do this after sublink processing, since it does not recurse
-	 * into sublinks.
-	 */
-	if (parse->hasJoinRTEs)
-		expr = flatten_join_alias_vars(expr, parse->rtable, false);
-
 	return expr;
 }
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1b5c72e163c43e78d757944ab015a899aa514277..1c9f8a27cff9044b7eac8887ecd81f967f5626e9 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.88 2003/01/13 18:10:53 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.89 2003/01/15 19:35:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -278,8 +278,7 @@ fix_expr_references_walker(Node *node, void *context)
  * Note: this same transformation has already been applied to the quals
  * of the join by createplan.c.  It's a little odd to do it here for the
  * targetlist and there for the quals, but it's easier that way.  (Look
- * at switch_outer() and the handling of nestloop inner indexscans to
- * see why.)
+ * at the handling of nestloop inner indexscans to see why.)
  *
  * Because the quals are reference-adjusted sooner, we cannot do equal()
  * comparisons between qual and tlist var nodes during the time between
@@ -379,8 +378,7 @@ set_uppernode_references(Plan *plan, Index subvarno)
  *	   Creates a new set of targetlist entries or join qual clauses by
  *	   changing the varno/varattno values of variables in the clauses
  *	   to reference target list values from the outer and inner join
- *	   relation target lists.  Also, any join alias variables in the
- *	   clauses are expanded into references to their component variables.
+ *	   relation target lists.
  *
  * This is used in two different scenarios: a normal join clause, where
  * all the Vars in the clause *must* be replaced by OUTER or INNER references;
@@ -428,7 +426,6 @@ join_references_mutator(Node *node,
 	{
 		Var		   *var = (Var *) node;
 		Resdom	   *resdom;
-		Node	   *newnode;
 
 		/* First look for the var in the input tlists */
 		resdom = tlist_member((Node *) var, context->outer_tlist);
@@ -454,20 +451,6 @@ join_references_mutator(Node *node,
 		if (var->varno == context->acceptable_rel)
 			return (Node *) copyObject(var);
 
-		/*
-		 * Perhaps it's a join alias that can be resolved to input vars?
-		 * We try this last since it's relatively slow.
-		 */
-		newnode = flatten_join_alias_vars((Node *) var,
-										  context->rtable,
-										  true);
-		if (!equal(newnode, (Node *) var))
-		{
-			/* Must now resolve the input vars... */
-			newnode = join_references_mutator(newnode, context);
-			return newnode;
-		}
-
 		/* No referent found for Var */
 		elog(ERROR, "join_references: variable not in subplan target lists");
 	}
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 5177e210d3d66cfde830e1dd2c7f99507f81bbd8..4af395b860c2b43d0cbca0946d6f6f71f0e2d1d8 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.85 2003/01/12 22:35:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.86 2003/01/15 19:35:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -837,9 +837,7 @@ adjust_inherited_attrs_mutator(Node *node,
 	}
 
 	/*
-	 * We have to process RestrictInfo nodes specially: we do NOT want to
-	 * copy the original subclauseindices list, since the new rel may have
-	 * different indices.  The list will be rebuilt during later planning.
+	 * We have to process RestrictInfo nodes specially.
 	 */
 	if (IsA(node, RestrictInfo))
 	{
@@ -849,10 +847,41 @@ adjust_inherited_attrs_mutator(Node *node,
 		/* Copy all flat-copiable fields */
 		memcpy(newinfo, oldinfo, sizeof(RestrictInfo));
 
+		/* Recursively fix the clause itself */
 		newinfo->clause = (Expr *)
 			adjust_inherited_attrs_mutator((Node *) oldinfo->clause, context);
 
+		/*
+		 * We do NOT want to copy the original subclauseindices list, since
+		 * the new rel will have different indices.  The list will be rebuilt
+		 * when needed during later planning.
+		 */
 		newinfo->subclauseindices = NIL;
+
+		/*
+		 * Adjust left/right relids lists too.
+		 */
+		if (intMember(context->old_rt_index, oldinfo->left_relids))
+		{
+			newinfo->left_relids = listCopy(oldinfo->left_relids);
+			newinfo->left_relids = lremovei(context->old_rt_index,
+											newinfo->left_relids);
+			newinfo->left_relids = lconsi(context->new_rt_index,
+										  newinfo->left_relids);
+		}
+		else
+			newinfo->left_relids = oldinfo->left_relids;
+		if (intMember(context->old_rt_index, oldinfo->right_relids))
+		{
+			newinfo->right_relids = listCopy(oldinfo->right_relids);
+			newinfo->right_relids = lremovei(context->old_rt_index,
+											 newinfo->right_relids);
+			newinfo->right_relids = lconsi(context->new_rt_index,
+										   newinfo->right_relids);
+		}
+		else
+			newinfo->right_relids = oldinfo->right_relids;
+
 		newinfo->eval_cost.startup = -1; /* reset these too */
 		newinfo->this_selec = -1;
 		newinfo->left_pathkey = NIL;	/* and these */
@@ -869,6 +898,7 @@ adjust_inherited_attrs_mutator(Node *node,
 	 * NOTE: we do not need to recurse into sublinks, because they should
 	 * already have been converted to subplans before we see them.
 	 */
+	Assert(!IsA(node, SubLink));
 
 	/*
 	 * BUT: although we don't need to recurse into subplans, we do need to
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e38fc46821d5eab93fdf18f5fe8eede2e77b1d75..233a8cb8947d45dd0384c756703f89d75942f72d 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.121 2003/01/10 21:08:13 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.122 2003/01/15 19:35:44 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -112,17 +112,13 @@ make_opclause(Oid opno, Oid opresulttype, bool opretset,
  *
  * Returns the left operand of a clause of the form (op expr expr)
  *		or (op expr)
- *
- * NB: for historical reasons, the result is declared Var *, even
- * though many callers can cope with results that are not Vars.
- * The result really ought to be declared Expr * or Node *.
  */
-Var *
+Node *
 get_leftop(Expr *clause)
 {
 	OpExpr *expr = (OpExpr *) clause;
 
-	if (expr->args != NULL)
+	if (expr->args != NIL)
 		return lfirst(expr->args);
 	else
 		return NULL;
@@ -134,12 +130,12 @@ get_leftop(Expr *clause)
  * Returns the right operand in a clause of the form (op expr expr).
  * NB: result will be NULL if applied to a unary op clause.
  */
-Var *
+Node *
 get_rightop(Expr *clause)
 {
 	OpExpr *expr = (OpExpr *) clause;
 
-	if (expr->args != NULL && lnext(expr->args) != NULL)
+	if (expr->args != NIL && lnext(expr->args) != NIL)
 		return lfirst(lnext(expr->args));
 	else
 		return NULL;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 296d211ad12166e58dc3f8d98a13e1473c837cd7..87207f617cc508eb2a46bf3365d2dc8a36d714ae 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.42 2003/01/12 22:35:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.43 2003/01/15 19:35:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -110,9 +110,9 @@ build_other_rel(Query *root, int relid)
 	/* No existing RelOptInfo for this other rel, so make a new one */
 	rel = make_base_rel(root, relid);
 
-	/* if it's not a join rel, must be a child rel */
-	if (rel->reloptkind == RELOPT_BASEREL)
-		rel->reloptkind = RELOPT_OTHER_CHILD_REL;
+	/* presently, must be an inheritance child rel */
+	Assert(rel->reloptkind == RELOPT_BASEREL);
+	rel->reloptkind = RELOPT_OTHER_CHILD_REL;
 
 	/* and add it to the list */
 	root->other_rel_list = lcons(rel, root->other_rel_list);
@@ -146,8 +146,6 @@ make_base_rel(Query *root, int relid)
 	rel->pages = 0;
 	rel->tuples = 0;
 	rel->subplan = NULL;
-	rel->joinrti = 0;
-	rel->joinrteids = NIL;
 	rel->baserestrictinfo = NIL;
 	rel->baserestrictcost.startup = 0;
 	rel->baserestrictcost.per_tuple = 0;
@@ -174,10 +172,6 @@ make_base_rel(Query *root, int relid)
 		case RTE_FUNCTION:
 			/* Subquery or function --- nothing to do here */
 			break;
-		case RTE_JOIN:
-			/* Join --- must be an otherrel */
-			rel->reloptkind = RELOPT_OTHER_JOIN_REL;
-			break;
 		default:
 			elog(ERROR, "make_base_rel: unsupported RTE kind %d",
 				 (int) rte->rtekind);
@@ -220,47 +214,6 @@ find_base_rel(Query *root, int relid)
 	return NULL;				/* keep compiler quiet */
 }
 
-/*
- * find_other_rel
- *	  Find an otherrel entry, if one exists for the given relid.
- *	  Return NULL if no entry.
- */
-RelOptInfo *
-find_other_rel(Query *root, int relid)
-{
-	List	   *rels;
-
-	foreach(rels, root->other_rel_list)
-	{
-		RelOptInfo *rel = (RelOptInfo *) lfirst(rels);
-
-		if (lfirsti(rel->relids) == relid)
-			return rel;
-	}
-	return NULL;
-}
-
-/*
- * find_other_rel_for_join
- *	  Look for an otherrel for a join RTE matching the given baserel set.
- *	  Return NULL if no entry.
- */
-RelOptInfo *
-find_other_rel_for_join(Query *root, List *relids)
-{
-	List	   *rels;
-
-	foreach(rels, root->other_rel_list)
-	{
-		RelOptInfo *rel = (RelOptInfo *) lfirst(rels);
-
-		if (rel->reloptkind == RELOPT_OTHER_JOIN_REL
-			&& sameseti(relids, rel->outerjoinset))
-			return rel;
-	}
-	return NULL;
-}
-
 /*
  * find_join_rel
  *	  Returns relation entry corresponding to 'relids' (a list of RT indexes),
@@ -310,7 +263,6 @@ build_join_rel(Query *root,
 {
 	List	   *joinrelids;
 	RelOptInfo *joinrel;
-	RelOptInfo *joinrterel;
 	List	   *restrictlist;
 	List	   *new_outer_tlist;
 	List	   *new_inner_tlist;
@@ -360,9 +312,6 @@ build_join_rel(Query *root,
 	joinrel->pages = 0;
 	joinrel->tuples = 0;
 	joinrel->subplan = NULL;
-	joinrel->joinrti = 0;
-	joinrel->joinrteids = nconc(listCopy(outer_rel->joinrteids),
-								inner_rel->joinrteids);
 	joinrel->baserestrictinfo = NIL;
 	joinrel->baserestrictcost.startup = 0;
 	joinrel->baserestrictcost.per_tuple = 0;
@@ -371,15 +320,6 @@ build_join_rel(Query *root,
 	joinrel->index_outer_relids = NIL;
 	joinrel->index_inner_paths = NIL;
 
-	/* Is there a join RTE matching this join? */
-	joinrterel = find_other_rel_for_join(root, joinrelids);
-	if (joinrterel)
-	{
-		/* Yes, remember its RT index */
-		joinrel->joinrti = lfirsti(joinrterel->relids);
-		joinrel->joinrteids = lconsi(joinrel->joinrti, joinrel->joinrteids);
-	}
-
 	/*
 	 * Create a new tlist by removing irrelevant elements from both tlists
 	 * of the outer and inner join relations and then merging the results
@@ -400,23 +340,6 @@ build_join_rel(Query *root,
 									 length(new_outer_tlist) + 1);
 	joinrel->targetlist = nconc(new_outer_tlist, new_inner_tlist);
 
-	/*
-	 * If there are any alias variables attached to the matching join RTE,
-	 * attach them to the tlist too, so that they will be evaluated for
-	 * use at higher plan levels.
-	 */
-	if (joinrterel)
-	{
-		List	   *jrtetl;
-
-		foreach(jrtetl, joinrterel->targetlist)
-		{
-			TargetEntry *jrtete = lfirst(jrtetl);
-
-			add_var_to_tlist(joinrel, (Var *) jrtete->expr);
-		}
-	}
-
 	/*
 	 * Construct restrict and join clause lists for the new joinrel. (The
 	 * caller might or might not need the restrictlist, but I need it
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 92ff0cd5b4c003b540af9d0c16e0c9d00489be67..11267428d7ae8a9936091e6e48d2e441c04e4686 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.43 2003/01/10 21:08:13 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.44 2003/01/15 19:35:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,6 +20,16 @@
 #include "parser/parsetree.h"
 
 
+/* macros borrowed from expression_tree_mutator */
+
+#define FLATCOPY(newnode, node, nodetype)  \
+	( (newnode) = makeNode(nodetype), \
+	  memcpy((newnode), (node), sizeof(nodetype)) )
+
+#define MUTATE(newfield, oldfield, fieldtype, mutator, context)  \
+		( (newfield) = (fieldtype) mutator((Node *) (oldfield), (context)) )
+
+
 typedef struct
 {
 	List	   *varlist;
@@ -42,7 +52,7 @@ typedef struct
 typedef struct
 {
 	List	   *rtable;
-	bool		force;
+	int			sublevels_up;
 } flatten_join_alias_vars_context;
 
 static bool pull_varnos_walker(Node *node,
@@ -314,26 +324,16 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
  *	  relation variables instead.  This allows quals involving such vars to be
  *	  pushed down.
  *
- * If force is TRUE then we will reduce all JOIN alias Vars to non-alias Vars
- * or expressions thereof (there may be COALESCE and/or type conversions
- * involved).  If force is FALSE we will not expand a Var to a non-Var
- * expression.	This is a hack to avoid confusing mergejoin planning, which
- * currently cannot cope with non-Var join items --- we leave the join vars
- * as Vars till after planning is done, then expand them during setrefs.c.
- *
- * Upper-level vars (with varlevelsup > 0) are ignored; normally there
- * should not be any by the time this routine is called.
- *
- * Does not examine subqueries, therefore must only be used after reduction
- * of sublinks to subplans!
+ * NOTE: this is used on not-yet-planned expressions.  We do not expect it
+ * to be applied directly to a Query node.
  */
 Node *
-flatten_join_alias_vars(Node *node, List *rtable, bool force)
+flatten_join_alias_vars(Node *node, List *rtable)
 {
 	flatten_join_alias_vars_context context;
 
 	context.rtable = rtable;
-	context.force = force;
+	context.sublevels_up = 0;
 
 	return flatten_join_alias_vars_mutator(node, &context);
 }
@@ -350,21 +350,31 @@ flatten_join_alias_vars_mutator(Node *node,
 		RangeTblEntry *rte;
 		Node	   *newvar;
 
-		if (var->varlevelsup != 0)
+		if (var->varlevelsup != context->sublevels_up)
 			return node;		/* no need to copy, really */
 		rte = rt_fetch(var->varno, context->rtable);
 		if (rte->rtekind != RTE_JOIN)
 			return node;
 		Assert(var->varattno > 0);
 		newvar = (Node *) nth(var->varattno - 1, rte->joinaliasvars);
-		if (IsA(newvar, Var) ||context->force)
-		{
-			/* expand it; recurse in case join input is itself a join */
-			return flatten_join_alias_vars_mutator(newvar, context);
-		}
-		/* we don't want to force expansion of this alias Var */
-		return node;
+		/* expand it; recurse in case join input is itself a join */
+		return flatten_join_alias_vars_mutator(newvar, context);
+	}
+	if (IsA(node, Query))
+	{
+		/* Recurse into RTE subquery or not-yet-planned sublink subquery */
+		Query	   *query = (Query *) node;
+		Query	   *newnode;
+
+		FLATCOPY(newnode, query, Query);
+		context->sublevels_up++;
+		query_tree_mutator(newnode, flatten_join_alias_vars_mutator,
+						   (void *) context, QTW_IGNORE_JOINALIASES);
+		context->sublevels_up--;
+		return (Node *) newnode;
 	}
+	/* Already-planned tree not supported */
+	Assert(!is_subplan(node));
 	return expression_tree_mutator(node, flatten_join_alias_vars_mutator,
 								   (void *) context);
 }
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 977cbe0a5e48a7c4d43f2e401e1f3c3583c0d281..fe6f38eee8509e5f7a7cb9723dba6364570f3353 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/selfuncs.c,v 1.125 2003/01/12 22:35:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/selfuncs.c,v 1.126 2003/01/15 19:35:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1754,8 +1754,8 @@ mergejoinscansel(Query *root, Node *clause,
 	if (!is_opclause(clause))
 		return;					/* shouldn't happen */
 	opno = ((OpExpr *) clause)->opno;
-	left = get_leftop((Expr *) clause);
-	right = get_rightop((Expr *) clause);
+	left = (Var *) get_leftop((Expr *) clause);
+	right = (Var *) get_rightop((Expr *) clause);
 	if (!right)
 		return;					/* shouldn't happen */
 
@@ -1766,8 +1766,6 @@ mergejoinscansel(Query *root, Node *clause,
 
 	/* Verify mergejoinability and get left and right "<" operators */
 	if (!op_mergejoinable(opno,
-						  left->vartype,
-						  right->vartype,
 						  &lsortop,
 						  &rsortop))
 		return;					/* shouldn't happen */
@@ -1892,17 +1890,6 @@ estimate_num_groups(Query *root, List *groupClauses, double input_rows)
 		List	   *varshere;
 
 		varshere = pull_var_clause(groupexpr, false);
-		/*
-		 * Replace any JOIN alias Vars with the underlying Vars.  (This
-		 * is not really right for FULL JOIN ...)
-		 */
-		if (root->hasJoinRTEs)
-		{
-			varshere = (List *) flatten_join_alias_vars((Node *) varshere,
-														root->rtable,
-														true);
-			varshere = pull_var_clause((Node *) varshere, false);
-		}
 		/*
 		 * If we find any variable-free GROUP BY item, then either it is
 		 * a constant (and we can ignore it) or it contains a volatile
@@ -1963,7 +1950,7 @@ estimate_num_groups(Query *root, List *groupClauses, double input_rows)
 			l2 = lnext(l2);
 
 			if (var->varno != varinfo->var->varno &&
-				vars_known_equal(root, var, varinfo->var))
+				exprs_known_equal(root, (Node *) var, (Node *) varinfo->var))
 			{
 				/* Found a match */
 				if (varinfo->ndistinct <= ndistinct)
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 56dabcd754284f633f72437bcbca4889d4c0a8e7..a6c838974c0d30562d5853c95f9edfdf414a24d7 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.88 2002/12/05 04:04:44 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.89 2003/01/15 19:35:44 tgl Exp $
  *
  * NOTES
  *	  Eventually, the index information should go through here, too.
@@ -315,11 +315,11 @@ get_opname(Oid opno)
 /*
  * op_mergejoinable
  *
- *		Returns the left and right sort operators and types corresponding to a
- *		mergejoinable operator, or nil if the operator is not mergejoinable.
+ *		Returns the left and right sort operators corresponding to a
+ *		mergejoinable operator, or false if the operator is not mergejoinable.
  */
 bool
-op_mergejoinable(Oid opno, Oid ltype, Oid rtype, Oid *leftOp, Oid *rightOp)
+op_mergejoinable(Oid opno, Oid *leftOp, Oid *rightOp)
 {
 	HeapTuple	tp;
 	bool		result = false;
@@ -332,9 +332,7 @@ op_mergejoinable(Oid opno, Oid ltype, Oid rtype, Oid *leftOp, Oid *rightOp)
 		Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp);
 
 		if (optup->oprlsortop &&
-			optup->oprrsortop &&
-			optup->oprleft == ltype &&
-			optup->oprright == rtype)
+			optup->oprrsortop)
 		{
 			*leftOp = optup->oprlsortop;
 			*rightOp = optup->oprrsortop;
@@ -391,14 +389,13 @@ op_mergejoin_crossops(Oid opno, Oid *ltop, Oid *gtop,
 /*
  * op_hashjoinable
  *
- * Returns the hash operator corresponding to a hashjoinable operator,
- * or InvalidOid if the operator is not hashjoinable.
+ * Returns true if the operator is hashjoinable.
  */
-Oid
-op_hashjoinable(Oid opno, Oid ltype, Oid rtype)
+bool
+op_hashjoinable(Oid opno)
 {
 	HeapTuple	tp;
-	Oid			result = InvalidOid;
+	bool		result = false;
 
 	tp = SearchSysCache(OPEROID,
 						ObjectIdGetDatum(opno),
@@ -407,10 +404,7 @@ op_hashjoinable(Oid opno, Oid ltype, Oid rtype)
 	{
 		Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp);
 
-		if (optup->oprcanhash &&
-			optup->oprleft == ltype &&
-			optup->oprright == rtype)
-			result = opno;
+		result = optup->oprcanhash;
 		ReleaseSysCache(tp);
 	}
 	return result;
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index d3fb2231f1e99be2a4201207106158a6767d9555..a21debe02f90c81d1a9f9f87e50f52d16658622f 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.75 2003/01/12 22:35:29 tgl Exp $
+ * $Id: relation.h,v 1.76 2003/01/15 19:35:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -50,7 +50,7 @@ typedef struct QualCost
  *		Per-relation information for planning/optimization
  *
  * For planning purposes, a "base rel" is either a plain relation (a table)
- * or the output of a sub-SELECT that appears in the range table.
+ * or the output of a sub-SELECT or function that appears in the range table.
  * In either case it is uniquely identified by an RT index.  A "joinrel"
  * is the joining of two or more base rels.  A joinrel is identified by
  * the set of RT indexes for its component baserels.  We create RelOptInfo
@@ -63,14 +63,10 @@ typedef struct QualCost
  *
  * We also have "other rels", which are like base rels in that they refer to
  * single RT indexes; but they are not part of the join tree, and are stored
- * in other_rel_list not base_rel_list.  An otherrel is created for each
- * join RTE as an aid in processing Vars that refer to the join's outputs,
- * but it serves no other purpose in planning.	It is important not to
- * confuse this otherrel with the joinrel that represents the matching set
- * of base relations.
- *
- * A second category of otherrels are those made for child relations of an
- * inheritance scan (SELECT FROM foo*).  The parent table's RTE and
+ * in other_rel_list not base_rel_list.
+ *
+ * Currently the only kind of otherrels are those made for child relations
+ * of an inheritance scan (SELECT FROM foo*).  The parent table's RTE and
  * corresponding baserel represent the whole result of the inheritance scan.
  * The planner creates separate RTEs and associated RelOptInfos for each child
  * table (including the parent table, in its capacity as a member of the
@@ -80,6 +76,10 @@ typedef struct QualCost
  * the inheritance set; then the parent baserel is given an Append plan
  * comprising the best plans for the individual child tables.
  *
+ * At one time we also made otherrels to represent join RTEs, for use in
+ * handling join alias Vars.  Currently this is not needed because all join
+ * alias Vars are expanded to non-aliased form during preprocess_expression.
+ *
  * Parts of this data structure are specific to various scan and join
  * mechanisms.	It didn't seem worth creating new node types for them.
  *
@@ -114,15 +114,7 @@ typedef struct QualCost
  *		set_base_rel_pathlist processes the object.
  *
  *		For otherrels that are inheritance children, these fields are filled
- *		in just as for a baserel.  In otherrels for join RTEs, these fields
- *		are empty --- the only useful field of a join otherrel is its
- *		outerjoinset.
- *
- * If the relation is a join relation it will have these fields set:
- *
- *		joinrti - RT index of corresponding JOIN RTE, if any; 0 if none
- *		joinrteids - List of RT indexes of JOIN RTEs included in this join
- *					 (including joinrti)
+ *		in just as for a baserel.
  *
  * The presence of the remaining fields depends on the restrictions
  * and joins that the relation participates in:
@@ -135,8 +127,7 @@ typedef struct QualCost
  *		outerjoinset - For a base rel: if the rel appears within the nullable
  *					side of an outer join, the list of all relids
  *					participating in the highest such outer join; else NIL.
- *					For a join otherrel: the list of all baserel relids
- *					syntactically within the join.	Otherwise, unused.
+ *					Otherwise, unused.
  *		joininfo  - List of JoinInfo nodes, containing info about each join
  *					clause in which this relation participates
  *		index_outer_relids - only used for base rels; list of outer relids
@@ -170,7 +161,6 @@ typedef enum RelOptKind
 {
 	RELOPT_BASEREL,
 	RELOPT_JOINREL,
-	RELOPT_OTHER_JOIN_REL,
 	RELOPT_OTHER_CHILD_REL
 } RelOptKind;
 
@@ -202,10 +192,6 @@ typedef struct RelOptInfo
 	double		tuples;
 	struct Plan *subplan;		/* if subquery */
 
-	/* information about a join rel (not set for base rels!) */
-	Index		joinrti;
-	List	   *joinrteids;
-
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;		/* RestrictInfo structures (if
 										 * base rel) */
@@ -275,15 +261,6 @@ typedef struct IndexOptInfo
 } IndexOptInfo;
 
 
-/*
- * A Var is considered to belong to a relation if it's either from one
- * of the actual base rels making up the relation, or it's a join alias
- * var that is included in the relation.
- */
-#define VARISRELMEMBER(varno,rel) (intMember((varno), (rel)->relids) || \
-								   intMember((varno), (rel)->joinrteids))
-
-
 /*
  * PathKeys
  *
@@ -583,6 +560,15 @@ typedef struct RestrictInfo
 	QualCost	eval_cost;		/* eval cost of clause; -1 if not yet set */
 	Selectivity this_selec;		/* selectivity; -1 if not yet set */
 
+	/*
+	 * If the clause looks useful for joining --- that is, it is a binary
+	 * opclause with nonoverlapping sets of relids referenced in the left
+	 * and right sides --- then these two fields are set to lists of the
+	 * referenced relids.  Otherwise they are both NIL.
+	 */
+	List	   *left_relids;	/* relids in left side of join clause */
+	List	   *right_relids;	/* relids in right side of join clause */
+
 	/* valid if clause is mergejoinable, else InvalidOid: */
 	Oid			mergejoinoperator;		/* copy of clause operator */
 	Oid			left_sortop;	/* leftside sortop needed for mergejoin */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index f280e420d9b4c85a44123aad48df1398b503df9b..4ed022b15e766c4de1006cd32b7f43b639bbb47a 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.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: clauses.h,v 1.58 2002/12/14 00:17:59 tgl Exp $
+ * $Id: clauses.h,v 1.59 2003/01/15 19:35:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,8 +25,8 @@
 
 extern Expr *make_opclause(Oid opno, Oid opresulttype, bool opretset,
 						   Expr *leftop, Expr *rightop);
-extern Var *get_leftop(Expr *clause);
-extern Var *get_rightop(Expr *clause);
+extern Node *get_leftop(Expr *clause);
+extern Node *get_rightop(Expr *clause);
 
 extern Expr *make_funcclause(Oid funcid, Oid funcresulttype, bool funcretset,
 							 CoercionForm funcformat, List *funcargs);
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 65abeb0cf61694abacf6fbbd778341b455b86b79..77ed27e7e55b23de9a2acc3c87567af330b03edd 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.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: pathnode.h,v 1.46 2002/11/30 05:21:03 tgl Exp $
+ * $Id: pathnode.h,v 1.47 2003/01/15 19:35:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -74,8 +74,6 @@ extern HashPath *create_hashjoin_path(Query *root,
 extern void build_base_rel(Query *root, int relid);
 extern RelOptInfo *build_other_rel(Query *root, int relid);
 extern RelOptInfo *find_base_rel(Query *root, int relid);
-extern RelOptInfo *find_other_rel(Query *root, int relid);
-extern RelOptInfo *find_other_rel_for_join(Query *root, List *relids);
 extern RelOptInfo *build_join_rel(Query *root,
 			   RelOptInfo *outer_rel,
 			   RelOptInfo *inner_rel,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 134adad9b604c99f35225bf6042a7cebd9036f78..f2604527001e039ffb1048e293b2fde2d3bfa876 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.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: planmain.h,v 1.64 2002/12/12 15:49:41 tgl Exp $
+ * $Id: planmain.h,v 1.65 2003/01/15 19:35:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,8 +32,6 @@ extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
 extern Append *make_append(List *appendplans, bool isTarget, List *tlist);
 extern Sort *make_sort(Query *root, List *tlist,
 		  Plan *lefttree, int keycount);
-extern Sort *make_sort_from_pathkeys(Query *root, List *tlist,
-						Plan *lefttree, List *pathkeys);
 extern Agg *make_agg(Query *root, List *tlist, List *qual,
 					 AggStrategy aggstrategy,
 					 int numGroupCols, AttrNumber *grpColIdx,
@@ -54,12 +52,12 @@ extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
 /*
  * prototypes for plan/initsplan.c
  */
-extern List *add_base_rels_to_query(Query *root, Node *jtnode);
+extern void add_base_rels_to_query(Query *root, Node *jtnode);
 extern void build_base_rel_tlists(Query *root, List *tlist);
 extern Relids distribute_quals_to_rels(Query *root, Node *jtnode);
 extern void process_implied_equality(Query *root, Node *item1, Node *item2,
 						 Oid sortop1, Oid sortop2);
-extern bool vars_known_equal(Query *root, Var *var1, Var *var2);
+extern bool exprs_known_equal(Query *root, Node *item1, Node *item2);
 
 /*
  * prototypes for plan/setrefs.c
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index 102e928a544b87e99595f78d010d9efeaebb4c62..07b8b311d076ae4f43d79cec6ea28bd2d1d44d92 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.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: var.h,v 1.23 2002/12/12 20:35:16 tgl Exp $
+ * $Id: var.h,v 1.24 2003/01/15 19:35:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,6 +23,6 @@ extern bool contain_var_reference(Node *node, int varno, int varattno,
 extern bool contain_whole_tuple_var(Node *node, int varno, int levelsup);
 extern bool contain_var_clause(Node *node);
 extern List *pull_var_clause(Node *node, bool includeUpperVars);
-extern Node *flatten_join_alias_vars(Node *node, List *rtable, bool force);
+extern Node *flatten_join_alias_vars(Node *node, List *rtable);
 
 #endif   /* VAR_H */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index a0d3574b246e61266efa4bfab102671859f4fe61..8200730f6a25676031c670d7710f2a546022bf5d 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: lsyscache.h,v 1.65 2002/12/01 21:05:14 tgl Exp $
+ * $Id: lsyscache.h,v 1.66 2003/01/15 19:35:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -26,11 +26,10 @@ extern void get_atttypetypmod(Oid relid, AttrNumber attnum,
 extern bool opclass_is_btree(Oid opclass);
 extern RegProcedure get_opcode(Oid opno);
 extern char *get_opname(Oid opno);
-extern bool op_mergejoinable(Oid opno, Oid ltype, Oid rtype,
-				 Oid *leftOp, Oid *rightOp);
+extern bool op_mergejoinable(Oid opno, Oid *leftOp, Oid *rightOp);
 extern void op_mergejoin_crossops(Oid opno, Oid *ltop, Oid *gtop,
 					  RegProcedure *ltproc, RegProcedure *gtproc);
-extern Oid	op_hashjoinable(Oid opno, Oid ltype, Oid rtype);
+extern bool op_hashjoinable(Oid opno);
 extern bool op_strict(Oid opno);
 extern char op_volatile(Oid opno);
 extern Oid	get_commutator(Oid opno);
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 3decbdb7991dfa01557f019eb5cbb413d80adc46..7ef807a95db1375103f19a0f1d55ad210dc8c48c 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -498,6 +498,17 @@ WHERE p1.oprcode = p2.oid AND
 -----+---------+-----+---------
 (0 rows)
 
+-- If the operator is mergejoinable or hashjoinable, its underlying function
+-- should not be volatile.
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprcode = p2.oid AND
+    (p1.oprlsortop != 0 OR p1.oprcanhash) AND
+    p2.provolatile = 'v';
+ oid | oprname | oid | proname 
+-----+---------+-----+---------
+(0 rows)
+
 -- If oprrest is set, the operator must return boolean,
 -- and it must link to a proc with the right signature
 -- to be a restriction selectivity estimator.
@@ -583,7 +594,8 @@ WHERE a.aggfnoid = p.oid AND
      a.aggtranstype != p2.prorettype OR
      a.aggtranstype != p2.proargtypes[0] OR
      NOT ((p2.pronargs = 2 AND p.proargtypes[0] = p2.proargtypes[1]) OR
-          (p2.pronargs = 1 AND p.proargtypes[0] = '"any"'::regtype)));
+          (p2.pronargs = 1 AND p.proargtypes[0] = '"any"'::regtype)))
+ORDER BY 1;
  aggfnoid | proname | oid |   proname   
 ----------+---------+-----+-------------
      2121 | max     | 768 | int4larger
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 46c153e5d14042ed1722548f7f57c68939815521..8f943188d24e87cc3e3eab4e9228e66e5beaddc0 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -107,7 +107,8 @@ SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typinput = p2.oid AND p1.typtype in ('b', 'p') AND NOT
     (p1.typelem != 0 AND p1.typlen < 0) AND NOT
-    (p2.prorettype = p1.oid AND NOT p2.proretset);
+    (p2.prorettype = p1.oid AND NOT p2.proretset)
+ORDER BY 1;
  oid  |  typname  | oid |  proname  
 ------+-----------+-----+-----------
    32 | SET       | 109 | unknownin
@@ -132,7 +133,8 @@ FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typoutput = p2.oid AND p1.typtype in ('b', 'p') AND NOT
     ((p2.pronargs = 1 AND p2.proargtypes[0] = p1.oid) OR
      (p2.oid = 'array_out'::regproc AND
-      p1.typelem != 0 AND p1.typlen = -1));
+      p1.typelem != 0 AND p1.typlen = -1))
+ORDER BY 1;
  oid  |  typname  | oid |  proname   
 ------+-----------+-----+------------
    32 | SET       | 110 | unknownout
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index faacc83850e8c7f673e054387d3ea811c2abab28..650073cccc1dd128cd6930dd50d4b51261ad9a2d 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -416,6 +416,15 @@ WHERE p1.oprcode = p2.oid AND
      (p1.oprleft != p2.proargtypes[0] AND p2.proargtypes[0] != 0) OR
      p1.oprright != 0);
 
+-- If the operator is mergejoinable or hashjoinable, its underlying function
+-- should not be volatile.
+
+SELECT p1.oid, p1.oprname, p2.oid, p2.proname
+FROM pg_operator AS p1, pg_proc AS p2
+WHERE p1.oprcode = p2.oid AND
+    (p1.oprlsortop != 0 OR p1.oprcanhash) AND
+    p2.provolatile = 'v';
+
 -- If oprrest is set, the operator must return boolean,
 -- and it must link to a proc with the right signature
 -- to be a restriction selectivity estimator.
@@ -490,7 +499,8 @@ WHERE a.aggfnoid = p.oid AND
      a.aggtranstype != p2.prorettype OR
      a.aggtranstype != p2.proargtypes[0] OR
      NOT ((p2.pronargs = 2 AND p.proargtypes[0] = p2.proargtypes[1]) OR
-          (p2.pronargs = 1 AND p.proargtypes[0] = '"any"'::regtype)));
+          (p2.pronargs = 1 AND p.proargtypes[0] = '"any"'::regtype)))
+ORDER BY 1;
 
 -- Cross-check finalfn (if present) against its entry in pg_proc.
 -- FIXME: what about binary-compatible types?
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 0cc4748fa89b95b6363a493e00880529ee80019d..f0ffa5984dc374abb0e851bc0e2146950888b353 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -90,7 +90,8 @@ SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typinput = p2.oid AND p1.typtype in ('b', 'p') AND NOT
     (p1.typelem != 0 AND p1.typlen < 0) AND NOT
-    (p2.prorettype = p1.oid AND NOT p2.proretset);
+    (p2.prorettype = p1.oid AND NOT p2.proretset)
+ORDER BY 1;
 
 -- Varlena array types will point to array_in
 SELECT p1.oid, p1.typname, p2.oid, p2.proname
@@ -108,7 +109,8 @@ FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typoutput = p2.oid AND p1.typtype in ('b', 'p') AND NOT
     ((p2.pronargs = 1 AND p2.proargtypes[0] = p1.oid) OR
      (p2.oid = 'array_out'::regproc AND
-      p1.typelem != 0 AND p1.typlen = -1));
+      p1.typelem != 0 AND p1.typlen = -1))
+ORDER BY 1;
 
 SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2