From de97072e3c88e104a55b0d5c67477f1b0097c003 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 15 Jan 2003 19:35:48 +0000
Subject: [PATCH] Allow merge and hash joins to occur on arbitrary expressions
 (anything not containing a volatile function), rather than only on 'Var =
 Var' clauses as before.  This makes it practical to do
 flatten_join_alias_vars at the start of planning, which in turn eliminates a
 bunch of klugery inside the planner to deal with alias vars.  As a free side
 effect, we now detect implied equality of non-Var expressions; for example in
 	SELECT ... WHERE a.x = b.y and b.y = 42 we will deduce a.x = 42 and
 use that as a restriction qual on a.  Also, we can remove the restriction
 introduced 12/5/02 to prevent pullup of subqueries whose targetlists contain
 sublinks. Still TODO: make statistical estimation routines in selfuncs.c and
 costsize.c smarter about expressions that are more complex than plain Vars. 
 The need for this is considerably greater now that we have to be able to
 estimate the suitability of merge and hash join techniques on such
 expressions.

---
 doc/src/sgml/xoper.sgml                   |  31 ++-
 src/backend/nodes/copyfuncs.c             |   4 +-
 src/backend/nodes/equalfuncs.c            |  10 +-
 src/backend/nodes/outfuncs.c              |   4 +-
 src/backend/nodes/print.c                 |   6 +-
 src/backend/optimizer/README              |   6 +-
 src/backend/optimizer/path/clausesel.c    |   6 +-
 src/backend/optimizer/path/costsize.c     |  25 +-
 src/backend/optimizer/path/indxpath.c     |  42 ++--
 src/backend/optimizer/path/joinpath.c     |  62 +++--
 src/backend/optimizer/path/pathkeys.c     |  42 ++--
 src/backend/optimizer/plan/createplan.c   | 223 +++++++++-------
 src/backend/optimizer/plan/initsplan.c    | 293 +++++++---------------
 src/backend/optimizer/plan/planmain.c     |   4 +-
 src/backend/optimizer/plan/planner.c      |  33 +--
 src/backend/optimizer/plan/setrefs.c      |  23 +-
 src/backend/optimizer/prep/prepunion.c    |  38 ++-
 src/backend/optimizer/util/clauses.c      |  14 +-
 src/backend/optimizer/util/relnode.c      |  85 +------
 src/backend/optimizer/util/var.c          |  58 +++--
 src/backend/utils/adt/selfuncs.c          |  21 +-
 src/backend/utils/cache/lsyscache.c       |  26 +-
 src/include/nodes/relation.h              |  56 ++---
 src/include/optimizer/clauses.h           |   6 +-
 src/include/optimizer/pathnode.h          |   4 +-
 src/include/optimizer/planmain.h          |   8 +-
 src/include/optimizer/var.h               |   4 +-
 src/include/utils/lsyscache.h             |   7 +-
 src/test/regress/expected/opr_sanity.out  |  14 +-
 src/test/regress/expected/type_sanity.out |   6 +-
 src/test/regress/sql/opr_sanity.sql       |  12 +-
 src/test/regress/sql/type_sanity.sql      |   6 +-
 32 files changed, 520 insertions(+), 659 deletions(-)

diff --git a/doc/src/sgml/xoper.sgml b/doc/src/sgml/xoper.sgml
index 395306bbd60..24c74cd8b60 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 e260bc65950..8663c6c4a14 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 0ef9b3fa220..a4e9e1092d8 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 e7d8fa71ed7..e72b52570e5 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 dd5186860bf..43b8e99893c 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 f4d64ebbcad..955e022d8f6 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 0294c828124..84041a566d1 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 1d736b34b91..efd80dff1ed 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 228a876f71a..7e68c41ef37 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 65d0d8fa358..8a6fcd3f060 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 9c6ed4d1bd3..194bdddc2f0 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 3d9ee6e4f4f..03ec3538479 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 1c1a848ea72..87c77e52fc3 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 5d2634bf82d..6e265931eb2 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 76a16cd833d..4cf6a09acfe 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 1b5c72e163c..1c9f8a27cff 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 5177e210d3d..4af395b860c 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 e38fc46821d..233a8cb8947 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 296d211ad12..87207f617cc 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 92ff0cd5b4c..11267428d7a 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 977cbe0a5e4..fe6f38eee85 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 56dabcd7542..a6c838974c0 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 d3fb2231f1e..a21debe02f9 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 f280e420d9b..4ed022b15e7 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 65abeb0cf61..77ed27e7e55 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 134adad9b60..f2604527001 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 102e928a544..07b8b311d07 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 a0d3574b246..8200730f6a2 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 3decbdb7991..7ef807a95db 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 46c153e5d14..8f943188d24 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 faacc83850e..650073cccc1 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 0cc4748fa89..f0ffa5984dc 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
-- 
GitLab