First cut at full support for OUTER JOINs. There are still a few loose
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 12 Sep 2000 21:07:18 +0000 (21:07 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 12 Sep 2000 21:07:18 +0000 (21:07 +0000)
ends to clean up (see my message of same date to pghackers), but mostly
it works.  INITDB REQUIRED!

93 files changed:
src/backend/catalog/heap.c
src/backend/commands/command.c
src/backend/commands/creatinh.c
src/backend/commands/explain.c
src/backend/commands/view.c
src/backend/executor/execMain.c
src/backend/executor/execTuples.c
src/backend/executor/execUtils.c
src/backend/executor/nodeHashjoin.c
src/backend/executor/nodeMergejoin.c
src/backend/executor/nodeNestloop.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/list.c
src/backend/nodes/outfuncs.c
src/backend/nodes/print.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/README
src/backend/optimizer/geqo/geqo_eval.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/path/joinpath.c
src/backend/optimizer/path/joinrels.c
src/backend/optimizer/path/orindxpath.c
src/backend/optimizer/path/pathkeys.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/initsplan.c
src/backend/optimizer/plan/planmain.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/prep/prepkeyset.c
src/backend/optimizer/prep/prepunion.c
src/backend/optimizer/util/clauses.c
src/backend/optimizer/util/pathnode.c
src/backend/optimizer/util/relnode.c
src/backend/optimizer/util/restrictinfo.c
src/backend/optimizer/util/var.c
src/backend/parser/Makefile
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_agg.c
src/backend/parser/parse_clause.c
src/backend/parser/parse_expr.c
src/backend/parser/parse_func.c
src/backend/parser/parse_node.c
src/backend/parser/parse_relation.c
src/backend/parser/parse_target.c
src/backend/parser/parser.c
src/backend/parser/scan.l
src/backend/rewrite/locks.c
src/backend/rewrite/rewriteHandler.c
src/backend/rewrite/rewriteManip.c
src/backend/utils/adt/ruleutils.c
src/include/catalog/catversion.h
src/include/executor/execdebug.h
src/include/executor/execdefs.h
src/include/executor/executor.h
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/pg_list.h
src/include/nodes/plannodes.h
src/include/nodes/primnodes.h
src/include/nodes/relation.h
src/include/optimizer/clauses.h
src/include/optimizer/pathnode.h
src/include/optimizer/paths.h
src/include/optimizer/planmain.h
src/include/optimizer/restrictinfo.h
src/include/parser/gramparse.h
src/include/parser/parse_clause.h
src/include/parser/parse_func.h
src/include/parser/parse_node.h
src/include/parser/parse_relation.h
src/include/parser/parsetree.h
src/include/rewrite/rewriteHandler.h
src/include/rewrite/rewriteManip.h
src/test/regress/expected/case.out
src/test/regress/expected/geometry-cygwin-precision.out
src/test/regress/expected/geometry-i86-gnulibc.out
src/test/regress/expected/geometry-positive-zeros-bsd.out
src/test/regress/expected/geometry-positive-zeros.out
src/test/regress/expected/geometry-powerpc-aix4.out
src/test/regress/expected/geometry-powerpc-linux-gnulibc1.out
src/test/regress/expected/geometry-solaris-precision.out
src/test/regress/expected/geometry.out
src/test/regress/expected/join.out
src/test/regress/expected/point.out
src/test/regress/expected/rules.out
src/test/regress/expected/select_implicit.out
src/test/regress/sql/join.sql
src/test/regress/sql/rules.sql

index 68bb8276981568f3c9ed3c4cded35f1ba725d309..44728bf9c9aa8652cb87eb0ffab517d875aed821 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.143 2000/09/12 04:49:06 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.144 2000/09/12 21:06:46 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -1538,11 +1538,9 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin,
         */
        rte = makeNode(RangeTblEntry);
        rte->relname = RelationGetRelationName(rel);
-#ifndef DISABLE_EREF
-       rte->ref = makeNode(Attr);
-       rte->ref->relname = RelationGetRelationName(rel);
-#endif
        rte->relid = RelationGetRelid(rel);
+       rte->eref = makeNode(Attr);
+       rte->eref->relname = RelationGetRelationName(rel);
        rte->inh = false;
        rte->inFromCl = true;
        rte->skipAcl = false;
@@ -1623,11 +1621,9 @@ StoreRelCheck(Relation rel, char *ccname, char *ccbin)
         */
        rte = makeNode(RangeTblEntry);
        rte->relname = RelationGetRelationName(rel);
-#ifndef DISABLE_EREF
-       rte->ref = makeNode(Attr);
-       rte->ref->relname = RelationGetRelationName(rel);
-#endif
        rte->relid = RelationGetRelid(rel);
+       rte->eref = makeNode(Attr);
+       rte->eref->relname = RelationGetRelationName(rel);
        rte->inh = false;
        rte->inFromCl = true;
        rte->skipAcl = false;
@@ -1723,6 +1719,7 @@ AddRelationRawConstraints(Relation rel,
        int                     numoldchecks;
        ConstrCheck *oldchecks;
        ParseState *pstate;
+       RangeTblEntry *rte;
        int                     numchecks;
        List       *listptr;
        Relation        relrel;
@@ -1752,7 +1749,8 @@ AddRelationRawConstraints(Relation rel,
         */
        pstate = make_parsestate(NULL);
        makeRangeTable(pstate, NULL);
-       addRangeTableEntry(pstate, relname, makeAttr(relname, NULL), false, true, true);
+       rte = addRangeTableEntry(pstate, relname, NULL, false, true);
+       addRTEtoJoinTree(pstate, rte);
 
        /*
         * Process column default expressions.
index 9535e19741750216ae12bc1842de28c8b536a1d8..841806810e4377beccb4184eea79cdb1f89457ca 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.102 2000/09/12 05:09:43 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.103 2000/09/12 21:06:47 tgl Exp $
  *
  * NOTES
  *       The PerformAddAttribute() code, like most of the relation
@@ -61,8 +61,6 @@ static bool is_viewr(char *relname);
 static bool is_view(Relation rel);
 
 
-
-
 /* --------------------------------
  *             PortalCleanup
  * --------------------------------
@@ -536,7 +534,6 @@ AlterTableAlterColumn(const char *relationName,
        rel = heap_openr(relationName, AccessExclusiveLock);
        if ( rel->rd_rel->relkind == RELKIND_VIEW )
                elog(ERROR, "ALTER TABLE: %s is a view", relationName);
-
        myrelid = RelationGetRelid(rel);
        heap_close(rel, NoLock);
 
@@ -782,7 +779,7 @@ systable_getnext(void *scan)
  *     find a specified attribute in a node entry
  */
 static bool
-find_attribute_walker(Node *node, int attnum)
+find_attribute_walker(Node *node, int *attnump)
 {
        if (node == NULL)
                return false;
@@ -791,16 +788,17 @@ find_attribute_walker(Node *node, int attnum)
                Var                *var = (Var *) node;
 
                if (var->varlevelsup == 0 && var->varno == 1 &&
-                       var->varattno == attnum)
+                       var->varattno == *attnump)
                        return true;
        }
-       return expression_tree_walker(node, find_attribute_walker, (void *) attnum);
+       return expression_tree_walker(node, find_attribute_walker,
+                                                                 (void *) attnump);
 }
 
 static bool
 find_attribute_in_node(Node *node, int attnum)
 {
-       return expression_tree_walker(node, find_attribute_walker, (void *) attnum);
+       return find_attribute_walker(node, &attnum);
 }
 
 /*
@@ -1096,7 +1094,6 @@ void
 AlterTableAddConstraint(char *relationName,
                                                bool inh, Node *newConstraint)
 {
-
        if (newConstraint == NULL)
                elog(ERROR, "ALTER TABLE / ADD CONSTRAINT passed invalid constraint.");
 
@@ -1108,328 +1105,330 @@ AlterTableAddConstraint(char *relationName,
        /* check to see if the table to be constrained is a view. */
        if (is_viewr(relationName))
                 elog(ERROR, "ALTER TABLE: Cannot add constraints to views.");
-               
+
        switch (nodeTag(newConstraint))
        {
                case T_Constraint:
+               {
+                       Constraint *constr = (Constraint *) newConstraint;
+
+                       switch (constr->contype)
                        {
-                               Constraint *constr=(Constraint *)newConstraint;
-                               switch (constr->contype) {
-                                       case CONSTR_CHECK:
+                               case CONSTR_CHECK:
+                               {
+                                       ParseState *pstate;
+                                       bool successful = TRUE;
+                                       HeapScanDesc scan;
+                                       ExprContext *econtext;
+                                       TupleTableSlot *slot = makeNode(TupleTableSlot);
+                                       HeapTuple tuple;
+                                       RangeTblEntry *rte;
+                                       List       *rtlist;
+                                       List       *qual;
+                                       List       *constlist;
+                                       Relation        rel;
+                                       Node *expr;
+                                       char *name;
+
+                                       if (constr->name)
+                                               name=constr->name;
+                                       else
+                                               name="<unnamed>";
+
+                                       constlist=lcons(constr, NIL);
+
+                                       rel = heap_openr(relationName, AccessExclusiveLock);
+
+                                       /* make sure it is not a view */
+                                       if (rel->rd_rel->relkind == RELKIND_VIEW)
+                                               elog(ERROR, "ALTER TABLE: cannot add constraint to a view");
+
+                                       /*
+                                        * Scan all of the rows, looking for a false match
+                                        */
+                                       scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+                                       AssertState(scan != NULL);
+
+                                       /* 
+                                        * We need to make a parse state and range table to allow
+                                        * us to transformExpr and fix_opids to get a version of
+                                        * the expression we can pass to ExecQual
+                                        */
+                                       pstate = make_parsestate(NULL);
+                                       makeRangeTable(pstate, NULL);
+                                       rte = addRangeTableEntry(pstate, relationName, NULL,
+                                                                                        false, true);
+                                       addRTEtoJoinTree(pstate, rte);
+
+                                       /* Convert the A_EXPR in raw_expr into an EXPR */
+                                       expr = transformExpr(pstate, constr->raw_expr, EXPR_COLUMN_FIRST);
+
+                                       /*
+                                        * Make sure it yields a boolean result.
+                                        */
+                                       if (exprType(expr) != BOOLOID)
+                                               elog(ERROR, "CHECK '%s' does not yield boolean result",
+                                                        name);
+
+                                       /*
+                                        * Make sure no outside relations are referred to.
+                                        */
+                                       if (length(pstate->p_rtable) != 1)
+                                               elog(ERROR, "Only relation '%s' can be referenced in CHECK",
+                                                        relationName);
+
+                                       /*
+                                        * Might as well try to reduce any constant expressions.
+                                        */
+                                       expr = eval_const_expressions(expr);
+
+                                       /* And fix the opids */
+                                       fix_opids(expr);
+
+                                       qual = lcons(expr, NIL);
+
+                                       rte = makeNode(RangeTblEntry);
+                                       rte->relname = relationName;
+                                       rte->relid = RelationGetRelid(rel);
+                                       rte->eref = makeNode(Attr);
+                                       rte->eref->relname = relationName;
+                                       rtlist = lcons(rte, NIL);
+
+                                       /* 
+                                        * Scan through the rows now, making the necessary things
+                                        * for ExecQual, and then call it to evaluate the
+                                        * expression.
+                                        */
+                                       while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
                                        {
-                                               ParseState *pstate;
-                                               bool successful=TRUE;
-                                               HeapScanDesc scan;
-                                               ExprContext *econtext;
-                                               TupleTableSlot *slot = makeNode(TupleTableSlot);
-                                               HeapTuple tuple;
-                                               RangeTblEntry *rte = makeNode(RangeTblEntry);
-                                               List       *rtlist;
-                                               List       *qual;
-                                               List       *constlist;
-                                               Relation        rel;
-                                               Node *expr;
-                                               char *name;
-                                               if (constr->name)
-                                                       name=constr->name;
-                                               else
-                                                       name="<unnamed>";
-
-                                               rel = heap_openr(relationName, AccessExclusiveLock);
-
-                                               /* make sure it is not a view */
-                                               if (rel->rd_rel->relkind == RELKIND_VIEW)
-                                                  elog(ERROR, "ALTER TABLE: cannot add constraint to a view");
-
-                                               /*
-                                                * Scan all of the rows, looking for a false match
-                                                */
-                                               scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
-                                               AssertState(scan != NULL);
-
-                                               /* 
-                                                *We need to make a parse state and range table to allow us
-                                                * to transformExpr and fix_opids to get a version of the
-                                                * expression we can pass to ExecQual
-                                                */
-                                               pstate = make_parsestate(NULL);
-                                               makeRangeTable(pstate, NULL);
-                                               addRangeTableEntry(pstate, relationName, 
-                                                       makeAttr(relationName, NULL), false, true,true);
-                                               constlist=lcons(constr, NIL);
-
-                                               /* Convert the A_EXPR in raw_expr into an EXPR */
-                                               expr = transformExpr(pstate, constr->raw_expr, EXPR_COLUMN_FIRST);
-
-                                               /*
-                                                * Make sure it yields a boolean result.
-                                                */
-                                               if (exprType(expr) != BOOLOID)
-                                                       elog(ERROR, "CHECK '%s' does not yield boolean result",
-                                                        name);
-
-                                               /*
-                                                * Make sure no outside relations are referred to.
-                                                */
-                                               if (length(pstate->p_rtable) != 1)
-                                                       elog(ERROR, "Only relation '%s' can be referenced in CHECK",
-                                                        relationName);
-
-                                               /*
-                                                * Might as well try to reduce any constant expressions.
-                                                */
-                                               expr = eval_const_expressions(expr);
-
-                                               /* And fix the opids */
-                                               fix_opids(expr);
-
-                                               qual = lcons(expr, NIL);
-                                                       rte->relname = relationName;
-                                               rte->ref = makeNode(Attr);
-                                               rte->ref->relname = rte->relname;
-                                               rte->relid = RelationGetRelid(rel);
-                                               rtlist = lcons(rte, NIL);
-
-                                               /* 
-                                                * Scan through the rows now, making the necessary things for
-                                                * ExecQual, and then call it to evaluate the expression.
-                                                */
-                                               while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+                                               slot->val = tuple;
+                                               slot->ttc_shouldFree = false;
+                                               slot->ttc_descIsNew = true;
+                                               slot->ttc_tupleDescriptor = rel->rd_att;
+                                               slot->ttc_buffer = InvalidBuffer;
+                                               slot->ttc_whichplan = -1;
+
+                                               econtext = MakeExprContext(slot, CurrentMemoryContext);
+                                               econtext->ecxt_range_table = rtlist; /* range table */
+                                               if (!ExecQual(qual, econtext, true))
                                                {
-                                                       slot->val = tuple;
-                                                       slot->ttc_shouldFree = false;
-                                                       slot->ttc_descIsNew = true;
-                                                       slot->ttc_tupleDescriptor = rel->rd_att;
-                                                       slot->ttc_buffer = InvalidBuffer;
-                                                       slot->ttc_whichplan = -1;
-
-                                                       econtext = MakeExprContext(slot, CurrentMemoryContext);
-                                                       econtext->ecxt_range_table = rtlist;            /* range table */
-                                                       if (!ExecQual(qual, econtext, true)) {
-                                                               successful=false;
-                                                               break;
-                                                       }
-                                                       FreeExprContext(econtext);
+                                                       successful=false;
+                                                       break;
                                                }
+                                               FreeExprContext(econtext);
+                                       }
 
-                                               pfree(slot);
-                                               pfree(rtlist);
-                                               pfree(rte);
+                                       pfree(slot);
+                                       pfree(rtlist);
+                                       pfree(rte);
 
-                                               heap_endscan(scan);
-                                               heap_close(rel, NoLock);                
+                                       heap_endscan(scan);
+                                       heap_close(rel, NoLock);                
 
-                                               if (!successful) 
-                                               {
-                                                       elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name);
-                                               }
-                                               /* 
-                                                * Call AddRelationRawConstraints to do the real adding -- It duplicates some
-                                                * of the above, but does not check the validity of the constraint against
-                                                * tuples already in the table.
-                                                */
-                                               AddRelationRawConstraints(rel, NIL, constlist);
-                                               pfree(constlist);
-
-                                               break;
+                                       if (!successful) 
+                                       {
+                                               elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name);
                                        }
-                                       default:
-                                               elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
+                                       /* 
+                                        * Call AddRelationRawConstraints to do the real adding --
+                                        * It duplicates some of the above, but does not check the
+                                        * validity of the constraint against tuples already in
+                                        * the table.
+                                        */
+                                       AddRelationRawConstraints(rel, NIL, constlist);
+                                       pfree(constlist);
+
+                                       break;
                                }
+                               default:
+                                       elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
                        }
                        break;
+               }
                case T_FkConstraint:
-                       {
-                               FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
-                               Relation        rel, pkrel;
-                               HeapScanDesc scan;
-                               HeapTuple       tuple;
-                               Trigger         trig;
-                               List       *list;
-                               int                     count;
-                               List       *indexoidlist,
-                                          *indexoidscan;
-                               Form_pg_index indexStruct = NULL;
-                               Form_pg_attribute *rel_attrs = NULL;
-                               int                     i;
-                               int found=0;
-
-                               if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL &&
-                                   get_temp_rel_by_username(relationName)==NULL) {
-                                       elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
-                               }
+               {
+                       FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
+                       Relation        rel, pkrel;
+                       HeapScanDesc scan;
+                       HeapTuple       tuple;
+                       Trigger         trig;
+                       List       *list;
+                       int                     count;
+                       List       *indexoidlist,
+                               *indexoidscan;
+                       Form_pg_index indexStruct = NULL;
+                       Form_pg_attribute *rel_attrs = NULL;
+                       int                     i;
+                       int found=0;
+
+                       if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL &&
+                               get_temp_rel_by_username(relationName)==NULL) {
+                               elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
+                       }
+
+                       /*
+                        * Grab an exclusive lock on the pk table, so that someone
+                        * doesn't delete rows out from under us.
+                        */
+
+                       pkrel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock);
+                       if (pkrel->rd_rel->relkind != RELKIND_RELATION)
+                               elog(ERROR, "referenced table \"%s\" not a relation", 
+                                        fkconstraint->pktable_name);
+
+                       /*
+                        * Grab an exclusive lock on the fk table, and then scan
+                        * through each tuple, calling the RI_FKey_Match_Ins
+                        * (insert trigger) as if that tuple had just been
+                        * inserted.  If any of those fail, it should elog(ERROR)
+                        * and that's that.
+                        */
+                       rel = heap_openr(relationName, AccessExclusiveLock);
+                       if (rel->rd_rel->relkind != RELKIND_RELATION)
+                               elog(ERROR, "referencing table \"%s\" not a relation",
+                                        relationName);
+
+                       /* First we check for limited correctness of the constraint */
+
+                       rel_attrs = pkrel->rd_att->attrs;
+                       indexoidlist = RelationGetIndexList(pkrel);
 
-                               /*
-                                * Grab an exclusive lock on the pk table, so that someone
-                                * doesn't delete rows out from under us.
-                                */
-
-                               pkrel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock);
-                               if (pkrel == NULL)
-                                               elog(ERROR, "referenced table \"%s\" not found",
-                                                        fkconstraint->pktable_name);
-
-                               if (pkrel->rd_rel->relkind != RELKIND_RELATION)
-                                       elog(ERROR, "referenced table \"%s\" not a relation", 
-                                        fkconstraint->pktable_name);
-                               
-
-                               /*
-                                * Grab an exclusive lock on the fk table, and then scan
-                                * through each tuple, calling the RI_FKey_Match_Ins
-                                * (insert trigger) as if that tuple had just been
-                                * inserted.  If any of those fail, it should elog(ERROR)
-                                * and that's that.
-                                */
-                               rel = heap_openr(relationName, AccessExclusiveLock);
-                               if (rel == NULL)
-                                       elog(ERROR, "table \"%s\" not found",
-                                               relationName);
-
-                               if (rel->rd_rel->relkind != RELKIND_RELATION)
-                                       elog(ERROR, "referencing table \"%s\" not a relation", relationName);
-
-                               /* First we check for limited correctness of the constraint */
-
-                               rel_attrs = pkrel->rd_att->attrs;
-                               indexoidlist = RelationGetIndexList(pkrel);
-
-                               foreach(indexoidscan, indexoidlist)
-                               {
-                                       Oid             indexoid = lfirsti(indexoidscan);
-                                       HeapTuple       indexTuple;
-                                       List *attrl;
-                                       indexTuple = SearchSysCacheTuple(INDEXRELID,
-                                                                                 ObjectIdGetDatum(indexoid),
-                                                                                 0, 0, 0);
-                                       if (!HeapTupleIsValid(indexTuple))
-                                               elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found",
-                                                        indexoid);
-                                       indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
-
-                                       if (indexStruct->indisunique) {
-                                               /* go through the fkconstraint->pk_attrs list */
-                                               foreach(attrl, fkconstraint->pk_attrs) {
-                                                       Ident *attr=lfirst(attrl);
-                                                       found=0;
-                                                       for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++)
-                                                       {
-                                                               int pkattno = indexStruct->indkey[i];
+                       foreach(indexoidscan, indexoidlist)
+                               {
+                                       Oid             indexoid = lfirsti(indexoidscan);
+                                       HeapTuple       indexTuple;
+                                       List *attrl;
+                                       indexTuple = SearchSysCacheTuple(INDEXRELID,
+                                                                                                        ObjectIdGetDatum(indexoid),
+                                                                                                        0, 0, 0);
+                                       if (!HeapTupleIsValid(indexTuple))
+                                               elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found",
+                                                        indexoid);
+                                       indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
+
+                                       if (indexStruct->indisunique) {
+                                               /* go through the fkconstraint->pk_attrs list */
+                                               foreach(attrl, fkconstraint->pk_attrs) {
+                                                       Ident *attr=lfirst(attrl);
+                                                       found=0;
+                                                       for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++)
+                                                       {
+                                                               int pkattno = indexStruct->indkey[i];
                                                                if (pkattno>0) {
                                                                        char *name = NameStr(rel_attrs[pkattno-1]->attname);
-                                                                       if (strcmp(name, attr->name)==0) {
-                                                                               found=1;
-                                                                               break;
-                                                                       }
+                                                                       if (strcmp(name, attr->name)==0) {
+                                                                               found=1;
+                                                                               break;
+                                                                       }
                                                                }
-                                                       }
-                                                       if (!found)
-                                                               break;
-                                               }
-                                       }
-                                       if (found)
-                                               break;          
-                                       indexStruct = NULL;
-                               }
-                               if (!found)
-                                       elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found",
-                                                fkconstraint->pktable_name);
-
-                               freeList(indexoidlist);
-                               heap_close(pkrel, NoLock);
-
-                               rel_attrs = rel->rd_att->attrs;
-                               if (fkconstraint->fk_attrs!=NIL) {
-                                       int found=0;
-                                       List *fkattrs;
-                                       Ident *fkattr;
-                                       foreach(fkattrs, fkconstraint->fk_attrs) {
-                                               int count=0;
-                                               found=0;
-                                               fkattr=lfirst(fkattrs);
-                                               for (; count < rel->rd_att->natts; count++) {
-                                                       char *name = NameStr(rel->rd_att->attrs[count]->attname);
-                                                       if (strcmp(name, fkattr->name)==0) {
-                                                               found=1;
-                                                               break;
-                                                       }
-                                               }
-                                               if (!found)
-                                                       break;
-                                       }
-                                       if (!found)
-                                               elog(ERROR, "columns referenced in foreign key constraint not found.");
-                               }
-
-                               trig.tgoid = 0;
-                               if (fkconstraint->constr_name)
-                                       trig.tgname = fkconstraint->constr_name;
-                               else
-                                       trig.tgname = "<unknown>";
-                               trig.tgfoid = 0;
-                               trig.tgtype = 0;
-                               trig.tgenabled = TRUE;
-                               trig.tgisconstraint = TRUE;
-                               trig.tginitdeferred = FALSE;
-                               trig.tgdeferrable = FALSE;
-
-                               trig.tgargs = (char **) palloc(
-                                        sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
-                                                                          + length(fkconstraint->pk_attrs)));
-
-                               if (fkconstraint->constr_name)
-                                       trig.tgargs[0] = fkconstraint->constr_name;
-                               else
-                                       trig.tgargs[0] = "<unknown>";
-                               trig.tgargs[1] = (char *) relationName;
-                               trig.tgargs[2] = fkconstraint->pktable_name;
-                               trig.tgargs[3] = fkconstraint->match_type;
-                               count = 4;
-                               foreach(list, fkconstraint->fk_attrs)
+                                                       }
+                                                       if (!found)
+                                                               break;
+                                               }
+                                       }
+                                       if (found)
+                                               break;          
+                                       indexStruct = NULL;
+                               }
+                       if (!found)
+                               elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found",
+                                        fkconstraint->pktable_name);
+
+                       freeList(indexoidlist);
+                       heap_close(pkrel, NoLock);
+
+                       rel_attrs = rel->rd_att->attrs;
+                       if (fkconstraint->fk_attrs!=NIL) {
+                               int found=0;
+                               List *fkattrs;
+                               Ident *fkattr;
+                               foreach(fkattrs, fkconstraint->fk_attrs) {
+                                       int count=0;
+                                       found=0;
+                                       fkattr=lfirst(fkattrs);
+                                       for (; count < rel->rd_att->natts; count++) {
+                                               char *name = NameStr(rel->rd_att->attrs[count]->attname);
+                                               if (strcmp(name, fkattr->name)==0) {
+                                                       found=1;
+                                                       break;
+                                               }
+                                       }
+                                       if (!found)
+                                               break;
+                               }
+                               if (!found)
+                                       elog(ERROR, "columns referenced in foreign key constraint not found.");
+                       }
+
+                       trig.tgoid = 0;
+                       if (fkconstraint->constr_name)
+                               trig.tgname = fkconstraint->constr_name;
+                       else
+                               trig.tgname = "<unknown>";
+                       trig.tgfoid = 0;
+                       trig.tgtype = 0;
+                       trig.tgenabled = TRUE;
+                       trig.tgisconstraint = TRUE;
+                       trig.tginitdeferred = FALSE;
+                       trig.tgdeferrable = FALSE;
+
+                       trig.tgargs = (char **) palloc(
+                               sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
+                                                                 + length(fkconstraint->pk_attrs)));
+
+                       if (fkconstraint->constr_name)
+                               trig.tgargs[0] = fkconstraint->constr_name;
+                       else
+                               trig.tgargs[0] = "<unknown>";
+                       trig.tgargs[1] = (char *) relationName;
+                       trig.tgargs[2] = fkconstraint->pktable_name;
+                       trig.tgargs[3] = fkconstraint->match_type;
+                       count = 4;
+                       foreach(list, fkconstraint->fk_attrs)
                                {
                                        Ident      *fk_at = lfirst(list);
 
                                        trig.tgargs[count++] = fk_at->name;
                                }
-                               foreach(list, fkconstraint->pk_attrs)
+                       foreach(list, fkconstraint->pk_attrs)
                                {
                                        Ident      *pk_at = lfirst(list);
 
                                        trig.tgargs[count++] = pk_at->name;
                                }
-                               trig.tgnargs = count;
+                       trig.tgnargs = count;
 
-                               scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
-                               AssertState(scan != NULL);
+                       scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+                       AssertState(scan != NULL);
 
-                               while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
-                               {
-                                       /* Make a call to the check function */
-                                       /* No parameters are passed, but we do set a context */
-                                       FunctionCallInfoData    fcinfo;
-                                       TriggerData                             trigdata;
-
-                                       MemSet(&fcinfo, 0, sizeof(fcinfo));
-                                       /* We assume RI_FKey_check_ins won't look at flinfo... */
+                       while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+                       {
+                               /* Make a call to the check function */
+                               /* No parameters are passed, but we do set a context */
+                               FunctionCallInfoData    fcinfo;
+                               TriggerData                             trigdata;
 
-                                       trigdata.type = T_TriggerData;
-                                       trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
-                                       trigdata.tg_relation = rel;
-                                       trigdata.tg_trigtuple = tuple;
-                                       trigdata.tg_newtuple = NULL;
-                                       trigdata.tg_trigger = &trig;
+                               MemSet(&fcinfo, 0, sizeof(fcinfo));
+                               /* We assume RI_FKey_check_ins won't look at flinfo... */
 
-                                       fcinfo.context = (Node *) &trigdata;
+                               trigdata.type = T_TriggerData;
+                               trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
+                               trigdata.tg_relation = rel;
+                               trigdata.tg_trigtuple = tuple;
+                               trigdata.tg_newtuple = NULL;
+                               trigdata.tg_trigger = &trig;
 
-                                       RI_FKey_check_ins(&fcinfo);
-                               }
-                               heap_endscan(scan);
-                               heap_close(rel, NoLock);                /* close rel but keep
-                                                                                                * lock! */
+                               fcinfo.context = (Node *) &trigdata;
 
-                               pfree(trig.tgargs);
+                               RI_FKey_check_ins(&fcinfo);
                        }
+                       heap_endscan(scan);
+                       heap_close(rel, NoLock);                /* close rel but keep
+                                                                                        * lock! */
+
+                       pfree(trig.tgargs);
                        break;
+               }
                default:
                        elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed");
        }
@@ -1449,7 +1448,6 @@ AlterTableDropConstraint(const char *relationName,
 }
 
 
-
 /*
  * ALTER TABLE OWNER
  */
@@ -1464,14 +1462,14 @@ AlterTableOwner(const char *relationName, const char *newOwnerName)
        /*
         * first check that we are a superuser
         */
-       if (! superuser() )
+       if (! superuser())
                elog(ERROR, "ALTER TABLE: permission denied");
 
        /*
         * look up the new owner in pg_shadow and get the sysid
         */
        tuple = SearchSysCacheTuple(SHADOWNAME, PointerGetDatum(newOwnerName),
-                                                          0, 0, 0);
+                                                               0, 0, 0);
        if (!HeapTupleIsValid(tuple))
                elog(ERROR, "ALTER TABLE: user \"%s\" not found", newOwnerName);
 
@@ -1510,10 +1508,9 @@ AlterTableOwner(const char *relationName, const char *newOwnerName)
         */
        heap_freetuple(tuple);
        heap_close(class_rel, RowExclusiveLock);
-
-       return;
 }
 
+
 /*
  * ALTER TABLE CREATE TOAST TABLE
  */
@@ -1579,6 +1576,7 @@ AlterTableCreateToastTable(const char *relationName, bool silent)
         * allow to create TOAST tables for views. But why not - someone
         * can insert into a view, so it shouldn't be impossible to hide
         * huge data there :-)
+        *
         * Not any more.
         */
        if (((Form_pg_class) GETSTRUCT(reltup))->relkind != RELKIND_RELATION)
@@ -1799,8 +1797,7 @@ LockTableCommand(LockStmt *lockstmt)
 }
 
 
-static
-bool
+static bool
 is_viewr(char *name)
 {
        Relation rel = heap_openr(name, NoLock);
@@ -1812,18 +1809,15 @@ is_viewr(char *name)
        return retval;
 }
 
-static
-bool
-is_view (Relation rel)
+static bool
+is_view(Relation rel)
 {
        Relation        RewriteRelation;
        HeapScanDesc scanDesc;
        ScanKeyData scanKeyData;
        HeapTuple       tuple;
        Form_pg_rewrite data;
-
-
-       bool retval = 0;
+       bool retval = false;
 
        /*
         * Open the pg_rewrite relation.
@@ -1849,7 +1843,7 @@ is_view (Relation rel)
                        data = (Form_pg_rewrite) GETSTRUCT(tuple);
                        if (data->ev_type == '1')
                        {
-                               retval = 1;
+                               retval = true;
                                break;
                        }
                }
index e39c24f8dfa6f936c3e75c6a3ee80e3a1744ba06..b6485850eb3bdd911402afb3c0a3953826cd615a 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/Attic/creatinh.c,v 1.63 2000/08/04 06:12:11 inoue Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/Attic/creatinh.c,v 1.64 2000/09/12 21:06:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -259,7 +259,6 @@ change_varattnos_walker(Node *node, const AttrNumber *newattno)
        {
                Var     *var = (Var *) node;
 
-               Assert(newattno != NULL);
                if (var->varlevelsup == 0 && var->varno == 1)
                {
                        /*
@@ -270,18 +269,19 @@ change_varattnos_walker(Node *node, const AttrNumber *newattno)
                         */
                        Assert(newattno[var->varattno - 1] > 0);
                        var->varattno = newattno[var->varattno - 1];
-                       return true;
                }
-               else
-                       return false;
+               return false;
        }
-       return expression_tree_walker(node, change_varattnos_walker, (void *)newattno);
+       return expression_tree_walker(node, change_varattnos_walker,
+                                                                 (void *) newattno);
 }
+
 static bool
 change_varattnos_of_a_node(Node *node, const AttrNumber *newattno)
 {
-       return expression_tree_walker(node, change_varattnos_walker, (void *)newattno);
+       return change_varattnos_walker(node, newattno);
 }
+
 /*
  * MergeAttributes
  *             Returns new schema given initial schema and supers.
index 25915fe42bd014e0c552763d3b4067b2b5d88947..2b3d8b85726ab745d5e57f1ffa8fbd6dbe2dbd72 100644 (file)
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.57 2000/06/18 22:43:58 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.58 2000/09/12 21:06:47 tgl Exp $
  *
  */
 
@@ -229,21 +229,21 @@ explain_outNode(StringInfo str, Plan *plan, int indent, ExplainState *es)
 
                                appendStringInfo(str, " on %s",
                                                                 stringStringInfo(rte->relname));
-                               if (rte->ref != NULL)
+                               if (rte->alias != NULL)
                                {
-                                       if ((strcmp(rte->ref->relname, rte->relname) != 0)
-                                               || (length(rte->ref->attrs) > 0))
+                                       if ((strcmp(rte->alias->relname, rte->relname) != 0)
+                                               || (length(rte->alias->attrs) > 0))
                                        {
                                                appendStringInfo(str, " %s",
-                                                                       stringStringInfo(rte->ref->relname));
+                                                                       stringStringInfo(rte->alias->relname));
 
-                                               if (length(rte->ref->attrs) > 0)
+                                               if (length(rte->alias->attrs) > 0)
                                                {
                                                        List       *c;
                                                        int                     firstEntry = true;
 
                                                        appendStringInfo(str, " (");
-                                                       foreach(c, rte->ref->attrs)
+                                                       foreach(c, rte->alias->attrs)
                                                        {
                                                                if (!firstEntry)
                                                                {
index af10805b71f39f28ff843257c21217c60cfe8970..d1d630009991fc8ce3df54a052cabe9811bcbd05 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $Id: view.c,v 1.47 2000/09/12 04:49:07 momjian Exp $
+ *     $Id: view.c,v 1.48 2000/09/12 21:06:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -116,12 +116,14 @@ char *
 MakeRetrieveViewRuleName(char *viewName)
 {
        char       *buf;
+#ifdef MULTIBYTE
+       int                     len;
+#endif
 
        buf = palloc(strlen(viewName) + 5);
        snprintf(buf, strlen(viewName) + 5, "_RET%s", viewName);
 
 #ifdef MULTIBYTE
-       int len;
        len = pg_mbcliplen(buf,strlen(buf),NAMEDATALEN-1);
        buf[len] = '\0';
 #else
@@ -203,6 +205,10 @@ DefineViewRules(char *viewName, Query *viewParse)
  * Of course we must also increase the 'varnos' of all the Var nodes
  * by 2...
  *
+ * These extra RT entries are not actually used in the query, obviously.
+ * We add them so that views look the same as ON SELECT rules ---
+ * the rule rewriter assumes that ALL rules have OLD and NEW RTEs.
+ *
  * NOTE: these are destructive changes. It would be difficult to
  * make a complete copy of the parse tree and make the changes
  * in the copy.
@@ -211,43 +217,32 @@ DefineViewRules(char *viewName, Query *viewParse)
 static void
 UpdateRangeTableOfViewParse(char *viewName, Query *viewParse)
 {
-       List       *old_rt;
        List       *new_rt;
        RangeTblEntry *rt_entry1,
                           *rt_entry2;
 
-       /*
-        * first offset all var nodes by 2
-        */
-       OffsetVarNodes((Node *) viewParse->targetList, 2, 0);
-       OffsetVarNodes(viewParse->qual, 2, 0);
-
-       OffsetVarNodes(viewParse->havingQual, 2, 0);
-
-
-       /*
-        * find the old range table...
-        */
-       old_rt = viewParse->rtable;
-
        /*
         * create the 2 new range table entries and form the new range
         * table... OLD first, then NEW....
         */
-       rt_entry1 = addRangeTableEntry(NULL, (char *) viewName,
+       rt_entry1 = addRangeTableEntry(NULL, viewName,
                                                                   makeAttr("*OLD*", NULL),
-                                                                  FALSE, FALSE, FALSE);
-       rt_entry2 = addRangeTableEntry(NULL, (char *) viewName,
+                                                                  false, false);
+       rt_entry2 = addRangeTableEntry(NULL, viewName,
                                                                   makeAttr("*NEW*", NULL),
-                                                                  FALSE, FALSE, FALSE);
-       new_rt = lcons(rt_entry2, old_rt);
-       new_rt = lcons(rt_entry1, new_rt);
+                                                                  false, false);
+       new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
        /*
         * Now the tricky part.... Update the range table in place... Be
         * careful here, or hell breaks loooooooooooooOOOOOOOOOOOOOOOOOOSE!
         */
        viewParse->rtable = new_rt;
+
+       /*
+        * now offset all var nodes by 2, and jointree RT indexes too.
+        */
+       OffsetVarNodes((Node *) viewParse, 2, 0);
 }
 
 /*-------------------------------------------------------------------
@@ -270,7 +265,7 @@ DefineView(char *viewName, Query *viewParse)
        viewTlist = viewParse->targetList;
 
        /*
-        * Create the "view" relation NOTE: if it already exists, the xaxt
+        * Create the "view" relation NOTE: if it already exists, the xact
         * will be aborted.
         */
        DefineVirtualRelation(viewName, viewTlist);
index d25530b44fb9d0e7219f828cd443b5c346676b00..d46e0d30f55eaf87f965973b868ee6db0260a2af 100644 (file)
@@ -27,7 +27,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.126 2000/09/12 04:49:08 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.127 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -399,16 +399,17 @@ ExecCheckQueryPerms(CmdType operation, Query *parseTree, Plan *plan)
         * If we have a result relation, determine whether the result rel is
         * scanned or merely written.  If scanned, we will insist on read
         * permission as well as modify permission.
+        *
+        * Note: it might look faster to apply rangeTableEntry_used(), but
+        * that's not correct since it will trigger on jointree references
+        * to the RTE.  We only want to know about actual Var nodes.
         */
        if (resultRelation > 0)
        {
-               List       *qvars = pull_varnos(parseTree->qual);
-               List       *tvars = pull_varnos((Node *) parseTree->targetList);
+               List       *qvars = pull_varnos((Node *) parseTree);
 
-               resultIsScanned = (intMember(resultRelation, qvars) ||
-                                                  intMember(resultRelation, tvars));
+               resultIsScanned = intMember(resultRelation, qvars);
                freeList(qvars);
-               freeList(tvars);
        }
 
        /*
@@ -571,8 +572,8 @@ ExecCheckRTEPerms(RangeTblEntry *rte, CmdType operation,
                                  bool isResultRelation, bool resultIsScanned)
 {
        char       *relName;
+       Oid                     userid;
        int32           aclcheck_result;
-       Oid             userid;
 
        if (rte->skipAcl)
        {
@@ -703,13 +704,11 @@ InitPlan(CmdType operation, Query *parseTree, Plan *plan, EState *estate)
                 */
                RelationInfo *resultRelationInfo;
                Index           resultRelationIndex;
-               RangeTblEntry *rtentry;
                Oid                     resultRelationOid;
                Relation        resultRelationDesc;
 
                resultRelationIndex = resultRelation;
-               rtentry = rt_fetch(resultRelationIndex, rangeTable);
-               resultRelationOid = rtentry->relid;
+               resultRelationOid = getrelid(resultRelationIndex, rangeTable);
                resultRelationDesc = heap_open(resultRelationOid, RowExclusiveLock);
 
                if (resultRelationDesc->rd_rel->relkind == RELKIND_SEQUENCE)
@@ -770,7 +769,7 @@ InitPlan(CmdType operation, Query *parseTree, Plan *plan, EState *estate)
 
                        if (!(rm->info & ROW_MARK_FOR_UPDATE))
                                continue;
-                       relid = rt_fetch(rm->rti, rangeTable)->relid;
+                       relid = getrelid(rm->rti, rangeTable);
                        relation = heap_open(relid, RowShareLock);
                        erm = (execRowMark *) palloc(sizeof(execRowMark));
                        erm->relation = relation;
@@ -1623,10 +1622,10 @@ ExecRelCheck(Relation rel, TupleTableSlot *slot, EState *estate)
                rte = makeNode(RangeTblEntry);
 
                rte->relname = RelationGetRelationName(rel);
-               rte->ref = makeNode(Attr);
-               rte->ref->relname = rte->relname;
                rte->relid = RelationGetRelid(rel);
-               /* inh, inFromCl, inJoinSet, skipAcl won't be used, leave them zero */
+               rte->eref = makeNode(Attr);
+               rte->eref->relname = rte->relname;
+               /* inh, inFromCl, skipAcl won't be used, leave them zero */
 
                /* Set up single-entry range table */
                econtext->ecxt_range_table = lcons(rte, NIL);
index 37b092fc20f03201343eaa84e4b53619a41739b3..05474bc64bc0ce9703e9349868f5bc86c9c12bfd 100644 (file)
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.38 2000/07/12 02:37:02 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.39 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  *                                                               type of tuple in a slot
  *
  *      CONVENIENCE INITIALIZATION ROUTINES
- *             ExecInitResultTupleSlot    \    convience routines to initialize
+ *             ExecInitResultTupleSlot    \    convenience routines to initialize
  *             ExecInitScanTupleSlot           \       the various tuple slots for nodes
- *             ExecInitMarkedTupleSlot         /  which store copies of tuples.
- *             ExecInitOuterTupleSlot     /
- *             ExecInitHashTupleSlot     /
+ *             ExecInitExtraTupleSlot          /       which store copies of tuples.
+ *             ExecInitNullTupleSlot      /
  *
  *      old routines:
  *             ExecGetTupType                  - get type of tuple returned by this node
@@ -560,10 +559,11 @@ ExecSlotDescriptorIsNew(TupleTableSlot *slot)     /* slot to inspect */
  * ----------------------------------------------------------------
  */
 /* --------------------------------
- *             ExecInit{Result,Scan,Raw,Marked,Outer,Hash}TupleSlot
+ *             ExecInit{Result,Scan,Extra}TupleSlot
  *
- *             These are convenience routines to initialize the specfied slot
- *             in nodes inheriting the appropriate state.
+ *             These are convenience routines to initialize the specified slot
+ *             in nodes inheriting the appropriate state.  ExecInitExtraTupleSlot
+ *             is used for initializing special-purpose slots.
  * --------------------------------
  */
 #define INIT_SLOT_DEFS \
@@ -583,7 +583,7 @@ ExecInitResultTupleSlot(EState *estate, CommonState *commonstate)
 {
        INIT_SLOT_DEFS;
        INIT_SLOT_ALLOC;
-       commonstate->cs_ResultTupleSlot = (TupleTableSlot *) slot;
+       commonstate->cs_ResultTupleSlot = slot;
 }
 
 /* ----------------
@@ -595,50 +595,51 @@ ExecInitScanTupleSlot(EState *estate, CommonScanState *commonscanstate)
 {
        INIT_SLOT_DEFS;
        INIT_SLOT_ALLOC;
-       commonscanstate->css_ScanTupleSlot = (TupleTableSlot *) slot;
+       commonscanstate->css_ScanTupleSlot = slot;
 }
 
-#ifdef NOT_USED
 /* ----------------
- *             ExecInitMarkedTupleSlot
+ *             ExecInitExtraTupleSlot
  * ----------------
  */
-void
-ExecInitMarkedTupleSlot(EState *estate, MergeJoinState *mergestate)
+TupleTableSlot *
+ExecInitExtraTupleSlot(EState *estate)
 {
        INIT_SLOT_DEFS;
        INIT_SLOT_ALLOC;
-       mergestate->mj_MarkedTupleSlot = (TupleTableSlot *) slot;
+       return slot;
 }
 
-#endif
-
 /* ----------------
- *             ExecInitOuterTupleSlot
+ *             ExecInitNullTupleSlot
+ *
+ * Build a slot containing an all-nulls tuple of the given type.
+ * This is used as a substitute for an input tuple when performing an
+ * outer join.
  * ----------------
  */
-void
-ExecInitOuterTupleSlot(EState *estate, HashJoinState *hashstate)
+TupleTableSlot *
+ExecInitNullTupleSlot(EState *estate, TupleDesc tupType)
 {
-       INIT_SLOT_DEFS;
-       INIT_SLOT_ALLOC;
-       hashstate->hj_OuterTupleSlot = slot;
-}
+       TupleTableSlot*   slot = ExecInitExtraTupleSlot(estate);
+       /*
+        * Since heap_getattr() will treat attributes beyond a tuple's t_natts
+        * as being NULL, we can make an all-nulls tuple just by making it be of
+        * zero length.  However, the slot descriptor must match the real tupType.
+        */
+       HeapTuple       nullTuple;
+       Datum           values[1];
+       char            nulls[1];
+       static struct tupleDesc NullTupleDesc;          /* we assume this inits to
+                                                                                                * zeroes */
 
-/* ----------------
- *             ExecInitHashTupleSlot
- * ----------------
- */
-#ifdef NOT_USED
-void
-ExecInitHashTupleSlot(EState *estate, HashJoinState *hashstate)
-{
-       INIT_SLOT_DEFS;
-       INIT_SLOT_ALLOC;
-       hashstate->hj_HashTupleSlot = slot;
+       ExecSetSlotDescriptor(slot, tupType);
+
+       nullTuple = heap_formtuple(&NullTupleDesc, values, nulls);
+
+       return ExecStoreTuple(nullTuple, slot, InvalidBuffer, true);
 }
 
-#endif
 
 static TupleTableSlot *
 NodeGetResultTupleSlot(Plan *node)
index 63c1e9e157f95e143a518c5676aae1a06781af55..39ae7dff10a58df9b7172861877f204c7ab28b6a 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.65 2000/08/22 04:06:19 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.66 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -275,53 +275,17 @@ void
 ExecAssignResultTypeFromTL(Plan *node, CommonState *commonstate)
 {
        List       *targetList;
-       int                     i;
+       TupleDesc       tupDesc;
        int                     len;
-       List       *tl;
-       TargetEntry *tle;
-       List       *fjtl;
-       TupleDesc       origTupDesc;
 
        targetList = node->targetlist;
-       origTupDesc = ExecTypeFromTL(targetList);
+       tupDesc = ExecTypeFromTL(targetList);
        len = ExecTargetListLength(targetList);
 
-       fjtl = NIL;
-       tl = targetList;
-       i = 0;
-       while (tl != NIL || fjtl != NIL)
-       {
-               if (fjtl != NIL)
-               {
-                       tle = lfirst(fjtl);
-                       fjtl = lnext(fjtl);
-               }
-               else
-               {
-                       tle = lfirst(tl);
-                       tl = lnext(tl);
-               }
-#ifdef SETS_FIXED
-               if (!tl_is_resdom(tle))
-               {
-                       Fjoin      *fj = (Fjoin *) lfirst(tle);
-
-                       /* it is a FJoin */
-                       fjtl = lnext(tle);
-                       tle = fj->fj_innerNode;
-               }
-#endif
-               i++;
-       }
-
        if (len > 0)
-       {
-               ExecAssignResultType(commonstate,
-                                                        origTupDesc);
-       }
+               ExecAssignResultType(commonstate, tupDesc);
        else
-               ExecAssignResultType(commonstate,
-                                                        (TupleDesc) NULL);
+               ExecAssignResultType(commonstate, (TupleDesc) NULL);
 }
 
 /* ----------------
index 4b3b4a825050e4acdaa5a17c1f3c93030a2ec81f..d0eef4380b7650e31f37930013c15a3e205196d2 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.33 2000/08/24 03:29:03 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.34 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -50,7 +50,8 @@ ExecHashJoin(HashJoin *node)
        Hash       *hashNode;
        List       *hjclauses;
        Expr       *clause;
-       List       *qual;
+       List       *joinqual;
+       List       *otherqual;
        ScanDirection dir;
        TupleTableSlot *inntuple;
        Node       *outerVar;
@@ -70,11 +71,12 @@ ExecHashJoin(HashJoin *node)
        hjstate = node->hashjoinstate;
        hjclauses = node->hashclauses;
        clause = lfirst(hjclauses);
-       estate = node->join.state;
-       qual = node->join.qual;
+       estate = node->join.plan.state;
+       joinqual = node->join.joinqual;
+       otherqual = node->join.plan.qual;
        hashNode = (Hash *) innerPlan(node);
        outerNode = outerPlan(node);
-       hashPhaseDone = node->hashdone;
+       hashPhaseDone = hjstate->hj_hashdone;
        dir = estate->es_direction;
 
        /* -----------------
@@ -132,7 +134,7 @@ ExecHashJoin(HashJoin *node)
                        hashNode->hashstate->hashtable = hashtable;
                        innerTupleSlot = ExecProcNode((Plan *) hashNode, (Plan *) node);
                }
-               node->hashdone = true;
+               hjstate->hj_hashdone = true;
                /* ----------------
                 * Open temp files for outer batches, if needed.
                 * Note that file buffers are palloc'd in regular executor context.
@@ -153,11 +155,10 @@ ExecHashJoin(HashJoin *node)
 
        for (;;)
        {
-
                /*
-                * if the current outer tuple is nil, get a new one
+                * If we don't have an outer tuple, get the next one
                 */
-               if (TupIsNull(outerTupleSlot))
+               if (hjstate->hj_NeedNewOuter)
                {
                        outerTupleSlot = ExecHashJoinOuterGetTuple(outerNode,
                                                                                                           (Plan *) node,
@@ -173,11 +174,15 @@ ExecHashJoin(HashJoin *node)
                                return NULL;
                        }
 
+                       hjstate->jstate.cs_OuterTupleSlot = outerTupleSlot;
+                       econtext->ecxt_outertuple = outerTupleSlot;
+                       hjstate->hj_NeedNewOuter = false;
+                       hjstate->hj_MatchedOuter = false;
+
                        /*
                         * now we have an outer tuple, find the corresponding bucket
                         * for this tuple from the hash table
                         */
-                       econtext->ecxt_outertuple = outerTupleSlot;
                        hjstate->hj_CurBucketNo = ExecHashGetBucket(hashtable, econtext,
                                                                                                                outerVar);
                        hjstate->hj_CurTuple = NULL;
@@ -205,7 +210,7 @@ ExecHashJoin(HashJoin *node)
                                        hashtable->outerBatchSize[batchno]++;
                                        ExecHashJoinSaveTuple(outerTupleSlot->val,
                                                                         hashtable->outerBatchFile[batchno]);
-                                       ExecClearTuple(outerTupleSlot);
+                                       hjstate->hj_NeedNewOuter = true;
                                        continue;       /* loop around for a new outer tuple */
                                }
                        }
@@ -223,7 +228,7 @@ ExecHashJoin(HashJoin *node)
                                break;                  /* out of matches */
 
                        /*
-                        * we've got a match, but still need to test qpqual
+                        * we've got a match, but still need to test non-hashed quals
                         */
                        inntuple = ExecStoreTuple(curtuple,
                                                                          hjstate->hj_HashTupleSlot,
@@ -231,35 +236,77 @@ ExecHashJoin(HashJoin *node)
                                                                          false);       /* don't pfree this tuple */
                        econtext->ecxt_innertuple = inntuple;
 
-                       /* reset temp memory each time to avoid leaks from qpqual */
+                       /* reset temp memory each time to avoid leaks from qual expr */
                        ResetExprContext(econtext);
 
                        /* ----------------
                         * if we pass the qual, then save state for next call and
                         * have ExecProject form the projection, store it
                         * in the tuple table, and return the slot.
+                        *
+                        * Only the joinquals determine MatchedOuter status,
+                        * but all quals must pass to actually return the tuple.
                         * ----------------
                         */
-                       if (ExecQual(qual, econtext, false))
+                       if (ExecQual(joinqual, econtext, false))
                        {
-                               TupleTableSlot *result;
+                               hjstate->hj_MatchedOuter = true;
 
-                               hjstate->jstate.cs_OuterTupleSlot = outerTupleSlot;
-                               result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
-                               if (isDone != ExprEndResult)
+                               if (otherqual == NIL || ExecQual(otherqual, econtext, false))
                                {
-                                       hjstate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
-                                       return result;
+                                       TupleTableSlot *result;
+
+                                       result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
+
+                                       if (isDone != ExprEndResult)
+                                       {
+                                               hjstate->jstate.cs_TupFromTlist =
+                                                       (isDone == ExprMultipleResult);
+                                               return result;
+                                       }
                                }
                        }
                }
 
                /* ----------------
                 *       Now the current outer tuple has run out of matches,
-                *       so we free it and loop around to get a new outer tuple.
+                *       so check whether to emit a dummy outer-join tuple.
+                *       If not, loop around to get a new outer tuple.
                 * ----------------
                 */
-               ExecClearTuple(outerTupleSlot);
+               hjstate->hj_NeedNewOuter = true;
+
+               if (! hjstate->hj_MatchedOuter &&
+                       node->join.jointype == JOIN_LEFT)
+               {
+                       /*
+                        * We are doing an outer join and there were no join matches
+                        * for this outer tuple.  Generate a fake join tuple with
+                        * nulls for the inner tuple, and return it if it passes
+                        * the non-join quals.
+                        */
+                       econtext->ecxt_innertuple = hjstate->hj_NullInnerTupleSlot;
+
+                       if (ExecQual(otherqual, econtext, false))
+                       {
+                               /* ----------------
+                                *      qualification was satisfied so we project and
+                                *      return the slot containing the result tuple
+                                *      using ExecProject().
+                                * ----------------
+                                */
+                               TupleTableSlot *result;
+
+                               result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
+
+                               if (isDone != ExprEndResult)
+                               {
+                                       hjstate->jstate.cs_TupFromTlist =
+                                               (isDone == ExprMultipleResult);
+                                       return result;
+                               }
+                       }
+               }
        }
 }
 
@@ -280,14 +327,13 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
         *      assign the node's execution state
         * ----------------
         */
-       node->join.state = estate;
+       node->join.plan.state = estate;
 
        /* ----------------
         * create state structure
         * ----------------
         */
        hjstate = makeNode(HashJoinState);
-
        node->hashjoinstate = hjstate;
 
        /* ----------------
@@ -298,14 +344,6 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
         */
        ExecAssignExprContext(estate, &hjstate->jstate);
 
-#define HASHJOIN_NSLOTS 2
-       /* ----------------
-        *      tuple table initialization
-        * ----------------
-        */
-       ExecInitResultTupleSlot(estate, &hjstate->jstate);
-       ExecInitOuterTupleSlot(estate, hjstate);
-
        /* ----------------
         * initializes child nodes
         * ----------------
@@ -316,6 +354,28 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
        ExecInitNode(outerNode, estate, (Plan *) node);
        ExecInitNode((Plan *) hashNode, estate, (Plan *) node);
 
+#define HASHJOIN_NSLOTS 3
+       /* ----------------
+        *      tuple table initialization
+        * ----------------
+        */
+       ExecInitResultTupleSlot(estate, &hjstate->jstate);
+       hjstate->hj_OuterTupleSlot = ExecInitExtraTupleSlot(estate);
+
+       switch (node->join.jointype)
+       {
+               case JOIN_INNER:
+                       break;
+               case JOIN_LEFT:
+                       hjstate->hj_NullInnerTupleSlot =
+                               ExecInitNullTupleSlot(estate,
+                                                                         ExecGetTupType((Plan *) hashNode));
+                       break;
+               default:
+                       elog(ERROR, "ExecInitHashJoin: unsupported join type %d",
+                                (int) node->join.jointype);
+       }
+
        /* ----------------
         *      now for some voodoo.  our temporary tuple slot
         *      is actually the result tuple slot of the Hash node
@@ -331,11 +391,6 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
 
                hjstate->hj_HashTupleSlot = slot;
        }
-       hjstate->hj_OuterTupleSlot->ttc_tupleDescriptor = ExecGetTupType(outerNode);
-
-/*
-       hjstate->hj_OuterTupleSlot->ttc_execTupDescriptor = ExecGetExecTupDesc(outerNode);
-*/
 
        /* ----------------
         *      initialize tuple type and projection info
@@ -344,20 +399,25 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
        ExecAssignResultTypeFromTL((Plan *) node, &hjstate->jstate);
        ExecAssignProjectionInfo((Plan *) node, &hjstate->jstate);
 
+       ExecSetSlotDescriptor(hjstate->hj_OuterTupleSlot,
+                                                 ExecGetTupType(outerNode));
+
        /* ----------------
         *      initialize hash-specific info
         * ----------------
         */
 
-       node->hashdone = false;
+       hjstate->hj_hashdone = false;
 
        hjstate->hj_HashTable = (HashJoinTable) NULL;
        hjstate->hj_CurBucketNo = 0;
        hjstate->hj_CurTuple = (HashJoinTuple) NULL;
        hjstate->hj_InnerHashKey = (Node *) NULL;
 
-       hjstate->jstate.cs_OuterTupleSlot = (TupleTableSlot *) NULL;
+       hjstate->jstate.cs_OuterTupleSlot = NULL;
        hjstate->jstate.cs_TupFromTlist = false;
+       hjstate->hj_NeedNewOuter = true;
+       hjstate->hj_MatchedOuter = false;
 
        return TRUE;
 }
@@ -646,10 +706,10 @@ ExecReScanHashJoin(HashJoin *node, ExprContext *exprCtxt, Plan *parent)
 {
        HashJoinState *hjstate = node->hashjoinstate;
 
-       if (!node->hashdone)
+       if (!hjstate->hj_hashdone)
                return;
 
-       node->hashdone = false;
+       hjstate->hj_hashdone = false;
 
        /*
         * Unfortunately, currently we have to destroy hashtable in all
@@ -667,6 +727,8 @@ ExecReScanHashJoin(HashJoin *node, ExprContext *exprCtxt, Plan *parent)
 
        hjstate->jstate.cs_OuterTupleSlot = (TupleTableSlot *) NULL;
        hjstate->jstate.cs_TupFromTlist = false;
+       hjstate->hj_NeedNewOuter = true;
+       hjstate->hj_MatchedOuter = false;
 
        /*
         * if chgParam of subnodes is not null then plans will be re-scanned
index 5a2f45028a0348d44ed9e75352eb472e9e96a44d..9d4ca0a8d54e91ec96ee390fce4ee390226bc8f8 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.37 2000/08/24 03:29:03 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.38 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  * INTERFACE ROUTINES
  *             ExecMergeJoin                   mergejoin outer and inner relations.
  *             ExecInitMergeJoin               creates and initializes run time states
- *             ExecEndMergeJoin                cleand up the node.
+ *             ExecEndMergeJoin                cleans up the node.
  *
  * NOTES
  *             Essential operation of the merge join algorithm is as follows:
- *             (** indicates the tuples satisfy the merge clause).
  *
  *             Join {                                                                                             -
  *                     get initial outer and inner tuples                              INITIALIZE
  *                     }                                                                                                  -
  *             }                                                                                                          -
  *
- *             Skip Outer {                                                                            SKIPOUTER
+ *             Skip Outer {                                                                            SKIPOUTER_BEGIN
  *                     if (inner == outer) Join Tuples                                 JOINTUPLES
- *                     while (outer < inner)                                                   SKIPOUTER
- *                             advance outer                                                           SKIPOUTER
- *                     if (outer > inner)                                                              SKIPOUTER
+ *                     while (outer < inner)                                                   SKIPOUTER_TEST
+ *                             advance outer                                                           SKIPOUTER_ADVANCE
+ *                     if (outer > inner)                                                              SKIPOUTER_TEST
  *                             Skip Inner                                                                      SKIPINNER
  *             }                                                                                                          -
  *
- *             Skip Inner {                                                                            SKIPINNER
+ *             Skip Inner {                                                                            SKIPINNER_BEGIN
  *                     if (inner == outer) Join Tuples                                 JOINTUPLES
- *                     while (outer > inner)                                                   SKIPINNER
- *                             advance inner                                                           SKIPINNER
- *                     if (outer < inner)                                                              SKIPINNER
+ *                     while (outer > inner)                                                   SKIPINNER_TEST
+ *                             advance inner                                                           SKIPINNER_ADVANCE
+ *                     if (outer < inner)                                                              SKIPINNER_TEST
  *                             Skip Outer                                                                      SKIPOUTER
  *             }                                                                                                          -
  *
@@ -68,6 +67,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/printtup.h"
 #include "catalog/pg_operator.h"
 #include "executor/execdebug.h"
 #include "executor/execdefs.h"
@@ -273,52 +273,39 @@ MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext)
  * ----------------------------------------------------------------
  */
 #ifdef EXEC_MERGEJOINDEBUG
-void
-                       ExecMergeTupleDumpInner(ExprContext *econtext);
 
-void
-ExecMergeTupleDumpInner(ExprContext *econtext)
+static void
+ExecMergeTupleDumpOuter(MergeJoinState *mergestate)
 {
-       TupleTableSlot *innerSlot;
+       TupleTableSlot *outerSlot = mergestate->mj_OuterTupleSlot;
 
-       printf("==== inner tuple ====\n");
-       innerSlot = econtext->ecxt_innertuple;
-       if (TupIsNull(innerSlot))
+       printf("==== outer tuple ====\n");
+       if (TupIsNull(outerSlot))
                printf("(nil)\n");
        else
-               MJ_debugtup(innerSlot->val,
-                                       innerSlot->ttc_tupleDescriptor);
+               MJ_debugtup(outerSlot->val,
+                                       outerSlot->ttc_tupleDescriptor);
 }
 
-void
-                       ExecMergeTupleDumpOuter(ExprContext *econtext);
-
-void
-ExecMergeTupleDumpOuter(ExprContext *econtext)
+static void
+ExecMergeTupleDumpInner(MergeJoinState *mergestate)
 {
-       TupleTableSlot *outerSlot;
+       TupleTableSlot *innerSlot = mergestate->mj_InnerTupleSlot;
 
-       printf("==== outer tuple ====\n");
-       outerSlot = econtext->ecxt_outertuple;
-       if (TupIsNull(outerSlot))
+       printf("==== inner tuple ====\n");
+       if (TupIsNull(innerSlot))
                printf("(nil)\n");
        else
-               MJ_debugtup(outerSlot->val,
-                                       outerSlot->ttc_tupleDescriptor);
+               MJ_debugtup(innerSlot->val,
+                                       innerSlot->ttc_tupleDescriptor);
 }
 
-void ExecMergeTupleDumpMarked(ExprContext *econtext,
-                                                MergeJoinState *mergestate);
-
-void
-ExecMergeTupleDumpMarked(ExprContext *econtext,
-                                                MergeJoinState *mergestate)
+static void
+ExecMergeTupleDumpMarked(MergeJoinState *mergestate)
 {
-       TupleTableSlot *markedSlot;
+       TupleTableSlot *markedSlot = mergestate->mj_MarkedTupleSlot;
 
        printf("==== marked tuple ====\n");
-       markedSlot = mergestate->mj_MarkedTupleSlot;
-
        if (TupIsNull(markedSlot))
                printf("(nil)\n");
        else
@@ -326,17 +313,14 @@ ExecMergeTupleDumpMarked(ExprContext *econtext,
                                        markedSlot->ttc_tupleDescriptor);
 }
 
-void
-                       ExecMergeTupleDump(ExprContext *econtext, MergeJoinState *mergestate);
-
-void
-ExecMergeTupleDump(ExprContext *econtext, MergeJoinState *mergestate)
+static void
+ExecMergeTupleDump(MergeJoinState *mergestate)
 {
        printf("******** ExecMergeTupleDump ********\n");
 
-       ExecMergeTupleDumpInner(econtext);
-       ExecMergeTupleDumpOuter(econtext);
-       ExecMergeTupleDumpMarked(econtext, mergestate);
+       ExecMergeTupleDumpOuter(mergestate);
+       ExecMergeTupleDumpInner(mergestate);
+       ExecMergeTupleDumpMarked(mergestate);
 
        printf("******** \n");
 }
@@ -404,7 +388,8 @@ ExecMergeJoin(MergeJoin *node)
        List       *innerSkipQual;
        List       *outerSkipQual;
        List       *mergeclauses;
-       List       *qual;
+       List       *joinqual;
+       List       *otherqual;
        bool            qualResult;
        bool            compareResult;
        Plan       *innerPlan;
@@ -412,27 +397,48 @@ ExecMergeJoin(MergeJoin *node)
        Plan       *outerPlan;
        TupleTableSlot *outerTupleSlot;
        ExprContext *econtext;
-#ifdef ENABLE_OUTER_JOINS
-       /*
-        * These should be set from the expression context! - thomas
-        * 1999-02-20
-        */
-       static bool isLeftJoin = true;
-       static bool isRightJoin = false;
-#endif
+       bool            doFillOuter;
+       bool            doFillInner;
 
        /* ----------------
         *      get information from node
         * ----------------
         */
        mergestate = node->mergestate;
-       estate = node->join.state;
+       estate = node->join.plan.state;
        direction = estate->es_direction;
        innerPlan = innerPlan((Plan *) node);
        outerPlan = outerPlan((Plan *) node);
        econtext = mergestate->jstate.cs_ExprContext;
        mergeclauses = node->mergeclauses;
-       qual = node->join.qual;
+       joinqual = node->join.joinqual;
+       otherqual = node->join.plan.qual;
+
+       switch (node->join.jointype)
+       {
+               case JOIN_INNER:
+                       doFillOuter = false;
+                       doFillInner = false;
+                       break;
+               case JOIN_LEFT:
+                       doFillOuter = true;
+                       doFillInner = false;
+                       break;
+               case JOIN_FULL:
+                       doFillOuter = true;
+                       doFillInner = true;
+                       break;
+               case JOIN_RIGHT:
+                       doFillOuter = false;
+                       doFillInner = true;
+                       break;
+               default:
+                       elog(ERROR, "ExecMergeJoin: unsupported join type %d",
+                                (int) node->join.jointype);
+                       doFillOuter = false; /* keep compiler quiet */
+                       doFillInner = false;
+                       break;
+       }
 
        if (ScanDirectionIsForward(direction))
        {
@@ -483,7 +489,7 @@ ExecMergeJoin(MergeJoin *node)
                 *                improved readability.
                 * ----------------
                 */
-               MJ_dump(econtext, mergestate);
+               MJ_dump(mergestate);
 
                switch (mergestate->mj_JoinState)
                {
@@ -491,46 +497,60 @@ ExecMergeJoin(MergeJoin *node)
                                /*
                                 * EXEC_MJ_INITIALIZE means that this is the first time
                                 * ExecMergeJoin() has been called and so we have to
-                                * initialize the inner, outer and marked tuples as well
-                                * as various stuff in the expression context.
+                                * fetch the first tuple for both outer and inner subplans.
+                                * If we fail to get a tuple here, then that subplan is
+                                * empty, and we either end the join or go to one of the
+                                * fill-remaining-tuples states.
                                 */
                        case EXEC_MJ_INITIALIZE:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE\n");
 
-                               /*
-                                * Note: at this point, if either of our inner or outer
-                                * tuples are nil, then the join ends immediately because
-                                * we know one of the subplans is empty.
-                                */
-                               innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
-                               if (TupIsNull(innerTupleSlot))
+                               outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+                               mergestate->mj_OuterTupleSlot = outerTupleSlot;
+                               if (TupIsNull(outerTupleSlot))
                                {
-                                       MJ_printf("ExecMergeJoin: **** inner tuple is nil ****\n");
+                                       MJ_printf("ExecMergeJoin: outer subplan is empty\n");
+                                       if (doFillInner)
+                                       {
+                                               /*
+                                                * Need to emit right-join tuples for remaining
+                                                * inner tuples.  We set MatchedInner = true to
+                                                * force the ENDOUTER state to advance inner.
+                                                */
+                                               mergestate->mj_JoinState = EXEC_MJ_ENDOUTER;
+                                               mergestate->mj_MatchedInner = true;
+                                               break;
+                                       }
+                                       /* Otherwise we're done. */
                                        return NULL;
                                }
 
-                               outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
-                               if (TupIsNull(outerTupleSlot))
+                               innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+                               mergestate->mj_InnerTupleSlot = innerTupleSlot;
+                               if (TupIsNull(innerTupleSlot))
                                {
-                                       MJ_printf("ExecMergeJoin: **** outer tuple is nil ****\n");
+                                       MJ_printf("ExecMergeJoin: inner subplan is empty\n");
+                                       if (doFillOuter)
+                                       {
+                                               /*
+                                                * Need to emit left-join tuples for remaining
+                                                * outer tuples.  We set MatchedOuter = true to
+                                                * force the ENDINNER state to advance outer.
+                                                */
+                                               mergestate->mj_JoinState = EXEC_MJ_ENDINNER;
+                                               mergestate->mj_MatchedOuter = true;
+                                               break;
+                                       }
+                                       /* Otherwise we're done. */
                                        return NULL;
                                }
 
                                /* ----------------
-                                *       store the inner and outer tuple in the merge state
+                                *      OK, we have the initial tuples.  Begin by skipping
+                                *      unmatched inner tuples.
                                 * ----------------
                                 */
-                               econtext->ecxt_innertuple = innerTupleSlot;
-                               econtext->ecxt_outertuple = outerTupleSlot;
-
-                               mergestate->mj_MarkedTupleSlot->ttc_tupleDescriptor =
-                                       innerTupleSlot->ttc_tupleDescriptor;
-
-                               /* ----------------
-                                *      initialize merge join state to skip inner tuples.
-                                * ----------------
-                                */
-                               mergestate->mj_JoinState = EXEC_MJ_SKIPINNER;
+                               mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_BEGIN;
                                break;
 
                                /*
@@ -541,9 +561,10 @@ ExecMergeJoin(MergeJoin *node)
                                 */
                        case EXEC_MJ_JOINMARK:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_JOINMARK\n");
+
                                ExecMarkPos(innerPlan);
 
-                               MarkInnerTuple(econtext->ecxt_innertuple, mergestate);
+                               MarkInnerTuple(mergestate->mj_InnerTupleSlot, mergestate);
 
                                mergestate->mj_JoinState = EXEC_MJ_JOINTEST;
                                break;
@@ -562,7 +583,12 @@ ExecMergeJoin(MergeJoin *node)
 
                                ResetExprContext(econtext);
 
-                               qualResult = ExecQual((List *) mergeclauses, econtext, false);
+                               outerTupleSlot = mergestate->mj_OuterTupleSlot;
+                               econtext->ecxt_outertuple = outerTupleSlot;
+                               innerTupleSlot = mergestate->mj_InnerTupleSlot;
+                               econtext->ecxt_innertuple = innerTupleSlot;
+
+                               qualResult = ExecQual(mergeclauses, econtext, false);
                                MJ_DEBUG_QUAL(mergeclauses, qualResult);
 
                                if (qualResult)
@@ -578,38 +604,57 @@ ExecMergeJoin(MergeJoin *node)
                                 */
                        case EXEC_MJ_JOINTUPLES:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTUPLES\n");
+
                                mergestate->mj_JoinState = EXEC_MJ_NEXTINNER;
 
                                /*
-                                * Check the qpqual to see if we actually want to return
-                                * this join tuple.  If not, can proceed with merge.
+                                * Check the extra qual conditions to see if we actually
+                                * want to return this join tuple.  If not, can proceed with
+                                * merge.  We must distinguish the additional joinquals
+                                * (which must pass to consider the tuples "matched" for
+                                * outer-join logic) from the otherquals (which must pass
+                                * before we actually return the tuple).
                                 *
-                                * (We don't bother with a ResetExprContext here, on the
+                                * We don't bother with a ResetExprContext here, on the
                                 * assumption that we just did one before checking the merge
-                                * qual.  One per tuple should be sufficient.)
+                                * qual.  One per tuple should be sufficient.  Also, the
+                                * econtext's tuple pointers were set up before checking
+                                * the merge qual, so we needn't do it again.
                                 */
-                               qualResult = ExecQual((List *) qual, econtext, false);
-                               MJ_DEBUG_QUAL(qual, qualResult);
+                               qualResult = (joinqual == NIL ||
+                                                         ExecQual(joinqual, econtext, false));
+                               MJ_DEBUG_QUAL(joinqual, qualResult);
 
                                if (qualResult)
                                {
-                                       /* ----------------
-                                        *      qualification succeeded.  now form the desired
-                                        *      projection tuple and return the slot containing it.
-                                        * ----------------
-                                        */
-                                       TupleTableSlot *result;
-                                       ExprDoneCond isDone;
+                                       mergestate->mj_MatchedOuter = true;
+                                       mergestate->mj_MatchedInner = true;
 
-                                       MJ_printf("ExecMergeJoin: **** returning tuple ****\n");
+                                       qualResult = (otherqual == NIL ||
+                                                                 ExecQual(otherqual, econtext, false));
+                                       MJ_DEBUG_QUAL(otherqual, qualResult);
 
-                                       result = ExecProject(mergestate->jstate.cs_ProjInfo,
-                                                                                &isDone);
-
-                                       if (isDone != ExprEndResult)
+                                       if (qualResult)
                                        {
-                                               mergestate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
-                                               return result;
+                                               /* ----------------
+                                                *      qualification succeeded.  now form the desired
+                                                *      projection tuple and return the slot containing it.
+                                                * ----------------
+                                                */
+                                               TupleTableSlot *result;
+                                               ExprDoneCond isDone;
+
+                                               MJ_printf("ExecMergeJoin: returning tuple\n");
+
+                                               result = ExecProject(mergestate->jstate.cs_ProjInfo,
+                                                                                        &isDone);
+
+                                               if (isDone != ExprEndResult)
+                                               {
+                                                       mergestate->jstate.cs_TupFromTlist =
+                                                               (isDone == ExprMultipleResult);
+                                                       return result;
+                                               }
                                        }
                                }
                                break;
@@ -618,17 +663,60 @@ ExecMergeJoin(MergeJoin *node)
                                 * EXEC_MJ_NEXTINNER means advance the inner scan to the
                                 * next tuple. If the tuple is not nil, we then proceed to
                                 * test it against the join qualification.
+                                *
+                                * Before advancing, we check to see if we must emit an
+                                * outer-join fill tuple for this inner tuple.
                                 */
                        case EXEC_MJ_NEXTINNER:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTINNER\n");
 
+                               if (doFillInner && !mergestate->mj_MatchedInner)
+                               {
+                                       /*
+                                        * Generate a fake join tuple with nulls for the outer
+                                        * tuple, and return it if it passes the non-join quals.
+                                        */
+                                       mergestate->mj_MatchedInner = true;     /* do it only once */
+
+                                       ResetExprContext(econtext);
+
+                                       outerTupleSlot = mergestate->mj_NullOuterTupleSlot;
+                                       econtext->ecxt_outertuple = outerTupleSlot;
+                                       innerTupleSlot = mergestate->mj_InnerTupleSlot;
+                                       econtext->ecxt_innertuple = innerTupleSlot;
+
+                                       if (ExecQual(otherqual, econtext, false))
+                                       {
+                                               /* ----------------
+                                                *      qualification succeeded.  now form the desired
+                                                *      projection tuple and return the slot containing it.
+                                                * ----------------
+                                                */
+                                               TupleTableSlot *result;
+                                               ExprDoneCond isDone;
+
+                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+                                               result = ExecProject(mergestate->jstate.cs_ProjInfo,
+                                                                                        &isDone);
+
+                                               if (isDone != ExprEndResult)
+                                               {
+                                                       mergestate->jstate.cs_TupFromTlist =
+                                                               (isDone == ExprMultipleResult);
+                                                       return result;
+                                               }
+                                       }
+                               }
+
                                /* ----------------
                                 *      now we get the next inner tuple, if any
                                 * ----------------
                                 */
                                innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+                               mergestate->mj_InnerTupleSlot = innerTupleSlot;
                                MJ_DEBUG_PROC_NODE(innerTupleSlot);
-                               econtext->ecxt_innertuple = innerTupleSlot;
+                               mergestate->mj_MatchedInner = false;
 
                                if (TupIsNull(innerTupleSlot))
                                        mergestate->mj_JoinState = EXEC_MJ_NEXTOUTER;
@@ -650,23 +738,81 @@ ExecMergeJoin(MergeJoin *node)
                                 * so get a new outer tuple and then
                                 * proceed to test it against the marked tuple
                                 * (EXEC_MJ_TESTOUTER)
+                                *
+                                * Before advancing, we check to see if we must emit an
+                                * outer-join fill tuple for this outer tuple.
                                 *------------------------------------------------
                                 */
                        case EXEC_MJ_NEXTOUTER:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTOUTER\n");
 
+                               if (doFillOuter && !mergestate->mj_MatchedOuter)
+                               {
+                                       /*
+                                        * Generate a fake join tuple with nulls for the inner
+                                        * tuple, and return it if it passes the non-join quals.
+                                        */
+                                       mergestate->mj_MatchedOuter = true;     /* do it only once */
+
+                                       ResetExprContext(econtext);
+
+                                       outerTupleSlot = mergestate->mj_OuterTupleSlot;
+                                       econtext->ecxt_outertuple = outerTupleSlot;
+                                       innerTupleSlot = mergestate->mj_NullInnerTupleSlot;
+                                       econtext->ecxt_innertuple = innerTupleSlot;
+
+                                       if (ExecQual(otherqual, econtext, false))
+                                       {
+                                               /* ----------------
+                                                *      qualification succeeded.  now form the desired
+                                                *      projection tuple and return the slot containing it.
+                                                * ----------------
+                                                */
+                                               TupleTableSlot *result;
+                                               ExprDoneCond isDone;
+
+                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+                                               result = ExecProject(mergestate->jstate.cs_ProjInfo,
+                                                                                        &isDone);
+
+                                               if (isDone != ExprEndResult)
+                                               {
+                                                       mergestate->jstate.cs_TupFromTlist =
+                                                               (isDone == ExprMultipleResult);
+                                                       return result;
+                                               }
+                                       }
+                               }
+
+                               /* ----------------
+                                *      now we get the next outer tuple, if any
+                                * ----------------
+                                */
                                outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+                               mergestate->mj_OuterTupleSlot = outerTupleSlot;
                                MJ_DEBUG_PROC_NODE(outerTupleSlot);
-                               econtext->ecxt_outertuple = outerTupleSlot;
+                               mergestate->mj_MatchedOuter = false;
 
                                /* ----------------
-                                *      if the outer tuple is null then we know
-                                *      we are done with the join
+                                *      if the outer tuple is null then we are done with the
+                                *      join, unless we have inner tuples we need to null-fill.
                                 * ----------------
                                 */
                                if (TupIsNull(outerTupleSlot))
                                {
-                                       MJ_printf("ExecMergeJoin: **** outer tuple is nil ****\n");
+                                       MJ_printf("ExecMergeJoin: end of outer subplan\n");
+                                       innerTupleSlot = mergestate->mj_InnerTupleSlot;
+                                       if (doFillInner && !TupIsNull(innerTupleSlot))
+                                       {
+                                               /*
+                                                * Need to emit right-join tuples for remaining
+                                                * inner tuples.
+                                                */
+                                               mergestate->mj_JoinState = EXEC_MJ_ENDOUTER;
+                                               break;
+                                       }
+                                       /* Otherwise we're done. */
                                        return NULL;
                                }
 
@@ -712,39 +858,45 @@ ExecMergeJoin(MergeJoin *node)
 
                                /* ----------------
                                 *      here we compare the outer tuple with the marked inner tuple
-                                *      by using the marked tuple in place of the inner tuple.
                                 * ----------------
                                 */
-                               innerTupleSlot = econtext->ecxt_innertuple;
-                               econtext->ecxt_innertuple = mergestate->mj_MarkedTupleSlot;
-
                                ResetExprContext(econtext);
 
-                               qualResult = ExecQual((List *) mergeclauses, econtext, false);
+                               outerTupleSlot = mergestate->mj_OuterTupleSlot;
+                               econtext->ecxt_outertuple = outerTupleSlot;
+                               innerTupleSlot = mergestate->mj_MarkedTupleSlot;
+                               econtext->ecxt_innertuple = innerTupleSlot;
+
+                               qualResult = ExecQual(mergeclauses, econtext, false);
                                MJ_DEBUG_QUAL(mergeclauses, qualResult);
 
                                if (qualResult)
                                {
 
                                        /*
-                                        * the merge clause matched so now we juggle the slots
-                                        * back the way they were and proceed to JOINTEST.
+                                        * the merge clause matched so now we restore the inner
+                                        * scan position to the first mark, and loop back to
+                                        * JOINTEST.  Actually, since we know the mergeclause
+                                        * matches, we can skip JOINTEST and go straight to
+                                        * JOINTUPLES.
                                         *
-                                        * I can't understand why we have to go to JOINTEST and
-                                        * compare outer tuple with the same inner one again
-                                        * -> go to JOINTUPLES...        - vadim 02/27/98
+                                        * NOTE: we do not need to worry about the MatchedInner
+                                        * state for the rescanned inner tuples.  We know all
+                                        * of them will match this new outer tuple and therefore
+                                        * won't be emitted as fill tuples.  This works *only*
+                                        * because we require the extra joinquals to be nil when
+                                        * doing a right or full join --- otherwise some of the
+                                        * rescanned tuples might fail the extra joinquals.
                                         */
-
                                        ExecRestrPos(innerPlan);
                                        mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES;
                                }
                                else
                                {
-                                       econtext->ecxt_innertuple = innerTupleSlot;
                                        /* ----------------
                                         *      if the inner tuple was nil and the new outer
                                         *      tuple didn't match the marked outer tuple then
-                                        *      we may have the case:
+                                        *      we have the case:
                                         *
                                         *                       outer inner
                                         *                         4     4      - marked tuple
@@ -753,31 +905,33 @@ ExecMergeJoin(MergeJoin *node)
                                         *                         7
                                         *
                                         *      which means that all subsequent outer tuples will be
-                                        *      larger than our inner tuples.
+                                        *      larger than our marked inner tuples.  So we're done.
                                         * ----------------
                                         */
+                                       innerTupleSlot = mergestate->mj_InnerTupleSlot;
                                        if (TupIsNull(innerTupleSlot))
                                        {
-#ifdef ENABLE_OUTER_JOINS
-                                               if (isLeftJoin)
+                                               if (doFillOuter)
                                                {
-                                                       /* continue on to null fill outer tuples */
-                                                       mergestate->mj_JoinState = EXEC_MJ_FILLOUTER;
+                                                       /*
+                                                        * Need to emit left-join tuples for remaining
+                                                        * outer tuples.
+                                                        */
+                                                       mergestate->mj_JoinState = EXEC_MJ_ENDINNER;
                                                        break;
                                                }
-#endif
-                                               MJ_printf("ExecMergeJoin: **** weird case 1 ****\n");
+                                               /* Otherwise we're done. */
                                                return NULL;
                                        }
 
                                        /* continue on to skip outer tuples */
-                                       mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER;
+                                       mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_BEGIN;
                                }
                                break;
 
                                /*----------------------------------------------------------
                                 * EXEC_MJ_SKIPOUTER means skip over tuples in the outer plan
-                                * until we find an outer tuple > current inner tuple.
+                                * until we find an outer tuple >= current inner tuple.
                                 *
                                 * For example:
                                 *
@@ -790,10 +944,14 @@ ExecMergeJoin(MergeJoin *node)
                                 *
                                 * we have to advance the outer scan
                                 * until we find the outer 8.
+                                *
+                                * To avoid redundant tests, we divide this into three
+                                * sub-states: BEGIN, TEST, ADVANCE.
                                 *----------------------------------------------------------
                                 */
-                       case EXEC_MJ_SKIPOUTER:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER\n");
+                       case EXEC_MJ_SKIPOUTER_BEGIN:
+                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_BEGIN\n");
+
                                /* ----------------
                                 *      before we advance, make sure the current tuples
                                 *      do not satisfy the mergeclauses.  If they do, then
@@ -802,23 +960,39 @@ ExecMergeJoin(MergeJoin *node)
                                 */
                                ResetExprContext(econtext);
 
-                               qualResult = ExecQual((List *) mergeclauses, econtext, false);
+                               outerTupleSlot = mergestate->mj_OuterTupleSlot;
+                               econtext->ecxt_outertuple = outerTupleSlot;
+                               innerTupleSlot = mergestate->mj_InnerTupleSlot;
+                               econtext->ecxt_innertuple = innerTupleSlot;
+
+                               qualResult = ExecQual(mergeclauses, econtext, false);
                                MJ_DEBUG_QUAL(mergeclauses, qualResult);
 
                                if (qualResult)
                                {
                                        ExecMarkPos(innerPlan);
 
-                                       MarkInnerTuple(econtext->ecxt_innertuple, mergestate);
+                                       MarkInnerTuple(innerTupleSlot, mergestate);
 
                                        mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES;
                                        break;
                                }
 
+                               mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_TEST;
+                               break;
+
+                       case EXEC_MJ_SKIPOUTER_TEST:
+                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_TEST\n");
+
                                /* ----------------
                                 *      ok, now test the skip qualification
                                 * ----------------
                                 */
+                               outerTupleSlot = mergestate->mj_OuterTupleSlot;
+                               econtext->ecxt_outertuple = outerTupleSlot;
+                               innerTupleSlot = mergestate->mj_InnerTupleSlot;
+                               econtext->ecxt_innertuple = innerTupleSlot;
+
                                compareResult = MergeCompare(mergeclauses,
                                                                                         outerSkipQual,
                                                                                         econtext);
@@ -827,42 +1001,12 @@ ExecMergeJoin(MergeJoin *node)
 
                                /* ----------------
                                 *      compareResult is true as long as we should
-                                *      continue skipping tuples.
+                                *      continue skipping outer tuples.
                                 * ----------------
                                 */
                                if (compareResult)
                                {
-#ifdef ENABLE_OUTER_JOINS
-                                       /* ----------------
-                                        *      if this is a left or full outer join, then fill
-                                        * ----------------
-                                        */
-                                       if (isLeftJoin)
-                                       {
-                                               mergestate->mj_JoinState = EXEC_MJ_FILLOUTER;
-                                               break;
-                                       }
-#endif
-
-                                       outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
-                                       MJ_DEBUG_PROC_NODE(outerTupleSlot);
-                                       econtext->ecxt_outertuple = outerTupleSlot;
-
-                                       /* ----------------
-                                        *      if the outer tuple is null then we know
-                                        *      we are done with the join
-                                        * ----------------
-                                        */
-                                       if (TupIsNull(outerTupleSlot))
-                                       {
-                                               MJ_printf("ExecMergeJoin: **** outerTuple is nil ****\n");
-                                               return NULL;
-                                       }
-                                       /* ----------------
-                                        *      otherwise test the new tuple against the skip qual.
-                                        *      (we remain in the EXEC_MJ_SKIPOUTER state)
-                                        * ----------------
-                                        */
+                                       mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
                                        break;
                                }
 
@@ -880,14 +1024,99 @@ ExecMergeJoin(MergeJoin *node)
                                MJ_DEBUG_MERGE_COMPARE(innerSkipQual, compareResult);
 
                                if (compareResult)
-                                       mergestate->mj_JoinState = EXEC_MJ_SKIPINNER;
+                                       mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_BEGIN;
                                else
                                        mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
                                break;
 
+                               /*------------------------------------------------
+                                * Before advancing, we check to see if we must emit an
+                                * outer-join fill tuple for this outer tuple.
+                                *------------------------------------------------
+                                */
+                       case EXEC_MJ_SKIPOUTER_ADVANCE:
+                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_ADVANCE\n");
+
+                               if (doFillOuter && !mergestate->mj_MatchedOuter)
+                               {
+                                       /*
+                                        * Generate a fake join tuple with nulls for the inner
+                                        * tuple, and return it if it passes the non-join quals.
+                                        */
+                                       mergestate->mj_MatchedOuter = true;     /* do it only once */
+
+                                       ResetExprContext(econtext);
+
+                                       outerTupleSlot = mergestate->mj_OuterTupleSlot;
+                                       econtext->ecxt_outertuple = outerTupleSlot;
+                                       innerTupleSlot = mergestate->mj_NullInnerTupleSlot;
+                                       econtext->ecxt_innertuple = innerTupleSlot;
+
+                                       if (ExecQual(otherqual, econtext, false))
+                                       {
+                                               /* ----------------
+                                                *      qualification succeeded.  now form the desired
+                                                *      projection tuple and return the slot containing it.
+                                                * ----------------
+                                                */
+                                               TupleTableSlot *result;
+                                               ExprDoneCond isDone;
+
+                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+                                               result = ExecProject(mergestate->jstate.cs_ProjInfo,
+                                                                                        &isDone);
+
+                                               if (isDone != ExprEndResult)
+                                               {
+                                                       mergestate->jstate.cs_TupFromTlist =
+                                                               (isDone == ExprMultipleResult);
+                                                       return result;
+                                               }
+                                       }
+                               }
+
+                               /* ----------------
+                                *      now we get the next outer tuple, if any
+                                * ----------------
+                                */
+                               outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+                               mergestate->mj_OuterTupleSlot = outerTupleSlot;
+                               MJ_DEBUG_PROC_NODE(outerTupleSlot);
+                               mergestate->mj_MatchedOuter = false;
+
+                               /* ----------------
+                                *      if the outer tuple is null then we are done with the
+                                *      join, unless we have inner tuples we need to null-fill.
+                                * ----------------
+                                */
+                               if (TupIsNull(outerTupleSlot))
+                               {
+                                       MJ_printf("ExecMergeJoin: end of outer subplan\n");
+                                       innerTupleSlot = mergestate->mj_InnerTupleSlot;
+                                       if (doFillInner && !TupIsNull(innerTupleSlot))
+                                       {
+                                               /*
+                                                * Need to emit right-join tuples for remaining
+                                                * inner tuples.
+                                                */
+                                               mergestate->mj_JoinState = EXEC_MJ_ENDOUTER;
+                                               break;
+                                       }
+                                       /* Otherwise we're done. */
+                                       return NULL;
+                               }
+
+                               /* ----------------
+                                *      otherwise test the new tuple against the skip qual.
+                                * ----------------
+                                */
+                               mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_TEST;
+                               break;
+
                                /*-----------------------------------------------------------
                                 * EXEC_MJ_SKIPINNER means skip over tuples in the inner plan
-                                * until we find an inner tuple > current outer tuple.
+                                * until we find an inner tuple >= current outer tuple.
                                 *
                                 * For example:
                                 *
@@ -901,10 +1130,13 @@ ExecMergeJoin(MergeJoin *node)
                                 * we have to advance the inner scan
                                 * until we find the inner 12.
                                 *
+                                * To avoid redundant tests, we divide this into three
+                                * sub-states: BEGIN, TEST, ADVANCE.
                                 *-------------------------------------------------------
                                 */
-                       case EXEC_MJ_SKIPINNER:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER\n");
+                       case EXEC_MJ_SKIPINNER_BEGIN:
+                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_BEGIN\n");
+
                                /* ----------------
                                 *      before we advance, make sure the current tuples
                                 *      do not satisfy the mergeclauses.  If they do, then
@@ -913,23 +1145,39 @@ ExecMergeJoin(MergeJoin *node)
                                 */
                                ResetExprContext(econtext);
 
-                               qualResult = ExecQual((List *) mergeclauses, econtext, false);
+                               outerTupleSlot = mergestate->mj_OuterTupleSlot;
+                               econtext->ecxt_outertuple = outerTupleSlot;
+                               innerTupleSlot = mergestate->mj_InnerTupleSlot;
+                               econtext->ecxt_innertuple = innerTupleSlot;
+
+                               qualResult = ExecQual(mergeclauses, econtext, false);
                                MJ_DEBUG_QUAL(mergeclauses, qualResult);
 
                                if (qualResult)
                                {
                                        ExecMarkPos(innerPlan);
 
-                                       MarkInnerTuple(econtext->ecxt_innertuple, mergestate);
+                                       MarkInnerTuple(innerTupleSlot, mergestate);
 
                                        mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES;
                                        break;
                                }
 
+                               mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_TEST;
+                               break;
+
+                       case EXEC_MJ_SKIPINNER_TEST:
+                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_TEST\n");
+
                                /* ----------------
                                 *      ok, now test the skip qualification
                                 * ----------------
                                 */
+                               outerTupleSlot = mergestate->mj_OuterTupleSlot;
+                               econtext->ecxt_outertuple = outerTupleSlot;
+                               innerTupleSlot = mergestate->mj_InnerTupleSlot;
+                               econtext->ecxt_innertuple = innerTupleSlot;
+
                                compareResult = MergeCompare(mergeclauses,
                                                                                         innerSkipQual,
                                                                                         econtext);
@@ -938,70 +1186,20 @@ ExecMergeJoin(MergeJoin *node)
 
                                /* ----------------
                                 *      compareResult is true as long as we should
-                                *      continue skipping tuples.
+                                *      continue skipping inner tuples.
                                 * ----------------
                                 */
                                if (compareResult)
                                {
-#ifdef ENABLE_OUTER_JOINS
-                                       /* ----------------
-                                        *      if this is a right or full outer join, then fill
-                                        * ----------------
-                                        */
-                                       if (isRightJoin)
-                                       {
-                                               mergestate->mj_JoinState = EXEC_MJ_FILLINNER;
-                                               break;
-                                       }
-#endif
-
-                                       /* ----------------
-                                        *      now try and get a new inner tuple
-                                        * ----------------
-                                        */
-                                       innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
-                                       MJ_DEBUG_PROC_NODE(innerTupleSlot);
-                                       econtext->ecxt_innertuple = innerTupleSlot;
-
-                                       /* ----------------
-                                        *      if the inner tuple is null then we know
-                                        *      we have to restore the inner scan
-                                        *      and advance to the next outer tuple
-                                        * ----------------
-                                        */
-                                       if (TupIsNull(innerTupleSlot))
-                                       {
-                                               /* ----------------
-                                                *      this is an interesting case.. all our
-                                                *      inner tuples are smaller then our outer
-                                                *      tuples so we never found an inner tuple
-                                                *      to mark.
-                                                *
-                                                *                        outer inner
-                                                *       outer tuple -  5         4
-                                                *                                      5         4
-                                                *                                      6        nil  - inner tuple
-                                                *                                      7
-                                                *
-                                                *      This means the join should end.
-                                                * ----------------
-                                                */
-                                               MJ_printf("ExecMergeJoin: **** weird case 2 ****\n");
-                                               return NULL;
-                                       }
-
-                                       /* ----------------
-                                        *      otherwise test the new tuple against the skip qual.
-                                        *      (we remain in the EXEC_MJ_SKIPINNER state)
-                                        * ----------------
-                                        */
+                                       mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
                                        break;
                                }
 
                                /* ----------------
-                                *      compare finally failed and we have stopped skipping
-                                *      inner tuples so now check the outer skip qual
-                                *      to see if we should now skip outer tuples...
+                                *      now check the outer skip qual to see if we
+                                *      should now skip outer tuples... if we fail the
+                                *      outer skip qual, then we know we have a new pair
+                                *      of matching tuples.
                                 * ----------------
                                 */
                                compareResult = MergeCompare(mergeclauses,
@@ -1011,120 +1209,237 @@ ExecMergeJoin(MergeJoin *node)
                                MJ_DEBUG_MERGE_COMPARE(outerSkipQual, compareResult);
 
                                if (compareResult)
-                                       mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER;
+                                       mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_BEGIN;
                                else
                                        mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
-
                                break;
 
-#ifdef ENABLE_OUTER_JOINS
-
-                               /*
-                                * EXEC_MJ_FILLINNER means we have an unmatched inner
-                                * tuple which must be null-expanded into the projection
-                                * tuple. get the next inner tuple and reset markers
-                                * (EXEC_MJ_JOINMARK).
+                               /*------------------------------------------------
+                                * Before advancing, we check to see if we must emit an
+                                * outer-join fill tuple for this inner tuple.
+                                *------------------------------------------------
                                 */
-                       case EXEC_MJ_FILLINNER:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_FILLINNER\n");
-                               mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
+                       case EXEC_MJ_SKIPINNER_ADVANCE:
+                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_ADVANCE\n");
 
-                               /* ----------------
-                                *      project the inner tuple into the result
-                                * ----------------
-                                */
-                               MJ_printf("ExecMergeJoin: project inner tuple into the result (not yet implemented)\n");
+                               if (doFillInner && !mergestate->mj_MatchedInner)
+                               {
+                                       /*
+                                        * Generate a fake join tuple with nulls for the outer
+                                        * tuple, and return it if it passes the non-join quals.
+                                        */
+                                       mergestate->mj_MatchedInner = true;     /* do it only once */
+
+                                       ResetExprContext(econtext);
+
+                                       outerTupleSlot = mergestate->mj_NullOuterTupleSlot;
+                                       econtext->ecxt_outertuple = outerTupleSlot;
+                                       innerTupleSlot = mergestate->mj_InnerTupleSlot;
+                                       econtext->ecxt_innertuple = innerTupleSlot;
+
+                                       if (ExecQual(otherqual, econtext, false))
+                                       {
+                                               /* ----------------
+                                                *      qualification succeeded.  now form the desired
+                                                *      projection tuple and return the slot containing it.
+                                                * ----------------
+                                                */
+                                               TupleTableSlot *result;
+                                               ExprDoneCond isDone;
+
+                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+                                               result = ExecProject(mergestate->jstate.cs_ProjInfo,
+                                                                                        &isDone);
+
+                                               if (isDone != ExprEndResult)
+                                               {
+                                                       mergestate->jstate.cs_TupFromTlist =
+                                                               (isDone == ExprMultipleResult);
+                                                       return result;
+                                               }
+                                       }
+                               }
 
                                /* ----------------
-                                *      now skip this inner tuple
+                                *      now we get the next inner tuple, if any
                                 * ----------------
                                 */
                                innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+                               mergestate->mj_InnerTupleSlot = innerTupleSlot;
                                MJ_DEBUG_PROC_NODE(innerTupleSlot);
-                               econtext->ecxt_innertuple = innerTupleSlot;
+                               mergestate->mj_MatchedInner = false;
 
                                /* ----------------
-                                *      if the inner tuple is null then we know
-                                *      we have to restore the inner scan
-                                *      and advance to the next outer tuple
+                                *      if the inner tuple is null then we are done with the
+                                *      join, unless we have outer tuples we need to null-fill.
                                 * ----------------
                                 */
                                if (TupIsNull(innerTupleSlot))
                                {
-                                       if (isLeftJoin && !TupIsNull(outerTupleSlot))
+                                       MJ_printf("ExecMergeJoin: end of inner subplan\n");
+                                       outerTupleSlot = mergestate->mj_OuterTupleSlot;
+                                       if (doFillOuter && !TupIsNull(outerTupleSlot))
                                        {
-                                               mergestate->mj_JoinState = EXEC_MJ_FILLOUTER;
-                                               MJ_printf("ExecMergeJoin: try to complete outer fill\n");
+                                               /*
+                                                * Need to emit left-join tuples for remaining
+                                                * outer tuples.
+                                                */
+                                               mergestate->mj_JoinState = EXEC_MJ_ENDINNER;
                                                break;
                                        }
-
-                                       MJ_printf("ExecMergeJoin: **** weird case 2 ****\n");
+                                       /* Otherwise we're done. */
                                        return NULL;
                                }
 
                                /* ----------------
                                 *      otherwise test the new tuple against the skip qual.
-                                *      (we move to the EXEC_MJ_JOINMARK state)
                                 * ----------------
                                 */
+                               mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_TEST;
                                break;
 
                                /*
-                                * EXEC_MJ_FILLOUTER means we have an unmatched outer
-                                * tuple which must be null-expanded into the projection
-                                * tuple. get the next outer tuple and reset markers
-                                * (EXEC_MJ_JOINMARK).
+                                * EXEC_MJ_ENDOUTER means we have run out of outer tuples,
+                                * but are doing a right/full join and therefore must null-
+                                * fill any remaing unmatched inner tuples.
                                 */
-                       case EXEC_MJ_FILLOUTER:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_FILLOUTER\n");
-                               mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
+                       case EXEC_MJ_ENDOUTER:
+                               MJ_printf("ExecMergeJoin: EXEC_MJ_ENDOUTER\n");
+
+                               Assert(doFillInner);
+
+                               if (!mergestate->mj_MatchedInner)
+                               {
+                                       /*
+                                        * Generate a fake join tuple with nulls for the outer
+                                        * tuple, and return it if it passes the non-join quals.
+                                        */
+                                       mergestate->mj_MatchedInner = true;     /* do it only once */
+
+                                       ResetExprContext(econtext);
+
+                                       outerTupleSlot = mergestate->mj_NullOuterTupleSlot;
+                                       econtext->ecxt_outertuple = outerTupleSlot;
+                                       innerTupleSlot = mergestate->mj_InnerTupleSlot;
+                                       econtext->ecxt_innertuple = innerTupleSlot;
+
+                                       if (ExecQual(otherqual, econtext, false))
+                                       {
+                                               /* ----------------
+                                                *      qualification succeeded.  now form the desired
+                                                *      projection tuple and return the slot containing it.
+                                                * ----------------
+                                                */
+                                               TupleTableSlot *result;
+                                               ExprDoneCond isDone;
+
+                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+                                               result = ExecProject(mergestate->jstate.cs_ProjInfo,
+                                                                                        &isDone);
+
+                                               if (isDone != ExprEndResult)
+                                               {
+                                                       mergestate->jstate.cs_TupFromTlist =
+                                                               (isDone == ExprMultipleResult);
+                                                       return result;
+                                               }
+                                       }
+                               }
 
                                /* ----------------
-                                *      project the outer tuple into the result
+                                *      now we get the next inner tuple, if any
                                 * ----------------
                                 */
-                               MJ_printf("ExecMergeJoin: project outer tuple into the result (not yet implemented)\n");
+                               innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+                               mergestate->mj_InnerTupleSlot = innerTupleSlot;
+                               MJ_DEBUG_PROC_NODE(innerTupleSlot);
+                               mergestate->mj_MatchedInner = false;
+
+                               if (TupIsNull(innerTupleSlot))
+                               {
+                                       MJ_printf("ExecMergeJoin: end of inner subplan\n");
+                                       return NULL;
+                               }
+
+                               /* Else remain in ENDOUTER state and process next tuple. */
+                               break;
+
+                               /*
+                                * EXEC_MJ_ENDINNER means we have run out of inner tuples,
+                                * but are doing a left/full join and therefore must null-
+                                * fill any remaing unmatched outer tuples.
+                                */
+                       case EXEC_MJ_ENDINNER:
+                               MJ_printf("ExecMergeJoin: EXEC_MJ_ENDINNER\n");
+
+                               Assert(doFillOuter);
+
+                               if (!mergestate->mj_MatchedOuter)
+                               {
+                                       /*
+                                        * Generate a fake join tuple with nulls for the inner
+                                        * tuple, and return it if it passes the non-join quals.
+                                        */
+                                       mergestate->mj_MatchedOuter = true;     /* do it only once */
+
+                                       ResetExprContext(econtext);
+
+                                       outerTupleSlot = mergestate->mj_OuterTupleSlot;
+                                       econtext->ecxt_outertuple = outerTupleSlot;
+                                       innerTupleSlot = mergestate->mj_NullInnerTupleSlot;
+                                       econtext->ecxt_innertuple = innerTupleSlot;
+
+                                       if (ExecQual(otherqual, econtext, false))
+                                       {
+                                               /* ----------------
+                                                *      qualification succeeded.  now form the desired
+                                                *      projection tuple and return the slot containing it.
+                                                * ----------------
+                                                */
+                                               TupleTableSlot *result;
+                                               ExprDoneCond isDone;
+
+                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+                                               result = ExecProject(mergestate->jstate.cs_ProjInfo,
+                                                                                        &isDone);
+
+                                               if (isDone != ExprEndResult)
+                                               {
+                                                       mergestate->jstate.cs_TupFromTlist =
+                                                               (isDone == ExprMultipleResult);
+                                                       return result;
+                                               }
+                                       }
+                               }
 
                                /* ----------------
-                                *      now skip this outer tuple
+                                *      now we get the next outer tuple, if any
                                 * ----------------
                                 */
                                outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+                               mergestate->mj_OuterTupleSlot = outerTupleSlot;
                                MJ_DEBUG_PROC_NODE(outerTupleSlot);
-                               econtext->ecxt_outertuple = outerTupleSlot;
+                               mergestate->mj_MatchedOuter = false;
 
-                               /* ----------------
-                                *      if the outer tuple is null then we know
-                                *      we are done with the left half of the join
-                                * ----------------
-                                */
                                if (TupIsNull(outerTupleSlot))
                                {
-                                       if (isRightJoin && !TupIsNull(innerTupleSlot))
-                                       {
-                                               mergestate->mj_JoinState = EXEC_MJ_FILLINNER;
-                                               MJ_printf("ExecMergeJoin: try to complete inner fill\n");
-                                               break;
-                                       }
-
-                                       MJ_printf("ExecMergeJoin: **** outerTuple is nil ****\n");
+                                       MJ_printf("ExecMergeJoin: end of outer subplan\n");
                                        return NULL;
                                }
 
-                               /* ----------------
-                                *      otherwise test the new tuple against the skip qual.
-                                *      (we move to the EXEC_MJ_JOINMARK state)
-                                * ----------------
-                                */
+                               /* Else remain in ENDINNER state and process next tuple. */
                                break;
-#endif
 
                                /*
                                 * if we get here it means our code is fouled up and so we
                                 * just end the join prematurely.
                                 */
                        default:
-                               elog(NOTICE, "ExecMergeJoin: invalid join state. aborting");
+                               elog(NOTICE, "ExecMergeJoin: invalid join state %d, aborting",
+                                        mergestate->mj_JoinState);
                                return NULL;
                }
        }
@@ -1143,7 +1458,6 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
 {
        MergeJoinState *mergestate;
        List       *joinclauses;
-       TupleTableSlot *mjSlot;
 
        MJ1_printf("ExecInitMergeJoin: %s\n",
                           "initializing node");
@@ -1153,17 +1467,13 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
         *      get the range table and direction from it
         * ----------------
         */
-       node->join.state = estate;
+       node->join.plan.state = estate;
 
        /* ----------------
         *      create new merge state for node
         * ----------------
         */
        mergestate = makeNode(MergeJoinState);
-       mergestate->mj_OuterSkipQual = NIL;
-       mergestate->mj_InnerSkipQual = NIL;
-       mergestate->mj_JoinState = 0;
-       mergestate->mj_MarkedTupleSlot = NULL;
        node->mergestate = mergestate;
 
        /* ----------------
@@ -1174,22 +1484,67 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
         */
        ExecAssignExprContext(estate, &mergestate->jstate);
 
-#define MERGEJOIN_NSLOTS 2
+       /* ----------------
+        *      initialize subplans
+        * ----------------
+        */
+       ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
+       ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
+
+#define MERGEJOIN_NSLOTS 4
        /* ----------------
         *      tuple table initialization
-        *
-        *      XXX why aren't we getting a tuple table slot in the normal way?
         * ----------------
         */
        ExecInitResultTupleSlot(estate, &mergestate->jstate);
-       mjSlot = makeNode(TupleTableSlot);
-       mjSlot->val = NULL;
-       mjSlot->ttc_shouldFree = true;
-       mjSlot->ttc_descIsNew = true;
-       mjSlot->ttc_tupleDescriptor = NULL;
-       mjSlot->ttc_buffer = InvalidBuffer;
-       mjSlot->ttc_whichplan = -1;
-       mergestate->mj_MarkedTupleSlot = mjSlot;
+
+       mergestate->mj_MarkedTupleSlot = ExecInitExtraTupleSlot(estate);
+       ExecSetSlotDescriptor(mergestate->mj_MarkedTupleSlot,
+                                                 ExecGetTupType(innerPlan((Plan *) node)));
+
+       switch (node->join.jointype)
+       {
+               case JOIN_INNER:
+                       break;
+               case JOIN_LEFT:
+                       mergestate->mj_NullInnerTupleSlot =
+                               ExecInitNullTupleSlot(estate,
+                                                                         ExecGetTupType(innerPlan((Plan*) node)));
+                       break;
+               case JOIN_RIGHT:
+                       mergestate->mj_NullOuterTupleSlot =
+                               ExecInitNullTupleSlot(estate,
+                                                                         ExecGetTupType(outerPlan((Plan*) node)));
+                       /*
+                        * Can't handle right or full join with non-nil extra joinclauses.
+                        */
+                       if (node->join.joinqual != NIL)
+                               elog(ERROR, "RIGHT JOIN is only supported with mergejoinable join conditions");
+                       break;
+               case JOIN_FULL:
+                       mergestate->mj_NullOuterTupleSlot =
+                               ExecInitNullTupleSlot(estate,
+                                                                         ExecGetTupType(outerPlan((Plan*) node)));
+                       mergestate->mj_NullInnerTupleSlot =
+                               ExecInitNullTupleSlot(estate,
+                                                                         ExecGetTupType(innerPlan((Plan*) node)));
+                       /*
+                        * Can't handle right or full join with non-nil extra joinclauses.
+                        */
+                       if (node->join.joinqual != NIL)
+                               elog(ERROR, "FULL JOIN is only supported with mergejoinable join conditions");
+                       break;
+               default:
+                       elog(ERROR, "ExecInitMergeJoin: unsupported join type %d",
+                                (int) node->join.jointype);
+       }
+
+       /* ----------------
+        *      initialize tuple type and projection info
+        * ----------------
+        */
+       ExecAssignResultTypeFromTL((Plan *) node, &mergestate->jstate);
+       ExecAssignProjectionInfo((Plan *) node, &mergestate->jstate);
 
        /* ----------------
         *      form merge skip qualifications
@@ -1210,22 +1565,12 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
         * ----------------
         */
        mergestate->mj_JoinState = EXEC_MJ_INITIALIZE;
-
-       /* ----------------
-        *      initialize subplans
-        * ----------------
-        */
-       ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
-       ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
-
-       /* ----------------
-        *      initialize tuple type and projection info
-        * ----------------
-        */
-       ExecAssignResultTypeFromTL((Plan *) node, &mergestate->jstate);
-       ExecAssignProjectionInfo((Plan *) node, &mergestate->jstate);
-
        mergestate->jstate.cs_TupFromTlist = false;
+       mergestate->mj_MatchedOuter = false;
+       mergestate->mj_MatchedInner = false;
+       mergestate->mj_OuterTupleSlot = NULL;
+       mergestate->mj_InnerTupleSlot = NULL;
+
        /* ----------------
         *      initialization successful
         * ----------------
@@ -1285,15 +1630,11 @@ ExecEndMergeJoin(MergeJoin *node)
        ExecEndNode((Plan *) outerPlan((Plan *) node), (Plan *) node);
 
        /* ----------------
-        *      clean out the tuple table so that we don't try and
-        *      pfree the marked tuples..  see HACK ALERT at the top of
-        *      this file.
+        *      clean out the tuple table
         * ----------------
         */
        ExecClearTuple(mergestate->jstate.cs_ResultTupleSlot);
        ExecClearTuple(mergestate->mj_MarkedTupleSlot);
-       pfree(mergestate->mj_MarkedTupleSlot);
-       mergestate->mj_MarkedTupleSlot = NULL;
 
        MJ1_printf("ExecEndMergeJoin: %s\n",
                           "node processing ended");
@@ -1303,14 +1644,15 @@ void
 ExecReScanMergeJoin(MergeJoin *node, ExprContext *exprCtxt, Plan *parent)
 {
        MergeJoinState *mergestate = node->mergestate;
-       TupleTableSlot *mjSlot = mergestate->mj_MarkedTupleSlot;
 
-       ExecClearTuple(mjSlot);
-       mjSlot->ttc_tupleDescriptor = NULL;
-       mjSlot->ttc_descIsNew = true;
-       mjSlot->ttc_whichplan = -1;
+       ExecClearTuple(mergestate->mj_MarkedTupleSlot);
 
        mergestate->mj_JoinState = EXEC_MJ_INITIALIZE;
+       mergestate->jstate.cs_TupFromTlist = false;
+       mergestate->mj_MatchedOuter = false;
+       mergestate->mj_MatchedInner = false;
+       mergestate->mj_OuterTupleSlot = NULL;
+       mergestate->mj_InnerTupleSlot = NULL;
 
        /*
         * if chgParam of subnodes is not null then plans will be re-scanned
index 3685232c7e42b15e5b9777e81a573920d461ec5f..5abd4ffc3a18712ce7b46f5d603a04275292e86c 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.20 2000/08/24 03:29:03 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.21 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -62,10 +62,10 @@ ExecNestLoop(NestLoop *node)
        NestLoopState *nlstate;
        Plan       *innerPlan;
        Plan       *outerPlan;
-       bool            needNewOuterTuple;
        TupleTableSlot *outerTupleSlot;
        TupleTableSlot *innerTupleSlot;
-       List       *qual;
+       List       *joinqual;
+       List       *otherqual;
        ExprContext *econtext;
 
        /* ----------------
@@ -75,9 +75,10 @@ ExecNestLoop(NestLoop *node)
        ENL1_printf("getting info from node");
 
        nlstate = node->nlstate;
-       qual = node->join.qual;
-       outerPlan = outerPlan(&node->join);
-       innerPlan = innerPlan(&node->join);
+       joinqual = node->join.joinqual;
+       otherqual = node->join.plan.qual;
+       outerPlan = outerPlan((Plan *) node);
+       innerPlan = innerPlan((Plan *) node);
        econtext = nlstate->jstate.cs_ExprContext;
 
        /* ----------------
@@ -115,7 +116,7 @@ ExecNestLoop(NestLoop *node)
 
        /* ----------------
         *      Ok, everything is setup for the join so now loop until
-        *      we return a qualifying join tuple..
+        *      we return a qualifying join tuple.
         * ----------------
         */
        ENL1_printf("entering main loop");
@@ -123,44 +124,14 @@ ExecNestLoop(NestLoop *node)
        for (;;)
        {
                /* ----------------
-                *      The essential idea now is to get the next inner tuple
-                *      and join it with the current outer tuple.
+                *      If we don't have an outer tuple, get the next one and
+                *      reset the inner scan.
                 * ----------------
                 */
-               needNewOuterTuple = TupIsNull(outerTupleSlot);
-
-               /* ----------------
-                *      if we have an outerTuple, try to get the next inner tuple.
-                * ----------------
-                */
-               if (!needNewOuterTuple)
-               {
-                       ENL1_printf("getting new inner tuple");
-
-                       innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
-                       econtext->ecxt_innertuple = innerTupleSlot;
-
-                       if (TupIsNull(innerTupleSlot))
-                       {
-                               ENL1_printf("no inner tuple, need new outer tuple");
-                               needNewOuterTuple = true;
-                       }
-               }
-
-               /* ----------------
-                *      loop until we have a new outer tuple and a new
-                *      inner tuple.
-                * ----------------
-                */
-               while (needNewOuterTuple)
+               if (nlstate->nl_NeedNewOuter)
                {
-                       /* ----------------
-                        *      now try to get the next outer tuple
-                        * ----------------
-                        */
                        ENL1_printf("getting new outer tuple");
                        outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
-                       econtext->ecxt_outertuple = outerTupleSlot;
 
                        /* ----------------
                         *      if there are no more outer tuples, then the join
@@ -175,12 +146,14 @@ ExecNestLoop(NestLoop *node)
 
                        ENL1_printf("saving new outer tuple information");
                        nlstate->jstate.cs_OuterTupleSlot = outerTupleSlot;
+                       econtext->ecxt_outertuple = outerTupleSlot;
+                       nlstate->nl_NeedNewOuter = false;
+                       nlstate->nl_MatchedOuter = false;
 
                        /* ----------------
-                        *      now rescan the inner plan and get a new inner tuple
+                        *      now rescan the inner plan
                         * ----------------
                         */
-
                        ENL1_printf("rescanning inner plan");
 
                        /*
@@ -189,48 +162,101 @@ ExecNestLoop(NestLoop *node)
                         * expr context.
                         */
                        ExecReScan(innerPlan, econtext, (Plan *) node);
+               }
+
+               /* ----------------
+                *      we have an outerTuple, try to get the next inner tuple.
+                * ----------------
+                */
+               ENL1_printf("getting new inner tuple");
+
+               innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+               econtext->ecxt_innertuple = innerTupleSlot;
 
-                       ENL1_printf("getting new inner tuple");
+               if (TupIsNull(innerTupleSlot))
+               {
+                       ENL1_printf("no inner tuple, need new outer tuple");
 
-                       innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
-                       econtext->ecxt_innertuple = innerTupleSlot;
+                       nlstate->nl_NeedNewOuter = true;
 
-                       if (TupIsNull(innerTupleSlot))
-                               ENL1_printf("couldn't get inner tuple - need new outer tuple");
-                       else
+                       if (! nlstate->nl_MatchedOuter &&
+                               node->join.jointype == JOIN_LEFT)
                        {
-                               ENL1_printf("got inner and outer tuples");
-                               needNewOuterTuple = false;
+                               /*
+                                * We are doing an outer join and there were no join matches
+                                * for this outer tuple.  Generate a fake join tuple with
+                                * nulls for the inner tuple, and return it if it passes
+                                * the non-join quals.
+                                */
+                               econtext->ecxt_innertuple = nlstate->nl_NullInnerTupleSlot;
+
+                               ENL1_printf("testing qualification for outer-join tuple");
+
+                               if (ExecQual(otherqual, econtext, false))
+                               {
+                                       /* ----------------
+                                        *      qualification was satisfied so we project and
+                                        *      return the slot containing the result tuple
+                                        *      using ExecProject().
+                                        * ----------------
+                                        */
+                                       TupleTableSlot *result;
+                                       ExprDoneCond isDone;
+
+                                       ENL1_printf("qualification succeeded, projecting tuple");
+
+                                       result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
+
+                                       if (isDone != ExprEndResult)
+                                       {
+                                               nlstate->jstate.cs_TupFromTlist =
+                                                       (isDone == ExprMultipleResult);
+                                               return result;
+                                       }
+                               }
                        }
-               }                                               /* while (needNewOuterTuple) */
+                       /*
+                        * Otherwise just return to top of loop for a new outer tuple.
+                        */
+                       continue;
+               }
 
                /* ----------------
                 *       at this point we have a new pair of inner and outer
                 *       tuples so we test the inner and outer tuples to see
-                *       if they satisify the node's qualification.
+                *       if they satisfy the node's qualification.
+                *
+                *       Only the joinquals determine MatchedOuter status,
+                *       but all quals must pass to actually return the tuple.
                 * ----------------
                 */
                ENL1_printf("testing qualification");
 
-               if (ExecQual((List *) qual, econtext, false))
+               if (ExecQual(joinqual, econtext, false))
                {
-                       /* ----------------
-                        *      qualification was satisified so we project and
-                        *      return the slot containing the result tuple
-                        *      using ExecProject().
-                        * ----------------
-                        */
-                       TupleTableSlot *result;
-                       ExprDoneCond isDone;
+                       nlstate->nl_MatchedOuter = true;
 
-                       ENL1_printf("qualification succeeded, projecting tuple");
-
-                       result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
-
-                       if (isDone != ExprEndResult)
+                       if (otherqual == NIL || ExecQual(otherqual, econtext, false))
                        {
-                               nlstate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
-                               return result;
+                               /* ----------------
+                                *      qualification was satisfied so we project and
+                                *      return the slot containing the result tuple
+                                *      using ExecProject().
+                                * ----------------
+                                */
+                               TupleTableSlot *result;
+                               ExprDoneCond isDone;
+
+                               ENL1_printf("qualification succeeded, projecting tuple");
+
+                               result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
+
+                               if (isDone != ExprEndResult)
+                               {
+                                       nlstate->jstate.cs_TupFromTlist =
+                                               (isDone == ExprMultipleResult);
+                                       return result;
+                               }
                        }
                }
 
@@ -264,7 +290,7 @@ ExecInitNestLoop(NestLoop *node, EState *estate, Plan *parent)
         *      assign execution state to node
         * ----------------
         */
-       node->join.state = estate;
+       node->join.plan.state = estate;
 
        /* ----------------
         *        create new nest loop state
@@ -281,19 +307,33 @@ ExecInitNestLoop(NestLoop *node, EState *estate, Plan *parent)
         */
        ExecAssignExprContext(estate, &nlstate->jstate);
 
-#define NESTLOOP_NSLOTS 1
        /* ----------------
-        *      tuple table initialization
+        *        now initialize children
         * ----------------
         */
-       ExecInitResultTupleSlot(estate, &nlstate->jstate);
+       ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
+       ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
 
+#define NESTLOOP_NSLOTS 2
        /* ----------------
-        *        now initialize children
+        *      tuple table initialization
         * ----------------
         */
-       ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
-       ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
+       ExecInitResultTupleSlot(estate, &nlstate->jstate);
+
+       switch (node->join.jointype)
+       {
+               case JOIN_INNER:
+                       break;
+               case JOIN_LEFT:
+                       nlstate->nl_NullInnerTupleSlot =
+                               ExecInitNullTupleSlot(estate,
+                                                                         ExecGetTupType(innerPlan((Plan*) node)));
+                       break;
+               default:
+                       elog(ERROR, "ExecInitNestLoop: unsupported join type %d",
+                                (int) node->join.jointype);
+       }
 
        /* ----------------
         *      initialize tuple type and projection info
@@ -308,6 +348,8 @@ ExecInitNestLoop(NestLoop *node, EState *estate, Plan *parent)
         */
        nlstate->jstate.cs_OuterTupleSlot = NULL;
        nlstate->jstate.cs_TupFromTlist = false;
+       nlstate->nl_NeedNewOuter = true;
+       nlstate->nl_MatchedOuter = false;
 
        NL1_printf("ExecInitNestLoop: %s\n",
                           "node initialized");
@@ -394,4 +436,6 @@ ExecReScanNestLoop(NestLoop *node, ExprContext *exprCtxt, Plan *parent)
        /* let outerPlan to free its result tuple ... */
        nlstate->jstate.cs_OuterTupleSlot = NULL;
        nlstate->jstate.cs_TupFromTlist = false;
+       nlstate->nl_NeedNewOuter = true;
+       nlstate->nl_MatchedOuter = false;
 }
index 7270d3116d8823ce3cdaf88da94e200e50df0703..77f17ee0a60f8eeb81e5ffbf3d69bb67dfbeb33e 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.120 2000/08/11 23:45:31 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.121 2000/09/12 21:06:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -311,8 +311,12 @@ _copyTidScan(TidScan *from)
 static void
 CopyJoinFields(Join *from, Join *newnode)
 {
-       /* nothing extra */
-       return;
+       newnode->jointype = from->jointype;
+       Node_Copy(from, newnode, joinqual);
+       /* subPlan list must point to subplans in the new subtree, not the old */
+       if (from->plan.subPlan != NIL)
+               newnode->plan.subPlan = nconc(newnode->plan.subPlan,
+                                                                         pull_subplans((Node *) newnode->joinqual));
 }
 
 
@@ -381,8 +385,8 @@ _copyMergeJoin(MergeJoin *from)
        /*
         * We must add subplans in mergeclauses to the new plan's subPlan list
         */
-       if (from->join.subPlan != NIL)
-               newnode->join.subPlan = nconc(newnode->join.subPlan,
+       if (from->join.plan.subPlan != NIL)
+               newnode->join.plan.subPlan = nconc(newnode->join.plan.subPlan,
                                                  pull_subplans((Node *) newnode->mergeclauses));
 
        return newnode;
@@ -414,8 +418,8 @@ _copyHashJoin(HashJoin *from)
        /*
         * We must add subplans in hashclauses to the new plan's subPlan list
         */
-       if (from->join.subPlan != NIL)
-               newnode->join.subPlan = nconc(newnode->join.subPlan,
+       if (from->join.plan.subPlan != NIL)
+               newnode->join.plan.subPlan = nconc(newnode->join.plan.subPlan,
                                                   pull_subplans((Node *) newnode->hashclauses));
 
        return newnode;
@@ -510,21 +514,6 @@ _copyGroupClause(GroupClause *from)
        return newnode;
 }
 
-static JoinExpr *
-_copyJoinExpr(JoinExpr *from)
-{
-       JoinExpr *newnode = makeNode(JoinExpr);
-
-       newnode->jointype = from->jointype;
-       newnode->isNatural = from->isNatural;
-       Node_Copy(from, newnode, larg);
-       Node_Copy(from, newnode, rarg);
-       Node_Copy(from, newnode, alias);
-       Node_Copy(from, newnode, quals);
-
-       return newnode;
-}
-
 /* ----------------
  *             _copyUnique
  * ----------------
@@ -914,6 +903,34 @@ _copyRelabelType(RelabelType *from)
        return newnode;
 }
 
+static RangeTblRef *
+_copyRangeTblRef(RangeTblRef *from)
+{
+       RangeTblRef *newnode = makeNode(RangeTblRef);
+
+       newnode->rtindex = from->rtindex;
+
+       return newnode;
+}
+
+static JoinExpr *
+_copyJoinExpr(JoinExpr *from)
+{
+       JoinExpr *newnode = makeNode(JoinExpr);
+
+       newnode->jointype = from->jointype;
+       newnode->isNatural = from->isNatural;
+       Node_Copy(from, newnode, larg);
+       Node_Copy(from, newnode, rarg);
+       Node_Copy(from, newnode, using);
+       Node_Copy(from, newnode, quals);
+       Node_Copy(from, newnode, alias);
+       Node_Copy(from, newnode, colnames);
+       Node_Copy(from, newnode, colvars);
+
+       return newnode;
+}
+
 /* ----------------
  *             _copyCaseExpr
  * ----------------
@@ -1014,6 +1031,7 @@ _copyRelOptInfo(RelOptInfo *from)
 
        Node_Copy(from, newnode, baserestrictinfo);
        newnode->baserestrictcost = from->baserestrictcost;
+       newnode->outerjoinset = listCopy(from->outerjoinset);
        Node_Copy(from, newnode, joininfo);
        Node_Copy(from, newnode, innerjoin);
 
@@ -1137,6 +1155,7 @@ _copyIndexPath(IndexPath *from)
        Node_Copy(from, newnode, indexqual);
        newnode->indexscandir = from->indexscandir;
        newnode->joinrelids = listCopy(from->joinrelids);
+       newnode->alljoinquals = from->alljoinquals;
        newnode->rows = from->rows;
 
        return newnode;
@@ -1177,6 +1196,7 @@ _copyTidPath(TidPath *from)
 static void
 CopyJoinPathFields(JoinPath *from, JoinPath *newnode)
 {
+       newnode->jointype = from->jointype;
        Node_Copy(from, newnode, outerjoinpath);
        Node_Copy(from, newnode, innerjoinpath);
        Node_Copy(from, newnode, joinrestrictinfo);
@@ -1286,6 +1306,7 @@ _copyRestrictInfo(RestrictInfo *from)
         * ----------------
         */
        Node_Copy(from, newnode, clause);
+       newnode->isjoinqual = from->isjoinqual;
        Node_Copy(from, newnode, subclauseindices);
        newnode->mergejoinoperator = from->mergejoinoperator;
        newnode->left_sortop = from->left_sortop;
@@ -1370,12 +1391,11 @@ _copyRangeTblEntry(RangeTblEntry *from)
 
        if (from->relname)
                newnode->relname = pstrdup(from->relname);
-       Node_Copy(from, newnode, ref);
-       Node_Copy(from, newnode, eref);
        newnode->relid = from->relid;
+       Node_Copy(from, newnode, alias);
+       Node_Copy(from, newnode, eref);
        newnode->inh = from->inh;
        newnode->inFromCl = from->inFromCl;
-       newnode->inJoinSet = from->inJoinSet;
        newnode->skipAcl = from->skipAcl;
 
        return newnode;
@@ -1526,18 +1546,6 @@ _copyTypeName(TypeName *from)
        return newnode;
 }
 
-static RelExpr *
-_copyRelExpr(RelExpr *from)
-{
-       RelExpr   *newnode = makeNode(RelExpr);
-
-       if (from->relname)
-               newnode->relname = pstrdup(from->relname);
-       newnode->inh = from->inh;
-
-       return newnode;
-}
-
 static SortGroupBy *
 _copySortGroupBy(SortGroupBy *from)
 {
@@ -1555,7 +1563,20 @@ _copyRangeVar(RangeVar *from)
 {
        RangeVar   *newnode = makeNode(RangeVar);
 
-       Node_Copy(from, newnode, relExpr);
+       if (from->relname)
+               newnode->relname = pstrdup(from->relname);
+       newnode->inh = from->inh;
+       Node_Copy(from, newnode, name);
+
+       return newnode;
+}
+
+static RangeSubselect *
+_copyRangeSubselect(RangeSubselect *from)
+{
+       RangeSubselect   *newnode = makeNode(RangeSubselect);
+
+       Node_Copy(from, newnode, subquery);
        Node_Copy(from, newnode, name);
 
        return newnode;
@@ -1650,6 +1671,8 @@ _copyQuery(Query *from)
        newnode->hasSubLinks = from->hasSubLinks;
 
        Node_Copy(from, newnode, rtable);
+       Node_Copy(from, newnode, jointree);
+
        Node_Copy(from, newnode, targetList);
        Node_Copy(from, newnode, qual);
        Node_Copy(from, newnode, rowMark);
@@ -2548,6 +2571,12 @@ copyObject(void *from)
                case T_RelabelType:
                        retval = _copyRelabelType(from);
                        break;
+               case T_RangeTblRef:
+                       retval = _copyRangeTblRef(from);
+                       break;
+               case T_JoinExpr:
+                       retval = _copyJoinExpr(from);
+                       break;
 
                        /*
                         * RELATION NODES
@@ -2809,15 +2838,15 @@ copyObject(void *from)
                case T_TypeCast:
                        retval = _copyTypeCast(from);
                        break;
-               case T_RelExpr:
-                       retval = _copyRelExpr(from);
-                       break;
                case T_SortGroupBy:
                        retval = _copySortGroupBy(from);
                        break;
                case T_RangeVar:
                        retval = _copyRangeVar(from);
                        break;
+               case T_RangeSubselect:
+                       retval = _copyRangeSubselect(from);
+                       break;
                case T_TypeName:
                        retval = _copyTypeName(from);
                        break;
@@ -2845,9 +2874,6 @@ copyObject(void *from)
                case T_GroupClause:
                        retval = _copyGroupClause(from);
                        break;
-               case T_JoinExpr:
-                       retval = _copyJoinExpr(from);
-                       break;
                case T_CaseExpr:
                        retval = _copyCaseExpr(from);
                        break;
index b059e5cd5f0b92fed74c6ac3efc4bc434b51124f..51a7a03fc1b8deabe62ef3bb1eaa3c82ac9abc12 100644 (file)
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.72 2000/08/11 23:45:31 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.73 2000/09/12 21:06:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -256,6 +256,26 @@ _equalSubLink(SubLink *a, SubLink *b)
        return true;
 }
 
+static bool
+_equalArrayRef(ArrayRef *a, ArrayRef *b)
+{
+       if (a->refelemtype != b->refelemtype)
+               return false;
+       if (a->refattrlength != b->refattrlength)
+               return false;
+       if (a->refelemlength != b->refelemlength)
+               return false;
+       if (a->refelembyval != b->refelembyval)
+               return false;
+       if (!equal(a->refupperindexpr, b->refupperindexpr))
+               return false;
+       if (!equal(a->reflowerindexpr, b->reflowerindexpr))
+               return false;
+       if (!equal(a->refexpr, b->refexpr))
+               return false;
+       return equal(a->refassgnexpr, b->refassgnexpr);
+}
+
 static bool
 _equalFieldSelect(FieldSelect *a, FieldSelect *b)
 {
@@ -283,23 +303,37 @@ _equalRelabelType(RelabelType *a, RelabelType *b)
 }
 
 static bool
-_equalArrayRef(ArrayRef *a, ArrayRef *b)
+_equalRangeTblRef(RangeTblRef *a, RangeTblRef *b)
 {
-       if (a->refelemtype != b->refelemtype)
+       if (a->rtindex != b->rtindex)
                return false;
-       if (a->refattrlength != b->refattrlength)
+
+       return true;
+}
+
+static bool
+_equalJoinExpr(JoinExpr *a, JoinExpr *b)
+{
+       if (a->jointype != b->jointype)
                return false;
-       if (a->refelemlength != b->refelemlength)
+       if (a->isNatural != b->isNatural)
                return false;
-       if (a->refelembyval != b->refelembyval)
+       if (!equal(a->larg, b->larg))
                return false;
-       if (!equal(a->refupperindexpr, b->refupperindexpr))
+       if (!equal(a->rarg, b->rarg))
                return false;
-       if (!equal(a->reflowerindexpr, b->reflowerindexpr))
+       if (!equal(a->using, b->using))
                return false;
-       if (!equal(a->refexpr, b->refexpr))
+       if (!equal(a->quals, b->quals))
                return false;
-       return equal(a->refassgnexpr, b->refassgnexpr);
+       if (!equal(a->alias, b->alias))
+               return false;
+       if (!equal(a->colnames, b->colnames))
+               return false;
+       if (!equal(a->colvars, b->colvars))
+               return false;
+
+       return true;
 }
 
 /*
@@ -370,6 +404,8 @@ _equalIndexPath(IndexPath *a, IndexPath *b)
                return false;
        if (!equali(a->joinrelids, b->joinrelids))
                return false;
+       if (a->alljoinquals != b->alljoinquals)
+               return false;
 
        /*
         * Skip 'rows' because of possibility of floating-point roundoff
@@ -395,6 +431,8 @@ _equalJoinPath(JoinPath *a, JoinPath *b)
 {
        if (!_equalPath((Path *) a, (Path *) b))
                return false;
+       if (a->jointype != b->jointype)
+               return false;
        if (!equal(a->outerjoinpath, b->outerjoinpath))
                return false;
        if (!equal(a->innerjoinpath, b->innerjoinpath))
@@ -457,6 +495,8 @@ _equalRestrictInfo(RestrictInfo *a, RestrictInfo *b)
 {
        if (!equal(a->clause, b->clause))
                return false;
+       if (a->isjoinqual != b->isjoinqual)
+               return false;
        if (!equal(a->subclauseindices, b->subclauseindices))
                return false;
        if (a->mergejoinoperator != b->mergejoinoperator)
@@ -557,6 +597,8 @@ _equalQuery(Query *a, Query *b)
                return false;
        if (!equal(a->rtable, b->rtable))
                return false;
+       if (!equal(a->jointree, b->jointree))
+               return false;
        if (!equal(a->targetList, b->targetList))
                return false;
        if (!equal(a->qual, b->qual))
@@ -1476,31 +1518,33 @@ _equalTypeCast(TypeCast *a, TypeCast *b)
 }
 
 static bool
-_equalRelExpr(RelExpr *a, RelExpr *b)
+_equalSortGroupBy(SortGroupBy *a, SortGroupBy *b)
 {
-       if (!equalstr(a->relname, b->relname))
+       if (!equalstr(a->useOp, b->useOp))
                return false;
-       if (a->inh != b->inh)
+       if (!equal(a->node, b->node))
                return false;
 
        return true;
 }
 
 static bool
-_equalSortGroupBy(SortGroupBy *a, SortGroupBy *b)
+_equalRangeVar(RangeVar *a, RangeVar *b)
 {
-       if (!equalstr(a->useOp, b->useOp))
+       if (!equalstr(a->relname, b->relname))
                return false;
-       if (!equal(a->node, b->node))
+       if (a->inh != b->inh)
+               return false;
+       if (!equal(a->name, b->name))
                return false;
 
        return true;
 }
 
 static bool
-_equalRangeVar(RangeVar *a, RangeVar *b)
+_equalRangeSubselect(RangeSubselect *a, RangeSubselect *b)
 {
-       if (!equal(a->relExpr, b->relExpr))
+       if (!equal(a->subquery, b->subquery))
                return false;
        if (!equal(a->name, b->name))
                return false;
@@ -1605,17 +1649,16 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
 {
        if (!equalstr(a->relname, b->relname))
                return false;
-       if (!equal(a->ref, b->ref))
-               return false;
-       /* XXX what about eref? */
        if (a->relid != b->relid)
                return false;
+       if (!equal(a->alias, b->alias))
+               return false;
+       if (!equal(a->eref, b->eref))
+               return false;
        if (a->inh != b->inh)
                return false;
        if (a->inFromCl != b->inFromCl)
                return false;
-       if (a->inJoinSet != b->inJoinSet)
-               return false;
        if (a->skipAcl != b->skipAcl)
                return false;
 
@@ -1644,25 +1687,6 @@ _equalRowMark(RowMark *a, RowMark *b)
        return true;
 }
 
-static bool
-_equalJoinExpr(JoinExpr *a, JoinExpr *b)
-{
-       if (a->jointype != b->jointype)
-               return false;
-       if (a->isNatural != b->isNatural)
-               return false;
-       if (!equal(a->larg, b->larg))
-               return false;
-       if (!equal(a->rarg, b->rarg))
-               return false;
-       if (!equal(a->alias, b->alias))
-               return false;
-       if (!equal(a->quals, b->quals))
-               return false;
-
-       return true;
-}
-
 static bool
 _equalFkConstraint(FkConstraint *a, FkConstraint *b)
 {
@@ -1808,6 +1832,12 @@ equal(void *a, void *b)
                case T_RelabelType:
                        retval = _equalRelabelType(a, b);
                        break;
+               case T_RangeTblRef:
+                       retval = _equalRangeTblRef(a, b);
+                       break;
+               case T_JoinExpr:
+                       retval = _equalJoinExpr(a, b);
+                       break;
 
                case T_RelOptInfo:
                        retval = _equalRelOptInfo(a, b);
@@ -2067,15 +2097,15 @@ equal(void *a, void *b)
                case T_TypeCast:
                        retval = _equalTypeCast(a, b);
                        break;
-               case T_RelExpr:
-                       retval = _equalRelExpr(a, b);
-                       break;
                case T_SortGroupBy:
                        retval = _equalSortGroupBy(a, b);
                        break;
                case T_RangeVar:
                        retval = _equalRangeVar(a, b);
                        break;
+               case T_RangeSubselect:
+                       retval = _equalRangeSubselect(a, b);
+                       break;
                case T_TypeName:
                        retval = _equalTypeName(a, b);
                        break;
@@ -2104,9 +2134,6 @@ equal(void *a, void *b)
                        /* GroupClause is equivalent to SortClause */
                        retval = _equalSortClause(a, b);
                        break;
-               case T_JoinExpr:
-                       retval = _equalJoinExpr(a, b);
-                       break;
                case T_CaseExpr:
                        retval = _equalCaseExpr(a, b);
                        break;
index 45f42dc502448d315f1a63ffe10398384f526b3d..e94b357d24b1db8ed740068353a496736f3c758c 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.32 2000/06/09 01:44:12 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.33 2000/09/12 21:06:49 tgl Exp $
  *
  * NOTES
  *       XXX a few of the following functions are duplicated to handle
@@ -351,6 +351,25 @@ member(void *l1, List *l2)
        return false;
 }
 
+/*
+ * like member(), but use when pointer-equality comparison is sufficient
+ */
+bool
+ptrMember(void *l1, List *l2)
+{
+       List       *i;
+
+       foreach(i, l2)
+       {
+               if (l1 == ((void *) lfirst(i)))
+                       return true;
+       }
+       return false;
+}
+
+/*
+ * membership test for integer lists
+ */
 bool
 intMember(int l1, List *l2)
 {
index 14f2ab106c73e1e4d6a0fa2d87f508ac91075530..8b24b82122fdbd8763c80d30d932098549ab2712 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.125 2000/08/08 15:41:26 tgl Exp $
+ *     $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.126 2000/09/12 21:06:49 tgl Exp $
  *
  * NOTES
  *       Every (plan) node in POSTGRES has an associated "out" routine which
@@ -268,6 +268,9 @@ _outQuery(StringInfo str, Query *node)
        appendStringInfo(str, " :rtable ");
        _outNode(str, node->rtable);
 
+       appendStringInfo(str, " :jointree ");
+       _outNode(str, node->jointree);
+
        appendStringInfo(str, " :targetlist ");
        _outNode(str, node->targetList);
 
@@ -389,7 +392,6 @@ _outAppend(StringInfo str, Append *node)
                                         " :inheritrelid %u :inheritrtable ",
                                         node->inheritrelid);
        _outNode(str, node->inheritrtable);
-
 }
 
 /*
@@ -400,7 +402,9 @@ _outJoin(StringInfo str, Join *node)
 {
        appendStringInfo(str, " JOIN ");
        _outPlanInfo(str, (Plan *) node);
-
+       appendStringInfo(str, " :jointype %d :joinqual ",
+                                        (int) node->jointype);
+       _outNode(str, node->joinqual);
 }
 
 /*
@@ -411,6 +415,9 @@ _outNestLoop(StringInfo str, NestLoop *node)
 {
        appendStringInfo(str, " NESTLOOP ");
        _outPlanInfo(str, (Plan *) node);
+       appendStringInfo(str, " :jointype %d :joinqual ",
+                                        (int) node->join.jointype);
+       _outNode(str, node->join.joinqual);
 }
 
 /*
@@ -421,6 +428,9 @@ _outMergeJoin(StringInfo str, MergeJoin *node)
 {
        appendStringInfo(str, " MERGEJOIN ");
        _outPlanInfo(str, (Plan *) node);
+       appendStringInfo(str, " :jointype %d :joinqual ",
+                                        (int) node->join.jointype);
+       _outNode(str, node->join.joinqual);
 
        appendStringInfo(str, " :mergeclauses ");
        _outNode(str, node->mergeclauses);
@@ -434,17 +444,14 @@ _outHashJoin(StringInfo str, HashJoin *node)
 {
        appendStringInfo(str, " HASHJOIN ");
        _outPlanInfo(str, (Plan *) node);
+       appendStringInfo(str, " :jointype %d :joinqual ",
+                                        (int) node->join.jointype);
+       _outNode(str, node->join.joinqual);
 
        appendStringInfo(str, " :hashclauses ");
        _outNode(str, node->hashclauses);
-
-       appendStringInfo(str,
-                                        " :hashjoinop %u ",
+       appendStringInfo(str, " :hashjoinop %u ",
                                         node->hashjoinop);
-
-       appendStringInfo(str,
-                                        " :hashdone %d ",
-                                        node->hashdone);
 }
 
 static void
@@ -757,32 +764,6 @@ _outSubLink(StringInfo str, SubLink *node)
        _outNode(str, node->subselect);
 }
 
-/*
- *     FieldSelect
- */
-static void
-_outFieldSelect(StringInfo str, FieldSelect *node)
-{
-       appendStringInfo(str, " FIELDSELECT :arg ");
-       _outNode(str, node->arg);
-
-       appendStringInfo(str, " :fieldnum %d :resulttype %u :resulttypmod %d ",
-                                        node->fieldnum, node->resulttype, node->resulttypmod);
-}
-
-/*
- *     RelabelType
- */
-static void
-_outRelabelType(StringInfo str, RelabelType *node)
-{
-       appendStringInfo(str, " RELABELTYPE :arg ");
-       _outNode(str, node->arg);
-
-       appendStringInfo(str, " :resulttype %u :resulttypmod %d ",
-                                        node->resulttype, node->resulttypmod);
-}
-
 /*
  *     ArrayRef is a subclass of Expr
  */
@@ -846,6 +827,66 @@ _outParam(StringInfo str, Param *node)
        appendStringInfo(str, " :paramtype %u ", node->paramtype);
 }
 
+/*
+ *     FieldSelect
+ */
+static void
+_outFieldSelect(StringInfo str, FieldSelect *node)
+{
+       appendStringInfo(str, " FIELDSELECT :arg ");
+       _outNode(str, node->arg);
+
+       appendStringInfo(str, " :fieldnum %d :resulttype %u :resulttypmod %d ",
+                                        node->fieldnum, node->resulttype, node->resulttypmod);
+}
+
+/*
+ *     RelabelType
+ */
+static void
+_outRelabelType(StringInfo str, RelabelType *node)
+{
+       appendStringInfo(str, " RELABELTYPE :arg ");
+       _outNode(str, node->arg);
+
+       appendStringInfo(str, " :resulttype %u :resulttypmod %d ",
+                                        node->resulttype, node->resulttypmod);
+}
+
+/*
+ *     RangeTblRef
+ */
+static void
+_outRangeTblRef(StringInfo str, RangeTblRef *node)
+{
+       appendStringInfo(str, " RANGETBLREF %d ",
+                                        node->rtindex);
+}
+
+/*
+ *     JoinExpr
+ */
+static void
+_outJoinExpr(StringInfo str, JoinExpr *node)
+{
+       appendStringInfo(str, " JOINEXPR :jointype %d :isNatural %s :larg ",
+                                        (int) node->jointype,
+                                        node->isNatural ? "true" : "false");
+       _outNode(str, node->larg);
+       appendStringInfo(str, " :rarg ");
+       _outNode(str, node->rarg);
+       appendStringInfo(str, " :using ");
+       _outNode(str, node->using);
+       appendStringInfo(str, " :quals ");
+       _outNode(str, node->quals);
+       appendStringInfo(str, " :alias ");
+       _outNode(str, node->alias);
+       appendStringInfo(str, " :colnames ");
+       _outNode(str, node->colnames);
+       appendStringInfo(str, " :colvars ");
+       _outNode(str, node->colvars);
+}
+
 /*
  *     Stuff from execnodes.h
  */
@@ -897,6 +938,11 @@ _outRelOptInfo(StringInfo str, RelOptInfo *node)
                                         node->pruneable ? "true" : "false");
        _outNode(str, node->baserestrictinfo);
 
+       appendStringInfo(str,
+                                        " :baserestrictcost %.2f :outerjoinset ",
+                                        node->baserestrictcost);
+       _outIntList(str, node->outerjoinset);
+
        appendStringInfo(str, " :joininfo ");
        _outNode(str, node->joininfo);
 
@@ -931,14 +977,14 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
 {
        appendStringInfo(str, " RTE :relname ");
        _outToken(str, node->relname);
-       appendStringInfo(str, " :ref ");
-       _outNode(str, node->ref);
-       appendStringInfo(str,
-                        " :relid %u :inh %s :inFromCl %s :inJoinSet %s :skipAcl %s",
-                                        node->relid,
+       appendStringInfo(str, " :relid %u :alias ",
+                                        node->relid);
+       _outNode(str, node->alias);
+       appendStringInfo(str, " :eref ");
+       _outNode(str, node->eref);
+       appendStringInfo(str, " :inh %s :inFromCl %s :skipAcl %s",
                                         node->inh ? "true" : "false",
                                         node->inFromCl ? "true" : "false",
-                                        node->inJoinSet ? "true" : "false",
                                         node->skipAcl ? "true" : "false");
 }
 
@@ -985,7 +1031,8 @@ _outIndexPath(StringInfo str, IndexPath *node)
                                         (int) node->indexscandir);
        _outIntList(str, node->joinrelids);
 
-       appendStringInfo(str, " :rows %.2f ",
+       appendStringInfo(str, " :alljoinquals %s :rows %.2f ",
+                                        node->alljoinquals ? "true" : "false",
                                         node->rows);
 }
 
@@ -1021,7 +1068,8 @@ _outNestPath(StringInfo str, NestPath *node)
                                         node->path.startup_cost,
                                         node->path.total_cost);
        _outNode(str, node->path.pathkeys);
-       appendStringInfo(str, " :outerjoinpath ");
+       appendStringInfo(str, " :jointype %d :outerjoinpath ",
+                                        (int) node->jointype);
        _outNode(str, node->outerjoinpath);
        appendStringInfo(str, " :innerjoinpath ");
        _outNode(str, node->innerjoinpath);
@@ -1041,7 +1089,8 @@ _outMergePath(StringInfo str, MergePath *node)
                                         node->jpath.path.startup_cost,
                                         node->jpath.path.total_cost);
        _outNode(str, node->jpath.path.pathkeys);
-       appendStringInfo(str, " :outerjoinpath ");
+       appendStringInfo(str, " :jointype %d :outerjoinpath ",
+                                        (int) node->jpath.jointype);
        _outNode(str, node->jpath.outerjoinpath);
        appendStringInfo(str, " :innerjoinpath ");
        _outNode(str, node->jpath.innerjoinpath);
@@ -1070,7 +1119,8 @@ _outHashPath(StringInfo str, HashPath *node)
                                         node->jpath.path.startup_cost,
                                         node->jpath.path.total_cost);
        _outNode(str, node->jpath.path.pathkeys);
-       appendStringInfo(str, " :outerjoinpath ");
+       appendStringInfo(str, " :jointype %d :outerjoinpath ",
+                                        (int) node->jpath.jointype);
        _outNode(str, node->jpath.outerjoinpath);
        appendStringInfo(str, " :innerjoinpath ");
        _outNode(str, node->jpath.innerjoinpath);
@@ -1101,7 +1151,8 @@ _outRestrictInfo(StringInfo str, RestrictInfo *node)
        appendStringInfo(str, " RESTRICTINFO :clause ");
        _outNode(str, node->clause);
 
-       appendStringInfo(str, " :subclauseindices ");
+       appendStringInfo(str, " :isjoinqual %s :subclauseindices ",
+                                        node->isjoinqual ? "true" : "false");
        _outNode(str, node->subclauseindices);
 
        appendStringInfo(str, " :mergejoinoperator %u ", node->mergejoinoperator);
@@ -1483,12 +1534,6 @@ _outNode(StringInfo str, void *obj)
                        case T_SubLink:
                                _outSubLink(str, obj);
                                break;
-                       case T_FieldSelect:
-                               _outFieldSelect(str, obj);
-                               break;
-                       case T_RelabelType:
-                               _outRelabelType(str, obj);
-                               break;
                        case T_ArrayRef:
                                _outArrayRef(str, obj);
                                break;
@@ -1501,6 +1546,18 @@ _outNode(StringInfo str, void *obj)
                        case T_Param:
                                _outParam(str, obj);
                                break;
+                       case T_FieldSelect:
+                               _outFieldSelect(str, obj);
+                               break;
+                       case T_RelabelType:
+                               _outRelabelType(str, obj);
+                               break;
+                       case T_RangeTblRef:
+                               _outRangeTblRef(str, obj);
+                               break;
+                       case T_JoinExpr:
+                               _outJoinExpr(str, obj);
+                               break;
                        case T_EState:
                                _outEState(str, obj);
                                break;
index 104735cf6f63813c72ae865163ca44127ca4c8dc..7bf78e134bc3968e35c174a73498d4fc69374acd 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.39 2000/06/18 22:44:05 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.40 2000/09/12 21:06:49 tgl Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -133,7 +133,7 @@ print_rt(List *rtable)
                RangeTblEntry *rte = lfirst(l);
 
                printf("%d\t%s(%s)\t%u\t%d\t%s\n",
-                          i, rte->relname, rte->ref->relname, rte->relid,
+                          i, rte->relname, rte->eref->relname, rte->relid,
                           rte->inFromCl,
                           (rte->inh ? "inh" : ""));
                i++;
@@ -157,7 +157,6 @@ print_expr(Node *expr, List *rtable)
        if (IsA(expr, Var))
        {
                Var                *var = (Var *) expr;
-               RangeTblEntry *rt;
                char       *relname,
                                   *attname;
 
@@ -173,10 +172,10 @@ print_expr(Node *expr, List *rtable)
                                break;
                        default:
                                {
+                                       RangeTblEntry *rt;
+
                                        rt = rt_fetch(var->varno, rtable);
-                                       relname = rt->relname;
-                                       if (rt->ref && rt->ref->relname)
-                                               relname = rt->ref->relname;             /* table renamed */
+                                       relname = rt->eref->relname;
                                        attname = get_attname(rt->relid, var->varattno);
                                }
                                break;
index 17e0396e5fee4f4d7e67a611b22f1575116835bf..00a6407db8b2c62012b74d9755310a5af61b24a5 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.95 2000/08/08 15:41:27 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.96 2000/09/12 21:06:49 tgl Exp $
  *
  * NOTES
  *       Most of the read functions for plan nodes are tested. (In fact, they
@@ -119,6 +119,9 @@ _readQuery()
        token = lsptok(NULL, &length);          /* skip :rtable */
        local_node->rtable = nodeRead(true);
 
+       token = lsptok(NULL, &length);          /* skip :jointree */
+       local_node->jointree = nodeRead(true);
+
        token = lsptok(NULL, &length);          /* skip :targetlist */
        local_node->targetList = nodeRead(true);
 
@@ -335,14 +338,22 @@ _readAppend()
 
 /* ----------------
  *             _getJoin
- *
- * In case Join is not the same structure as Plan someday.
  * ----------------
  */
 static void
 _getJoin(Join *node)
 {
+       char       *token;
+       int                     length;
+
        _getPlan((Plan *) node);
+
+       token = lsptok(NULL, &length);          /* skip the :jointype */
+       token = lsptok(NULL, &length);          /* get the jointype */
+       node->jointype = (JoinType) atoi(token);
+
+       token = lsptok(NULL, &length);          /* skip the :joinqual */
+       node->joinqual = nodeRead(true);        /* get the joinqual */
 }
 
 
@@ -399,6 +410,7 @@ _readMergeJoin()
        local_node = makeNode(MergeJoin);
 
        _getJoin((Join *) local_node);
+
        token = lsptok(NULL, &length);          /* eat :mergeclauses */
        local_node->mergeclauses = nodeRead(true);      /* now read it */
 
@@ -429,19 +441,13 @@ _readHashJoin()
        token = lsptok(NULL, &length);          /* get hashjoinop */
        local_node->hashjoinop = strtoul(token, NULL, 10);
 
-       token = lsptok(NULL, &length);          /* eat :hashdone */
-       token = lsptok(NULL, &length);          /* eat hashdone */
-       local_node->hashdone = false;
-
        return local_node;
 }
 
 /* ----------------
  *             _getScan
  *
- *     Scan is a subclass of Node
- *     (Actually, according to the plannodes.h include file, it is a
- *     subclass of Plan.  This is why _getPlan is used here.)
+ *     Scan is a subclass of Plan.
  *
  *     Scan gets its own get function since stuff inherits it.
  * ----------------
@@ -462,7 +468,7 @@ _getScan(Scan *node)
 /* ----------------
  *             _readScan
  *
- * Scan is a subclass of Plan (Not Node, see above).
+ * Scan is a subclass of Plan.
  * ----------------
  */
 static Scan *
@@ -1154,6 +1160,74 @@ _readRelabelType()
        return local_node;
 }
 
+/* ----------------
+ *             _readRangeTblRef
+ *
+ *     RangeTblRef is a subclass of Node
+ * ----------------
+ */
+static RangeTblRef *
+_readRangeTblRef()
+{
+       RangeTblRef *local_node;
+       char       *token;
+       int                     length;
+
+       local_node = makeNode(RangeTblRef);
+
+       token = lsptok(NULL, &length);          /* get rtindex */
+       local_node->rtindex = atoi(token);
+
+       return local_node;
+}
+
+/* ----------------
+ *             _readJoinExpr
+ *
+ *     JoinExpr is a subclass of Node
+ * ----------------
+ */
+static JoinExpr *
+_readJoinExpr()
+{
+       JoinExpr   *local_node;
+       char       *token;
+       int                     length;
+
+       local_node = makeNode(JoinExpr);
+
+       token = lsptok(NULL, &length);          /* eat :jointype */
+       token = lsptok(NULL, &length);          /* get jointype */
+       local_node->jointype = (JoinType) atoi(token);
+
+       token = lsptok(NULL, &length);          /* eat :isNatural */
+       token = lsptok(NULL, &length);          /* get :isNatural */
+       local_node->isNatural = (token[0] == 't') ? true : false;
+
+       token = lsptok(NULL, &length);          /* eat :larg */
+       local_node->larg = nodeRead(true);      /* now read it */
+
+       token = lsptok(NULL, &length);          /* eat :rarg */
+       local_node->rarg = nodeRead(true);      /* now read it */
+
+       token = lsptok(NULL, &length);          /* eat :using */
+       local_node->using = nodeRead(true);     /* now read it */
+
+       token = lsptok(NULL, &length);          /* eat :quals */
+       local_node->quals = nodeRead(true);     /* now read it */
+
+       token = lsptok(NULL, &length);          /* eat :alias */
+       local_node->alias = nodeRead(true);     /* now read it */
+
+       token = lsptok(NULL, &length);          /* eat :colnames */
+       local_node->colnames = nodeRead(true); /* now read it */
+
+       token = lsptok(NULL, &length);          /* eat :colvars */
+       local_node->colvars = nodeRead(true); /* now read it */
+
+       return local_node;
+}
+
 /*
  *     Stuff from execnodes.h
  */
@@ -1252,7 +1326,14 @@ _readRelOptInfo()
        local_node->pruneable = (token[0] == 't') ? true : false;
 
        token = lsptok(NULL, &length);          /* get :baserestrictinfo */
-       local_node->baserestrictinfo = nodeRead(true);          /* now read it */
+       local_node->baserestrictinfo = nodeRead(true); /* now read it */
+
+       token = lsptok(NULL, &length);          /* get :baserestrictcost */
+       token = lsptok(NULL, &length);          /* now read it */
+       local_node->baserestrictcost = (Cost) atof(token);
+
+       token = lsptok(NULL, &length);          /* get :outerjoinset */
+       local_node->outerjoinset = toIntList(nodeRead(true)); /* now read it */
 
        token = lsptok(NULL, &length);          /* get :joininfo */
        local_node->joininfo = nodeRead(true);          /* now read it */
@@ -1324,13 +1405,16 @@ _readRangeTblEntry()
        else
                local_node->relname = debackslash(token, length);
 
-       token = lsptok(NULL, &length);          /* eat :ref */
-       local_node->ref = nodeRead(true);       /* now read it */
-
        token = lsptok(NULL, &length);          /* eat :relid */
        token = lsptok(NULL, &length);          /* get :relid */
        local_node->relid = strtoul(token, NULL, 10);
 
+       token = lsptok(NULL, &length);          /* eat :alias */
+       local_node->alias = nodeRead(true);     /* now read it */
+
+       token = lsptok(NULL, &length);          /* eat :eref */
+       local_node->eref = nodeRead(true);      /* now read it */
+
        token = lsptok(NULL, &length);          /* eat :inh */
        token = lsptok(NULL, &length);          /* get :inh */
        local_node->inh = (token[0] == 't') ? true : false;
@@ -1339,10 +1423,6 @@ _readRangeTblEntry()
        token = lsptok(NULL, &length);          /* get :inFromCl */
        local_node->inFromCl = (token[0] == 't') ? true : false;
 
-       token = lsptok(NULL, &length);          /* eat :inJoinSet */
-       token = lsptok(NULL, &length);          /* get :inJoinSet */
-       local_node->inJoinSet = (token[0] == 't') ? true : false;
-
        token = lsptok(NULL, &length);          /* eat :skipAcl */
        token = lsptok(NULL, &length);          /* get :skipAcl */
        local_node->skipAcl = (token[0] == 't') ? true : false;
@@ -1444,6 +1524,10 @@ _readIndexPath()
        token = lsptok(NULL, &length);          /* get :joinrelids */
        local_node->joinrelids = toIntList(nodeRead(true));
 
+       token = lsptok(NULL, &length);          /* get :alljoinquals */
+       token = lsptok(NULL, &length);          /* now read it */
+       local_node->alljoinquals = (token[0] == 't') ? true : false;
+
        token = lsptok(NULL, &length);          /* get :rows */
        token = lsptok(NULL, &length);          /* now read it */
        local_node->rows = atof(token);
@@ -1520,6 +1604,10 @@ _readNestPath()
        token = lsptok(NULL, &length);          /* get :pathkeys */
        local_node->path.pathkeys = nodeRead(true); /* now read it */
 
+       token = lsptok(NULL, &length);          /* get :jointype */
+       token = lsptok(NULL, &length);          /* now read it */
+       local_node->jointype = (JoinType) atoi(token);
+
        token = lsptok(NULL, &length);          /* get :outerjoinpath */
        local_node->outerjoinpath = nodeRead(true); /* now read it */
 
@@ -1527,7 +1615,7 @@ _readNestPath()
        local_node->innerjoinpath = nodeRead(true); /* now read it */
 
        token = lsptok(NULL, &length);          /* get :joinrestrictinfo */
-       local_node->joinrestrictinfo = nodeRead(true);          /* now read it */
+       local_node->joinrestrictinfo = nodeRead(true); /* now read it */
 
        return local_node;
 }
@@ -1562,6 +1650,10 @@ _readMergePath()
        token = lsptok(NULL, &length);          /* get :pathkeys */
        local_node->jpath.path.pathkeys = nodeRead(true);       /* now read it */
 
+       token = lsptok(NULL, &length);          /* get :jointype */
+       token = lsptok(NULL, &length);          /* now read it */
+       local_node->jpath.jointype = (JoinType) atoi(token);
+
        token = lsptok(NULL, &length);          /* get :outerjoinpath */
        local_node->jpath.outerjoinpath = nodeRead(true);       /* now read it */
 
@@ -1569,7 +1661,7 @@ _readMergePath()
        local_node->jpath.innerjoinpath = nodeRead(true);       /* now read it */
 
        token = lsptok(NULL, &length);          /* get :joinrestrictinfo */
-       local_node->jpath.joinrestrictinfo = nodeRead(true);            /* now read it */
+       local_node->jpath.joinrestrictinfo = nodeRead(true); /* now read it */
 
        token = lsptok(NULL, &length);          /* get :path_mergeclauses */
        local_node->path_mergeclauses = nodeRead(true);         /* now read it */
@@ -1613,6 +1705,10 @@ _readHashPath()
        token = lsptok(NULL, &length);          /* get :pathkeys */
        local_node->jpath.path.pathkeys = nodeRead(true);       /* now read it */
 
+       token = lsptok(NULL, &length);          /* get :jointype */
+       token = lsptok(NULL, &length);          /* now read it */
+       local_node->jpath.jointype = (JoinType) atoi(token);
+
        token = lsptok(NULL, &length);          /* get :outerjoinpath */
        local_node->jpath.outerjoinpath = nodeRead(true);       /* now read it */
 
@@ -1620,7 +1716,7 @@ _readHashPath()
        local_node->jpath.innerjoinpath = nodeRead(true);       /* now read it */
 
        token = lsptok(NULL, &length);          /* get :joinrestrictinfo */
-       local_node->jpath.joinrestrictinfo = nodeRead(true);            /* now read it */
+       local_node->jpath.joinrestrictinfo = nodeRead(true); /* now read it */
 
        token = lsptok(NULL, &length);          /* get :path_hashclauses */
        local_node->path_hashclauses = nodeRead(true);          /* now read it */
@@ -1672,6 +1768,10 @@ _readRestrictInfo()
        token = lsptok(NULL, &length);          /* get :clause */
        local_node->clause = nodeRead(true);            /* now read it */
 
+       token = lsptok(NULL, &length);          /* get :isjoinqual */
+       token = lsptok(NULL, &length);          /* now read it */
+       local_node->isjoinqual = (token[0] == 't') ? true : false;
+
        token = lsptok(NULL, &length);          /* get :subclauseindices */
        local_node->subclauseindices = nodeRead(true);          /* now read it */
 
@@ -1789,6 +1889,10 @@ parsePlanString(void)
                return_value = _readFieldSelect();
        else if (length == 11 && strncmp(token, "RELABELTYPE", length) == 0)
                return_value = _readRelabelType();
+       else if (length == 11 && strncmp(token, "RANGETBLREF", length) == 0)
+               return_value = _readRangeTblRef();
+       else if (length == 8 && strncmp(token, "JOINEXPR", length) == 0)
+               return_value = _readJoinExpr();
        else if (length == 3 && strncmp(token, "AGG", length) == 0)
                return_value = _readAgg();
        else if (length == 4 && strncmp(token, "HASH", length) == 0)
index a867cd885edd04debb5c9bd0e759e3271860be2f..38901ede1fd9aff04492e7f4af925530d0a86aab 100644 (file)
@@ -35,10 +35,10 @@ RelOptInfo.pathlist.  (Actually, we discard Paths that are obviously
 inferior alternatives before they ever get into the pathlist --- what
 ends up in the pathlist is the cheapest way of generating each potentially
 useful sort ordering of the relation.)  Also create RelOptInfo.joininfo
-nodes that list all the joins that involve this relation.  For example,
-the WHERE clause "tab1.col1 = tab2.col1" generates a JoinInfo for tab1
-listing tab2 as an unjoined relation, and also one for tab2 showing tab1
-as an unjoined relation.
+nodes that list all the join clauses that involve this relation.  For
+example, the WHERE clause "tab1.col1 = tab2.col1" generates a JoinInfo
+for tab1 listing tab2 as an unjoined relation, and also one for tab2
+showing tab1 as an unjoined relation.
 
 If we have only a single base relation in the query, we are done now.
 Otherwise we have to figure out how to join the base relations into a
@@ -128,6 +128,19 @@ Once we have built the final join rel, we use either the cheapest path
 for it or the cheapest path with the desired ordering (if that's cheaper
 than applying a sort to the cheapest other path).
 
+The above dynamic-programming search is only conducted for simple cross
+joins (ie, SELECT FROM tab1, tab2, ...).  When the FROM clause contains
+explicit JOIN clauses, we join rels in exactly the order implied by the
+join tree.  Searching for the best possible join order is done only at
+the top implicit-cross-join level.  For example, in
+       SELECT FROM tab1, tab2, (tab3 NATURAL JOIN tab4)
+we will always join tab3 to tab4 and then consider all ways to join that
+result to tab1 and tab2.  Note that the JOIN syntax only constrains the
+order of joining --- we will still consider all available Paths and
+join methods for each JOIN operator.  We also consider both sides of
+the JOIN operator as inner or outer (so that we can transform RIGHT JOIN
+into LEFT JOIN).
+
 
 Optimizer Functions
 -------------------
@@ -158,13 +171,12 @@ planner()
    get a target list that only contains column names, no expressions
    if none, then return
 ---subplanner()
-    make list of relations in target
-    make list of relations in where clause
+    make list of base relations used in query
     split up the qual into restrictions (a=1) and joins (b=c)
-    find relation clauses can do merge sort and hash joins
+    find relation clauses that can do merge sort and hash joins
 ----make_one_rel()
      set_base_rel_pathlist()
-      find scan and all index paths for each relation
+      find scan and all index paths for each base relation
       find selectivity of columns used in joins
 -----make_one_rel_by_joins()
       jump to geqo if needed
index 7b9542cb1b21ced4fbc81c4ea6e2d7be9742b392..f32b0d64eebfc762a25ddd635d3bf68d4513fdb3 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: geqo_eval.c,v 1.53 2000/07/28 02:13:16 tgl Exp $
+ * $Id: geqo_eval.c,v 1.54 2000/09/12 21:06:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -93,11 +93,11 @@ geqo_eval(Query *root, Gene *tour, int num_gene)
  * Returns a new join relation incorporating all joins in a left-sided tree.
  */
 RelOptInfo *
-gimme_tree(Query *root, Gene *tour, int rel_count, int num_gene, RelOptInfo *old_rel)
+gimme_tree(Query *root, Gene *tour, int rel_count, int num_gene,
+                  RelOptInfo *old_rel)
 {
        RelOptInfo *inner_rel;          /* current relation */
        int                     base_rel_index;
-       RelOptInfo *new_rel;
 
        if (rel_count < num_gene)
        {                                                       /* tree not yet finished */
@@ -116,16 +116,22 @@ gimme_tree(Query *root, Gene *tour, int rel_count, int num_gene, RelOptInfo *old
                else
                {                                               /* tree main part */
                        List       *acceptable_rels = lcons(inner_rel, NIL);
-
-                       new_rel = make_rels_by_clause_joins(root, old_rel,
-                                                                                               acceptable_rels);
-                       if (!new_rel)
+                       List       *new_rels;
+                       RelOptInfo *new_rel;
+
+                       new_rels = make_rels_by_clause_joins(root, old_rel,
+                                                                                                acceptable_rels);
+                       /* Shouldn't get more than one result */
+                       Assert(length(new_rels) <= 1);
+                       if (new_rels == NIL)
                        {
-                               new_rel = make_rels_by_clauseless_joins(root, old_rel,
-                                                                                                               acceptable_rels);
-                               if (!new_rel)
+                               new_rels = make_rels_by_clauseless_joins(root, old_rel,
+                                                                                                                acceptable_rels);
+                               Assert(length(new_rels) <= 1);
+                               if (new_rels == NIL)
                                        elog(ERROR, "gimme_tree: failed to construct join rel");
                        }
+                       new_rel = (RelOptInfo *) lfirst(new_rels);
 
                        rel_count++;
                        Assert(length(new_rel->relids) == rel_count);
index 999364d563778132fd14fc9c60c7cd81d5b16bca..605b60b5845794349c916b9da6a616ca75df514d 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.62 2000/05/31 00:28:22 petere Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.63 2000/09/12 21:06:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -26,7 +26,9 @@ int                   geqo_rels = DEFAULT_GEQO_RELS;
 
 
 static void set_base_rel_pathlist(Query *root);
-static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed);
+static List *build_jointree_rels(Query *root);
+static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed,
+                                                                                List *initial_rels);
 
 #ifdef OPTIMIZER_DEBUG
 static void debug_print_rel(Query *root, RelOptInfo *rel);
@@ -43,27 +45,38 @@ RelOptInfo *
 make_one_rel(Query *root)
 {
        int                     levels_needed;
+       List       *initial_rels;
 
        /*
-        * Set the number of join (not nesting) levels yet to be processed.
+        * Count the number of top-level jointree nodes.  This is the depth
+        * of the dynamic-programming algorithm we must employ to consider
+        * all ways of joining the top-level nodes.  Currently, we build
+        * JoinExpr joins in exactly the order implied by the join expression,
+        * so no dynamic-programming search is needed within a JoinExpr.
         */
-       levels_needed = length(root->base_rel_list);
+       levels_needed = length(root->jointree);
 
        if (levels_needed <= 0)
-               return NULL;
+               return NULL;                    /* nothing to do? */
 
        /*
         * Generate access paths for the base rels.
         */
        set_base_rel_pathlist(root);
 
+       /*
+        * Construct a list of rels corresponding to the toplevel jointree nodes.
+        * This may contain both base rels and rels constructed according to
+        * explicit JOIN directives.
+        */
+       initial_rels = build_jointree_rels(root);
+
        if (levels_needed == 1)
        {
-
                /*
-                * Single relation, no more processing is required.
+                * Single jointree node, so we're done.
                 */
-               return (RelOptInfo *) lfirst(root->base_rel_list);
+               return (RelOptInfo *) lfirst(initial_rels);
        }
        else
        {
@@ -71,7 +84,7 @@ make_one_rel(Query *root)
                /*
                 * Generate join tree.
                 */
-               return make_one_rel_by_joins(root, levels_needed);
+               return make_one_rel_by_joins(root, levels_needed, initial_rels);
        }
 }
 
@@ -125,20 +138,47 @@ set_base_rel_pathlist(Query *root)
        }
 }
 
+/*
+ * build_jointree_rels
+ *       Construct a RelOptInfo for each item in the query's jointree.
+ *
+ * At present, we handle explicit joins in the FROM clause exactly as
+ * specified, with no search for other join orders.  Only the cross-product
+ * joins at the top level are involved in the dynamic-programming search.
+ */
+static List *
+build_jointree_rels(Query *root)
+{
+       List       *rels = NIL;
+       List       *jt;
+
+       foreach(jt, root->jointree)
+       {
+               Node       *jtnode = (Node *) lfirst(jt);
+
+               rels = lappend(rels, make_rel_from_jointree(root, jtnode));
+       }
+       return rels;
+}
+
 /*
  * make_one_rel_by_joins
  *       Find all possible joinpaths for a query by successively finding ways
  *       to join component relations into join relations.
  *
  * 'levels_needed' is the number of iterations needed, ie, the number of
- *             base relations present in the query
+ *             independent jointree items in the query.  This is > 1.
+ *
+ * 'initial_rels' is a list of RelOptInfo nodes for each independent
+ *             jointree item.  These are the components to be joined together.
  *
  * Returns the final level of join relations, i.e., the relation that is
  * the result of joining all the original relations together.
  */
 static RelOptInfo *
-make_one_rel_by_joins(Query *root, int levels_needed)
+make_one_rel_by_joins(Query *root, int levels_needed, List *initial_rels)
 {
+       List      **joinitems;
        int                     lev;
        RelOptInfo *rel;
 
@@ -152,34 +192,35 @@ make_one_rel_by_joins(Query *root, int levels_needed)
 
        /*
         * We employ a simple "dynamic programming" algorithm: we first find
-        * all ways to build joins of two base relations, then all ways to
-        * build joins of three base relations (from two-base-rel joins and
-        * other base rels), then four-base-rel joins, and so on until we have
-        * considered all ways to join all N relations into one rel.
+        * all ways to build joins of two jointree items, then all ways to
+        * build joins of three items (from two-item joins and single items),
+        * then four-item joins, and so on until we have considered all ways
+        * to join all the items into one rel.
+        *
+        * joinitems[j] is a list of all the j-item rels.  Initially we set
+        * joinitems[1] to represent all the single-jointree-item relations.
         */
+       joinitems = (List **) palloc((levels_needed+1) * sizeof(List *));
+       MemSet(joinitems, 0, (levels_needed+1) * sizeof(List *));
+
+       joinitems[1] = initial_rels;
 
        for (lev = 2; lev <= levels_needed; lev++)
        {
-               List       *first_old_rel = root->join_rel_list;
                List       *x;
 
                /*
                 * Determine all possible pairs of relations to be joined at this
                 * level, and build paths for making each one from every available
-                * pair of lower-level relations.  Results are prepended to
-                * root->join_rel_list.
+                * pair of lower-level relations.
                 */
-               make_rels_by_joins(root, lev);
+               joinitems[lev] = make_rels_by_joins(root, lev, joinitems);
 
                /*
-                * The relations created at the current level will appear at the
-                * front of root->join_rel_list.
+                * Do cleanup work on each just-processed rel.
                 */
-               foreach(x, root->join_rel_list)
+               foreach(x, joinitems[lev])
                {
-                       if (x == first_old_rel)
-                               break;                  /* no more rels added at this level */
-
                        rel = (RelOptInfo *) lfirst(x);
 
 #ifdef NOT_USED
@@ -202,14 +243,12 @@ make_one_rel_by_joins(Query *root, int levels_needed)
        }
 
        /*
-        * Now, the front of the join_rel_list should be the single rel
+        * We should have a single rel at the final level,
         * representing the join of all the base rels.
         */
-       Assert(length(root->join_rel_list) > 0);
-       rel = (RelOptInfo *) lfirst(root->join_rel_list);
-       Assert(length(rel->relids) == levels_needed);
-       Assert(length(root->join_rel_list) == 1 ||
-                  length(((RelOptInfo *) lsecond(root->join_rel_list))->relids) < levels_needed);
+       Assert(length(joinitems[levels_needed]) == 1);
+       rel = (RelOptInfo *) lfirst(joinitems[levels_needed]);
+       Assert(length(rel->relids) == length(root->base_rel_list));
 
        return rel;
 }
index 05f32d25972d24dd10a4f72dba920807d0b9c704..3156a95131427b063d5f36cd3d79880b89e5d4e4 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.94 2000/08/24 03:29:04 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.95 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1482,7 +1482,9 @@ index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index,
        {
                List       *clausegroup = lfirst(i);
                IndexPath  *pathnode = makeNode(IndexPath);
-               List       *indexquals;
+               List       *indexquals = NIL;
+               bool            alljoinquals = true;
+               List       *temp;
 
                /* XXX this code ought to be merged with create_index_path? */
 
@@ -1496,7 +1498,16 @@ index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index,
                 */
                pathnode->path.pathkeys = NIL;
 
-               indexquals = get_actual_clauses(clausegroup);
+               /* extract bare indexqual clauses, check whether all from JOIN/ON */
+               foreach(temp, clausegroup)
+               {
+                       RestrictInfo *clause = (RestrictInfo *) lfirst(temp);
+
+                       indexquals = lappend(indexquals, clause->clause);
+                       if (! clause->isjoinqual)
+                               alljoinquals = false;
+               }
+
                /* expand special operators to indexquals the executor can handle */
                indexquals = expand_indexqual_conditions(indexquals);
 
@@ -1514,6 +1525,8 @@ index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index,
                /* joinrelids saves the rels needed on the outer side of the join */
                pathnode->joinrelids = lfirst(outerrelids_list);
 
+               pathnode->alljoinquals = alljoinquals;
+
                /*
                 * We must compute the estimated number of output rows for the
                 * indexscan.  This is less than rel->rows because of the
index c2ca38490e393fecbcf286bd5e0b0faaa56ff42f..367e1ac9767c5f1f5c0961e1d60ae7ffb2d6b0c2 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.55 2000/05/30 00:49:47 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.56 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "utils/lsyscache.h"
 
 static void sort_inner_and_outer(Query *root, RelOptInfo *joinrel,
-                                        RelOptInfo *outerrel, RelOptInfo *innerrel,
-                                        List *restrictlist, List *mergeclause_list);
+                                                                RelOptInfo *outerrel, RelOptInfo *innerrel,
+                                                                List *restrictlist, List *mergeclause_list,
+                                                                JoinType jointype);
 static void match_unsorted_outer(Query *root, RelOptInfo *joinrel,
-                                        RelOptInfo *outerrel, RelOptInfo *innerrel,
-                                        List *restrictlist, List *mergeclause_list);
+                                                                RelOptInfo *outerrel, RelOptInfo *innerrel,
+                                                                List *restrictlist, List *mergeclause_list,
+                                                                JoinType jointype);
 
 #ifdef NOT_USED
 static void match_unsorted_inner(Query *root, RelOptInfo *joinrel,
-                                        RelOptInfo *outerrel, RelOptInfo *innerrel,
-                                        List *restrictlist, List *mergeclause_list);
+                                                                RelOptInfo *outerrel, RelOptInfo *innerrel,
+                                                                List *restrictlist, List *mergeclause_list,
+                                                                JoinType jointype);
 
 #endif
 static void hash_inner_and_outer(Query *root, RelOptInfo *joinrel,
-                                        RelOptInfo *outerrel, RelOptInfo *innerrel,
-                                        List *restrictlist);
-static Path *best_innerjoin(List *join_paths, List *outer_relid);
+                                                                RelOptInfo *outerrel, RelOptInfo *innerrel,
+                                                                List *restrictlist, JoinType jointype);
+static Path *best_innerjoin(List *join_paths, List *outer_relid,
+                                                       JoinType jointype);
 static Selectivity estimate_disbursion(Query *root, Var *var);
 static List *select_mergejoin_clauses(RelOptInfo *joinrel,
-                                                RelOptInfo *outerrel,
-                                                RelOptInfo *innerrel,
-                                                List *restrictlist);
+                                                                         RelOptInfo *outerrel,
+                                                                         RelOptInfo *innerrel,
+                                                                         List *restrictlist,
+                                                                         JoinType jointype);
 
 
 /*
@@ -64,6 +69,7 @@ add_paths_to_joinrel(Query *root,
                                         RelOptInfo *joinrel,
                                         RelOptInfo *outerrel,
                                         RelOptInfo *innerrel,
+                                        JoinType jointype,
                                         List *restrictlist)
 {
        List       *mergeclause_list = NIL;
@@ -75,14 +81,15 @@ add_paths_to_joinrel(Query *root,
                mergeclause_list = select_mergejoin_clauses(joinrel,
                                                                                                        outerrel,
                                                                                                        innerrel,
-                                                                                                       restrictlist);
+                                                                                                       restrictlist,
+                                                                                                       jointype);
 
        /*
         * 1. Consider mergejoin paths where both relations must be explicitly
         * sorted.
         */
        sort_inner_and_outer(root, joinrel, outerrel, innerrel,
-                                                restrictlist, mergeclause_list);
+                                                restrictlist, mergeclause_list, jointype);
 
        /*
         * 2. Consider paths where the outer relation need not be explicitly
@@ -90,7 +97,7 @@ add_paths_to_joinrel(Query *root,
         * path is already ordered.
         */
        match_unsorted_outer(root, joinrel, outerrel, innerrel,
-                                                restrictlist, mergeclause_list);
+                                                restrictlist, mergeclause_list, jointype);
 
 #ifdef NOT_USED
 
@@ -107,7 +114,7 @@ add_paths_to_joinrel(Query *root,
         * other order.
         */
        match_unsorted_inner(root, joinrel, outerrel, innerrel,
-                                                restrictlist, mergeclause_list);
+                                                restrictlist, mergeclause_list, jointype);
 #endif
 
        /*
@@ -116,7 +123,7 @@ add_paths_to_joinrel(Query *root,
         */
        if (enable_hashjoin)
                hash_inner_and_outer(root, joinrel, outerrel, innerrel,
-                                                        restrictlist);
+                                                        restrictlist, jointype);
 }
 
 /*
@@ -131,6 +138,7 @@ add_paths_to_joinrel(Query *root,
  *             clauses that apply to this join
  * 'mergeclause_list' is a list of RestrictInfo nodes for available
  *             mergejoin clauses in this join
+ * 'jointype' is the type of join to do
  */
 static void
 sort_inner_and_outer(Query *root,
@@ -138,7 +146,8 @@ sort_inner_and_outer(Query *root,
                                         RelOptInfo *outerrel,
                                         RelOptInfo *innerrel,
                                         List *restrictlist,
-                                        List *mergeclause_list)
+                                        List *mergeclause_list,
+                                        JoinType jointype)
 {
        List       *i;
 
@@ -187,10 +196,10 @@ sort_inner_and_outer(Query *root,
                 */
                outerkeys = make_pathkeys_for_mergeclauses(root,
                                                                                                   curclause_list,
-                                                                                                  outerrel->targetlist);
+                                                                                                  outerrel);
                innerkeys = make_pathkeys_for_mergeclauses(root,
                                                                                                   curclause_list,
-                                                                                                  innerrel->targetlist);
+                                                                                                  innerrel);
                /* Build pathkeys representing output sort order. */
                merge_pathkeys = build_join_pathkeys(outerkeys,
                                                                                         joinrel->targetlist,
@@ -204,6 +213,7 @@ sort_inner_and_outer(Query *root,
                 */
                add_path(joinrel, (Path *)
                                 create_mergejoin_path(joinrel,
+                                                                          jointype,
                                                                           outerrel->cheapest_total_path,
                                                                           innerrel->cheapest_total_path,
                                                                           restrictlist,
@@ -243,6 +253,7 @@ sort_inner_and_outer(Query *root,
  *             clauses that apply to this join
  * 'mergeclause_list' is a list of RestrictInfo nodes for available
  *             mergejoin clauses in this join
+ * 'jointype' is the type of join to do
  */
 static void
 match_unsorted_outer(Query *root,
@@ -250,16 +261,33 @@ match_unsorted_outer(Query *root,
                                         RelOptInfo *outerrel,
                                         RelOptInfo *innerrel,
                                         List *restrictlist,
-                                        List *mergeclause_list)
+                                        List *mergeclause_list,
+                                        JoinType jointype)
 {
+       bool            nestjoinOK;
        Path       *bestinnerjoin;
        List       *i;
 
+       /*
+        * Nestloop only supports inner and left joins.
+        */
+       switch (jointype)
+       {
+               case JOIN_INNER:
+               case JOIN_LEFT:
+                       nestjoinOK = true;
+                       break;
+               default:
+                       nestjoinOK = false;
+                       break;
+       }
+
        /*
         * Get the best innerjoin indexpath (if any) for this outer rel. It's
         * the same for all outer paths.
         */
-       bestinnerjoin = best_innerjoin(innerrel->innerjoin, outerrel->relids);
+       bestinnerjoin = best_innerjoin(innerrel->innerjoin, outerrel->relids,
+                                                                  jointype);
 
        foreach(i, outerrel->pathlist)
        {
@@ -282,31 +310,38 @@ match_unsorted_outer(Query *root,
                                                                                         joinrel->targetlist,
                                                                                         root->equi_key_list);
 
-               /*
-                * Always consider a nestloop join with this outer and cheapest-
-                * total-cost inner.  Consider nestloops using the cheapest-
-                * startup-cost inner as well, and the best innerjoin indexpath.
-                */
-               add_path(joinrel, (Path *)
-                                create_nestloop_path(joinrel,
-                                                                         outerpath,
-                                                                         innerrel->cheapest_total_path,
-                                                                         restrictlist,
-                                                                         merge_pathkeys));
-               if (innerrel->cheapest_startup_path != innerrel->cheapest_total_path)
-                       add_path(joinrel, (Path *)
-                                        create_nestloop_path(joinrel,
-                                                                                 outerpath,
-                                                                                 innerrel->cheapest_startup_path,
-                                                                                 restrictlist,
-                                                                                 merge_pathkeys));
-               if (bestinnerjoin != NULL)
+               if (nestjoinOK)
+               {
+                       /*
+                        * Always consider a nestloop join with this outer and cheapest-
+                        * total-cost inner.  Consider nestloops using the cheapest-
+                        * startup-cost inner as well, and the best innerjoin indexpath.
+                        */
                        add_path(joinrel, (Path *)
                                         create_nestloop_path(joinrel,
+                                                                                 jointype,
                                                                                  outerpath,
-                                                                                 bestinnerjoin,
+                                                                                 innerrel->cheapest_total_path,
                                                                                  restrictlist,
                                                                                  merge_pathkeys));
+                       if (innerrel->cheapest_startup_path !=
+                               innerrel->cheapest_total_path)
+                               add_path(joinrel, (Path *)
+                                                create_nestloop_path(joinrel,
+                                                                                         jointype,
+                                                                                         outerpath,
+                                                                                         innerrel->cheapest_startup_path,
+                                                                                         restrictlist,
+                                                                                         merge_pathkeys));
+                       if (bestinnerjoin != NULL)
+                               add_path(joinrel, (Path *)
+                                                create_nestloop_path(joinrel,
+                                                                                         jointype,
+                                                                                         outerpath,
+                                                                                         bestinnerjoin,
+                                                                                         restrictlist,
+                                                                                         merge_pathkeys));
+               }
 
                /* Look for useful mergeclauses (if any) */
                mergeclauses = find_mergeclauses_for_pathkeys(outerpath->pathkeys,
@@ -319,7 +354,7 @@ match_unsorted_outer(Query *root,
                /* Compute the required ordering of the inner path */
                innersortkeys = make_pathkeys_for_mergeclauses(root,
                                                                                                           mergeclauses,
-                                                                                                  innerrel->targetlist);
+                                                                                                          innerrel);
 
                /*
                 * Generate a mergejoin on the basis of sorting the cheapest
@@ -328,6 +363,7 @@ match_unsorted_outer(Query *root,
                 */
                add_path(joinrel, (Path *)
                                 create_mergejoin_path(joinrel,
+                                                                          jointype,
                                                                           outerpath,
                                                                           innerrel->cheapest_total_path,
                                                                           restrictlist,
@@ -373,6 +409,7 @@ match_unsorted_outer(Query *root,
                                        newclauses = mergeclauses;
                                add_path(joinrel, (Path *)
                                                 create_mergejoin_path(joinrel,
+                                                                                          jointype,
                                                                                           outerpath,
                                                                                           innerpath,
                                                                                           restrictlist,
@@ -409,6 +446,7 @@ match_unsorted_outer(Query *root,
                                        }
                                        add_path(joinrel, (Path *)
                                                         create_mergejoin_path(joinrel,
+                                                                                                  jointype,
                                                                                                   outerpath,
                                                                                                   innerpath,
                                                                                                   restrictlist,
@@ -437,6 +475,7 @@ match_unsorted_outer(Query *root,
  *             clauses that apply to this join
  * 'mergeclause_list' is a list of RestrictInfo nodes for available
  *             mergejoin clauses in this join
+ * 'jointype' is the type of join to do
  */
 static void
 match_unsorted_inner(Query *root,
@@ -444,7 +483,8 @@ match_unsorted_inner(Query *root,
                                         RelOptInfo *outerrel,
                                         RelOptInfo *innerrel,
                                         List *restrictlist,
-                                        List *mergeclause_list)
+                                        List *mergeclause_list,
+                                        JoinType jointype)
 {
        List       *i;
 
@@ -466,7 +506,7 @@ match_unsorted_inner(Query *root,
                /* Compute the required ordering of the outer path */
                outersortkeys = make_pathkeys_for_mergeclauses(root,
                                                                                                           mergeclauses,
-                                                                                                  outerrel->targetlist);
+                                                                                                          outerrel);
 
                /*
                 * Generate a mergejoin on the basis of sorting the cheapest
@@ -478,6 +518,7 @@ match_unsorted_inner(Query *root,
                                                                                         root->equi_key_list);
                add_path(joinrel, (Path *)
                                 create_mergejoin_path(joinrel,
+                                                                          jointype,
                                                                           outerrel->cheapest_total_path,
                                                                           innerpath,
                                                                           restrictlist,
@@ -506,6 +547,7 @@ match_unsorted_inner(Query *root,
                                                                                         root->equi_key_list);
                add_path(joinrel, (Path *)
                                 create_mergejoin_path(joinrel,
+                                                                          jointype,
                                                                           totalouterpath,
                                                                           innerpath,
                                                                           restrictlist,
@@ -524,6 +566,7 @@ match_unsorted_inner(Query *root,
                                                                                                 root->equi_key_list);
                        add_path(joinrel, (Path *)
                                         create_mergejoin_path(joinrel,
+                                                                                  jointype,
                                                                                   startupouterpath,
                                                                                   innerpath,
                                                                                   restrictlist,
@@ -547,18 +590,36 @@ match_unsorted_inner(Query *root,
  * 'innerrel' is the inner join relation
  * 'restrictlist' contains all of the RestrictInfo nodes for restriction
  *             clauses that apply to this join
+ * 'jointype' is the type of join to do
  */
 static void
 hash_inner_and_outer(Query *root,
                                         RelOptInfo *joinrel,
                                         RelOptInfo *outerrel,
                                         RelOptInfo *innerrel,
-                                        List *restrictlist)
+                                        List *restrictlist,
+                                        JoinType jointype)
 {
        Relids          outerrelids = outerrel->relids;
        Relids          innerrelids = innerrel->relids;
+       bool            isouterjoin;
        List       *i;
 
+       /*
+        * Hashjoin only supports inner and left joins.
+        */
+       switch (jointype)
+       {
+               case JOIN_INNER:
+                       isouterjoin = false;
+                       break;
+               case JOIN_LEFT:
+                       isouterjoin = true;
+                       break;
+               default:
+                       return;
+       }
+
        /*
         * Scan the join's restrictinfo list to find hashjoinable clauses that
         * are usable with this pair of sub-relations.  Since we currently
@@ -581,6 +642,13 @@ hash_inner_and_outer(Query *root,
                if (restrictinfo->hashjoinoperator == InvalidOid)
                        continue;                       /* not hashjoinable */
 
+               /*
+                * If processing an outer join, only use explicit join clauses for
+                * hashing.  For inner joins we need not be so picky.
+                */
+               if (isouterjoin && !restrictinfo->isjoinqual)
+                       continue;
+
                clause = restrictinfo->clause;
                /* these must be OK, since check_hashjoinable accepted the clause */
                left = get_leftop(clause);
@@ -609,6 +677,7 @@ hash_inner_and_outer(Query *root,
                 */
                add_path(joinrel, (Path *)
                                 create_hashjoin_path(joinrel,
+                                                                         jointype,
                                                                          outerrel->cheapest_total_path,
                                                                          innerrel->cheapest_total_path,
                                                                          restrictlist,
@@ -617,6 +686,7 @@ hash_inner_and_outer(Query *root,
                if (outerrel->cheapest_startup_path != outerrel->cheapest_total_path)
                        add_path(joinrel, (Path *)
                                         create_hashjoin_path(joinrel,
+                                                                                 jointype,
                                                                                  outerrel->cheapest_startup_path,
                                                                                  innerrel->cheapest_total_path,
                                                                                  restrictlist,
@@ -641,26 +711,49 @@ hash_inner_and_outer(Query *root,
  * usable path.
  */
 static Path *
-best_innerjoin(List *join_paths, Relids outer_relids)
+best_innerjoin(List *join_paths, Relids outer_relids, JoinType jointype)
 {
        Path       *cheapest = (Path *) NULL;
+       bool            isouterjoin;
        List       *join_path;
 
+       /*
+        * Nestloop only supports inner and left joins.
+        */
+       switch (jointype)
+       {
+               case JOIN_INNER:
+                       isouterjoin = false;
+                       break;
+               case JOIN_LEFT:
+                       isouterjoin = true;
+                       break;
+               default:
+                       return NULL;
+       }
+
        foreach(join_path, join_paths)
        {
-               Path       *path = (Path *) lfirst(join_path);
+               IndexPath   *path = (IndexPath *) lfirst(join_path);
 
                Assert(IsA(path, IndexPath));
 
+               /*
+                * If processing an outer join, only use explicit join clauses in the
+                * inner indexscan.  For inner joins we need not be so picky.
+                */
+               if (isouterjoin && !path->alljoinquals)
+                       continue;
+
                /*
                 * path->joinrelids is the set of base rels that must be part of
                 * outer_relids in order to use this inner path, because those
                 * rels are used in the index join quals of this inner path.
                 */
-               if (is_subseti(((IndexPath *) path)->joinrelids, outer_relids) &&
+               if (is_subseti(path->joinrelids, outer_relids) &&
                        (cheapest == NULL ||
-                        compare_path_costs(path, cheapest, TOTAL_COST) < 0))
-                       cheapest = path;
+                        compare_path_costs((Path *) path, cheapest, TOTAL_COST) < 0))
+                       cheapest = (Path *) path;
        }
        return cheapest;
 }
@@ -684,6 +777,9 @@ estimate_disbursion(Query *root, Var *var)
 
        relid = getrelid(var->varno, root->rtable);
 
+       if (relid == InvalidOid)
+               return 0.1;
+
        return (Selectivity) get_attdisbursion(relid, var->varattno, 0.1);
 }
 
@@ -707,11 +803,13 @@ static List *
 select_mergejoin_clauses(RelOptInfo *joinrel,
                                                 RelOptInfo *outerrel,
                                                 RelOptInfo *innerrel,
-                                                List *restrictlist)
+                                                List *restrictlist,
+                                                JoinType jointype)
 {
        List       *result_list = NIL;
        Relids          outerrelids = outerrel->relids;
        Relids          innerrelids = innerrel->relids;
+       bool            isouterjoin = IS_OUTER_JOIN(jointype);
        List       *i;
 
        foreach(i, restrictlist)
@@ -721,6 +819,37 @@ select_mergejoin_clauses(RelOptInfo *joinrel,
                Var                *left,
                                   *right;
 
+               /*
+                * If processing an outer join, only use explicit join clauses in the
+                * merge.  For inner joins we need not be so picky.
+                *
+                * Furthermore, if it is a right/full join then *all* the explicit
+                * join clauses must be mergejoinable, else the executor will fail.
+                * If we are asked for a right join then just return NIL to indicate
+                * no mergejoin is possible (we can handle it as a left join instead).
+                * If we are asked for a full join then emit an error, because there
+                * is no fallback.
+                */
+               if (isouterjoin)
+               {
+                       if (!restrictinfo->isjoinqual)
+                               continue;
+                       switch (jointype)
+                       {
+                               case JOIN_RIGHT:
+                                       if (restrictinfo->mergejoinoperator == InvalidOid)
+                                               return NIL;     /* not mergejoinable */
+                                       break;
+                               case JOIN_FULL:
+                                       if (restrictinfo->mergejoinoperator == InvalidOid)
+                                               elog(ERROR, "FULL JOIN is only supported with mergejoinable join conditions");
+                                       break;
+                               default:
+                                       /* otherwise, it's OK to have nonmergeable join quals */
+                                       break;
+                       }
+               }
+
                if (restrictinfo->mergejoinoperator == InvalidOid)
                        continue;                       /* not mergejoinable */
 
index 741efe928c20d1a38e793b6650fff7bf1fd14b46..3cab2daba5c6e25e15bc16460171e0b21687814a 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.46 2000/05/30 00:49:47 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.47 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 
 static RelOptInfo *make_join_rel(Query *root, RelOptInfo *rel1,
-                         RelOptInfo *rel2);
+                                                                RelOptInfo *rel2, JoinType jointype);
 
 
 /*
  * make_rels_by_joins
  *       Consider ways to produce join relations containing exactly 'level'
- *       base relations.  (This is one step of the dynamic-programming method
+ *       jointree items.  (This is one step of the dynamic-programming method
  *       embodied in make_one_rel_by_joins.)  Join rel nodes for each feasible
- *       combination of base rels are created and added to the front of the
- *       query's join_rel_list.  Implementation paths are created for each
- *       such joinrel, too.
+ *       combination of lower-level rels are created and returned in a list.
+ *       Implementation paths are created for each such joinrel, too.
  *
- * Returns nothing, but adds entries to root->join_rel_list.
+ * level: level of rels we want to make this time.
+ * joinrels[j], 1 <= j < level, is a list of rels containing j items.
  */
-void
-make_rels_by_joins(Query *root, int level)
+List *
+make_rels_by_joins(Query *root, int level, List **joinrels)
 {
-       List       *first_old_rel = root->join_rel_list;
+       List       *result_rels = NIL;
+       List       *new_rels;
+       List       *nr;
        List       *r;
+       int                     k;
 
        /*
         * First, consider left-sided and right-sided plans, in which rels of
-        * exactly level-1 member relations are joined against base relations.
-        * We prefer to join using join clauses, but if we find a rel of
-        * level-1 members that has no join clauses, we will generate
-        * Cartesian-product joins against all base rels not already contained
-        * in it.
+        * exactly level-1 member relations are joined against initial relations.
+        * We prefer to join using join clauses, but if we find a rel of level-1
+        * members that has no join clauses, we will generate Cartesian-product
+        * joins against all initial rels not already contained in it.
         *
-        * In the first pass (level == 2), we try to join each base rel to each
-        * base rel that appears later in base_rel_list.  (The mirror-image
+        * In the first pass (level == 2), we try to join each initial rel to each
+        * initial rel that appears later in joinrels[1].  (The mirror-image
         * joins are handled automatically by make_join_rel.)  In later
-        * passes, we try to join rels of size level-1 from join_rel_list to
-        * each base rel in base_rel_list.
-        *
-        * We assume that the rels already present in join_rel_list appear in
-        * decreasing order of level (number of members).  This should be true
-        * since we always add new higher-level rels to the front of the list.
+        * passes, we try to join rels of size level-1 from joinrels[level-1]
+        * to each initial rel in joinrels[1].
         */
-       if (level == 2)
-               r = root->base_rel_list;/* level-1 is base rels */
-       else
-               r = root->join_rel_list;
-       for (; r != NIL; r = lnext(r))
+       foreach(r, joinrels[level-1])
        {
                RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
-               int                     old_level = length(old_rel->relids);
                List       *other_rels;
 
-               if (old_level != level - 1)
-                       break;
-
                if (level == 2)
-                       other_rels = lnext(r);          /* only consider remaining base
+                       other_rels = lnext(r);          /* only consider remaining initial
                                                                                 * rels */
                else
-                       other_rels = root->base_rel_list;       /* consider all base rels */
+                       other_rels = joinrels[1];       /* consider all initial rels */
 
                if (old_rel->joininfo != NIL)
                {
@@ -87,9 +77,9 @@ make_rels_by_joins(Query *root, int level)
                         * have those other rels collected into a join rel.  See also
                         * the last-ditch case below.
                         */
-                       make_rels_by_clause_joins(root,
-                                                                         old_rel,
-                                                                         other_rels);
+                       new_rels = make_rels_by_clause_joins(root,
+                                                                                                old_rel,
+                                                                                                other_rels);
                }
                else
                {
@@ -98,64 +88,90 @@ make_rels_by_joins(Query *root, int level)
                         * Oops, we have a relation that is not joined to any other
                         * relation.  Cartesian product time.
                         */
-                       make_rels_by_clauseless_joins(root,
-                                                                                 old_rel,
-                                                                                 other_rels);
+                       new_rels = make_rels_by_clauseless_joins(root,
+                                                                                                        old_rel,
+                                                                                                        other_rels);
+               }
+
+               /*
+                * At levels above 2 we will generate the same joined relation
+                * in multiple ways --- for example (a join b) join c is the same
+                * RelOptInfo as (b join c) join a, though the second case will
+                * add a different set of Paths to it.  To avoid making extra work
+                * for subsequent passes, do not enter the same RelOptInfo into our
+                * output list multiple times.
+                */
+               foreach(nr, new_rels)
+               {
+                       RelOptInfo         *jrel = (RelOptInfo *) lfirst(nr);
+
+                       if (!ptrMember(jrel, result_rels))
+                               result_rels = lcons(jrel, result_rels);
                }
        }
 
        /*
-        * Now, consider "bushy plans" in which relations of k base rels are
-        * joined to relations of level-k base rels, for 2 <= k <= level-2.
-        * The previous loop left r pointing to the first rel of level
-        * level-2.
+        * Now, consider "bushy plans" in which relations of k initial rels are
+        * joined to relations of level-k initial rels, for 2 <= k <= level-2.
         *
         * We only consider bushy-plan joins for pairs of rels where there is a
         * suitable join clause, in order to avoid unreasonable growth of
         * planning time.
         */
-       for (; r != NIL; r = lnext(r))
+       for (k = 2; ; k++)
        {
-               RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
-               int                     old_level = length(old_rel->relids);
-               List       *r2;
+               int                     other_level = level - k;
 
                /*
-                * We can quit once past the halfway point (make_join_rel took
-                * care of making the opposite-direction joins)
+                * Since make_join_rel(x, y) handles both x,y and y,x cases,
+                * we only need to go as far as the halfway point.
                 */
-               if (old_level * 2 < level)
+               if (k > other_level)
                        break;
 
-               if (old_rel->joininfo == NIL)
-                       continue;                       /* we ignore clauseless joins here */
-
-               foreach(r2, lnext(r))
+               foreach(r, joinrels[k])
                {
-                       RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
-                       int                     new_level = length(new_rel->relids);
-
-                       if (old_level + new_level > level)
-                               continue;               /* scan down to new_rels of right size */
-                       if (old_level + new_level < level)
-                               break;                  /* no more new_rels of right size */
-                       if (nonoverlap_setsi(old_rel->relids, new_rel->relids))
+                       RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
+                       List       *other_rels;
+                       List       *r2;
+
+                       if (old_rel->joininfo == NIL)
+                               continue;               /* we ignore clauseless joins here */
+
+                       if (k == other_level)
+                               other_rels = lnext(r); /* only consider remaining rels */
+                       else
+                               other_rels = joinrels[other_level];
+
+                       foreach(r2, other_rels)
                        {
-                               List       *i;
-
-                               /*
-                                * OK, we can build a rel of the right level from this
-                                * pair of rels.  Do so if there is at least one usable
-                                * join clause.
-                                */
-                               foreach(i, old_rel->joininfo)
-                               {
-                                       JoinInfo   *joininfo = (JoinInfo *) lfirst(i);
+                               RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
 
-                                       if (is_subseti(joininfo->unjoined_relids, new_rel->relids))
+                               if (nonoverlap_setsi(old_rel->relids, new_rel->relids))
+                               {
+                                       List       *i;
+
+                                       /*
+                                        * OK, we can build a rel of the right level from this
+                                        * pair of rels.  Do so if there is at least one usable
+                                        * join clause.
+                                        */
+                                       foreach(i, old_rel->joininfo)
                                        {
-                                               make_join_rel(root, old_rel, new_rel);
-                                               break;
+                                               JoinInfo   *joininfo = (JoinInfo *) lfirst(i);
+
+                                               if (is_subseti(joininfo->unjoined_relids,
+                                                                          new_rel->relids))
+                                               {
+                                                       RelOptInfo *jrel;
+
+                                                       jrel = make_join_rel(root, old_rel, new_rel,
+                                                                                                JOIN_INNER);
+                                                       /* Avoid making duplicate entries ... */
+                                                       if (!ptrMember(jrel, result_rels))
+                                                               result_rels = lcons(jrel, result_rels);
+                                                       break; /* need not consider more joininfos */
+                                               }
                                        }
                                }
                        }
@@ -174,39 +190,41 @@ make_rels_by_joins(Query *root, int level)
         * no choice but to make cartesian joins.  We consider only left-sided
         * and right-sided cartesian joins in this case (no bushy).
         */
-       if (root->join_rel_list == first_old_rel)
+       if (result_rels == NIL)
        {
                /* This loop is just like the first one, except we always call
                 * make_rels_by_clauseless_joins().
                 */
-               if (level == 2)
-                       r = root->base_rel_list; /* level-1 is base rels */
-               else
-                       r = root->join_rel_list;
-               for (; r != NIL; r = lnext(r))
+               foreach(r, joinrels[level-1])
                {
                        RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
-                       int                     old_level = length(old_rel->relids);
                        List       *other_rels;
 
-                       if (old_level != level - 1)
-                               break;
-
                        if (level == 2)
-                               other_rels = lnext(r); /* only consider remaining base
+                               other_rels = lnext(r); /* only consider remaining initial
                                                                                * rels */
                        else
-                               other_rels = root->base_rel_list; /* consider all base rels */
+                               other_rels = joinrels[1]; /* consider all initial rels */
+
+                       new_rels = make_rels_by_clauseless_joins(root,
+                                                                                                        old_rel,
+                                                                                                        other_rels);
+
+                       foreach(nr, new_rels)
+                       {
+                               RelOptInfo         *jrel = (RelOptInfo *) lfirst(nr);
 
-                       make_rels_by_clauseless_joins(root,
-                                                                                 old_rel,
-                                                                                 other_rels);
+                               if (!ptrMember(jrel, result_rels))
+                                       result_rels = lcons(jrel, result_rels);
+                       }
                }
 
-               if (root->join_rel_list == first_old_rel)
+               if (result_rels == NIL)
                        elog(ERROR, "make_rels_by_joins: failed to build any %d-way joins",
                                 level);
        }
+
+       return result_rels;
 }
 
 /*
@@ -214,28 +232,23 @@ make_rels_by_joins(Query *root, int level)
  *       Build joins between the given relation 'old_rel' and other relations
  *       that are mentioned within old_rel's joininfo nodes (i.e., relations
  *       that participate in join clauses that 'old_rel' also participates in).
- *       The join rel nodes are added to root->join_rel_list.
+ *       The join rel nodes are returned in a list.
  *
  * 'old_rel' is the relation entry for the relation to be joined
  * 'other_rels': other rels to be considered for joining
  *
- * Currently, this is only used with base rels in other_rels, but it would
- * work for joining to joinrels too, if the caller ensures there is no
+ * Currently, this is only used with initial rels in other_rels, but it
+ * will work for joining to joinrels too, if the caller ensures there is no
  * membership overlap between old_rel and the rels in other_rels.  (We need
- * no extra test for overlap for base rels, since the is_subset test can
+ * no extra test for overlap for initial rels, since the is_subset test can
  * only succeed when other_rel is not already part of old_rel.)
- *
- * Returns NULL if no suitable joins were found, else the last suitable
- * joinrel processed.  (The only caller who checks the return value is
- * geqo_eval.c, and it sets things up so there can be no more than one
- * "suitable" joinrel; so we don't bother with returning a list.)
  */
-RelOptInfo *
+List *
 make_rels_by_clause_joins(Query *root,
                                                  RelOptInfo *old_rel,
                                                  List *other_rels)
 {
-       RelOptInfo *result = NULL;
+       List       *result = NIL;
        List       *i,
                           *j;
 
@@ -249,7 +262,9 @@ make_rels_by_clause_joins(Query *root,
                        RelOptInfo *other_rel = (RelOptInfo *) lfirst(j);
 
                        if (is_subseti(unjoined_relids, other_rel->relids))
-                               result = make_join_rel(root, old_rel, other_rel);
+                               result = lcons(make_join_rel(root, old_rel, other_rel,
+                                                                                        JOIN_INNER),
+                                                          result);
                }
        }
 
@@ -261,24 +276,20 @@ make_rels_by_clause_joins(Query *root,
  *       Given a relation 'old_rel' and a list of other relations
  *       'other_rels', create a join relation between 'old_rel' and each
  *       member of 'other_rels' that isn't already included in 'old_rel'.
+ *       The join rel nodes are returned in a list.
  *
  * 'old_rel' is the relation entry for the relation to be joined
  * 'other_rels': other rels to be considered for joining
  *
- * Currently, this is only used with base rels in other_rels, but it would
+ * Currently, this is only used with initial rels in other_rels, but it would
  * work for joining to joinrels too.
- *
- * Returns NULL if no suitable joins were found, else the last suitable
- * joinrel processed.  (The only caller who checks the return value is
- * geqo_eval.c, and it sets things up so there can be no more than one
- * "suitable" joinrel; so we don't bother with returning a list.)
  */
-RelOptInfo *
+List *
 make_rels_by_clauseless_joins(Query *root,
                                                          RelOptInfo *old_rel,
                                                          List *other_rels)
 {
-       RelOptInfo *result = NULL;
+       List       *result = NIL;
        List       *i;
 
        foreach(i, other_rels)
@@ -286,13 +297,61 @@ make_rels_by_clauseless_joins(Query *root,
                RelOptInfo *other_rel = (RelOptInfo *) lfirst(i);
 
                if (nonoverlap_setsi(other_rel->relids, old_rel->relids))
-                       result = make_join_rel(root, old_rel, other_rel);
+                       result = lcons(make_join_rel(root, old_rel, other_rel,
+                                                                                JOIN_INNER),
+                                                  result);
        }
 
        return result;
 }
 
 
+/*
+ * make_rel_from_jointree
+ *             Find or build a RelOptInfojoin rel representing a specific
+ *             jointree item.  For JoinExprs, we only consider the construction
+ *             path that corresponds exactly to what the user wrote.
+ */
+RelOptInfo *
+make_rel_from_jointree(Query *root, Node *jtnode)
+{
+       if (IsA(jtnode, RangeTblRef))
+       {
+               int                     varno = ((RangeTblRef *) jtnode)->rtindex;
+
+               return get_base_rel(root, varno);
+       }
+       else if (IsA(jtnode, JoinExpr))
+       {
+               JoinExpr   *j = (JoinExpr *) jtnode;
+               RelOptInfo *rel,
+                                  *lrel,
+                                  *rrel;
+
+               /* Recurse */
+               lrel = make_rel_from_jointree(root, j->larg);
+               rrel = make_rel_from_jointree(root, j->rarg);
+
+               /* Make this join rel */
+               rel = make_join_rel(root, lrel, rrel, j->jointype);
+
+               /*
+                * Since we are only going to consider this one way to do it,
+                * we're done generating Paths for this joinrel and can now select
+                * the cheapest.  In fact we *must* do so now, since next level up
+                * will need it!
+                */
+               set_cheapest(rel);
+
+               return rel;
+       }
+       else
+               elog(ERROR, "make_rel_from_jointree: unexpected node type %d",
+                        nodeTag(jtnode));
+       return NULL;                            /* keep compiler quiet */
+}
+
+
 /*
  * make_join_rel
  *        Find or create a join RelOptInfo that represents the join of
@@ -300,10 +359,10 @@ make_rels_by_clauseless_joins(Query *root,
  *        created with the two rels as outer and inner rel.
  *        (The join rel may already contain paths generated from other
  *        pairs of rels that add up to the same set of base rels.)
- *        The join rel is stored in the query's join_rel_list.
  */
 static RelOptInfo *
-make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2)
+make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2,
+                         JoinType jointype)
 {
        RelOptInfo *joinrel;
        List       *restrictlist;
@@ -315,10 +374,39 @@ make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2)
        joinrel = get_join_rel(root, rel1, rel2, &restrictlist);
 
        /*
-        * We consider paths using each rel as both outer and inner.
+        * Consider paths using each rel as both outer and inner.
         */
-       add_paths_to_joinrel(root, joinrel, rel1, rel2, restrictlist);
-       add_paths_to_joinrel(root, joinrel, rel2, rel1, restrictlist);
+       switch (jointype)
+       {
+               case JOIN_INNER:
+                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_INNER,
+                                                                restrictlist);
+                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_INNER,
+                                                                restrictlist);
+                       break;
+               case JOIN_LEFT:
+                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_LEFT,
+                                                                restrictlist);
+                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_RIGHT,
+                                                                restrictlist);
+                       break;
+               case JOIN_FULL:
+                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_FULL,
+                                                                restrictlist);
+                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_FULL,
+                                                                restrictlist);
+                       break;
+               case JOIN_RIGHT:
+                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_RIGHT,
+                                                                restrictlist);
+                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_LEFT,
+                                                                restrictlist);
+                       break;
+               default:
+                       elog(ERROR, "make_join_rel: unsupported join type %d",
+                                (int) jointype);
+                       break;
+       }
 
        return joinrel;
 }
index 62a02836fec33778b76a0393aa581b644fd8e476..2a76f63eb7c3aa18037a62b4f1093d0f371a7410 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.40 2000/05/30 00:49:47 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.41 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -101,6 +101,7 @@ create_or_index_paths(Query *root,
 
                                /* This isn't a nestloop innerjoin, so: */
                                pathnode->joinrelids = NIL;             /* no join clauses here */
+                               pathnode->alljoinquals = false;
                                pathnode->rows = rel->rows;
 
                                best_or_subclause_indices(root,
index 6d7b67bee3df6a359f54bdc05a29ba4965ff527b..c6eccddab195fdc1059d5806eff3363495d8122c 100644 (file)
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.24 2000/08/08 15:41:31 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.25 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -694,8 +694,8 @@ find_mergeclauses_for_pathkeys(List *pathkeys, List *restrictinfos)
  *
  * 'mergeclauses' is a list of RestrictInfos for mergejoin clauses
  *                     that will be used in a merge join.
- * 'tlist' is a relation target list for either the inner or outer
- *                     side of the proposed join rel.  (Not actually needed anymore)
+ * 'rel' is the relation the pathkeys will apply to (ie, either the inner
+ *                     or outer side of the proposed join rel).
  *
  * Returns a pathkeys list that can be applied to the indicated relation.
  *
@@ -706,7 +706,7 @@ find_mergeclauses_for_pathkeys(List *pathkeys, List *restrictinfos)
 List *
 make_pathkeys_for_mergeclauses(Query *root,
                                                           List *mergeclauses,
-                                                          List *tlist)
+                                                          RelOptInfo *rel)
 {
        List       *pathkeys = NIL;
        List       *i;
@@ -722,30 +722,37 @@ make_pathkeys_for_mergeclauses(Query *root,
                Assert(restrictinfo->mergejoinoperator != InvalidOid);
 
                /*
-                * Find the key and sortop needed for this mergeclause.
-                *
-                * Both sides of the mergeclause should appear in one of the query's
-                * pathkey equivalence classes, so it doesn't matter which one we
-                * use here.
+                * Which key and sortop is needed for this relation?
                 */
                key = (Node *) get_leftop(restrictinfo->clause);
                sortop = restrictinfo->left_sortop;
+               if (!IsA(key, Var) ||
+                       !intMember(((Var *) key)->varno, rel->relids))
+               {
+                       key = (Node *) get_rightop(restrictinfo->clause);
+                       sortop = restrictinfo->right_sortop;
+                       if (!IsA(key, Var) ||
+                               !intMember(((Var *) key)->varno, rel->relids))
+                               elog(ERROR, "make_pathkeys_for_mergeclauses: can't identify which side of mergeclause to use");
+               }
 
                /*
-                * Find pathkey sublist for this sort item.  We expect to find the
-                * canonical set including the mergeclause's left and right sides;
-                * if we get back just the one item, something is rotten.
+                * Find or create canonical pathkey sublist for this sort item.
                 */
                item = makePathKeyItem(key, sortop);
                pathkey = make_canonical_pathkey(root, item);
-               Assert(length(pathkey) > 1);
 
                /*
-                * Since the item we just made is not in the returned canonical
-                * set, we can free it --- this saves a useful amount of storage
-                * in a big join tree.
+                * Most of the time we will get back a canonical pathkey set
+                * including both the mergeclause's left and right sides (the only
+                * case where we don't is if the mergeclause appeared in an OUTER
+                * JOIN, which causes us not to generate an equijoin set from it).
+                * Therefore, most of the time the item we just made is not part
+                * of the returned structure, and we can free it.  This check
+                * saves a useful amount of storage in a big join tree.
                 */
-               pfree(item);
+               if (item != (PathKeyItem *) lfirst(pathkey))
+                       pfree(item);
 
                pathkeys = lappend(pathkeys, pathkey);
        }
index c049f5d86b6a6af4295632b73403c4ac5bdb70f3..96dc3327b7ff052cf15cdb3b5cc5267ce3c1f82a 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.95 2000/08/13 02:50:06 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.96 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,36 +42,47 @@ static IndexScan *create_indexscan_node(Query *root, IndexPath *best_path,
 static TidScan *create_tidscan_node(TidPath *best_path, List *tlist,
                                        List *scan_clauses);
 static NestLoop *create_nestloop_node(NestPath *best_path, List *tlist,
-                                        List *clauses, Plan *outer_node, List *outer_tlist,
-                                        Plan *inner_node, List *inner_tlist);
+                                                                         List *joinclauses, List *otherclauses,
+                                                                         Plan *outer_node, List *outer_tlist,
+                                                                         Plan *inner_node, List *inner_tlist);
 static MergeJoin *create_mergejoin_node(MergePath *best_path, List *tlist,
-                                         List *clauses, Plan *outer_node, List *outer_tlist,
-                                         Plan *inner_node, List *inner_tlist);
+                                                                               List *joinclauses, List *otherclauses,
+                                                                               Plan *outer_node, List *outer_tlist,
+                                                                               Plan *inner_node, List *inner_tlist);
 static HashJoin *create_hashjoin_node(HashPath *best_path, List *tlist,
-                                        List *clauses, Plan *outer_node, List *outer_tlist,
-                                        Plan *inner_node, List *inner_tlist);
+                                                                         List *joinclauses, List *otherclauses,
+                                                                         Plan *outer_node, List *outer_tlist,
+                                                                         Plan *inner_node, List *inner_tlist);
 static List *fix_indxqual_references(List *indexquals, IndexPath *index_path);
 static List *fix_indxqual_sublist(List *indexqual, int baserelid, Oid relam,
                                         Form_pg_index index);
 static Node *fix_indxqual_operand(Node *node, int baserelid,
                                         Form_pg_index index,
                                         Oid *opclass);
+static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
 static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
                           List *indxid, List *indxqual,
                           List *indxqualorig,
                           ScanDirection indexscandir);
 static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
                         List *tideval);
-static NestLoop *make_nestloop(List *qptlist, List *qpqual, Plan *lefttree,
-                         Plan *righttree);
-static HashJoin *make_hashjoin(List *tlist, List *qpqual,
-                         List *hashclauses, Plan *lefttree, Plan *righttree);
+static NestLoop *make_nestloop(List *tlist,
+                                                          List *joinclauses, List *otherclauses,
+                                                          Plan *lefttree, Plan *righttree,
+                                                          JoinType jointype);
+static HashJoin *make_hashjoin(List *tlist,
+                                                          List *joinclauses, List *otherclauses,
+                                                          List *hashclauses,
+                                                          Plan *lefttree, Plan *righttree,
+                                                          JoinType jointype);
 static Hash *make_hash(List *tlist, Node *hashkey, Plan *lefttree);
-static MergeJoin *make_mergejoin(List *tlist, List *qpqual,
-                          List *mergeclauses, Plan *righttree, Plan *lefttree);
+static MergeJoin *make_mergejoin(List *tlist,
+                                                                List *joinclauses, List *otherclauses,
+                                                                List *mergeclauses,
+                                                                Plan *lefttree, Plan *righttree,
+                                                                JoinType jointype);
 static void copy_path_costsize(Plan *dest, Path *src);
 static void copy_plan_costsize(Plan *dest, Plan *src);
-static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
 
 /*
  * create_plan
@@ -195,7 +206,8 @@ create_join_node(Query *root, JoinPath *best_path, List *tlist)
        List       *outer_tlist;
        Plan       *inner_node;
        List       *inner_tlist;
-       List       *clauses;
+       List       *joinclauses;
+       List       *otherclauses;
        Join       *retval = NULL;
 
        outer_node = create_plan(root, best_path->outerjoinpath);
@@ -204,14 +216,25 @@ create_join_node(Query *root, JoinPath *best_path, List *tlist)
        inner_node = create_plan(root, best_path->innerjoinpath);
        inner_tlist = inner_node->targetlist;
 
-       clauses = get_actual_clauses(best_path->joinrestrictinfo);
+       if (IS_OUTER_JOIN(best_path->jointype))
+       {
+               get_actual_join_clauses(best_path->joinrestrictinfo,
+                                                               &joinclauses, &otherclauses);
+       }
+       else
+       {
+               /* We can treat all clauses alike for an inner join */
+               joinclauses = get_actual_clauses(best_path->joinrestrictinfo);
+               otherclauses = NIL;
+       }
 
        switch (best_path->path.pathtype)
        {
                case T_MergeJoin:
                        retval = (Join *) create_mergejoin_node((MergePath *) best_path,
                                                                                                        tlist,
-                                                                                                       clauses,
+                                                                                                       joinclauses,
+                                                                                                       otherclauses,
                                                                                                        outer_node,
                                                                                                        outer_tlist,
                                                                                                        inner_node,
@@ -220,7 +243,8 @@ create_join_node(Query *root, JoinPath *best_path, List *tlist)
                case T_HashJoin:
                        retval = (Join *) create_hashjoin_node((HashPath *) best_path,
                                                                                                   tlist,
-                                                                                                  clauses,
+                                                                                                  joinclauses,
+                                                                                                  otherclauses,
                                                                                                   outer_node,
                                                                                                   outer_tlist,
                                                                                                   inner_node,
@@ -229,7 +253,8 @@ create_join_node(Query *root, JoinPath *best_path, List *tlist)
                case T_NestLoop:
                        retval = (Join *) create_nestloop_node((NestPath *) best_path,
                                                                                                   tlist,
-                                                                                                  clauses,
+                                                                                                  joinclauses,
+                                                                                                  otherclauses,
                                                                                                   outer_node,
                                                                                                   outer_tlist,
                                                                                                   inner_node,
@@ -411,30 +436,6 @@ create_indexscan_node(Query *root,
        return scan_node;
 }
 
-static TidScan *
-make_tidscan(List *qptlist,
-                        List *qpqual,
-                        Index scanrelid,
-                        List *tideval)
-{
-       TidScan    *node = makeNode(TidScan);
-       Plan       *plan = &node->scan.plan;
-
-       /* cost should be inserted by caller */
-       plan->state = (EState *) NULL;
-       plan->targetlist = qptlist;
-       plan->qual = qpqual;
-       plan->lefttree = NULL;
-       plan->righttree = NULL;
-       node->scan.scanrelid = scanrelid;
-       node->tideval = copyObject(tideval);            /* XXX do we really need a
-                                                                                                * copy? */
-       node->needRescan = false;
-       node->scan.scanstate = (CommonScanState *) NULL;
-
-       return node;
-}
-
 /*
  * create_tidscan_node
  *      Returns a tidscan node for the base relation scanned by 'best_path'
@@ -488,7 +489,8 @@ create_tidscan_node(TidPath *best_path, List *tlist, List *scan_clauses)
 static NestLoop *
 create_nestloop_node(NestPath *best_path,
                                         List *tlist,
-                                        List *clauses,
+                                        List *joinclauses,
+                                        List *otherclauses,
                                         Plan *outer_node,
                                         List *outer_tlist,
                                         Plan *inner_node,
@@ -535,7 +537,8 @@ create_nestloop_node(NestPath *best_path,
                         * attnos, and may have been commuted as well).
                         */
                        if (length(indxqualorig) == 1)          /* single indexscan? */
-                               clauses = set_difference(clauses, lfirst(indxqualorig));
+                               joinclauses = set_difference(joinclauses,
+                                                                                        lfirst(indxqualorig));
 
                        /* only refs to outer vars get changed in the inner indexqual */
                        innerscan->indxqualorig = join_references(indxqualorig,
@@ -577,15 +580,26 @@ create_nestloop_node(NestPath *best_path,
                                                                                        inner_node);
        }
 
+       /*
+        * Set quals to contain INNER/OUTER var references.
+        */
+       joinclauses = join_references(joinclauses,
+                                                                 outer_tlist,
+                                                                 inner_tlist,
+                                                                 (Index) 0);
+       otherclauses = join_references(otherclauses,
+                                                                  outer_tlist,
+                                                                  inner_tlist,
+                                                                  (Index) 0);
+
        join_node = make_nestloop(tlist,
-                                                         join_references(clauses,
-                                                                                         outer_tlist,
-                                                                                         inner_tlist,
-                                                                                         (Index) 0),
+                                                         joinclauses,
+                                                         otherclauses,
                                                          outer_node,
-                                                         inner_node);
+                                                         inner_node,
+                                                         best_path->jointype);
 
-       copy_path_costsize(&join_node->join, &best_path->path);
+       copy_path_costsize(&join_node->join.plan, &best_path->path);
 
        return join_node;
 }
@@ -593,14 +607,14 @@ create_nestloop_node(NestPath *best_path,
 static MergeJoin *
 create_mergejoin_node(MergePath *best_path,
                                          List *tlist,
-                                         List *clauses,
+                                         List *joinclauses,
+                                         List *otherclauses,
                                          Plan *outer_node,
                                          List *outer_tlist,
                                          Plan *inner_node,
                                          List *inner_tlist)
 {
-       List       *qpqual,
-                          *mergeclauses;
+       List       *mergeclauses;
        MergeJoin  *join_node;
 
        mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
@@ -610,10 +624,18 @@ create_mergejoin_node(MergePath *best_path,
         * the list of quals that must be checked as qpquals. Set those
         * clauses to contain INNER/OUTER var references.
         */
-       qpqual = join_references(set_difference(clauses, mergeclauses),
-                                                        outer_tlist,
-                                                        inner_tlist,
-                                                        (Index) 0);
+       joinclauses = join_references(set_difference(joinclauses, mergeclauses),
+                                                                 outer_tlist,
+                                                                 inner_tlist,
+                                                                 (Index) 0);
+
+       /*
+        * Fix the additional qpquals too.
+        */
+       otherclauses = join_references(otherclauses,
+                                                                  outer_tlist,
+                                                                  inner_tlist,
+                                                                  (Index) 0);
 
        /*
         * Now set the references in the mergeclauses and rearrange them so
@@ -640,13 +662,54 @@ create_mergejoin_node(MergePath *best_path,
                                                                        inner_node,
                                                                        best_path->innersortkeys);
 
+       /*
+        * The executor requires the inner side of a mergejoin to support "mark"
+        * and "restore" operations.  Not all plan types do, so we must be careful
+        * not to generate an invalid plan.  If necessary, an invalid inner plan
+        * can be handled by inserting a Materialize node.
+        *
+        * Since the inner side must be ordered, and only Sorts and IndexScans can
+        * create order to begin with, you might think there's no problem --- but
+        * you'd be wrong.  Nestloop and merge joins can *preserve* the order of
+        * their inputs, so they can be selected as the input of a mergejoin,
+        * and that won't work in the present executor.
+        *
+        * Doing this here is a bit of a kluge since the cost of the Materialize
+        * wasn't taken into account in our earlier decisions.  But Materialize
+        * is hard to estimate a cost for, and the above consideration shows that
+        * this is a rare case anyway, so this seems an acceptable way to proceed.
+        *
+        * This check must agree with ExecMarkPos/ExecRestrPos in
+        * executor/execAmi.c!
+        */
+       switch (nodeTag(inner_node))
+       {
+               case T_SeqScan:
+               case T_IndexScan:
+               case T_Material:
+               case T_Sort:
+                       /* OK, these inner plans support mark/restore */
+                       break;
+
+               default:
+                       /* Ooops, need to materialize the inner plan */
+                       inner_node = (Plan *) make_material(inner_tlist,
+                                                                                               inner_node);
+                       break;
+       }
+
+       /*
+        * Now we can build the mergejoin node.
+        */
        join_node = make_mergejoin(tlist,
-                                                          qpqual,
+                                                          joinclauses,
+                                                          otherclauses,
                                                           mergeclauses,
+                                                          outer_node,
                                                           inner_node,
-                                                          outer_node);
+                                                          best_path->jpath.jointype);
 
-       copy_path_costsize(&join_node->join, &best_path->jpath.path);
+       copy_path_costsize(&join_node->join.plan, &best_path->jpath.path);
 
        return join_node;
 }
@@ -654,13 +717,13 @@ create_mergejoin_node(MergePath *best_path,
 static HashJoin *
 create_hashjoin_node(HashPath *best_path,
                                         List *tlist,
-                                        List *clauses,
+                                        List *joinclauses,
+                                        List *otherclauses,
                                         Plan *outer_node,
                                         List *outer_tlist,
                                         Plan *inner_node,
                                         List *inner_tlist)
 {
-       List       *qpqual;
        List       *hashclauses;
        HashJoin   *join_node;
        Hash       *hash_node;
@@ -679,10 +742,18 @@ create_hashjoin_node(HashPath *best_path,
         * the list of quals that must be checked as qpquals. Set those
         * clauses to contain INNER/OUTER var references.
         */
-       qpqual = join_references(set_difference(clauses, hashclauses),
-                                                        outer_tlist,
-                                                        inner_tlist,
-                                                        (Index) 0);
+       joinclauses = join_references(set_difference(joinclauses, hashclauses),
+                                                                 outer_tlist,
+                                                                 inner_tlist,
+                                                                 (Index) 0);
+
+       /*
+        * Fix the additional qpquals too.
+        */
+       otherclauses = join_references(otherclauses,
+                                                                  outer_tlist,
+                                                                  inner_tlist,
+                                                                  (Index) 0);
 
        /*
         * Now set the references in the hashclauses and rearrange them so
@@ -701,12 +772,14 @@ create_hashjoin_node(HashPath *best_path,
         */
        hash_node = make_hash(inner_tlist, innerhashkey, inner_node);
        join_node = make_hashjoin(tlist,
-                                                         qpqual,
+                                                         joinclauses,
+                                                         otherclauses,
                                                          hashclauses,
                                                          outer_node,
-                                                         (Plan *) hash_node);
+                                                         (Plan *) hash_node,
+                                                         best_path->jpath.jointype);
 
-       copy_path_costsize(&join_node->join, &best_path->jpath.path);
+       copy_path_costsize(&join_node->join.plan, &best_path->jpath.path);
 
        return join_node;
 }
@@ -1065,45 +1138,75 @@ make_indexscan(List *qptlist,
        return node;
 }
 
+static TidScan *
+make_tidscan(List *qptlist,
+                        List *qpqual,
+                        Index scanrelid,
+                        List *tideval)
+{
+       TidScan    *node = makeNode(TidScan);
+       Plan       *plan = &node->scan.plan;
+
+       /* cost should be inserted by caller */
+       plan->state = (EState *) NULL;
+       plan->targetlist = qptlist;
+       plan->qual = qpqual;
+       plan->lefttree = NULL;
+       plan->righttree = NULL;
+       node->scan.scanrelid = scanrelid;
+       node->tideval = copyObject(tideval);            /* XXX do we really need a
+                                                                                                * copy? */
+       node->needRescan = false;
+       node->scan.scanstate = (CommonScanState *) NULL;
+
+       return node;
+}
+
 
 static NestLoop *
-make_nestloop(List *qptlist,
-                         List *qpqual,
+make_nestloop(List *tlist,
+                         List *joinclauses,
+                         List *otherclauses,
                          Plan *lefttree,
-                         Plan *righttree)
+                         Plan *righttree,
+                         JoinType jointype)
 {
        NestLoop   *node = makeNode(NestLoop);
-       Plan       *plan = &node->join;
+       Plan       *plan = &node->join.plan;
 
        /* cost should be inserted by caller */
        plan->state = (EState *) NULL;
-       plan->targetlist = qptlist;
-       plan->qual = qpqual;
+       plan->targetlist = tlist;
+       plan->qual = otherclauses;
        plan->lefttree = lefttree;
        plan->righttree = righttree;
-       node->nlstate = (NestLoopState *) NULL;
+       node->join.jointype = jointype;
+       node->join.joinqual = joinclauses;
 
        return node;
 }
 
 static HashJoin *
 make_hashjoin(List *tlist,
-                         List *qpqual,
+                         List *joinclauses,
+                         List *otherclauses,
                          List *hashclauses,
                          Plan *lefttree,
-                         Plan *righttree)
+                         Plan *righttree,
+                         JoinType jointype)
 {
        HashJoin   *node = makeNode(HashJoin);
-       Plan       *plan = &node->join;
+       Plan       *plan = &node->join.plan;
 
        /* cost should be inserted by caller */
        plan->state = (EState *) NULL;
        plan->targetlist = tlist;
-       plan->qual = qpqual;
+       plan->qual = otherclauses;
        plan->lefttree = lefttree;
        plan->righttree = righttree;
        node->hashclauses = hashclauses;
-       node->hashdone = false;
+       node->join.jointype = jointype;
+       node->join.joinqual = joinclauses;
 
        return node;
 }
@@ -1133,21 +1236,25 @@ make_hash(List *tlist, Node *hashkey, Plan *lefttree)
 
 static MergeJoin *
 make_mergejoin(List *tlist,
-                          List *qpqual,
+                          List *joinclauses,
+                          List *otherclauses,
                           List *mergeclauses,
+                          Plan *lefttree,
                           Plan *righttree,
-                          Plan *lefttree)
+                          JoinType jointype)
 {
        MergeJoin  *node = makeNode(MergeJoin);
-       Plan       *plan = &node->join;
+       Plan       *plan = &node->join.plan;
 
        /* cost should be inserted by caller */
        plan->state = (EState *) NULL;
        plan->targetlist = tlist;
-       plan->qual = qpqual;
+       plan->qual = otherclauses;
        plan->lefttree = lefttree;
        plan->righttree = righttree;
        node->mergeclauses = mergeclauses;
+       node->join.jointype = jointype;
+       node->join.joinqual = joinclauses;
 
        return node;
 }
index 8ffd35c9bb04f4479cfd72fd2178e001f01e1209..bf728ca1bdc51ba97ccd7e6ef6bc01585dd4f7e8 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.49 2000/08/13 02:50:07 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.50 2000/09/12 21:06:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "optimizer/planmain.h"
 #include "optimizer/tlist.h"
 #include "optimizer/var.h"
+#include "parser/parsetree.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_oper.h"
 #include "parser/parse_type.h"
 #include "utils/lsyscache.h"
 
 
-static void add_restrict_and_join_to_rel(Query *root, Node *clause);
+static void mark_baserels_for_outer_join(Query *root, Relids rels,
+                                                                                Relids outerrels);
+static void add_restrict_and_join_to_rel(Query *root, Node *clause,
+                                                                                bool isjoinqual,
+                                                                                Relids outerjoinrelids);
 static void add_join_info_to_rels(Query *root, RestrictInfo *restrictinfo,
                                          Relids join_relids);
 static void add_vars_to_targetlist(Query *root, List *vars);
@@ -47,14 +52,14 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  *****************************************************************************/
 
 /*
- * make_var_only_tlist
+ * build_base_rel_tlists
  *       Creates rel nodes for every relation mentioned in the target list
  *       'tlist' (if a node hasn't already been created) and adds them to
- *       *query_relation_list*.  Creates targetlist entries for each member of
- *       'tlist' and adds them to the tlist field of the appropriate rel node.
+ *       root->base_rel_list.  Creates targetlist entries for each var seen
+ *       in 'tlist' and adds them to the tlist of the appropriate rel node.
  */
 void
-make_var_only_tlist(Query *root, List *tlist)
+build_base_rel_tlists(Query *root, List *tlist)
 {
        List       *tlist_vars = pull_var_clause((Node *) tlist, false);
 
@@ -82,48 +87,75 @@ add_vars_to_targetlist(Query *root, List *vars)
        }
 }
 
-/*
+/*----------
  * add_missing_rels_to_query
  *
- *       If we have a range variable in the FROM clause that does not appear
+ *       If we have a relation listed in the join tree that does not appear
  *       in the target list nor qualifications, we must add it to the base
- *       relation list so that it will be joined.      For instance, "select f.x
- *       from foo f, foo f2" is a join of f and f2.  Note that if we have
- *       "select foo.x from foo f", it also gets turned into a join (between
- *       foo as foo and foo as f).
+ *       relation list so that it can be processed.  For instance,
+ *                     select f.x from foo f, foo f2
+ *       is a join of f and f2.  Note that if we have
+ *                     select foo.x from foo f
+ *       this also gets turned into a join (between foo as foo and foo as f).
  *
  *       To avoid putting useless entries into the per-relation targetlists,
  *       this should only be called after all the variables in the targetlist
  *       and quals have been processed by the routines above.
+ *
+ *       Returns a list of all the base relations (RelOptInfo nodes) that appear
+ *       in the join tree.  This list can be used for cross-checking in the
+ *       reverse direction, ie, that we have a join tree entry for every
+ *       relation used in the query.
+ *----------
  */
-void
-add_missing_rels_to_query(Query *root)
+List *
+add_missing_rels_to_query(Query *root, Node *jtnode)
 {
-       int                     varno = 1;
-       List       *l;
+       List       *result = NIL;
 
-       foreach(l, root->rtable)
+       if (jtnode == NULL)
+               return NIL;
+       if (IsA(jtnode, List))
        {
-               RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+               List       *l;
 
-               if (rte->inJoinSet)
+               foreach(l, (List *) jtnode)
                {
-                       RelOptInfo *rel = get_base_rel(root, varno);
+                       result = nconc(result,
+                                                  add_missing_rels_to_query(root, lfirst(l)));
+               }
+       }
+       else if (IsA(jtnode, RangeTblRef))
+       {
+               int                     varno = ((RangeTblRef *) jtnode)->rtindex;
+               RelOptInfo *rel = get_base_rel(root, varno);
 
-                       /*
-                        * If the rel isn't otherwise referenced, give it a dummy
-                        * targetlist consisting of its own OID.
-                        */
-                       if (rel->targetlist == NIL)
-                       {
-                               Var                *var = makeVar(varno, ObjectIdAttributeNumber,
-                                                                                 OIDOID, -1, 0);
+               /*
+                * If the rel isn't otherwise referenced, give it a dummy
+                * targetlist consisting of its own OID.
+                */
+               if (rel->targetlist == NIL)
+               {
+                       Var                *var = makeVar(varno, ObjectIdAttributeNumber,
+                                                                         OIDOID, -1, 0);
 
-                               add_var_to_tlist(rel, var);
-                       }
+                       add_var_to_tlist(rel, var);
                }
-               varno++;
+
+               result = lcons(rel, NIL);
        }
+       else if (IsA(jtnode, JoinExpr))
+       {
+               JoinExpr   *j = (JoinExpr *) jtnode;
+
+               result = add_missing_rels_to_query(root, j->larg);
+               result = nconc(result,
+                                          add_missing_rels_to_query(root, j->rarg));
+       }
+       else
+               elog(ERROR, "add_missing_rels_to_query: unexpected node type %d",
+                        nodeTag(jtnode));
+       return result;
 }
 
 
@@ -134,11 +166,145 @@ add_missing_rels_to_query(Query *root)
  *****************************************************************************/
 
 
+/*
+ * add_join_quals_to_rels
+ *       Recursively scan the join tree for JOIN/ON (and JOIN/USING) qual
+ *       clauses, and add these to the appropriate JoinInfo lists.  Also,
+ *       mark base RelOptInfos with outerjoinset information, which will
+ *       be needed for proper placement of WHERE clauses during
+ *       add_restrict_and_join_to_rels().
+ *
+ * NOTE: when dealing with inner joins, it is appropriate to let a qual clause
+ * be evaluated at the lowest level where all the variables it mentions are
+ * available.  However, we cannot do this within an outer join since the qual
+ * might eliminate matching rows and cause a NULL row to be added improperly.
+ * Therefore, rels appearing within (the nullable side of) an outer join
+ * are marked with outerjoinset = list of Relids used at the outer join node.
+ * This list will be added to the list of rels referenced by quals using
+ * such a rel, thereby forcing them up the join tree to the right level.
+ *
+ * To ease the calculation of these values, add_join_quals_to_rels() returns
+ * the list of Relids involved in its own level of join.  This is just an
+ * internal convenience; no outside callers pay attention to the result.
+ */
+Relids
+add_join_quals_to_rels(Query *root, Node *jtnode)
+{
+       Relids          result = NIL;
+
+       if (jtnode == NULL)
+               return result;
+       if (IsA(jtnode, List))
+       {
+               List       *l;
+
+               /*
+                * Note: we assume it's impossible to see same RT index from more
+                * than one subtree, so nconc() is OK rather than LispUnioni().
+                */
+               foreach(l, (List *) jtnode)
+                       result = nconc(result,
+                                                  add_join_quals_to_rels(root, lfirst(l)));
+       }
+       else if (IsA(jtnode, RangeTblRef))
+       {
+               int                     varno = ((RangeTblRef *) jtnode)->rtindex;
+
+               /* No quals to deal with, just return correct result */
+               result = lconsi(varno, NIL);
+       }
+       else if (IsA(jtnode, JoinExpr))
+       {
+               JoinExpr   *j = (JoinExpr *) jtnode;
+               Relids          leftids,
+                                       rightids,
+                                       outerjoinids;
+               List       *qual;
+
+               /*
+                * Order of operations here is subtle and critical.  First we recurse
+                * to handle sub-JOINs.  Their join quals will be placed without
+                * regard for whether this level is an outer join, which is correct.
+                * Then, if we are an outer join, we mark baserels contained within
+                * the nullable side(s) with our own rel list; this will restrict
+                * placement of subsequent quals using those rels, including our own
+                * quals, quals above us in the join tree, and WHERE quals.
+                * Finally we place our own join quals.
+                */
+               leftids = add_join_quals_to_rels(root, j->larg);
+               rightids = add_join_quals_to_rels(root, j->rarg);
+
+               result = nconc(listCopy(leftids), rightids);
+
+               outerjoinids = NIL;
+               switch (j->jointype)
+               {
+                       case JOIN_INNER:
+                               /* Inner join adds no restrictions for quals */
+                               break;
+                       case JOIN_LEFT:
+                               mark_baserels_for_outer_join(root, rightids, result);
+                               outerjoinids = result;
+                               break;
+                       case JOIN_FULL:
+                               mark_baserels_for_outer_join(root, result, result);
+                               outerjoinids = result;
+                               break;
+                       case JOIN_RIGHT:
+                               mark_baserels_for_outer_join(root, leftids, result);
+                               outerjoinids = result;
+                               break;
+                       case JOIN_UNION:
+                               /*
+                                * This is where we fail if upper levels of planner haven't
+                                * rewritten UNION JOIN as an Append ...
+                                */
+                               elog(ERROR, "UNION JOIN is not implemented yet");
+                               break;
+                       default:
+                               elog(ERROR, "add_join_quals_to_rels: unsupported join type %d",
+                                        (int) j->jointype);
+                               break;
+               }
+
+               foreach(qual, (List *) j->quals)
+                       add_restrict_and_join_to_rel(root, (Node *) lfirst(qual),
+                                                                                true, outerjoinids);
+       }
+       else
+               elog(ERROR, "add_join_quals_to_rels: unexpected node type %d",
+                        nodeTag(jtnode));
+       return result;
+}
+
+/*
+ * mark_baserels_for_outer_join
+ *       Mark all base rels listed in 'rels' as having the given outerjoinset.
+ */
+static void
+mark_baserels_for_outer_join(Query *root, Relids rels, Relids outerrels)
+{
+       List       *relid;
+
+       foreach(relid, rels)
+       {
+               RelOptInfo *rel = get_base_rel(root, lfirsti(relid));
+
+               /*
+                * Since we do this bottom-up, any outer-rels previously marked
+                * should be within the new outer join set.
+                */
+               Assert(is_subseti(rel->outerjoinset, outerrels));
+
+               rel->outerjoinset = outerrels;
+       }
+}
+
 /*
  * add_restrict_and_join_to_rels
  *       Fill RestrictInfo and JoinInfo lists of relation entries for all
  *       relations appearing within clauses.  Creates new relation entries if
- *       necessary, adding them to *query_relation_list*.
+ *       necessary, adding them to root->base_rel_list.
  *
  * 'clauses': the list of clauses in the cnfify'd query qualification.
  */
@@ -148,7 +314,8 @@ add_restrict_and_join_to_rels(Query *root, List *clauses)
        List       *clause;
 
        foreach(clause, clauses)
-               add_restrict_and_join_to_rel(root, (Node *) lfirst(clause));
+               add_restrict_and_join_to_rel(root, (Node *) lfirst(clause),
+                                                                        false, NIL);
 }
 
 /*
@@ -157,17 +324,31 @@ add_restrict_and_join_to_rels(Query *root, List *clauses)
  *       (depending on whether the clause is a join) of each base relation
  *       mentioned in the clause.      A RestrictInfo node is created and added to
  *       the appropriate list for each rel.  Also, if the clause uses a
- *       mergejoinable operator, enter the left- and right-side expressions
- *       into the query's lists of equijoined vars.
+ *       mergejoinable operator and is not an outer-join qual, enter the left-
+ *       and right-side expressions into the query's lists of equijoined vars.
+ *
+ * isjoinqual is true if the clause came from JOIN/ON or JOIN/USING;
+ * we have to mark the created RestrictInfo accordingly.  If the JOIN
+ * is an OUTER join, the caller must set outerjoinrelids = all relids of join,
+ * which will override the joinrel identifiers extracted from the clause
+ * itself.  For inner join quals and WHERE clauses, set outerjoinrelids = NIL.
+ * (Passing the whole list, and not just an "isouterjoin" boolean, is simply
+ * a speed optimization: we could extract the same list from the base rels'
+ * outerjoinsets, but since add_join_quals_to_rels() already knows what we
+ * should use, might as well pass it in instead of recalculating it.)
  */
 static void
-add_restrict_and_join_to_rel(Query *root, Node *clause)
+add_restrict_and_join_to_rel(Query *root, Node *clause,
+                                                        bool isjoinqual,
+                                                        Relids outerjoinrelids)
 {
        RestrictInfo *restrictinfo = makeNode(RestrictInfo);
        Relids          relids;
        List       *vars;
+       bool            can_be_equijoin;
 
        restrictinfo->clause = (Expr *) clause;
+       restrictinfo->isjoinqual = isjoinqual;
        restrictinfo->subclauseindices = NIL;
        restrictinfo->mergejoinoperator = InvalidOid;
        restrictinfo->left_sortop = InvalidOid;
@@ -179,6 +360,44 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
         */
        clause_get_relids_vars(clause, &relids, &vars);
 
+       /*
+        * If caller has given us a join relid list, use it; otherwise, we must
+        * scan the referenced base rels and add in any outer-join rel lists.
+        * This prevents the clause from being applied at a lower level of joining
+        * than any OUTER JOIN that should be evaluated before it.
+        */
+       if (outerjoinrelids)
+       {
+               /* Safety check: parser should have enforced this to start with */
+               if (! is_subseti(relids, outerjoinrelids))
+                       elog(ERROR, "JOIN qualification may not refer to other relations");
+               relids = outerjoinrelids;
+               can_be_equijoin = false;
+       }
+       else
+       {
+               Relids          newrelids = relids;
+               List       *relid;
+
+               /* We rely on LispUnioni to be nondestructive of its input lists... */
+               can_be_equijoin = true;
+               foreach(relid, relids)
+               {
+                       RelOptInfo *rel = get_base_rel(root, lfirsti(relid));
+
+                       if (rel->outerjoinset)
+                       {
+                               newrelids = LispUnioni(newrelids, rel->outerjoinset);
+                               /*
+                                * Because application of the qual will be delayed by outer
+                                * join, we mustn't assume its vars are equal everywhere.
+                                */
+                               can_be_equijoin = false;
+                       }
+               }
+               relids = newrelids;
+       }
+
        if (length(relids) == 1)
        {
 
@@ -199,7 +418,8 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
                 * that "a.x = a.y AND a.x = b.z AND a.y = c.q" allows us to
                 * consider z and q equal after their rels are joined.
                 */
-               check_mergejoinable(restrictinfo);
+               if (can_be_equijoin)
+                       check_mergejoinable(restrictinfo);
        }
        else if (relids != NIL)
        {
@@ -209,11 +429,11 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
                 * the relid list.      Set additional RestrictInfo fields for
                 * joining.
                 *
-                * We need the merge info whether or not mergejoin is enabled (for
-                * constructing equijoined-var lists), but we don't bother setting
-                * hash info if hashjoin is disabled.
+                * We don't bother setting the merge/hashjoin info if we're not
+                * going to need it.
                 */
-               check_mergejoinable(restrictinfo);
+               if (enable_mergejoin || can_be_equijoin)
+                       check_mergejoinable(restrictinfo);
                if (enable_hashjoin)
                        check_hashjoinable(restrictinfo);
 
@@ -223,7