<!--
-$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">
      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>
      </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
 
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
        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);
 
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
        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);
 
  *
  *
  * 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*
        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);
 
  *
  *
  * 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
                {
                        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");
 
 
 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
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
 
        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 */
        }
 
 
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
        Cost            cpu_per_tuple;
        QualCost        restrict_qual_cost;
        RestrictInfo *firstclause;
-       Var                *leftvar;
        double          outer_rows,
                                inner_rows;
        double          ntuples;
                                                 &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;
        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
                 * 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;
                        }
         * 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);
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
 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);
                                                 Oid opclass,
                                                 Expr *clause)
 {
-       Var                *leftop,
+       Node       *leftop,
                           *rightop;
 
        /* Clause must be a binary opclause. */
         * 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;
        }
 
        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;
                                                          Oid opclass,
                                                          Expr *clause)
 {
-       Var                *leftop,
+       Node       *leftop,
                           *rightop;
 
        /* Clause must be a binary opclause. */
         */
        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;
 
        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;
  */
 static bool
 match_index_to_operand(int indexkey,
-                                          Var *operand,
+                                          Node *operand,
                                           RelOptInfo *rel,
                                           IndexOptInfo *index)
 {
         * 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)
        {
                 * 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;
                                                         bool indexkey_on_left)
 {
        bool            isIndexable = false;
-       Var                *leftop,
+       Node       *leftop,
                           *rightop;
        Oid                     expr_op;
        Const      *patt = NULL;
                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;
  * 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;
  * 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;
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
        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 */
 
                /*
                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 */
                }
        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
                        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:
                        }
                }
 
-               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;
 
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
  *       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;
 
        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);
        }
        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 */
                }
 
                /*
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
 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);
 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);
                           List *mergeclauses,
                           Plan *lefttree, Plan *righttree,
                           JoinType jointype);
+static Sort *make_sort_from_pathkeys(Query *root, Plan *lefttree,
+                                                                        List *relids, List *pathkeys);
+
 
 /*
  * create_plan
 {
        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))
        {
                                                                                                  joinclauses,
                                                                                                  otherclauses,
                                                                                                  outer_plan,
-                                                                                                 outer_tlist,
-                                                                                                 inner_plan,
-                                                                                                 inner_tlist);
+                                                                                                 inner_plan);
                        break;
                case T_HashJoin:
                        plan = (Join *) create_hashjoin_plan(root,
                                                                                                 joinclauses,
                                                                                                 otherclauses,
                                                                                                 outer_plan,
-                                                                                                outer_tlist,
-                                                                                                inner_plan,
-                                                                                                inner_tlist);
+                                                                                                inner_plan);
                        break;
                case T_NestLoop:
                        plan = (Join *) create_nestloop_plan(root,
                                                                                                 joinclauses,
                                                                                                 otherclauses,
                                                                                                 outer_plan,
-                                                                                                outer_tlist,
-                                                                                                inner_plan,
-                                                                                                inner_tlist);
+                                                                                                inner_plan);
                        break;
                default:
                        elog(ERROR, "create_join_plan: unknown node type: %d",
  *
  * 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).
  *
  *****************************************************************************/
 
                                         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))
                                          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
        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);
 
        /*
                                         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)
 }
 
 /*
- * 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
  * 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;
                /*
                 * 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
                                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
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
  * 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))
        {
 
                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
                 */
        else
                elog(ERROR, "add_base_rels_to_query: unexpected node type %d",
                         nodeTag(jtnode));
-       return result;
 }
 
 
  * 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)
                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);
-               }
        }
 }
 
        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;
        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
                /*
                 * '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
 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;
        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
        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.
         */
 }
 
 /*
- * 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;
        }
                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 */
        }
 
        List       *olditem;
        Node       *newleft;
        Node       *newright;
-       List       *equalvars;
+       List       *equalexprs;
        bool            someadded;
 
        /*
                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;
                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;
 
                        /*
  *       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;
  *       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;
 }
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
        /*
         * 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
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
        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
 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.
         *
        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;
 }
 
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
  * 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
  *        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;
        {
                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);
                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");
        }
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
        }
 
        /*
-        * 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))
        {
                /* 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 */
         * 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
 
  *
  *
  * 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
  *
  * 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;
  * 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;
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
        /* 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);
        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;
                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);
        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),
 {
        List       *joinrelids;
        RelOptInfo *joinrel;
-       RelOptInfo *joinrterel;
        List       *restrictlist;
        List       *new_outer_tlist;
        List       *new_inner_tlist;
        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;
        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
                                                                         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
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
 #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;
 typedef struct
 {
        List       *rtable;
-       bool            force;
+       int                     sublevels_up;
 } flatten_join_alias_vars_context;
 
 static bool pull_varnos_walker(Node *node,
  *       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);
 }
                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);
 }
 
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
        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 */
 
 
        /* Verify mergejoinability and get left and right "<" operators */
        if (!op_mergejoinable(opno,
-                                                 left->vartype,
-                                                 right->vartype,
                                                  &lsortop,
                                                  &rsortop))
                return;                                 /* shouldn't happen */
                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
                        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)
 
  * 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.
 /*
  * 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;
                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;
 /*
  * 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),
        {
                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;
 
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
  *             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
  *
  * 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
  * 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.
  *
  *             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:
  *             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
 {
        RELOPT_BASEREL,
        RELOPT_JOINREL,
-       RELOPT_OTHER_JOIN_REL,
        RELOPT_OTHER_CHILD_REL
 } RelOptKind;
 
        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) */
 } 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
  *
        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 */
 
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
 
 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);
 
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
 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,
 
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
 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,
 /*
  * 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
 
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
 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 */
 
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
 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);
 
 -----+---------+-----+---------
 (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.
      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
 
 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
 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
 
      (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.
      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?
 
 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
 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