First cut at full support for OUTER JOINs. There are still a few loose
authorTom Lane
Tue, 12 Sep 2000 21:07:18 +0000 (21:07 +0000)
committerTom Lane
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="";
+
+                   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="";
-
-                       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 = "";
-               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] = "";
-               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 = "";
+           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] = "";
+           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 +443,7 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
        add_join_info_to_rels(root, restrictinfo, relids);
 
        /*
-        * Add vars used in the join clause to targetlists of member
+        * Add vars used in the join clause to targetlists of their
         * relations, so that they will be emitted by the plan nodes that
         * scan those relations (else they won't be available at the join
         * node!).
@@ -241,12 +461,14 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
    }
 
    /*
-    * If the clause has a mergejoinable operator, then the two sides
+    * If the clause has a mergejoinable operator, and is not an outer-join
+    * qualification nor bubbled up due to an outer join, then the two sides
     * represent equivalent PathKeyItems for path keys: any path that is
-    * sorted by one side will also be sorted by the other (after joining,
-    * that is).  Record the key equivalence for future use.
+    * sorted by one side will also be sorted by the other (as soon as the
+    * two rels are joined, that is).  Record the key equivalence for future
+    * use.
     */
-   if (restrictinfo->mergejoinoperator != InvalidOid)
+   if (can_be_equijoin && restrictinfo->mergejoinoperator != InvalidOid)
        add_equijoined_keys(root, restrictinfo);
 }
 
@@ -392,7 +614,8 @@ process_implied_equality(Query *root, Node *item1, Node *item2,
                                     BOOLOID); /* operator result type */
    clause->args = lcons(item1, lcons(item2, NIL));
 
-   add_restrict_and_join_to_rel(root, (Node *) clause);
+   add_restrict_and_join_to_rel(root, (Node *) clause,
+                                false, NIL);
 }
 
 
index abb468aa8d1dc9a3ed7002b402a79c2eb99120fa..1fcbe64e8880963bf2e7bae36aac481e83df2768 100644 (file)
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.58 2000/08/13 02:50:07 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.59 2000/09/12 21:06:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,6 +28,7 @@
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
 #include "optimizer/tlist.h"
+#include "parser/parsetree.h"
 #include "utils/memutils.h"
 
 
@@ -41,11 +42,8 @@ static Plan *subplanner(Query *root, List *flat_tlist, List *qual,
  *   not any fancier features.
  *
  * tlist is the target list the query should produce (NOT root->targetList!)
- * qual is the qualification of the query (likewise!)
  * tuple_fraction is the fraction of tuples we expect will be retrieved
  *
- * qual must already have been converted to implicit-AND form.
- *
  * Note: the Query node now also includes a query_pathkeys field, which
  * is both an input and an output of query_planner().  The input value
  * signals query_planner that the indicated sort order is wanted in the
@@ -75,9 +73,9 @@ static Plan *subplanner(Query *root, List *flat_tlist, List *qual,
 Plan *
 query_planner(Query *root,
              List *tlist,
-             List *qual,
              double tuple_fraction)
 {
+   List       *normal_qual;
    List       *noncachable_qual;
    List       *constant_qual;
    List       *var_only_tlist;
@@ -96,7 +94,7 @@ query_planner(Query *root,
        root->query_pathkeys = NIL;     /* signal unordered result */
 
        /* Make childless Result node to evaluate given tlist. */
-       return (Plan *) make_result(tlist, (Node *) qual, (Plan *) NULL);
+       return (Plan *) make_result(tlist, root->qual, (Plan *) NULL);
    }
 
    /*
@@ -111,10 +109,12 @@ query_planner(Query *root,
     * noncachable functions but no vars, such as "WHERE random() < 0.5".
     * These cannot be treated as normal restriction or join quals, but
     * they're not constants either.  Instead, attach them to the qpqual
-    * of the top-level plan, so that they get evaluated once per potential
+    * of the top plan, so that they get evaluated once per potential
     * output tuple.
     */
-   qual = pull_constant_clauses(qual, &noncachable_qual, &constant_qual);
+   normal_qual = pull_constant_clauses((List *) root->qual,
+                                       &noncachable_qual,
+                                       &constant_qual);
 
    /*
     * Create a target list that consists solely of (resdom var) target
@@ -132,7 +132,7 @@ query_planner(Query *root,
    /*
     * Choose the best access path and build a plan for it.
     */
-   subplan = subplanner(root, var_only_tlist, qual, tuple_fraction);
+   subplan = subplanner(root, var_only_tlist, normal_qual, tuple_fraction);
 
    /*
     * Handle the noncachable quals.
@@ -188,6 +188,8 @@ subplanner(Query *root,
           List *qual,
           double tuple_fraction)
 {
+   List       *joined_rels;
+   List       *brel;
    RelOptInfo *final_rel;
    Plan       *resultplan;
    MemoryContext mycontext;
@@ -196,7 +198,7 @@ subplanner(Query *root,
    Path       *presortedpath;
 
    /*
-    * Initialize the targetlist and qualification, adding entries to
+    * Examine the targetlist and qualifications, adding entries to
     * base_rel_list as relation references are found (e.g., in the
     * qualification, the targetlist, etc.).  Restrict and join clauses
     * are added to appropriate lists belonging to the mentioned
@@ -207,13 +209,29 @@ subplanner(Query *root,
    root->join_rel_list = NIL;
    root->equi_key_list = NIL;
 
-   make_var_only_tlist(root, flat_tlist);
+   build_base_rel_tlists(root, flat_tlist);
+   (void) add_join_quals_to_rels(root, (Node *) root->jointree);
+   /* this must happen after add_join_quals_to_rels: */
    add_restrict_and_join_to_rels(root, qual);
 
    /*
-    * Make sure we have RelOptInfo nodes for all relations used.
+    * Make sure we have RelOptInfo nodes for all relations to be joined.
+    */
+   joined_rels = add_missing_rels_to_query(root, (Node *) root->jointree);
+
+   /*
+    * Check that the join tree includes all the base relations used in
+    * the query --- otherwise, the parser or rewriter messed up.
     */
-   add_missing_rels_to_query(root);
+   foreach(brel, root->base_rel_list)
+   {
+       RelOptInfo *baserel = (RelOptInfo *) lfirst(brel);
+       int     relid = lfirsti(baserel->relids);
+
+       if (! ptrMember(baserel, joined_rels))
+           elog(ERROR, "Internal error: no jointree entry for rel %s (%d)",
+                rt_fetch(relid, root->rtable)->eref->relname, relid);
+   }
 
    /*
     * Use the completed lists of equijoined keys to deduce any implied
@@ -258,12 +276,11 @@ subplanner(Query *root,
         * We expect to end up here for a trivial INSERT ... VALUES query
         * (which will have a target relation, so it gets past
         * query_planner's check for empty range table; but the target rel
-        * is unreferenced and not marked inJoinSet, so we find there is
-        * nothing to join).
+        * is not in the join tree, so we find there is nothing to join).
         *
         * It's also possible to get here if the query was rewritten by the
-        * rule processor (creating rangetable entries not marked
-        * inJoinSet) but the rules either did nothing or were simplified
+        * rule processor (creating dummy rangetable entries that are not in
+        * the join tree) but the rules either did nothing or were simplified
         * to nothing by constant-expression folding.  So, don't complain.
         */
        root->query_pathkeys = NIL;     /* signal unordered result */
index 4be9b05bb900ee1ab7b7428c956b7ad33efa9f5b..7ffbb4666d9d588a7b9bbe2c8adc7445353b8d9b 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.88 2000/08/21 20:55:29 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.89 2000/09/12 21:06:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,6 +29,7 @@
 #include "utils/lsyscache.h"
 
 
+static void preprocess_join_conditions(Query *parse, Node *jtnode);
 static List *make_subplanTargetList(Query *parse, List *tlist,
                       AttrNumber **groupColIdx);
 static Plan *make_groupplan(List *group_tlist, bool tuplePerGroup,
@@ -163,6 +164,7 @@ subquery_planner(Query *parse, double tuple_fraction)
     * canonicalize_qual?
     */
    parse->qual = (Node *) canonicalize_qual((Expr *) parse->qual, true);
+
 #ifdef OPTIMIZER_DEBUG
    printf("After canonicalize_qual()\n");
    pprint(parse->qual);
@@ -211,6 +213,9 @@ subquery_planner(Query *parse, double tuple_fraction)
        parse->havingQual = SS_replace_correlation_vars(parse->havingQual);
    }
 
+   /* Do all the above for each qual condition (ON clause) in the join tree */
+   preprocess_join_conditions(parse, (Node *) parse->jointree);
+
    /* Do the main planning (potentially recursive) */
 
    return union_planner(parse, tuple_fraction);
@@ -224,6 +229,58 @@ subquery_planner(Query *parse, double tuple_fraction)
     */
 }
 
+/*
+ * preprocess_join_conditions
+ *     Recursively scan the query's jointree and do subquery_planner's
+ *     qual preprocessing work on each ON condition found therein.
+ */
+static void
+preprocess_join_conditions(Query *parse, Node *jtnode)
+{
+   if (jtnode == NULL)
+       return;
+   if (IsA(jtnode, List))
+   {
+       List       *l;
+
+       foreach(l, (List *) jtnode)
+           preprocess_join_conditions(parse, lfirst(l));
+   }
+   else if (IsA(jtnode, RangeTblRef))
+   {
+       /* nothing to do here */
+   }
+   else if (IsA(jtnode, JoinExpr))
+   {
+       JoinExpr   *j = (JoinExpr *) jtnode;
+
+       preprocess_join_conditions(parse, j->larg);
+       preprocess_join_conditions(parse, j->rarg);
+
+       /* Simplify constant expressions */
+       j->quals = eval_const_expressions(j->quals);
+
+       /* Canonicalize the qual, and convert it to implicit-AND format */
+       j->quals = (Node *) canonicalize_qual((Expr *) j->quals, true);
+
+       /* Expand SubLinks to SubPlans */
+       if (parse->hasSubLinks)
+       {
+           j->quals = SS_process_sublinks(j->quals);
+           /*
+            * ON conditions, like WHERE clauses, are evaluated pre-GROUP;
+            * so we allow ungrouped vars in them.
+            */
+       }
+
+       /* Replace uplevel vars with Param nodes */
+       if (PlannerQueryLevel > 1)
+           j->quals = SS_replace_correlation_vars(j->quals);
+   }
+   else
+       elog(ERROR, "preprocess_join_conditions: unexpected node type %d",
+            nodeTag(jtnode));
+}
 
 /*--------------------
  * union_planner
@@ -542,7 +599,6 @@ union_planner(Query *parse,
        /* Generate the (sub) plan */
        result_plan = query_planner(parse,
                                    sub_tlist,
-                                   (List *) parse->qual,
                                    tuple_fraction);
 
        /*
index d8a09c017dd0279e8ec5a23c3076e5beebd069f9..d30636c185e32b09b2dcb5cbb30ab2716c048f13 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.64 2000/06/04 20:50:50 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.65 2000/09/12 21:06:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -106,11 +106,13 @@ set_plan_references(Plan *plan)
            set_join_references((Join *) plan);
            fix_expr_references(plan, (Node *) plan->targetlist);
            fix_expr_references(plan, (Node *) plan->qual);
+           fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
            break;
        case T_MergeJoin:
            set_join_references((Join *) plan);
            fix_expr_references(plan, (Node *) plan->targetlist);
            fix_expr_references(plan, (Node *) plan->qual);
+           fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
            fix_expr_references(plan,
                                (Node *) ((MergeJoin *) plan)->mergeclauses);
            break;
@@ -118,6 +120,7 @@ set_plan_references(Plan *plan)
            set_join_references((Join *) plan);
            fix_expr_references(plan, (Node *) plan->targetlist);
            fix_expr_references(plan, (Node *) plan->qual);
+           fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
            fix_expr_references(plan,
                                (Node *) ((HashJoin *) plan)->hashclauses);
            break;
@@ -236,15 +239,15 @@ fix_expr_references(Plan *plan, Node *node)
 static void
 set_join_references(Join *join)
 {
-   Plan       *outer = join->lefttree;
-   Plan       *inner = join->righttree;
+   Plan       *outer = join->plan.lefttree;
+   Plan       *inner = join->plan.righttree;
    List       *outer_tlist = ((outer == NULL) ? NIL : outer->targetlist);
    List       *inner_tlist = ((inner == NULL) ? NIL : inner->targetlist);
 
-   join->targetlist = join_references(join->targetlist,
-                                      outer_tlist,
-                                      inner_tlist,
-                                      (Index) 0);
+   join->plan.targetlist = join_references(join->plan.targetlist,
+                                           outer_tlist,
+                                           inner_tlist,
+                                           (Index) 0);
 }
 
 /*
index b0772b83f1c7d860c45815b5f4a13917168fe1f8..24a0aae55cd4ab48a6f23b2742483390b86e9759 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.40 2000/08/06 04:13:22 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.41 2000/09/12 21:06:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -649,12 +649,21 @@ SS_finalize_plan(Plan *plan)
             */
            break;
 
+       case T_NestLoop:
+           finalize_primnode((Node *) ((Join *) plan)->joinqual,
+                             &results);
+           break;
+
        case T_MergeJoin:
+           finalize_primnode((Node *) ((Join *) plan)->joinqual,
+                             &results);
            finalize_primnode((Node *) ((MergeJoin *) plan)->mergeclauses,
                              &results);
            break;
 
        case T_HashJoin:
+           finalize_primnode((Node *) ((Join *) plan)->joinqual,
+                             &results);
            finalize_primnode((Node *) ((HashJoin *) plan)->hashclauses,
                              &results);
            break;
@@ -671,7 +680,6 @@ SS_finalize_plan(Plan *plan)
 
        case T_Agg:
        case T_SeqScan:
-       case T_NestLoop:
        case T_Material:
        case T_Sort:
        case T_Unique:
index fc192e6f28bae730bf9aeaed13da3f2678925eb6..a28e329e537a9f9e27cfb5cdf54f0ae918b9b89c 100644 (file)
@@ -107,6 +107,7 @@ transformKeySetQuery(Query *origNode)
        Node_Copy(origNode, unionNode, distinctClause);
        Node_Copy(origNode, unionNode, sortClause);
        Node_Copy(origNode, unionNode, rtable);
+       Node_Copy(origNode, unionNode, jointree);
        Node_Copy(origNode, unionNode, targetList);
 
        origNode->unionClause = lappend(origNode->unionClause, unionNode);
index f069cafdf66f716af515b9be7b3505ca9fdd8518..d284dd51e02f12ee5b7580c74ccf7424caff21be 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.51 2000/06/20 04:22:16 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.52 2000/09/12 21:06:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -528,12 +528,9 @@ fix_parsetree_attnums(Index rt_index,
    context.new_relid = new_relid;
    context.sublevels_up = 0;
 
-   /*
-    * We must scan both the targetlist and qual, but we know the
-    * havingQual is empty, so we can ignore it.
-    */
-   fix_parsetree_attnums_walker((Node *) parsetree->targetList, &context);
-   fix_parsetree_attnums_walker((Node *) parsetree->qual, &context);
+   query_tree_walker(parsetree,
+                     fix_parsetree_attnums_walker,
+                     (void *) &context);
 }
 
 /*
@@ -565,38 +562,17 @@ fix_parsetree_attnums_walker(Node *node,
        }
        return false;
    }
-   if (IsA(node, SubLink))
+   if (IsA(node, Query))
    {
+       /* Recurse into subselects */
+       bool        result;
 
-       /*
-        * Standard expression_tree_walker will not recurse into
-        * subselect, but here we must do so.
-        */
-       SubLink    *sub = (SubLink *) node;
-
-       if (fix_parsetree_attnums_walker((Node *) (sub->lefthand), context))
-           return true;
        context->sublevels_up++;
-       if (fix_parsetree_attnums_walker((Node *) (sub->subselect), context))
-       {
-           context->sublevels_up--;
-           return true;
-       }
+       result = query_tree_walker((Query *) node,
+                                  fix_parsetree_attnums_walker,
+                                  (void *) context);
        context->sublevels_up--;
-       return false;
-   }
-   if (IsA(node, Query))
-   {
-       /* Reach here after recursing down into subselect above... */
-       Query      *qry = (Query *) node;
-
-       if (fix_parsetree_attnums_walker((Node *) (qry->targetList), context))
-           return true;
-       if (fix_parsetree_attnums_walker((Node *) (qry->qual), context))
-           return true;
-       if (fix_parsetree_attnums_walker((Node *) (qry->havingQual), context))
-           return true;
-       return false;
+       return result;
    }
    return expression_tree_walker(node, fix_parsetree_attnums_walker,
                                  (void *) context);
index cf0b6dd703c76b3a904c4322b47b49b9b11ee5fc..36c7abd85b53de76b720f6ac6a7fc0fc2a58baa5 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.73 2000/08/24 03:29:05 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.74 2000/09/12 21:06:58 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -591,7 +591,7 @@ check_subplans_for_ungrouped_vars_walker(Node *node,
                    elog(ERROR, "cache lookup of attribute %d in relation %u failed",
                         var->varattno, rte->relid);
                elog(ERROR, "Sub-SELECT uses un-GROUPed attribute %s.%s from outer query",
-                    rte->ref->relname, attname);
+                    rte->eref->relname, attname);
            }
        }
    }
@@ -1639,25 +1639,44 @@ simplify_op_or_func(Expr *expr, List *args)
  * will have List structure at the top level, and it handles TargetEntry nodes
  * so that a scan of a target list can be handled without additional code.
  * (But only the "expr" part of a TargetEntry is examined, unless the walker
- * chooses to process TargetEntry nodes specially.)
+ * chooses to process TargetEntry nodes specially.)  Also, RangeTblRef and
+ * JoinExpr nodes are handled, so that qual expressions in a jointree can be
+ * processed without additional code.
+ *
+ * expression_tree_walker will handle SubLink and SubPlan nodes by recursing
+ * normally into the "lefthand" arguments (which belong to the outer plan).
+ * It will also call the walker on the sub-Query node; however, when
+ * expression_tree_walker itself is called on a Query node, it does nothing
+ * and returns "false".  The net effect is that unless the walker does
+ * something special at a Query node, sub-selects will not be visited
+ * during an expression tree walk.  This is exactly the behavior wanted
+ * in many cases --- and for those walkers that do want to recurse into
+ * sub-selects, special behavior is typically needed anyway at the entry
+ * to a sub-select (such as incrementing a depth counter).  A walker that
+ * wants to examine sub-selects should include code along the lines of:
+ *
+ *     if (IsA(node, Query))
+ *     {
+ *         adjust context for subquery;
+ *         result = query_tree_walker((Query *) node, my_walker, context);
+ *         restore context if needed;
+ *         return result;
+ *     }
  *
- * expression_tree_walker will handle a SUBPLAN_EXPR node by recursing into
- * the args and slink->oper lists (which belong to the outer plan), but it
- * will *not* visit the inner plan, since that's typically what expression
- * tree walkers want.  A walker that wants to visit the subplan can force
- * appropriate behavior by recognizing subplan expression nodes and doing
- * the right thing.
+ * query_tree_walker is a convenience routine (see below) that calls the
+ * walker on all the expression subtrees of the given Query node.
  *
- * Bare SubLink nodes (without a SUBPLAN_EXPR) are handled by recursing into
- * the "lefthand" argument list only.  (A bare SubLink should be seen only if
- * the tree has not yet been processed by subselect.c.)  Again, this can be
- * overridden by the walker, but it seems to be the most useful default
- * behavior.
+ * NOTE: currently, because make_subplan() clears the subselect link in
+ * a SubLink node, it is not actually possible to recurse into subselects
+ * of an already-planned expression tree.  This is OK for current uses,
+ * but ought to be cleaned up when we redesign querytree processing.
  *--------------------
  */
 
 bool
-           expression_tree_walker(Node *node, bool (*walker) (), void *context)
+expression_tree_walker(Node *node,
+                      bool (*walker) (),
+                      void *context)
 {
    List       *temp;
 
@@ -1677,6 +1696,7 @@ bool
        case T_Const:
        case T_Var:
        case T_Param:
+       case T_RangeTblRef:
            /* primitive node types with no subnodes */
            break;
        case T_Expr:
@@ -1750,17 +1770,31 @@ bool
 
                /*
                 * If the SubLink has already been processed by
-                * subselect.c, it will have lefthand=NIL, and we only
-                * need to look at the oper list.  Otherwise we only need
-                * to look at lefthand (the Oper nodes in the oper list
-                * are deemed uninteresting).
+                * subselect.c, it will have lefthand=NIL, and we need to
+                * scan the oper list.  Otherwise we only need to look at
+                * the lefthand list (the incomplete Oper nodes in the oper
+                * list are deemed uninteresting, perhaps even confusing).
                 */
                if (sublink->lefthand)
-                   return walker((Node *) sublink->lefthand, context);
+               {
+                   if (walker((Node *) sublink->lefthand, context))
+                       return true;
+               }
                else
-                   return walker((Node *) sublink->oper, context);
+               {
+                   if (walker((Node *) sublink->oper, context))
+                       return true;
+               }
+               /*
+                * Also invoke the walker on the sublink's Query node,
+                * so it can recurse into the sub-query if it wants to.
+                */
+               return walker(sublink->subselect, context);
            }
            break;
+       case T_Query:
+           /* Do nothing with a sub-Query, per discussion above */
+           break;
        case T_List:
            foreach(temp, (List *) node)
            {
@@ -1770,6 +1804,23 @@ bool
            break;
        case T_TargetEntry:
            return walker(((TargetEntry *) node)->expr, context);
+       case T_JoinExpr:
+           {
+               JoinExpr    *join = (JoinExpr *) node;
+
+               if (walker(join->larg, context))
+                   return true;
+               if (walker(join->rarg, context))
+                   return true;
+               if (walker(join->quals, context))
+                   return true;
+               if (walker((Node *) join->colvars, context))
+                   return true;
+               /* alias clause, using list, colnames list are deemed
+                * uninteresting.
+                */
+           }
+           break;
        default:
            elog(ERROR, "expression_tree_walker: Unexpected node type %d",
                 nodeTag(node));
@@ -1778,6 +1829,37 @@ bool
    return false;
 }
 
+/*
+ * query_tree_walker --- initiate a walk of a Query's expressions
+ *
+ * This routine exists just to reduce the number of places that need to know
+ * where all the expression subtrees of a Query are.  Note it can be used
+ * for starting a walk at top level of a Query regardless of whether the
+ * walker intends to descend into subqueries.  It is also useful for
+ * descending into subqueries within a walker.
+ */
+bool
+query_tree_walker(Query *query,
+                 bool (*walker) (),
+                 void *context)
+{
+   Assert(query != NULL && IsA(query, Query));
+
+   if (walker((Node *) query->targetList, context))
+       return true;
+   if (walker(query->qual, context))
+       return true;
+   if (walker(query->havingQual, context))
+       return true;
+   if (walker((Node *) query->jointree, context))
+       return true;
+   /*
+    * XXX for subselect-in-FROM, may need to examine rtable as well
+    */
+   return false;
+}
+
+
 /*--------------------
  * expression_tree_mutator() is designed to support routines that make a
  * modified copy of an expression tree, with some nodes being added,
@@ -1838,7 +1920,9 @@ bool
  */
 
 Node *
-           expression_tree_mutator(Node *node, Node *(*mutator) (), void *context)
+expression_tree_mutator(Node *node,
+                       Node *(*mutator) (),
+                       void *context)
 {
 
    /*
@@ -1866,6 +1950,7 @@ Node *
        case T_Const:
        case T_Var:
        case T_Param:
+       case T_RangeTblRef:
            /* primitive node types with no subnodes */
            return (Node *) copyObject(node);
        case T_Expr:
@@ -2044,6 +2129,20 @@ Node *
                return (Node *) newnode;
            }
            break;
+       case T_JoinExpr:
+           {
+               JoinExpr *join = (JoinExpr *) node;
+               JoinExpr *newnode;
+
+               FLATCOPY(newnode, join, JoinExpr);
+               MUTATE(newnode->larg, join->larg, Node *);
+               MUTATE(newnode->rarg, join->rarg, Node *);
+               MUTATE(newnode->quals, join->quals, Node *);
+               MUTATE(newnode->colvars, join->colvars, List *);
+               /* We do not mutate alias, using, or colnames by default */
+               return (Node *) newnode;
+           }
+           break;
        default:
            elog(ERROR, "expression_tree_mutator: Unexpected node type %d",
                 nodeTag(node));
index 5588e91e5b718d72fd72953dffcbbc92c47ea2dd..fc73bb2b6642459f861a78dd7ad914f7da1cc3b5 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.64 2000/05/30 00:49:49 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.65 2000/09/12 21:06:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -119,7 +119,9 @@ set_cheapest(RelOptInfo *parent_rel)
    Path       *cheapest_total_path;
 
    Assert(IsA(parent_rel, RelOptInfo));
-   Assert(pathlist != NIL);
+
+   if (pathlist == NIL)
+       elog(ERROR, "Unable to devise a query plan for the given query");
 
    cheapest_startup_path = cheapest_total_path = (Path *) lfirst(pathlist);
 
@@ -352,6 +354,7 @@ create_index_path(Query *root,
     * number of rows is the same as the parent rel's estimate.
     */
    pathnode->joinrelids = NIL; /* no join clauses here */
+   pathnode->alljoinquals = false;
    pathnode->rows = rel->rows;
 
    cost_index(&pathnode->path, root, rel, index, indexquals, false);
@@ -393,6 +396,7 @@ create_tidscan_path(RelOptInfo *rel, List *tideval)
  *   relations.
  *
  * 'joinrel' is the join relation.
+ * 'jointype' is the type of join required
  * 'outer_path' is the outer path
  * 'inner_path' is the inner path
  * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
@@ -403,6 +407,7 @@ create_tidscan_path(RelOptInfo *rel, List *tideval)
  */
 NestPath   *
 create_nestloop_path(RelOptInfo *joinrel,
+                    JoinType jointype,
                     Path *outer_path,
                     Path *inner_path,
                     List *restrict_clauses,
@@ -412,6 +417,7 @@ create_nestloop_path(RelOptInfo *joinrel,
 
    pathnode->path.pathtype = T_NestLoop;
    pathnode->path.parent = joinrel;
+   pathnode->jointype = jointype;
    pathnode->outerjoinpath = outer_path;
    pathnode->innerjoinpath = inner_path;
    pathnode->joinrestrictinfo = restrict_clauses;
@@ -428,6 +434,7 @@ create_nestloop_path(RelOptInfo *joinrel,
  *   two relations
  *
  * 'joinrel' is the join relation
+ * 'jointype' is the type of join required
  * 'outer_path' is the outer path
  * 'inner_path' is the inner path
  * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
@@ -440,6 +447,7 @@ create_nestloop_path(RelOptInfo *joinrel,
  */
 MergePath  *
 create_mergejoin_path(RelOptInfo *joinrel,
+                     JoinType jointype,
                      Path *outer_path,
                      Path *inner_path,
                      List *restrict_clauses,
@@ -463,6 +471,7 @@ create_mergejoin_path(RelOptInfo *joinrel,
 
    pathnode->jpath.path.pathtype = T_MergeJoin;
    pathnode->jpath.path.parent = joinrel;
+   pathnode->jpath.jointype = jointype;
    pathnode->jpath.outerjoinpath = outer_path;
    pathnode->jpath.innerjoinpath = inner_path;
    pathnode->jpath.joinrestrictinfo = restrict_clauses;
@@ -486,6 +495,7 @@ create_mergejoin_path(RelOptInfo *joinrel,
  *   Creates a pathnode corresponding to a hash join between two relations.
  *
  * 'joinrel' is the join relation
+ * 'jointype' is the type of join required
  * 'outer_path' is the cheapest outer path
  * 'inner_path' is the cheapest inner path
  * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
@@ -496,6 +506,7 @@ create_mergejoin_path(RelOptInfo *joinrel,
  */
 HashPath   *
 create_hashjoin_path(RelOptInfo *joinrel,
+                    JoinType jointype,
                     Path *outer_path,
                     Path *inner_path,
                     List *restrict_clauses,
@@ -506,6 +517,7 @@ create_hashjoin_path(RelOptInfo *joinrel,
 
    pathnode->jpath.path.pathtype = T_HashJoin;
    pathnode->jpath.path.parent = joinrel;
+   pathnode->jpath.jointype = jointype;
    pathnode->jpath.outerjoinpath = outer_path;
    pathnode->jpath.innerjoinpath = inner_path;
    pathnode->jpath.joinrestrictinfo = restrict_clauses;
index 070fabf7669049e3b6c5e207f630050d2262c649..87e87597d11e0dcb8c300cc5253e5b28b51d77ed 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.27 2000/06/18 22:44:12 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.28 2000/09/12 21:06:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -72,6 +72,7 @@ get_base_rel(Query *root, int relid)
    rel->tuples = 0;
    rel->baserestrictinfo = NIL;
    rel->baserestrictcost = 0;
+   rel->outerjoinset = NIL;
    rel->joininfo = NIL;
    rel->innerjoin = NIL;
 
@@ -178,6 +179,7 @@ get_join_rel(Query *root,
    joinrel->tuples = 0;
    joinrel->baserestrictinfo = NIL;
    joinrel->baserestrictcost = 0;
+   joinrel->outerjoinset = NIL;
    joinrel->joininfo = NIL;
    joinrel->innerjoin = NIL;
 
@@ -216,8 +218,7 @@ get_join_rel(Query *root,
                               restrictlist);
 
    /*
-    * Add the joinrel to the front of the query's joinrel list.
-    * (allpaths.c depends on this ordering!)
+    * Add the joinrel to the query's joinrel list.
     */
    root->join_rel_list = lcons(joinrel, root->join_rel_list);
 
index 3ce924de1bb812b6d89dd32ebbf7a2191fcb1d10..adbfd884c3647998a0537e11c92086111dc8192e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.10 2000/05/30 00:49:49 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.11 2000/09/12 21:06:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -54,3 +54,29 @@ get_actual_clauses(List *restrictinfo_list)
    }
    return result;
 }
+
+/*
+ * get_actual_join_clauses
+ *
+ * Extract clauses from 'restrictinfo_list', separating those that
+ * came from JOIN/ON conditions from those that didn't.
+ */
+void
+get_actual_join_clauses(List *restrictinfo_list,
+                       List **joinquals, List **otherquals)
+{
+   List       *temp;
+
+   *joinquals = NIL;
+   *otherquals = NIL;
+
+   foreach(temp, restrictinfo_list)
+   {
+       RestrictInfo *clause = (RestrictInfo *) lfirst(temp);
+
+       if (clause->isjoinqual)
+           *joinquals = lappend(*joinquals, clause->clause);
+       else
+           *otherquals = lappend(*otherquals, clause->clause);
+   }
+}
index bed7be7f08a583c600001613bb56255b4b059d95..ec9f9dafd0b4f92d84905f3ba7e6f4f36511d27a 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.26 2000/04/12 17:15:24 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.27 2000/09/12 21:06:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "nodes/plannodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/var.h"
 
 
+typedef struct
+{
+   List       *varlist;
+   int         sublevels_up;
+} pull_varnos_context;
+
 typedef struct
 {
    List       *varlist;
    bool        includeUpperVars;
 } pull_var_clause_context;
 
-static bool pull_varnos_walker(Node *node, List **listptr);
+static bool pull_varnos_walker(Node *node,
+                              pull_varnos_context *context);
 static bool contain_var_clause_walker(Node *node, void *context);
 static bool pull_var_clause_walker(Node *node,
                       pull_var_clause_context *context);
@@ -35,21 +43,39 @@ static bool pull_var_clause_walker(Node *node,
 /*
  *     pull_varnos
  *
- *     Create a list of all the distinct varnos present in a parsetree
- *     (tlist or qual).  Note that only varnos attached to level-zero
- *     Vars are considered --- upper Vars refer to some other rtable!
+ *     Create a list of all the distinct varnos present in a parsetree.
+ *     Only varnos that reference level-zero rtable entries are considered.
+ *
+ * NOTE: unlike other routines in this file, pull_varnos() is used on
+ * not-yet-planned expressions.  It may therefore find bare SubLinks,
+ * and if so it needs to recurse into them to look for uplevel references
+ * to the desired rtable level!  But when we find a completed SubPlan,
+ * we only need to look at the parameters passed to the subplan.
  */
 List *
 pull_varnos(Node *node)
 {
-   List       *result = NIL;
+   pull_varnos_context context;
+
+   context.varlist = NIL;
+   context.sublevels_up = 0;
+
+   /*
+    * Must be prepared to start with a Query or a bare expression tree;
+    * if it's a Query, go straight to query_tree_walker to make sure that
+    * sublevels_up doesn't get incremented prematurely.
+    */
+   if (node && IsA(node, Query))
+       query_tree_walker((Query *) node, pull_varnos_walker,
+                         (void *) &context);
+   else
+       pull_varnos_walker(node, &context);
 
-   pull_varnos_walker(node, &result);
-   return result;
+   return context.varlist;
 }
 
 static bool
-pull_varnos_walker(Node *node, List **listptr)
+pull_varnos_walker(Node *node, pull_varnos_context *context)
 {
    if (node == NULL)
        return false;
@@ -57,11 +83,42 @@ pull_varnos_walker(Node *node, List **listptr)
    {
        Var        *var = (Var *) node;
 
-       if (var->varlevelsup == 0 && !intMember(var->varno, *listptr))
-           *listptr = lconsi(var->varno, *listptr);
+       if (var->varlevelsup == context->sublevels_up &&
+           !intMember(var->varno, context->varlist))
+           context->varlist = lconsi(var->varno, context->varlist);
+       return false;
+   }
+   if (is_subplan(node))
+   {
+       /*
+        * Already-planned subquery.  Examine the args list (parameters
+        * to be passed to subquery), as well as the "oper" list which
+        * is executed by the outer query.  But short-circuit recursion into
+        * the subquery itself, which would be a waste of effort.
+        */
+       Expr       *expr = (Expr *) node;
+
+       if (pull_varnos_walker((Node*) ((SubPlan*) expr->oper)->sublink->oper,
+                              context))
+           return true;
+       if (pull_varnos_walker((Node *) expr->args,
+                              context))
+           return true;
        return false;
    }
-   return expression_tree_walker(node, pull_varnos_walker, (void *) listptr);
+   if (IsA(node, Query))
+   {
+       /* Recurse into not-yet-planned subquery */
+       bool        result;
+
+       context->sublevels_up++;
+       result = query_tree_walker((Query *) node, pull_varnos_walker,
+                                  (void *) context);
+       context->sublevels_up--;
+       return result;
+   }
+   return expression_tree_walker(node, pull_varnos_walker,
+                                 (void *) context);
 }
 
 /*
index 6ef084d04b6a63d61440b6e26c44c1b812f815d9..68fed79d382891ef920fb6ae5efb92ee1e5ab220 100644 (file)
@@ -2,7 +2,7 @@
 #
 # Makefile for parser
 #
-# $Header: /cvsroot/pgsql/src/backend/parser/Makefile,v 1.29 2000/08/28 11:53:19 petere Exp $
+# $Header: /cvsroot/pgsql/src/backend/parser/Makefile,v 1.30 2000/09/12 21:07:00 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -31,7 +31,7 @@ $(srcdir)/gram.c $(srcdir)/parse.h: gram.y
 
 $(srcdir)/scan.c: scan.l
 ifdef FLEX
-   $(FLEX) $(FLEXFLAGS) -o'$@' $<
+   $(FLEX) $(FLEXFLAGS) -Pbase_yy -o'$@' $<
 else
    @$(missing) flex $< $@
 endif
index fe21804a2c46ac336a0d2b09ebea5657c0b665ac..0165ef15c215f211ab2454b094543630843e672d 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: analyze.c,v 1.156 2000/08/29 04:20:44 momjian Exp $
+ * $Id: analyze.c,v 1.157 2000/09/12 21:07:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,6 +25,7 @@
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
 #include "parser/parse_type.h"
+#include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/relcache.h"
@@ -54,6 +55,8 @@ static void transformConstraintAttrs(List *constraintList);
 static void transformColumnType(ParseState *pstate, ColumnDef *column);
 static void transformFkeyCheckAttrs(FkConstraint *fkconstraint);
 
+static void release_pstate_resources(ParseState *pstate);
+
 /* kluge to return extra info from transformCreateStmt() */
 static List *extras_before;
 static List *extras_after;
@@ -71,28 +74,22 @@ List *
 parse_analyze(List *pl, ParseState *parentParseState)
 {
    List       *result = NIL;
-   ParseState *pstate;
-   Query      *parsetree;
 
    while (pl != NIL)
    {
+       ParseState *pstate = make_parsestate(parentParseState);
+       Query      *parsetree;
+
        extras_before = extras_after = NIL;
-       pstate = make_parsestate(parentParseState);
 
        parsetree = transformStmt(pstate, lfirst(pl));
-       if (pstate->p_target_relation != NULL)
-           heap_close(pstate->p_target_relation, AccessShareLock);
-       pstate->p_target_relation = NULL;
-       pstate->p_target_rangetblentry = NULL;
+       release_pstate_resources(pstate);
 
        while (extras_before != NIL)
        {
            result = lappend(result,
-                          transformStmt(pstate, lfirst(extras_before)));
-           if (pstate->p_target_relation != NULL)
-               heap_close(pstate->p_target_relation, AccessShareLock);
-           pstate->p_target_relation = NULL;
-           pstate->p_target_rangetblentry = NULL;
+                            transformStmt(pstate, lfirst(extras_before)));
+           release_pstate_resources(pstate);
            extras_before = lnext(extras_before);
        }
 
@@ -102,10 +99,7 @@ parse_analyze(List *pl, ParseState *parentParseState)
        {
            result = lappend(result,
                             transformStmt(pstate, lfirst(extras_after)));
-           if (pstate->p_target_relation != NULL)
-               heap_close(pstate->p_target_relation, AccessShareLock);
-           pstate->p_target_relation = NULL;
-           pstate->p_target_rangetblentry = NULL;
+           release_pstate_resources(pstate);
            extras_after = lnext(extras_after);
        }
 
@@ -116,6 +110,15 @@ parse_analyze(List *pl, ParseState *parentParseState)
    return result;
 }
 
+static void
+release_pstate_resources(ParseState *pstate)
+{
+   if (pstate->p_target_relation != NULL)
+       heap_close(pstate->p_target_relation, AccessShareLock);
+   pstate->p_target_relation = NULL;
+   pstate->p_target_rangetblentry = NULL;
+}
+
 /*
  * transformStmt -
  *   transform a Parse tree. If it is an optimizable statement, turn it
@@ -176,11 +179,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
                        Resdom     *rd;
 
                        id = nth(i, n->aliases);
-                       Assert(nodeTag(id) == T_Ident);
+                       Assert(IsA(id, Ident));
                        te = nth(i, targetList);
-                       Assert(nodeTag(te) == T_TargetEntry);
+                       Assert(IsA(te, TargetEntry));
                        rd = te->resdom;
-                       Assert(nodeTag(rd) == T_Resdom);
+                       Assert(IsA(rd, Resdom));
                        rd->resname = pstrdup(id->name);
                    }
                }
@@ -290,15 +293,17 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
    qry->commandType = CMD_DELETE;
 
    /* set up a range table */
-   makeRangeTable(pstate, NULL);
-   setTargetTable(pstate, stmt->relname, stmt->inh);
+   makeRangeTable(pstate, NIL);
+   setTargetTable(pstate, stmt->relname, stmt->inh, true);
 
    qry->distinctClause = NIL;
 
    /* fix where clause */
    qry->qual = transformWhereClause(pstate, stmt->whereClause);
 
+   /* done building the rtable */
    qry->rtable = pstate->p_rtable;
+   qry->jointree = pstate->p_jointree;
    qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
 
    qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -387,12 +392,14 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
     *
     * In particular, it's time to add the INSERT target to the rangetable.
     * (We didn't want it there until now since it shouldn't be visible in
-    * the SELECT part.)
+    * the SELECT part.)  Note that the INSERT target is NOT added to the
+    * join tree, since we don't want to join over it.
     */
-   setTargetTable(pstate, stmt->relname, FALSE);
+   setTargetTable(pstate, stmt->relname, false, false);
 
    /* now the range table will not change */
    qry->rtable = pstate->p_rtable;
+   qry->jointree = pstate->p_jointree;
    qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
 
    /* Prepare to assign non-conflicting resnos to resjunk attributes */
@@ -908,7 +915,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt)
    while (dlist != NIL)
    {
        constraint = lfirst(dlist);
-       Assert(nodeTag(constraint) == T_Constraint);
+       Assert(IsA(constraint, Constraint));
        Assert((constraint->contype == CONSTR_PRIMARY)
               || (constraint->contype == CONSTR_UNIQUE));
 
@@ -1427,17 +1434,68 @@ static Query *
 transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
 {
    Query      *qry;
-   Query      *action;
-   List       *actions;
+   RangeTblEntry *oldrte;
+   RangeTblEntry *newrte;
 
    qry = makeNode(Query);
    qry->commandType = CMD_UTILITY;
+   qry->utilityStmt = (Node *) stmt;
+
+   /*
+    * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW'
+    * equal to 2.  Set up their RTEs in the main pstate for use
+    * in parsing the rule qualification.
+    */
+   Assert(pstate->p_rtable == NIL);
+   oldrte = addRangeTableEntry(pstate, stmt->object->relname,
+                               makeAttr("*OLD*", NULL),
+                               false, true);
+   newrte = addRangeTableEntry(pstate, stmt->object->relname,
+                               makeAttr("*NEW*", NULL),
+                               false, true);
+   /*
+    * They must be in the jointree too for lookup purposes, but only add
+    * the one(s) that are relevant for the current kind of rule.  In an
+    * UPDATE rule, quals must refer to OLD.field or NEW.field to be
+    * unambiguous, but there's no need to be so picky for INSERT & DELETE.
+    * (Note we marked the RTEs "inFromCl = true" above to allow unqualified
+    * references to their fields.)
+    */
+   switch (stmt->event)
+   {
+       case CMD_SELECT:
+           addRTEtoJoinTree(pstate, oldrte);
+           break;
+       case CMD_UPDATE:
+           addRTEtoJoinTree(pstate, oldrte);
+           addRTEtoJoinTree(pstate, newrte);
+           break;
+       case CMD_INSERT:
+           addRTEtoJoinTree(pstate, newrte);
+           break;
+       case CMD_DELETE:
+           addRTEtoJoinTree(pstate, oldrte);
+           break;
+       default:
+           elog(ERROR, "transformRuleStmt: unexpected event type %d",
+                (int) stmt->event);
+           break;
+   }
+
+   /* take care of the where clause */
+   stmt->whereClause = transformWhereClause(pstate, stmt->whereClause);
+
+   if (length(pstate->p_rtable) != 2) /* naughty, naughty... */
+       elog(ERROR, "Rule WHERE condition may not contain references to other relations");
+
+   /* save info about sublinks in where clause */
+   qry->hasSubLinks = pstate->p_hasSubLinks;
 
    /*
-    * 'instead nothing' rules with a qualification need a query a
+    * 'instead nothing' rules with a qualification need a query
     * rangetable so the rewrite handler can add the negated rule
     * qualification to the original query. We create a query with the new
-    * command type CMD_NOTHING here that is treated special by the
+    * command type CMD_NOTHING here that is treated specially by the
     * rewrite system.
     */
    if (stmt->actions == NIL)
@@ -1445,54 +1503,95 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
        Query      *nothing_qry = makeNode(Query);
 
        nothing_qry->commandType = CMD_NOTHING;
-
-       addRangeTableEntry(pstate, stmt->object->relname,
-                          makeAttr("*OLD*", NULL),
-                          FALSE, FALSE, FALSE);
-       addRangeTableEntry(pstate, stmt->object->relname,
-                          makeAttr("*NEW*", NULL),
-                          FALSE, FALSE, FALSE);
-
        nothing_qry->rtable = pstate->p_rtable;
+       nothing_qry->jointree = NIL; /* no join actually wanted */
 
        stmt->actions = lappend(NIL, nothing_qry);
    }
-
-   actions = stmt->actions;
-
-   /*
-    * transform each statment, like parse_analyze()
-    */
-   while (actions != NIL)
+   else
    {
+       List       *actions;
 
        /*
-        * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW'
-        * equal to 2.
+        * transform each statement, like parse_analyze()
         */
-       addRangeTableEntry(pstate, stmt->object->relname,
-                          makeAttr("*OLD*", NULL),
-                          FALSE, FALSE, FALSE);
-       addRangeTableEntry(pstate, stmt->object->relname,
-                          makeAttr("*NEW*", NULL),
-                          FALSE, FALSE, FALSE);
-
-       pstate->p_last_resno = 1;
-       pstate->p_is_rule = true;       /* for expand all */
-       pstate->p_hasAggs = false;
-
-       action = (Query *) lfirst(actions);
-       if (action->commandType != CMD_NOTHING)
-           lfirst(actions) = transformStmt(pstate, lfirst(actions));
-       actions = lnext(actions);
-   }
+       foreach(actions, stmt->actions)
+       {
+           ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
+           Query      *sub_qry;
+           bool        has_old,
+                       has_new;
 
-   /* take care of the where clause */
-   stmt->whereClause = transformWhereClause(pstate, stmt->whereClause);
+           /*
+            * Set up OLD/NEW in the rtable for this statement.  The entries
+            * are marked not inFromCl because we don't want them to be
+            * referred to by unqualified field names nor "*" in the rule
+            * actions.  We don't need to add them to the jointree for
+            * qualified-name lookup, either (see qualifiedNameToVar()).
+            */
+           oldrte = addRangeTableEntry(sub_pstate, stmt->object->relname,
+                                       makeAttr("*OLD*", NULL),
+                                       false, false);
+           newrte = addRangeTableEntry(sub_pstate, stmt->object->relname,
+                                       makeAttr("*NEW*", NULL),
+                                       false, false);
 
-   qry->hasSubLinks = pstate->p_hasSubLinks;
+           /* Transform the rule action statement */
+           sub_qry = transformStmt(sub_pstate, lfirst(actions));
+
+           /*
+            * Validate action's use of OLD/NEW, qual too
+            */
+           has_old =
+               rangeTableEntry_used((Node *) sub_qry, PRS2_OLD_VARNO, 0) ||
+               rangeTableEntry_used(stmt->whereClause, PRS2_OLD_VARNO, 0);
+           has_new =
+               rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) ||
+               rangeTableEntry_used(stmt->whereClause, PRS2_NEW_VARNO, 0);
+
+           switch (stmt->event)
+           {
+               case CMD_SELECT:
+                   if (has_old)
+                       elog(ERROR, "ON SELECT rule may not use OLD");
+                   if (has_new)
+                       elog(ERROR, "ON SELECT rule may not use NEW");
+                   break;
+               case CMD_UPDATE:
+                   /* both are OK */
+                   break;
+               case CMD_INSERT:
+                   if (has_old)
+                       elog(ERROR, "ON INSERT rule may not use OLD");
+                   break;
+               case CMD_DELETE:
+                   if (has_new)
+                       elog(ERROR, "ON DELETE rule may not use NEW");
+                   break;
+               default:
+                   elog(ERROR, "transformRuleStmt: unexpected event type %d",
+                        (int) stmt->event);
+                   break;
+           }
+
+           /*
+            * For efficiency's sake, add OLD to the rule action's jointree
+            * only if it was actually referenced in the statement or qual.
+            * NEW is not really a relation and should never be added.
+            */
+           if (has_old)
+           {
+               addRTEtoJoinTree(sub_pstate, oldrte);
+               sub_qry->jointree = sub_pstate->p_jointree;
+           }
+
+           lfirst(actions) = sub_qry;
+
+           release_pstate_resources(sub_pstate);
+           pfree(sub_pstate);
+       }
+   }
 
-   qry->utilityStmt = (Node *) stmt;
    return qry;
 }
 
@@ -1558,6 +1657,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
    qry->intersectClause = stmt->intersectClause;
 
    qry->rtable = pstate->p_rtable;
+   qry->jointree = pstate->p_jointree;
 
    if (stmt->forUpdate != NULL)
        transformForUpdate(qry, stmt->forUpdate);
@@ -1585,17 +1685,17 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
     * do this with REPLACE in POSTQUEL so we keep the feature.
     */
    makeRangeTable(pstate, stmt->fromClause);
-   setTargetTable(pstate, stmt->relname, stmt->inh);
+   setTargetTable(pstate, stmt->relname, stmt->inh, true);
 
    qry->targetList = transformTargetList(pstate, stmt->targetList);
 
    qry->qual = transformWhereClause(pstate, stmt->whereClause);
 
-   qry->hasSubLinks = pstate->p_hasSubLinks;
-
    qry->rtable = pstate->p_rtable;
+   qry->jointree = pstate->p_jointree;
    qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
 
+   qry->hasSubLinks = pstate->p_hasSubLinks;
    qry->hasAggs = pstate->p_hasAggs;
    if (pstate->p_hasAggs)
        parseCheckAggregates(pstate, qry);
@@ -1689,7 +1789,7 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt)
            transformColumnType(pstate, (ColumnDef *) stmt->def);
            break;
        case 'C':
-           if (stmt->def && nodeTag(stmt->def) == T_FkConstraint)
+           if (stmt->def && IsA(stmt->def, FkConstraint))
            {
                CreateTrigStmt *fk_trigger;
                List       *fk_attr;
@@ -2085,7 +2185,7 @@ transformForUpdate(Query *qry, List *forUpdate)
            i++;
        }
        if (l2 == NULL)
-           elog(ERROR, "FOR UPDATE: relation '%s' not found in FROM clause",
+           elog(ERROR, "FOR UPDATE: relation \"%s\" not found in FROM clause",
                 relname);
    }
 
index 7e970ab1871d6d50420d2ae0c7a505fd8afae19c..301be9eb9b957c787e604ba7f7a723aaee1622f5 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.188 2000/09/12 05:09:44 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.189 2000/09/12 21:07:01 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -36,6 +36,7 @@
 #include 
 
 #include "postgres.h"
+
 #include "access/htup.h"
 #include "access/xact.h"
 #include "catalog/catname.h"
@@ -77,15 +78,10 @@ static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);
 static Node *makeTypeCast(Node *arg, TypeName *typename);
 static Node *makeRowExpr(char *opr, List *largs, List *rargs);
 static void mapTargetColumns(List *source, List *target);
-static void param_type_init(Oid *typev, int nargs);
 static bool exprIsNullConstant(Node *arg);
 static Node *doNegate(Node *n);
 static void doNegateFloat(Value *v);
 
-/* old versions of flex define this as a macro */
-#if defined(yywrap)
-#undef yywrap
-#endif /* yywrap */
 %}
 
 
@@ -95,6 +91,7 @@ static void doNegateFloat(Value *v);
    char                chr;
    char                *str;
    bool                boolean;
+   JoinType            jtype;
    List                *list;
    Node                *node;
    Value               *value;
@@ -108,7 +105,6 @@ static void doNegateFloat(Value *v);
    JoinExpr            *jexpr;
    IndexElem           *ielem;
    RangeVar            *range;
-   RelExpr             *relexp;
    A_Indices           *aind;
    ResTarget           *target;
    ParamNo             *paramno;
@@ -194,19 +190,8 @@ static void doNegateFloat(Value *v);
 %type     opt_table
 %type     opt_chain, opt_trans
 
-%type   from_expr, join_clause, join_expr
-%type   join_clause_with_union, join_expr_with_union
 %type    join_outer, join_qual
-%type    join_type
-%type    using_list
-%type   using_expr
-/***
-#ifdef ENABLE_ORACLE_JOIN_SYNTAX
-%type    oracle_list
-%type   oracle_expr
-%type     oracle_outer
-#endif
-***/
+%type   join_type
 
 %type    extract_list, position_list
 %type    substr_list, substr_from, substr_for, trim_list
@@ -246,8 +231,9 @@ static void doNegateFloat(Value *v);
 %type    event_object, attr, alias_clause
 %type         sortby
 %type   index_elem, func_index
-%type   table_expr
-%type  relation_expr
+%type    table_ref
+%type   joined_table
+%type   relation_expr
 %type  target_el, update_target_el
 %type  ParamNo
 
@@ -356,6 +342,12 @@ static void doNegateFloat(Value *v);
        TEMP, TOAST, TRUNCATE, TRUSTED, 
        UNLISTEN, UNTIL, VACUUM, VALID, VERBOSE, VERSION
 
+/* The grammar thinks these are keywords, but they are not in the keywords.c
+ * list and so can never be entered directly.  The filter in parser.c
+ * creates these tokens when required.
+ */
+%token         UNIONJOIN
+
 /* Special keywords, not in the query language - see the "lex" file */
 %token    IDENT, FCONST, SCONST, Op
 %token   ICONST, PARAM
@@ -364,7 +356,9 @@ static void doNegateFloat(Value *v);
 %token         OP
 
 /* precedence: lowest to highest */
-%left      UNION INTERSECT EXCEPT
+%left      UNION EXCEPT
+%left      INTERSECT
+%left      JOIN UNIONJOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
 %left      OR
 %left      AND
 %right     NOT
@@ -800,7 +794,7 @@ VariableSetStmt:  SET ColId TO var_value
                    n->value = $3;
                    $$ = (Node *) n;
 #else
-                   elog(ERROR, "SET NAMES is not supported.");
+                   elog(ERROR, "SET NAMES is not supported");
 #endif
                }
        ;
@@ -1031,7 +1025,6 @@ AlterTableStmt:
                    n->relname = $3;
                    $$ = (Node *)n;
                }
-
 /* ALTER TABLE  OWNER TO UserId */
        | ALTER TABLE relation_name OWNER TO UserId
                {
@@ -2956,7 +2949,7 @@ CreatedbStmt:  CREATE DATABASE database_name WITH createdb_opt_location createdb
                    CreatedbStmt *n;
 
                    if ($5 == NULL && $6 == -1)
-                       elog(ERROR, "CREATE DATABASE WITH requires at least one option.");
+                       elog(ERROR, "CREATE DATABASE WITH requires at least one option");
 
                     n = makeNode(CreatedbStmt);
                    n->dbname = $3;
@@ -3465,7 +3458,7 @@ SelectStmt:     select_clause sort_clause for_update_clause opt_select_limit
 /* This rule parses Select statements that can appear within set operations,
  * including UNION, INTERSECT and EXCEPT.  '(' and ')' can be used to specify
  * the ordering of the set operations.  Without '(' and ')' we want the
- * operations to be left associative.
+ * operations to be ordered per the precedence specs at the head of this file.
  *
  * Note that sort clauses cannot be included at this level --- a sort clause
  * can only appear at the end of the complete Select, and it will be handled
@@ -3486,10 +3479,12 @@ select_clause: '(' select_clause ')'
            {
                $$ = $1; 
            }
-       | select_clause EXCEPT select_clause
+       | select_clause EXCEPT opt_all select_clause
            {
                $$ = (Node *)makeA_Expr(AND,NULL,$1,
-                                       makeA_Expr(NOT,NULL,NULL,$3));
+                                       makeA_Expr(NOT,NULL,NULL,$4));
+               if ($3)
+                   elog(ERROR, "EXCEPT ALL is not implemented yet");
            }
        | select_clause UNION opt_all select_clause
            {   
@@ -3506,9 +3501,11 @@ select_clause: '(' select_clause ')'
                }
                $$ = (Node *)makeA_Expr(OR,NULL,$1,$4);
            }
-       | select_clause INTERSECT select_clause
+       | select_clause INTERSECT opt_all select_clause
            {
-               $$ = (Node *)makeA_Expr(AND,NULL,$1,$3);
+               $$ = (Node *)makeA_Expr(AND,NULL,$1,$4);
+               if ($3)
+                   elog(ERROR, "INTERSECT ALL is not implemented yet");
            }
        ; 
 
@@ -3741,113 +3738,63 @@ update_list:  OF va_list                       { $$ = $2; }
  *****************************************************************************/
 
 from_clause:  FROM from_list                   { $$ = $2; }
-/***
-#ifdef ENABLE_ORACLE_JOIN_SYNTAX
-       | FROM oracle_list                      { $$ = $2; }
-#endif
-***/
-       | FROM from_expr                        { $$ = lcons($2, NIL); }
        | /*EMPTY*/                             { $$ = NIL; }
        ;
 
-from_list:  from_list ',' table_expr           { $$ = lappend($1, $3); }
-       | table_expr                            { $$ = lcons($1, NIL); }
-       ;
-
-/***********
- * This results in one shift/reduce conflict, presumably due to the trailing "(+)"
- * - Thomas 1999-09-20
- *
-#ifdef ENABLE_ORACLE_JOIN_SYNTAX
-oracle_list:  oracle_expr                      { $$ = lcons($1, NIL); }
-       ;
-
-oracle_expr:  ColId ',' ColId oracle_outer
-               {
-                   elog(ERROR,"Oracle OUTER JOIN not yet supported");
-                   $$ = NULL;
-               }
-       | oracle_outer ColId ',' ColId
-               {
-                   elog(ERROR,"Oracle OUTER JOIN not yet supported");
-                   $$ = NULL;
-               }
-       ;
-
-oracle_outer:  '(' '+' ')'                     { $$ = TRUE; }
-       ;
-#endif
-***********/
-
-from_expr:  '(' join_clause_with_union ')' alias_clause
-               {
-                   JoinExpr *j = $2;
-                   j->alias = $4;
-                   $$ = j;
-               }
-       | join_clause
-               {   $$ = $1; }
-       ;
-
-table_expr:  relation_expr alias_clause
-               {
-                   $$ = makeNode(RangeVar);
-                   $$->relExpr = $1;
-                   $$->name = $2;
-
-#ifdef DISABLE_JOIN_SYNTAX
-                   if (($2 != NULL) && ($2->attrs != NULL))
-                       elog(ERROR, "Column aliases in table expressions not yet supported");
-#endif
-               }
+from_list:  from_list ',' table_ref                { $$ = lappend($1, $3); }
+       | table_ref                             { $$ = lcons($1, NIL); }
        ;
 
-alias_clause:  AS ColId '(' name_list ')'
-               {
-                   $$ = makeNode(Attr);
-                   $$->relname = $2;
-                   $$->attrs = $4;
-               }
-       | AS ColId
+/*
+ * table_ref is where an alias clause can be attached.  Note we cannot make
+ * alias_clause have an empty production because that causes parse conflicts
+ * between table_ref := '(' joined_table ')' alias_clause
+ * and joined_table := '(' joined_table ')'.  So, we must have the
+ * redundant-looking productions here instead.
+ */
+table_ref:  relation_expr
                {
-                   $$ = makeNode(Attr);
-                   $$->relname = $2;
+                   $$ = (Node *) $1;
                }
-       | ColId '(' name_list ')'
+       | relation_expr alias_clause
                {
-                   $$ = makeNode(Attr);
-                   $$->relname = $1;
-                   $$->attrs = $3;
+                   $1->name = $2;
+                   $$ = (Node *) $1;
                }
-       | ColId
+       | '(' select_clause ')'
                {
-                   $$ = makeNode(Attr);
-                   $$->relname = $1;
+                   RangeSubselect *n = makeNode(RangeSubselect);
+                   n->subquery = $2;
+                   n->name = NULL;
+                   $$ = (Node *) n;
                }
-       | /*EMPTY*/
+       | '(' select_clause ')' alias_clause
                {
-                   $$ = NULL;  /* no qualifiers */
+                   RangeSubselect *n = makeNode(RangeSubselect);
+                   n->subquery = $2;
+                   n->name = $4;
+                   $$ = (Node *) n;
                }
-       ;
-
-/* A UNION JOIN is the same as a FULL OUTER JOIN which *omits*
- * all result rows which would have matched on an INNER JOIN.
- * Syntactically, must enclose the UNION JOIN in parens to avoid
- * conflicts with SELECT/UNION.
- */
-join_clause:  join_clause join_expr
+       | joined_table
                {
-                   $2->larg = (Node *)$1;
-                   $$ = $2;
+                   $$ = (Node *) $1;
                }
-       | table_expr join_expr
+       | '(' joined_table ')' alias_clause
                {
-                   $2->larg = (Node *)$1;
-                   $$ = $2;
+                   $2->alias = $4;
+                   $$ = (Node *) $2;
                }
        ;
 
-/* This is everything but the left side of a join.
+/*
+ * It may seem silly to separate joined_table from table_ref, but there is
+ * method in SQL92's madness: if you don't do it this way you get reduce-
+ * reduce conflicts, because it's not clear to the parser generator whether
+ * to expect alias_clause after ')' or not.  For the same reason we must
+ * treat 'JOIN' and 'join_type JOIN' separately, rather than allowing
+ * join_type to expand to empty; if we try it, the parser generator can't
+ * figure out when to reduce an empty join_type right after table_ref.
+ *
  * Note that a CROSS JOIN is the same as an unqualified
  * INNER JOIN, and an INNER JOIN/ON has the same shape
  * but a qualification expression to limit membership.
@@ -3855,71 +3802,122 @@ join_clause:  join_clause join_expr
  * tables and the shape is determined by which columns are
  * in common. We'll collect columns during the later transformations.
  */
-join_expr:  join_type JOIN table_expr join_qual
+
+joined_table:  '(' joined_table ')'
+               {
+                   $$ = $2;
+               }
+       | table_ref CROSS JOIN table_ref
                {
+                   /* CROSS JOIN is same as unqualified inner join */
                    JoinExpr *n = makeNode(JoinExpr);
-                   n->jointype = $1;
-                   n->rarg = (Node *)$3;
-                   n->quals = (List *)$4;
+                   n->jointype = JOIN_INNER;
+                   n->isNatural = FALSE;
+                   n->larg = $1;
+                   n->rarg = $4;
+                   n->using = NIL;
+                   n->quals = NULL;
                    $$ = n;
                }
-       | NATURAL join_type JOIN table_expr
+       | table_ref UNIONJOIN table_ref
                {
+                   /* UNION JOIN is made into 1 token to avoid shift/reduce
+                    * conflict against regular UNION keyword.
+                    */
                    JoinExpr *n = makeNode(JoinExpr);
-                   n->jointype = $2;
-                   n->isNatural = TRUE;
-                   n->rarg = (Node *)$4;
-                   n->quals = NULL; /* figure out which columns later... */
+                   n->jointype = JOIN_UNION;
+                   n->isNatural = FALSE;
+                   n->larg = $1;
+                   n->rarg = $3;
+                   n->using = NIL;
+                   n->quals = NULL;
                    $$ = n;
                }
-       | CROSS JOIN table_expr
+       | table_ref join_type JOIN table_ref join_qual
                {
                    JoinExpr *n = makeNode(JoinExpr);
-                   n->jointype = INNER_P;
+                   n->jointype = $2;
                    n->isNatural = FALSE;
-                   n->rarg = (Node *)$3;
-                   n->quals = NULL;
+                   n->larg = $1;
+                   n->rarg = $4;
+                   if ($5 != NULL && IsA($5, List))
+                       n->using = (List *) $5; /* USING clause */
+                   else
+                       n->quals = $5; /* ON clause */
                    $$ = n;
                }
-       ;
-
-join_clause_with_union:  join_clause_with_union join_expr_with_union
+       | table_ref JOIN table_ref join_qual
                {
-                   $2->larg = (Node *)$1;
-                   $$ = $2;
+                   /* letting join_type reduce to empty doesn't work */
+                   JoinExpr *n = makeNode(JoinExpr);
+                   n->jointype = JOIN_INNER;
+                   n->isNatural = FALSE;
+                   n->larg = $1;
+                   n->rarg = $3;
+                   if ($4 != NULL && IsA($4, List))
+                       n->using = (List *) $4; /* USING clause */
+                   else
+                       n->quals = $4; /* ON clause */
+                   $$ = n;
                }
-       | table_expr join_expr_with_union
+       | table_ref NATURAL join_type JOIN table_ref
                {
-                   $2->larg = (Node *)$1;
-                   $$ = $2;
+                   JoinExpr *n = makeNode(JoinExpr);
+                   n->jointype = $3;
+                   n->isNatural = TRUE;
+                   n->larg = $1;
+                   n->rarg = $5;
+                   n->using = NIL; /* figure out which columns later... */
+                   n->quals = NULL; /* fill later */
+                   $$ = n;
                }
-       ;
-
-join_expr_with_union:  join_expr
-               {   $$ = $1; }
-       | UNION JOIN table_expr
+       | table_ref NATURAL JOIN table_ref
                {
+                   /* letting join_type reduce to empty doesn't work */
                    JoinExpr *n = makeNode(JoinExpr);
-                   n->jointype = UNION;
-                   n->rarg = (Node *)$3;
-                   n->quals = NULL;
+                   n->jointype = JOIN_INNER;
+                   n->isNatural = TRUE;
+                   n->larg = $1;
+                   n->rarg = $4;
+                   n->using = NIL; /* figure out which columns later... */
+                   n->quals = NULL; /* fill later */
                    $$ = n;
+               }
+       ;
 
-                   elog(ERROR,"UNION JOIN not yet implemented");
+alias_clause:  AS ColId '(' name_list ')'
+               {
+                   $$ = makeNode(Attr);
+                   $$->relname = $2;
+                   $$->attrs = $4;
+               }
+       | AS ColId
+               {
+                   $$ = makeNode(Attr);
+                   $$->relname = $2;
+               }
+       | ColId '(' name_list ')'
+               {
+                   $$ = makeNode(Attr);
+                   $$->relname = $1;
+                   $$->attrs = $3;
+               }
+       | ColId
+               {
+                   $$ = makeNode(Attr);
+                   $$->relname = $1;
                }
        ;
 
-/* OUTER is just noise... */
-join_type:  FULL join_outer                        { $$ = FULL; }
-       | LEFT join_outer                       { $$ = LEFT; }
-       | RIGHT join_outer                      { $$ = RIGHT; }
-       | OUTER_P                               { $$ = LEFT; }
-       | INNER_P                               { $$ = INNER_P; }
-       | /*EMPTY*/                             { $$ = INNER_P; }
+join_type:  FULL join_outer                        { $$ = JOIN_FULL; }
+       | LEFT join_outer                       { $$ = JOIN_LEFT; }
+       | RIGHT join_outer                      { $$ = JOIN_RIGHT; }
+       | INNER_P                               { $$ = JOIN_INNER; }
        ;
 
+/* OUTER is just noise... */
 join_outer:  OUTER_P                           { $$ = NULL; }
-       | /*EMPTY*/                             { $$ = NULL;  /* no qualifiers */ }
+       | /*EMPTY*/                             { $$ = NULL; }
        ;
 
 /* JOIN qualification clauses
@@ -3927,60 +3925,43 @@ join_outer:  OUTER_P                            { $$ = NULL; }
  *  USING ( column list ) allows only unqualified column names,
  *                        which must match between tables.
  *  ON expr allows more general qualifications.
- * - thomas 1999-01-07
+ *
+ * We return USING as a List node, while an ON-expr will not be a List.
  */
 
-join_qual:  USING '(' using_list ')'           { $$ = (Node *)$3; }
-       | ON a_expr                             { $$ = (Node *)$2; }
+join_qual:  USING '(' name_list ')'                { $$ = (Node *) $3; }
+       | ON a_expr                             { $$ = $2; }
        ;
 
-using_list:  using_list ',' using_expr         { $$ = lappend($1, $3); }
-       | using_expr                            { $$ = lcons($1, NIL); }
-       ;
-
-using_expr:  ColId
-               {
-                   /* could be a column name or a relation_name */
-                   Ident *n = makeNode(Ident);
-                   n->name = $1;
-                   n->indirection = NULL;
-                   $$ = n;
-               }
-       ;
-
-where_clause:  WHERE a_expr                        { $$ = $2; }
-       | /*EMPTY*/                             { $$ = NULL;  /* no qualifiers */ }
-       ;
 
 relation_expr: relation_name
                {
                    /* default inheritance */
-                   $$ = makeNode(RelExpr);
+                   $$ = makeNode(RangeVar);
                    $$->relname = $1;
                    $$->inh = SQL_inheritance;
+                   $$->name = NULL;
                }
        | relation_name '*'             %prec '='
                {
                    /* inheritance query */
-                   $$ = makeNode(RelExpr);
+                   $$ = makeNode(RangeVar);
                    $$->relname = $1;
                    $$->inh = TRUE;
+                   $$->name = NULL;
                }
        | ONLY relation_name            %prec '='
                {
                    /* no inheritance */
-                   $$ = makeNode(RelExpr);
+                   $$ = makeNode(RangeVar);
                    $$->relname = $2;
                    $$->inh = FALSE;
+                   $$->name = NULL;
                 }
        ;
 
-opt_array_bounds:  '[' ']' opt_array_bounds
-               {  $$ = lcons(makeInteger(-1), $3); }
-       | '[' Iconst ']' opt_array_bounds
-               {  $$ = lcons(makeInteger($2), $4); }
-       | /*EMPTY*/
-               {  $$ = NIL; }
+where_clause:  WHERE a_expr                        { $$ = $2; }
+       | /*EMPTY*/                             { $$ = NULL;  /* no qualifiers */ }
        ;
 
 
@@ -4023,6 +4004,14 @@ Typename:  SimpleTypename opt_array_bounds
                }
        ;
 
+opt_array_bounds:  '[' ']' opt_array_bounds
+               {  $$ = lcons(makeInteger(-1), $3); }
+       | '[' Iconst ']' opt_array_bounds
+               {  $$ = lcons(makeInteger($2), $4); }
+       | /*EMPTY*/
+               {  $$ = NIL; }
+       ;
+
 SimpleTypename:  ConstTypename
        | ConstInterval
        ;
@@ -6024,29 +6013,19 @@ xlateSqlType(char *name)
 
 void parser_init(Oid *typev, int nargs)
 {
+   saved_relname[0] = '\0';
    QueryIsRule = FALSE;
-   saved_relname[0]= '\0';
-
-   param_type_init(typev, nargs);
-}
-
-
-/*
- * param_type_init()
- *
- * Keep enough information around to fill out the type of param nodes
- * used in postquel functions
- */
-static void
-param_type_init(Oid *typev, int nargs)
-{
-   pfunc_num_args = nargs;
+   /*
+    * Keep enough information around to fill out the type of param nodes
+    * used in postquel functions
+    */
    param_type_info = typev;
+   pfunc_num_args = nargs;
 }
 
 Oid param_type(int t)
 {
-   if ((t > pfunc_num_args) || (t == 0))
+   if ((t > pfunc_num_args) || (t <= 0))
        return InvalidOid;
    return param_type_info[t - 1];
 }
index bbc8f5c70764e37c987008267136bbc772b079b8..955be022e4e235b7ad401dc9a64ffec798880c48 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.39 2000/07/17 03:05:02 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.40 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -152,6 +152,11 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
     */
    if (contain_agg_clause(qry->qual))
        elog(ERROR, "Aggregates not allowed in WHERE clause");
+   /*
+    * ON-conditions in JOIN expressions are like WHERE clauses.
+    */
+   if (contain_agg_clause((Node *) qry->jointree))
+       elog(ERROR, "Aggregates not allowed in JOIN conditions");
 
    /*
     * No aggregates allowed in GROUP BY clauses, either.
index 3f874cc9643d3b900ab3853171ef7b29e2ea8198..c35b41b911b27a577f47859bec860ea80a153803 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.65 2000/06/15 03:32:19 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.66 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,7 +18,9 @@
 #include "access/heapam.h"
 #include "optimizer/tlist.h"
 #include "nodes/makefuncs.h"
+#include "parser/analyze.h"
 #include "parser/parse.h"
+#include "parser/parsetree.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 
 static char *clauseText[] = {"ORDER BY", "GROUP BY", "DISTINCT ON"};
 
+static void extractUniqueColumns(List *common_colnames,
+                                List *src_colnames, List *src_colvars,
+                                List **res_colnames, List **res_colvars);
+static Node *transformUsingClause(ParseState *pstate,
+                                 List *leftVars, List *rightVars);
+static RangeTblRef *transformTableEntry(ParseState *pstate, RangeVar *r);
+static RangeTblRef *transformRangeSubselect(ParseState *pstate,
+                                           RangeSubselect *r);
+static Node *transformFromClauseItem(ParseState *pstate, Node *n);
 static TargetEntry *findTargetlistEntry(ParseState *pstate, Node *node,
                    List *tlist, int clause);
-static void parseFromClause(ParseState *pstate, List *frmList);
-static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r);
 static List *addTargetToSortList(TargetEntry *tle, List *sortlist,
                    List *targetlist, char *opname);
 static bool exprIsInSortList(Node *expr, List *sortList, List *targetList);
 
-#ifndef DISABLE_OUTER_JOINS
-static List *transformUsingClause(ParseState *pstate, List *using,
-                                 List *left, List *right);
-#endif
-
 
 /*
  * makeRangeTable -
  *   Build the initial range table from the FROM clause.
+ *
+ * The range table constructed here may grow as we transform the expressions
+ * in the query's quals and target list. (Note that this happens because in
+ * POSTQUEL, we allow references to relations not specified in the
+ * from-clause.  PostgreSQL keeps this extension to standard SQL.)
+ *
+ * Note: we assume that pstate's p_rtable and p_jointree lists were
+ * initialized to NIL when the pstate was created.  We will add onto
+ * any entries already present --- this is needed for rule processing!
  */
 void
 makeRangeTable(ParseState *pstate, List *frmList)
 {
-   /* Currently, nothing to do except this: */
-   parseFromClause(pstate, frmList);
+   List       *fl;
+
+   /*
+    * The grammar will have produced a list of RangeVars, RangeSubselects,
+    * and/or JoinExprs. Transform each one, and then add it to the join tree.
+    */
+   foreach(fl, frmList)
+   {
+       Node       *n = lfirst(fl);
+
+       n = transformFromClauseItem(pstate, n);
+       pstate->p_jointree = lappend(pstate->p_jointree, n);
+   }
 }
 
 /*
  * setTargetTable
- *   Add the target relation of INSERT or UPDATE to the range table,
+ *   Add the target relation of INSERT/UPDATE/DELETE to the range table,
  *   and make the special links to it in the ParseState.
  *
- *   Note that the target is not marked as either inFromCl or inJoinSet.
+ *   inJoinSet says whether to add the target to the join tree.
  *   For INSERT, we don't want the target to be joined to; it's a
  *   destination of tuples, not a source.  For UPDATE/DELETE, we do
- *   need to scan or join the target.  This will happen without the
- *   inJoinSet flag because the planner's preprocess_targetlist()
- *   adds the destination's CTID attribute to the targetlist, and
- *   therefore the destination will be a referenced table even if
- *   there is no other use of any of its attributes.  Tricky, eh?
+ *   need to scan or join the target.
  */
 void
-setTargetTable(ParseState *pstate, char *relname, bool inh)
+setTargetTable(ParseState *pstate, char *relname, bool inh, bool inJoinSet)
 {
    RangeTblEntry *rte;
 
    /* look for relname only at current nesting level... */
    if (refnameRangeTablePosn(pstate, relname, NULL) == 0)
-       rte = addRangeTableEntry(pstate, relname,
-                                makeAttr(relname, NULL),
-                                inh, FALSE, FALSE);
+   {
+       rte = addRangeTableEntry(pstate, relname, NULL, inh, false);
+   }
    else
+   {
        rte = refnameRangeTableEntry(pstate, relname);
+       /* XXX what if pre-existing entry has wrong inh setting? */
+   }
+
+   if (inJoinSet)
+       addRTEtoJoinTree(pstate, rte);
 
    /* This could only happen for multi-action rules */
    if (pstate->p_target_relation != NULL)
@@ -95,625 +121,500 @@ setTargetTable(ParseState *pstate, char *relname, bool inh)
 }
 
 
-static Node *
-mergeInnerJoinQuals(ParseState *pstate, Node *clause)
-{
-   List       *jquals;
-
-   foreach(jquals, pstate->p_join_quals)
-   {
-       Node       *jqual = (Node *) lfirst(jquals);
-
-       if (clause == NULL)
-           clause = jqual;
-       else
-       {
-           A_Expr     *a = makeNode(A_Expr);
-
-           a->oper = AND;
-           a->opname = NULL;
-           a->lexpr = clause;
-           a->rexpr = jqual;
-           clause = (Node *) a;
-       }
-   }
-
-   /* Make sure that we don't add same quals twice... */
-   pstate->p_join_quals = NIL;
-
-   return clause;
-}  /* mergeInnerJoinQuals() */
-
 /*
- * transformWhereClause -
- *   transforms the qualification and make sure it is of type Boolean
+ * Extract all not-in-common columns from column lists of a source table
  */
-Node *
-transformWhereClause(ParseState *pstate, Node *clause)
-{
-   Node       *qual;
-
-   if (pstate->p_join_quals != NIL)
-       clause = mergeInnerJoinQuals(pstate, clause);
-
-   if (clause == NULL)
-       return NULL;
-
-   pstate->p_in_where_clause = true;
-   qual = transformExpr(pstate, clause, EXPR_COLUMN_FIRST);
-   pstate->p_in_where_clause = false;
-
-   if (exprType(qual) != BOOLOID)
-   {
-       elog(ERROR, "WHERE clause must return type bool, not type %s",
-            typeidTypeName(exprType(qual)));
-   }
-   return qual;
-}
-
-#ifndef DISABLE_JOIN_SYNTAX
-char *
-           AttrString(Attr *attr);
-
-char *
-AttrString(Attr *attr)
-{
-   Value      *val;
-
-   Assert(length(attr->attrs) == 1);
-
-   val = lfirst(attr->attrs);
-
-   Assert(IsA(val, String));
-
-   return strVal(val);
-}
-
-List *
-           ListTableAsAttrs(ParseState *pstate, char *table);
-List *
-ListTableAsAttrs(ParseState *pstate, char *table)
-{
-   Attr       *attr = expandTable(pstate, table, TRUE);
-   List       *rlist = NIL;
-   List       *col;
-
-   foreach(col, attr->attrs)
-   {
-       Attr       *a = makeAttr(table, strVal((Value *) lfirst(col)));
-
-       rlist = lappend(rlist, a);
-   }
-
-   return rlist;
-}
-
-List *
-           makeUniqueAttrList(List *candidates, List *idents);
-List *
-makeUniqueAttrList(List *attrs, List *filter)
+static void
+extractUniqueColumns(List *common_colnames,
+                    List *src_colnames, List *src_colvars,
+                    List **res_colnames, List **res_colvars)
 {
-   List       *result = NULL;
-   List       *candidate;
+   List       *new_colnames = NIL;
+   List       *new_colvars = NIL;
+   List       *lnames,
+              *lvars = src_colvars;
 
-   foreach(candidate, attrs)
+   foreach(lnames, src_colnames)
    {
-       List       *fmember;
-       bool        match = FALSE;
-       Attr       *cattr = lfirst(candidate);
+       char       *colname = strVal(lfirst(lnames));
+       bool        match = false;
+       List       *cnames;
 
-       Assert(IsA(cattr, Attr));
-       Assert(length(cattr->attrs) == 1);
-
-       foreach(fmember, filter)
+       foreach(cnames, common_colnames)
        {
-           Attr       *fattr = lfirst(fmember);
-
-           Assert(IsA(fattr, Attr));
-           Assert(length(fattr->attrs) == 1);
+           char       *ccolname = strVal(lfirst(cnames));
 
-           if (strcmp(strVal(lfirst(cattr->attrs)), strVal(lfirst(fattr->attrs))) == 0)
+           if (strcmp(colname, ccolname) == 0)
            {
-               match = TRUE;
+               match = true;
                break;
            }
        }
 
        if (!match)
-           result = lappend(result, cattr);
-   }
-
-   return result;
-}
-
-List *
-           makeAttrList(Attr *attr);
-
-List *
-makeAttrList(Attr *attr)
-{
-   List       *result = NULL;
-
-   char       *name = attr->relname;
-   List       *col;
-
-   foreach(col, attr->attrs)
-   {
-       Attr       *newattr = makeAttr(name, strVal((Value *) lfirst(col)));
-
-       result = lappend(result, newattr);
-   }
-
-   return result;
-}
-#ifdef NOT_USED
-/* ExpandAttrs()
- * Take an existing attribute node and return a list of attribute nodes
- * with one attribute name per node.
- */
-List *
-ExpandAttrs(Attr *attr)
-{
-   List       *col;
-   char       *relname = attr->relname;
-   List       *rlist = NULL;
-
-   Assert(attr != NULL);
-
-   if ((attr->attrs == NULL) || (length(attr->attrs) <= 1))
-       return lcons(attr, NIL);
-
-   foreach(col, attr->attrs)
-   {
-       Attr       *attr = lfirst(col);
+       {
+           new_colnames = lappend(new_colnames, lfirst(lnames));
+           new_colvars = lappend(new_colvars, lfirst(lvars));
+       }
 
-       rlist = lappend(rlist, makeAttr(relname, AttrString(attr)));
+       lvars = lnext(lvars);
    }
 
-   return rlist;
+   *res_colnames = new_colnames;
+   *res_colvars = new_colvars;
 }
-#endif
 
 /* transformUsingClause()
- * Take an ON or USING clause from a join expression and expand if necessary.
- * Result is an implicitly-ANDed list of untransformed qualification clauses.
+ * Build a complete ON clause from a partially-transformed USING list.
+ * We are given lists of Var nodes representing left and right match columns.
+ * Result is a transformed qualification expression.
  */
-static List *
-transformUsingClause(ParseState *pstate, List *usingList,
-                    List *leftList, List *rightList)
+static Node *
+transformUsingClause(ParseState *pstate, List *leftVars, List *rightVars)
 {
-   List       *result = NIL;
-   List       *using;
+   Node       *result = NULL;
+   List       *lvars,
+              *rvars = rightVars;
 
-   foreach(using, usingList)
+   /*
+    * We cheat a little bit here by building an untransformed operator
+    * tree whose leaves are the already-transformed Vars.  This is OK
+    * because transformExpr() won't complain about already-transformed
+    * subnodes.
+    */
+   foreach(lvars, leftVars)
    {
-       Attr       *uattr = lfirst(using);
-       Attr       *lattr = NULL,
-                  *rattr = NULL;
-       List       *col;
+       Node       *lvar = (Node *) lfirst(lvars);
+       Node       *rvar = (Node *) lfirst(rvars);
        A_Expr     *e;
 
-       /*
-        * find the first instances of this column in the shape list and
-        * the last table in the shape list...
-        */
-       foreach(col, leftList)
-       {
-           Attr       *attr = lfirst(col);
+       e = makeNode(A_Expr);
+       e->oper = OP;
+       e->opname = "=";
+       e->lexpr = copyObject(lvar);
+       e->rexpr = copyObject(rvar);
 
-           if (strcmp(AttrString(attr), AttrString(uattr)) == 0)
-           {
-               lattr = attr;
-               break;
-           }
-       }
-       foreach(col, rightList)
+       if (result == NULL)
+           result = (Node *) e;
+       else
        {
-           Attr       *attr = lfirst(col);
+           A_Expr     *a = makeNode(A_Expr);
 
-           if (strcmp(AttrString(attr), AttrString(uattr)) == 0)
-           {
-               rattr = attr;
-               break;
-           }
+           a->oper = AND;
+           a->opname = NULL;
+           a->lexpr = result;
+           a->rexpr = (Node *) e;
+           result = (Node *) a;
        }
 
-       Assert((lattr != NULL) && (rattr != NULL));
+       rvars = lnext(rvars);
+   }
 
-       e = makeNode(A_Expr);
-       e->oper = OP;
-       e->opname = "=";
-       e->lexpr = (Node *) lattr;
-       e->rexpr = (Node *) rattr;
+   result = transformExpr(pstate, result, EXPR_COLUMN_FIRST);
 
-       result = lappend(result, e);
+   if (exprType(result) != BOOLOID)
+   {
+       /* This could only happen if someone defines a funny version of '=' */
+       elog(ERROR, "USING clause must return type bool, not type %s",
+            typeidTypeName(exprType(result)));
    }
 
    return result;
 }  /* transformUsingClause() */
 
-#endif
-
 
-static RangeTblEntry *
+/*
+ * transformTableEntry --- transform a RangeVar (simple relation reference)
+ */
+static RangeTblRef *
 transformTableEntry(ParseState *pstate, RangeVar *r)
 {
-   RelExpr    *baserel = r->relExpr;
-   char       *relname = baserel->relname;
-
-#if 0
-   char       *refname;
-   List       *columns;
-
-#endif
+   char       *relname = r->relname;
    RangeTblEntry *rte;
-
-#if 0
-   if (r->name != NULL)
-       refname = r->name->relname;
-   else
-       refname = NULL;
-
-   columns = ListTableAsAttrs(pstate, relname);
-
-   /* alias might be specified... */
-   if (r->name != NULL)
-   {
-#ifndef DISABLE_JOIN_SYNTAX
-       if (length(columns) > 0)
-       {
-           if (length(r->name->attrs) > 0)
-           {
-               if (length(columns) != length(r->name->attrs))
-                   elog(ERROR, "'%s' has %d columns but %d %s specified",
-                        relname, length(columns), length(r->name->attrs),
-                        ((length(r->name->attrs) != 1) ? "aliases" : "alias"));
-
-               aliasList = nconc(aliasList, r->name->attrs);
-           }
-           else
-           {
-               r->name->attrs = columns;
-
-               aliasList = nconc(aliasList, r->name->attrs);
-           }
-       }
-       else
-           elog(NOTICE, "transformTableEntry: column aliases not handled (internal error)");
-#else
-       elog(ERROR, "Column aliases not yet supported");
-#endif
-   }
-   else
-   {
-       refname = relname;
-       aliasList = nconc(aliasList, columns);
-   }
-#endif
-
-   if (r->name == NULL)
-       r->name = makeAttr(relname, NULL);
+   RangeTblRef *rtr;
 
    /*
-    * marks this entry to indicate it comes from the FROM clause. In SQL,
+    * mark this entry to indicate it comes from the FROM clause. In SQL,
     * the target list can only refer to range variables specified in the
     * from clause but we follow the more powerful POSTQUEL semantics and
     * automatically generate the range variable if not specified. However
     * there are times we need to know whether the entries are legitimate.
-    *
-    * eg. select * from foo f where f.x = 1; will generate wrong answer if
-    * we expand * to foo.x.
     */
+   rte = addRangeTableEntry(pstate, relname, r->name, r->inh, true);
 
-   rte = addRangeTableEntry(pstate, relname, r->name,
-                            baserel->inh, TRUE, TRUE);
+   /*
+    * We create a RangeTblRef, but we do not add it to the jointree here.
+    * makeRangeTable will do so, if we are at top level of the FROM clause.
+    */
+   rtr = makeNode(RangeTblRef);
+   /* assume new rte is at end */
+   rtr->rtindex = length(pstate->p_rtable);
+   Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
 
-   return rte;
-}  /* transformTableEntry() */
+   return rtr;
+}
 
 
 /*
- * parseFromClause -
- *   turns the table references specified in the from-clause into a
- *   range table. The range table may grow as we transform the expressions
- *   in the target list. (Note that this happens because in POSTQUEL, we
- *   allow references to relations not specified in the from-clause. We
- *   also allow now as an extension.)
- *
- * The FROM clause can now contain JoinExpr nodes, which contain parsing info
- * for inner and outer joins. The USING clause must be expanded into a qualification
- * for an inner join at least, since that is compatible with the old syntax.
- * Not sure yet how to handle outer joins, but it will become clear eventually?
- * - thomas 1998-12-16
+ * transformRangeSubselect --- transform a sub-SELECT appearing in FROM
  */
-static void
-parseFromClause(ParseState *pstate, List *frmList)
+static RangeTblRef *
+transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 {
-   List       *fl;
+   SelectStmt *subquery = (SelectStmt *) r->subquery;
+   List       *parsetrees;
+   Query      *query;
 
-   foreach(fl, frmList)
-   {
-       Node       *n = lfirst(fl);
+   /*
+    * subquery node might not be SelectStmt if user wrote something like
+    * FROM (SELECT ... UNION SELECT ...).  Our current implementation of
+    * UNION/INTERSECT/EXCEPT is too messy to deal with here, so punt until
+    * we redesign querytrees to make it more reasonable.
+    */
+   if (subquery == NULL || !IsA(subquery, SelectStmt))
+       elog(ERROR, "Set operations not yet supported in subselects in FROM");
 
-       /*
-        * marks this entry to indicate it comes from the FROM clause. In
-        * SQL, the target list can only refer to range variables
-        * specified in the from clause but we follow the more powerful
-        * POSTQUEL semantics and automatically generate the range
-        * variable if not specified. However there are times we need to
-        * know whether the entries are legitimate.
-        *
-        * eg. select * from foo f where f.x = 1; will generate wrong answer
-        * if we expand * to foo.x.
-        */
+   /*
+    * Analyze and transform the subquery as if it were an independent
+    * statement (we do NOT want it to see the outer query as a parent).
+    */
+   parsetrees = parse_analyze(lcons(subquery, NIL), NULL);
 
-       /* Plain vanilla inner join, just like we've always had? */
-       if (IsA(n, RangeVar))
-           transformTableEntry(pstate, (RangeVar *) n);
+   /*
+    * Check that we got something reasonable.  Some of these conditions
+    * are probably impossible given restrictions of the grammar, but
+    * check 'em anyway.
+    */
+   if (length(parsetrees) != 1)
+       elog(ERROR, "Unexpected parse analysis result for subselect in FROM");
+   query = (Query *) lfirst(parsetrees);
+   if (query == NULL || !IsA(query, Query))
+       elog(ERROR, "Unexpected parse analysis result for subselect in FROM");
 
-       /* A newfangled join expression? */
-       else if (IsA(n, JoinExpr))
-       {
-#ifndef DISABLE_JOIN_SYNTAX
-           RangeTblEntry *l_rte,
-                      *r_rte;
-           Attr       *l_name,
-                      *r_name = NULL;
-           JoinExpr   *j = (JoinExpr *) n;
-
-           if (j->alias != NULL)
-               elog(ERROR, "JOIN table aliases are not supported");
-
-           /* nested join? then handle the left one first... */
-           if (IsA(j->larg, JoinExpr))
-           {
-               parseFromClause(pstate, lcons(j->larg, NIL));
-               l_name = ((JoinExpr *) j->larg)->alias;
-           }
-           else
-           {
-               Assert(IsA(j->larg, RangeVar));
-               l_rte = transformTableEntry(pstate, (RangeVar *) j->larg);
-               l_name = expandTable(pstate, l_rte->eref->relname, TRUE);
-           }
+   if (query->commandType != CMD_SELECT)
+       elog(ERROR, "Expected SELECT query from subselect in FROM");
+   if (query->resultRelation != 0 || query->into != NULL)
+       elog(ERROR, "Subselect in FROM may not have SELECT INTO");
 
-           if (IsA(j->rarg, JoinExpr))
-           {
-               parseFromClause(pstate, lcons(j->rarg, NIL));
-               l_name = ((JoinExpr *) j->larg)->alias;
-           }
-           else
-           {
-               Assert(IsA(j->rarg, RangeVar));
-               r_rte = transformTableEntry(pstate, (RangeVar *) j->rarg);
-               r_name = expandTable(pstate, r_rte->eref->relname, TRUE);
-           }
 
-           /*
-            * Natural join does not explicitly specify columns; must
-            * generate columns to join. Need to run through the list of
-            * columns from each table or join result and match up the
-            * column names. Use the first table, and check every column
-            * in the second table for a match.
-            */
-           if (j->isNatural)
-           {
-               List       *lx,
-                          *rx;
-               List       *rlist = NULL;
+   elog(ERROR, "Subselect in FROM not done yet");
 
-               foreach(lx, l_name->attrs)
-               {
-                   Ident      *id = NULL;
-                   Value      *l_col = lfirst(lx);
+   return NULL;
+}
 
-                   Assert(IsA(l_col, String));
 
-                   foreach(rx, r_name->attrs)
-                   {
-                       Value      *r_col = lfirst(rx);
+/*
+ * transformFromClauseItem -
+ *   Transform a FROM-clause item, adding any required entries to the
+ *   range table list being built in the ParseState, and return the
+ *   transformed item ready to include in the jointree list.
+ *   This routine can recurse to handle SQL92 JOIN expressions.
+ */
+static Node *
+transformFromClauseItem(ParseState *pstate, Node *n)
+{
+   if (IsA(n, RangeVar))
+   {
+       /* Plain relation reference */
+       return (Node *) transformTableEntry(pstate, (RangeVar *) n);
+   }
+   else if (IsA(n, RangeSubselect))
+   {
+       /* Plain relation reference */
+       return (Node *) transformRangeSubselect(pstate, (RangeSubselect *) n);
+   }
+   else if (IsA(n, JoinExpr))
+   {
+       /* A newfangled join expression */
+       JoinExpr   *j = (JoinExpr *) n;
+       List       *l_colnames,
+                  *r_colnames,
+                  *res_colnames,
+                  *l_colvars,
+                  *r_colvars,
+                  *res_colvars;
 
-                       Assert(IsA(r_col, String));
+       /*
+        * Recursively process the left and right subtrees
+        */
+       j->larg = transformFromClauseItem(pstate, j->larg);
+       j->rarg = transformFromClauseItem(pstate, j->rarg);
 
-                       if (strcmp(strVal(l_col), strVal(r_col)) == 0)
-                       {
-                           id = (Ident *) makeNode(Ident);
-                           id->name = strVal(l_col);
-                           break;
-                       }
-                   }
+       /*
+        * Extract column name and var lists from both subtrees
+        */
+       if (IsA(j->larg, JoinExpr))
+       {
+           /* Make a copy of the subtree's lists so we can modify! */
+           l_colnames = copyObject(((JoinExpr *) j->larg)->colnames);
+           l_colvars = copyObject(((JoinExpr *) j->larg)->colvars);
+       }
+       else
+       {
+           RangeTblEntry *rte;
 
-                   /* right column matched? then keep as join column... */
-                   if (id != NULL)
-                       rlist = lappend(rlist, id);
-               }
-               j->quals = rlist;
+           Assert(IsA(j->larg, RangeTblRef));
+           rte = rt_fetch(((RangeTblRef *) j->larg)->rtindex,
+                          pstate->p_rtable);
+           expandRTE(pstate, rte, &l_colnames, &l_colvars);
+           /* expandRTE returns new lists, so no need for copyObject */
+       }
+       if (IsA(j->rarg, JoinExpr))
+       {
+           /* Make a copy of the subtree's lists so we can modify! */
+           r_colnames = copyObject(((JoinExpr *) j->rarg)->colnames);
+           r_colvars = copyObject(((JoinExpr *) j->rarg)->colvars);
+       }
+       else
+       {
+           RangeTblEntry *rte;
 
-               printf("NATURAL JOIN columns are %s\n", nodeToString(rlist));
-           }
+           Assert(IsA(j->rarg, RangeTblRef));
+           rte = rt_fetch(((RangeTblRef *) j->rarg)->rtindex,
+                          pstate->p_rtable);
+           expandRTE(pstate, rte, &r_colnames, &r_colvars);
+           /* expandRTE returns new lists, so no need for copyObject */
+       }
 
-           if (j->jointype == INNER_P)
+       /*
+        * Natural join does not explicitly specify columns; must
+        * generate columns to join. Need to run through the list of
+        * columns from each table or join result and match up the
+        * column names. Use the first table, and check every column
+        * in the second table for a match.  (We'll check that the
+        * matches were unique later on.)
+        * The result of this step is a list of column names just like an
+        * explicitly-written USING list.
+        */
+       if (j->isNatural)
+       {
+           List       *rlist = NIL;
+           List       *lx,
+                      *rx;
+
+           Assert(j->using == NIL); /* shouldn't have USING() too */
+
+           foreach(lx, l_colnames)
            {
-               /* CROSS JOIN */
-               if (j->quals == NULL)
-                   printf("CROSS JOIN...\n");
+               char       *l_colname = strVal(lfirst(lx));
+               Value      *m_name = NULL;
 
-               /*
-                * JOIN/USING This is an inner join, so rip apart the join
-                * node and transform into a traditional FROM list.
-                * NATURAL JOIN and JOIN USING both change the shape of
-                * the result. Need to generate a list of result columns
-                * to use for target list expansion and validation.
-                */
-               else if (IsA(j->quals, List))
+               foreach(rx, r_colnames)
                {
+                   char       *r_colname = strVal(lfirst(rx));
 
-                   /*
-                    * List of Ident nodes means column names from a real
-                    * USING clause. Determine the shape of the joined
-                    * table.
-                    */
-                   List       *ucols,
-                              *ucol;
-                   List       *shape = NULL;
-                   List       *alias = NULL;
-                   List       *l_shape,
-                              *r_shape;
-
-                   List       *l_cols = makeAttrList(l_name);
-                   List       *r_cols = makeAttrList(r_name);
-
-                   printf("USING input tables are:\n %s\n %s\n",
-                          nodeToString(l_name), nodeToString(r_name));
-
-                   printf("USING expanded tables are:\n %s\n %s\n",
-                          nodeToString(l_cols), nodeToString(r_cols));
-
-                   /* Columns from the USING clause... */
-                   ucols = (List *) j->quals;
-                   foreach(ucol, ucols)
+                   if (strcmp(l_colname, r_colname) == 0)
                    {
-                       List       *col;
-                       Attr       *l_attr = NULL,
-                                  *r_attr = NULL;
-                       Ident      *id = lfirst(ucol);
-
-                       Attr       *attr = makeAttr("", id->name);
-
-                       foreach(col, l_cols)
-                       {
-                           attr = lfirst(col);
-                           if (strcmp(AttrString(attr), id->name) == 0)
-                           {
-                               l_attr = attr;
-                               break;
-                           }
-                       }
-
-                       foreach(col, r_cols)
-                       {
-                           attr = lfirst(col);
-                           if (strcmp(AttrString(attr), id->name) == 0)
-                           {
-                               r_attr = attr;
-                               break;
-                           }
-                       }
-
-                       if (l_attr == NULL)
-                           elog(ERROR, "USING column '%s' not found in table '%s'",
-                                id->name, l_name->relname);
-                       if (r_attr == NULL)
-                           elog(ERROR, "USING column '%s' not found in table '%s'",
-                                id->name, r_name->relname);
-
-                       shape = lappend(shape, l_attr);
-                       alias = lappend(alias, makeAttr("", AttrString(l_attr)));
+                       m_name = makeString(l_colname);
+                       break;
                    }
-                   printf("JOIN/USING join columns are %s\n", nodeToString(shape));
-
-                   /* Remaining columns from the left side... */
-                   l_shape = makeUniqueAttrList(makeAttrList(l_name), shape);
-
-                   printf("JOIN/USING left columns are %s\n", nodeToString(l_shape));
-
-                   r_shape = makeUniqueAttrList(makeAttrList(r_name), shape);
+               }
 
-                   printf("JOIN/USING right columns are %s\n", nodeToString(r_shape));
+               /* matched a right column? then keep as join column... */
+               if (m_name != NULL)
+                   rlist = lappend(rlist, m_name);
+           }
 
-                   printf("JOIN/USING input quals are %s\n", nodeToString(j->quals));
+           j->using = rlist;
+       }
 
-                   j->quals = transformUsingClause(pstate, shape, l_cols, r_cols);
+       /*
+        * Now transform the join qualifications, if any.
+        */
+       res_colnames = NIL;
+       res_colvars = NIL;
 
-                   printf("JOIN/USING transformed quals are %s\n", nodeToString(j->quals));
+       if (j->using)
+       {
+           /*
+            * JOIN/USING (or NATURAL JOIN, as transformed above).
+            * Transform the list into an explicit ON-condition,
+            * and generate a list of result columns.
+            */
+           List       *ucols = j->using;
+           List       *l_usingvars = NIL;
+           List       *r_usingvars = NIL;
+           List       *ucol;
 
-                   alias = nconc(nconc(alias, listCopy(l_shape)), listCopy(r_shape));
-                   shape = nconc(nconc(shape, l_shape), r_shape);
+           Assert(j->quals == NULL); /* shouldn't have ON() too */
 
-                   printf("JOIN/USING shaped table is %s\n", nodeToString(shape));
-                   printf("JOIN/USING alias list is %s\n", nodeToString(alias));
+           foreach(ucol, ucols)
+           {
+               char       *u_colname = strVal(lfirst(ucol));
+               List       *col;
+               Node       *l_colvar,
+                          *r_colvar,
+                          *colvar;
+               int         ndx;
+               int         l_index = -1;
+               int         r_index = -1;
+
+               ndx = 0;
+               foreach(col, l_colnames)
+               {
+                   char       *l_colname = strVal(lfirst(col));
 
-                   pstate->p_shape = shape;
-                   pstate->p_alias = alias;
+                   if (strcmp(l_colname, u_colname) == 0)
+                   {
+                       if (l_index >= 0)
+                           elog(ERROR, "Common column name \"%s\" appears more than once in left table", u_colname);
+                       l_index = ndx;
+                   }
+                   ndx++;
                }
+               if (l_index < 0)
+                   elog(ERROR, "USING column \"%s\" not found in left table",
+                        u_colname);
 
-               /* otherwise, must be an expression from an ON clause... */
-               else
-                   j->quals = (List *) lcons(j->quals, NIL);
-
-               /* listCopy may not be needed here --- will j->quals list
-                * be used again anywhere?  The #ifdef'd code below may need
-                * it, if it ever gets used...
-                */
-               pstate->p_join_quals = nconc(pstate->p_join_quals,
-                                            listCopy(j->quals));
-
-#if 0
-               if (qual == NULL)
-                   elog(ERROR, "JOIN/ON not supported in this context");
-
-               printf("Table aliases are %s\n", nodeToString(*aliasList));
-#endif
-
-#if 0
-               /* XXX this code is WRONG because j->quals is a List
-                * not a simple expression.  Perhaps *qual
-                * ought also to be a List and we append to it,
-                * similarly to the way p_join_quals is handled above?
-                */
-               if (*qual == NULL)
+               ndx = 0;
+               foreach(col, r_colnames)
                {
-                   /* merge qualified join clauses... */
-                   if (j->quals != NULL)
+                   char       *r_colname = strVal(lfirst(col));
+
+                   if (strcmp(r_colname, u_colname) == 0)
                    {
-                       if (*qual != NULL)
-                       {
-                           A_Expr     *a = makeNode(A_Expr);
-
-                           a->oper = AND;
-                           a->opname = NULL;
-                           a->lexpr = (Node *) *qual;
-                           a->rexpr = (Node *) j->quals;
-
-                           *qual = (Node *) a;
-                       }
-                       else
-                           *qual = (Node *) j->quals;
+                       if (r_index >= 0)
+                           elog(ERROR, "Common column name \"%s\" appears more than once in right table", u_colname);
+                       r_index = ndx;
                    }
+                   ndx++;
                }
-               else
+               if (r_index < 0)
+                   elog(ERROR, "USING column \"%s\" not found in right table",
+                        u_colname);
+
+               l_colvar = nth(l_index, l_colvars);
+               l_usingvars = lappend(l_usingvars, l_colvar);
+               r_colvar = nth(r_index, r_colvars);
+               r_usingvars = lappend(r_usingvars, r_colvar);
+
+               res_colnames = lappend(res_colnames,
+                                      nth(l_index, l_colnames));
+               switch (j->jointype)
                {
-                   elog(ERROR, "Multiple JOIN/ON clauses not handled (internal error)");
-                   *qual = lappend(*qual, j->quals);
+                   case JOIN_INNER:
+                   case JOIN_LEFT:
+                       colvar = l_colvar;
+                       break;
+                   case JOIN_RIGHT:
+                       colvar = r_colvar;
+                       break;
+                   default:
+                   {
+                       /* Need COALESCE(l_colvar, r_colvar) */
+                       CaseExpr *c = makeNode(CaseExpr);
+                       CaseWhen *w = makeNode(CaseWhen);
+                       A_Expr *a = makeNode(A_Expr);
+
+                       a->oper = NOTNULL;
+                       a->lexpr = l_colvar;
+                       w->expr = (Node *) a;
+                       w->result = l_colvar;
+                       c->args = lcons(w, NIL);
+                       c->defresult = r_colvar;
+                       colvar = transformExpr(pstate, (Node *) c,
+                                              EXPR_COLUMN_FIRST);
+                       break;
+                   }
                }
-#endif
-
-               /*
-                * if we are transforming this node back into a FROM list,
-                * then we will need to replace the node with two nodes.
-                * Will need access to the previous list item to change
-                * the link pointer to reference these new nodes. Try
-                * accumulating and returning a new list. - thomas
-                * 1999-01-08 Not doing this yet though!
-                */
+               res_colvars = lappend(res_colvars, colvar);
+           }
 
+           j->quals = transformUsingClause(pstate, l_usingvars, r_usingvars);
+       }
+       else if (j->quals)
+       {
+           /* User-written ON-condition; transform it */
+           j->quals = transformExpr(pstate, j->quals, EXPR_COLUMN_FIRST);
+           if (exprType(j->quals) != BOOLOID)
+           {
+               elog(ERROR, "ON clause must return type bool, not type %s",
+                    typeidTypeName(exprType(j->quals)));
            }
-           else if ((j->jointype == LEFT)
-                    || (j->jointype == RIGHT)
-                    || (j->jointype == FULL))
-               elog(ERROR, "OUTER JOIN is not yet supported");
-           else
-               elog(ERROR, "Unrecognized JOIN clause; tag is %d (internal error)",
-                    j->jointype);
-#else
-           elog(ERROR, "JOIN expressions are not yet implemented");
-#endif
+           /* XXX should check that ON clause refers only to joined tbls */
        }
        else
-           elog(ERROR, "parseFromClause: unexpected FROM clause node (internal error)"
-                "\n\t%s", nodeToString(n));
+       {
+           /* CROSS JOIN: no quals */
+       }
+
+       /* Add remaining columns from each side to the output columns */
+       extractUniqueColumns(res_colnames,
+                            l_colnames, l_colvars,
+                            &l_colnames, &l_colvars);
+       extractUniqueColumns(res_colnames,
+                            r_colnames, r_colvars,
+                            &r_colnames, &r_colvars);
+       res_colnames = nconc(res_colnames, l_colnames);
+       res_colvars = nconc(res_colvars, l_colvars);
+       res_colnames = nconc(res_colnames, r_colnames);
+       res_colvars = nconc(res_colvars, r_colvars);
+
+       /*
+        * Process alias (AS clause), if any.
+        *
+        * The given table alias must be unique in the current nesting level,
+        * ie it cannot match any RTE refname or jointable alias.  This is
+        * a bit painful to check because my own child joins are not yet in
+        * the pstate's jointree, so they have to be scanned separately.
+        */
+       if (j->alias)
+       {
+           /* Check against previously created RTEs and jointree entries */
+           if (refnameRangeOrJoinEntry(pstate, j->alias->relname, NULL))
+               elog(ERROR, "Table name \"%s\" specified more than once",
+                    j->alias->relname);
+           /* Check children */
+           if (scanJoinTreeForRefname(j->larg, j->alias->relname) ||
+               scanJoinTreeForRefname(j->rarg, j->alias->relname))
+               elog(ERROR, "Table name \"%s\" specified more than once",
+                    j->alias->relname);
+           /*
+            * If a column alias list is specified, substitute the alias
+            * names into my output-column list
+            */
+           if (j->alias->attrs != NIL)
+           {
+               if (length(j->alias->attrs) != length(res_colnames))
+                   elog(ERROR, "Column alias list for \"%s\" has wrong number of entries (need %d)",
+                        j->alias->relname, length(res_colnames));
+               res_colnames = j->alias->attrs;
+           }
+       }
+
+       j->colnames = res_colnames;
+       j->colvars = res_colvars;
+
+       return (Node *) j;
+   }
+   else
+       elog(ERROR, "transformFromClauseItem: unexpected node (internal error)"
+            "\n\t%s", nodeToString(n));
+   return NULL;                /* can't get here, just keep compiler quiet */
+}
+
+
+/*
+ * transformWhereClause -
+ *   transforms the qualification and make sure it is of type Boolean
+ */
+Node *
+transformWhereClause(ParseState *pstate, Node *clause)
+{
+   Node       *qual;
+
+   if (clause == NULL)
+       return NULL;
+
+   qual = transformExpr(pstate, clause, EXPR_COLUMN_FIRST);
+
+   if (exprType(qual) != BOOLOID)
+   {
+       elog(ERROR, "WHERE clause must return type bool, not type %s",
+            typeidTypeName(exprType(qual)));
    }
-}  /* parseFromClause() */
+   return qual;
+}
 
 
 /*
@@ -786,10 +687,10 @@ findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause)
             * is a matching column.  If so, fall through to let
             * transformExpr() do the rest.  NOTE: if name could refer
             * ambiguously to more than one column name exposed by FROM,
-            * colnameRangeTableEntry will elog(ERROR).  That's just what
+            * colnameToVar will elog(ERROR).  That's just what
             * we want here.
             */
-           if (colnameRangeTableEntry(pstate, name) != NULL)
+           if (colnameToVar(pstate, name) != NULL)
                name = NULL;
        }
 
index 7976f5e779590ee41d59a98209ba5dc4b631d379..a033ff4be22edd26b20ec09158ea2303e94fc845 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.82 2000/08/08 15:42:03 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.83 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -157,41 +157,51 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
                {
                    case OP:
                        {
-                           Node       *lexpr = transformExpr(pstate, a->lexpr, precedence);
-                           Node       *rexpr = transformExpr(pstate, a->rexpr, precedence);
+                           Node       *lexpr = transformExpr(pstate,
+                                                             a->lexpr,
+                                                             precedence);
+                           Node       *rexpr = transformExpr(pstate,
+                                                             a->rexpr,
+                                                             precedence);
 
                            result = (Node *) make_op(a->opname, lexpr, rexpr);
                        }
                        break;
                    case ISNULL:
                        {
-                           Node       *lexpr = transformExpr(pstate, a->lexpr, precedence);
+                           Node       *lexpr = transformExpr(pstate,
+                                                             a->lexpr,
+                                                             precedence);
 
                            result = ParseFuncOrColumn(pstate,
                                                       "nullvalue",
                                                       lcons(lexpr, NIL),
                                                       false, false,
-                                                  &pstate->p_last_resno,
                                                       precedence);
                        }
                        break;
                    case NOTNULL:
                        {
-                           Node       *lexpr = transformExpr(pstate, a->lexpr, precedence);
+                           Node       *lexpr = transformExpr(pstate,
+                                                             a->lexpr,
+                                                             precedence);
 
                            result = ParseFuncOrColumn(pstate,
                                                       "nonnullvalue",
                                                       lcons(lexpr, NIL),
                                                       false, false,
-                                                  &pstate->p_last_resno,
                                                       precedence);
                        }
                        break;
                    case AND:
                        {
+                           Node       *lexpr = transformExpr(pstate,
+                                                             a->lexpr,
+                                                             precedence);
+                           Node       *rexpr = transformExpr(pstate,
+                                                             a->rexpr,
+                                                             precedence);
                            Expr       *expr = makeNode(Expr);
-                           Node       *lexpr = transformExpr(pstate, a->lexpr, precedence);
-                           Node       *rexpr = transformExpr(pstate, a->rexpr, precedence);
 
                            if (exprType(lexpr) != BOOLOID)
                                elog(ERROR, "left-hand side of AND is type '%s', not '%s'",
@@ -209,9 +219,13 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
                        break;
                    case OR:
                        {
+                           Node       *lexpr = transformExpr(pstate,
+                                                             a->lexpr,
+                                                             precedence);
+                           Node       *rexpr = transformExpr(pstate,
+                                                             a->rexpr,
+                                                             precedence);
                            Expr       *expr = makeNode(Expr);
-                           Node       *lexpr = transformExpr(pstate, a->lexpr, precedence);
-                           Node       *rexpr = transformExpr(pstate, a->rexpr, precedence);
 
                            if (exprType(lexpr) != BOOLOID)
                                elog(ERROR, "left-hand side of OR is type '%s', not '%s'",
@@ -227,8 +241,10 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
                        break;
                    case NOT:
                        {
+                           Node       *rexpr = transformExpr(pstate,
+                                                             a->rexpr,
+                                                             precedence);
                            Expr       *expr = makeNode(Expr);
-                           Node       *rexpr = transformExpr(pstate, a->rexpr, precedence);
 
                            if (exprType(rexpr) != BOOLOID)
                                elog(ERROR, "argument to NOT is type '%s', not '%s'",
@@ -254,13 +270,14 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
 
                /* transform the list of arguments */
                foreach(args, fn->args)
-                   lfirst(args) = transformExpr(pstate, (Node *) lfirst(args), precedence);
+                   lfirst(args) = transformExpr(pstate,
+                                                (Node *) lfirst(args),
+                                                precedence);
                result = ParseFuncOrColumn(pstate,
                                           fn->funcname,
                                           fn->args,
                                           fn->agg_star,
                                           fn->agg_distinct,
-                                          &pstate->p_last_resno,
                                           precedence);
                break;
            }
@@ -609,8 +626,7 @@ transformAttr(ParseState *pstate, Attr *att, int precedence)
 {
    Node       *basenode;
 
-   basenode = ParseNestedFuncOrColumn(pstate, att, &pstate->p_last_resno,
-                                      precedence);
+   basenode = ParseNestedFuncOrColumn(pstate, att, precedence);
    return transformIndirection(pstate, basenode, att->indirection);
 }
 
@@ -618,7 +634,6 @@ static Node *
 transformIdent(ParseState *pstate, Ident *ident, int precedence)
 {
    Node       *result = NULL;
-   RangeTblEntry *rte;
 
    /*
     * try to find the ident as a relation ... but not if subscripts
@@ -634,14 +649,10 @@ transformIdent(ParseState *pstate, Ident *ident, int precedence)
    if (result == NULL || precedence == EXPR_COLUMN_FIRST)
    {
        /* try to find the ident as a column */
-       if ((rte = colnameRangeTableEntry(pstate, ident->name)) != NULL)
-       {
-           /* Convert it to a fully qualified Attr, and transform that */
-           Attr       *att = makeAttr(rte->eref->relname, ident->name);
-
-           att->indirection = ident->indirection;
-           return transformAttr(pstate, att, precedence);
-       }
+       Node   *var = colnameToVar(pstate, ident->name);
+
+       if (var != NULL)
+           result = transformIndirection(pstate, var, ident->indirection);
    }
 
    if (result == NULL)
index bad9401c609f9118525c006b425477c6a1bf50eb..1f19b1b949e4eaf38513e34a6e0635878500bd21 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.89 2000/08/24 03:29:05 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.90 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -64,19 +64,20 @@ static Oid  agg_select_candidate(Oid typeid, CandidateList candidates);
  ** a tree with of Iter and Func nodes.
  */
 Node *
-ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int *curr_resno, int precedence)
+ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int precedence)
 {
    List       *mutator_iter;
    Node       *retval = NULL;
 
    if (attr->paramNo != NULL)
    {
-       Param      *param = (Param *) transformExpr(pstate, (Node *) attr->paramNo, EXPR_RELATION_FIRST);
+       Param      *param = (Param *) transformExpr(pstate,
+                                                   (Node *) attr->paramNo,
+                                                   EXPR_RELATION_FIRST);
 
        retval = ParseFuncOrColumn(pstate, strVal(lfirst(attr->attrs)),
                                   lcons(param, NIL),
                                   false, false,
-                                  curr_resno,
                                   precedence);
    }
    else
@@ -88,7 +89,6 @@ ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int *curr_resno, int pre
        retval = ParseFuncOrColumn(pstate, strVal(lfirst(attr->attrs)),
                                   lcons(ident, NIL),
                                   false, false,
-                                  curr_resno,
                                   precedence);
    }
 
@@ -98,7 +98,6 @@ ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int *curr_resno, int pre
        retval = ParseFuncOrColumn(pstate, strVal(lfirst(mutator_iter)),
                                   lcons(retval, NIL),
                                   false, false,
-                                  curr_resno,
                                   precedence);
    }
 
@@ -241,17 +240,15 @@ agg_select_candidate(Oid typeid, CandidateList candidates)
 Node *
 ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
                  bool agg_star, bool agg_distinct,
-                 int *curr_resno, int precedence)
+                 int precedence)
 {
    Oid         rettype = InvalidOid;
    Oid         argrelid = InvalidOid;
    Oid         funcid = InvalidOid;
    List       *i = NIL;
    Node       *first_arg = NULL;
-   char       *relname = NULL;
-   char       *refname = NULL;
+   char       *refname;
    Relation    rd;
-   Oid         relid;
    int         nargs = length(fargs);
    Func       *funcnode;
    Oid         oid_array[FUNC_MAX_ARGS];
@@ -283,81 +280,17 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
        if (IsA(first_arg, Ident) && ((Ident *) first_arg)->isRel)
        {
            Ident      *ident = (Ident *) first_arg;
-           RangeTblEntry *rte;
-           AttrNumber  attnum;
 
            /*
             * first arg is a relation. This could be a projection.
             */
            refname = ident->name;
 
-           rte = refnameRangeTableEntry(pstate, refname);
-           if (rte == NULL)
-           {
-               rte = addRangeTableEntry(pstate, refname,
-                                        makeAttr(refname, NULL),
-                                        FALSE, FALSE, TRUE);
-               warnAutoRange(pstate, refname);
-           }
-
-           relname = rte->relname;
-           relid = rte->relid;
-           attnum = InvalidAttrNumber;
-
-           /*
-            * If the attr isn't a set, just make a var for it.  If it is
-            * a set, treat it like a function and drop through. Look
-            * through the explicit column list first, since we now allow
-            * column aliases. - thomas 2000-02-07
-            */
-           if (rte->eref->attrs != NULL)
-           {
-               List       *c;
-
-               /*
-                * start counting attributes/columns from one. zero is
-                * reserved for InvalidAttrNumber. - thomas 2000-01-27
-                */
-               int         i = 1;
-
-               foreach(c, rte->eref->attrs)
-               {
-                   char       *colname = strVal(lfirst(c));
-
-                   /* found a match? */
-                   if (strcmp(colname, funcname) == 0)
-                   {
-                       char       *basename = get_attname(relid, i);
-
-                       if (basename != NULL)
-                       {
-                           funcname = basename;
-                           attnum = i;
-                       }
-
-                       /*
-                        * attnum was initialized to InvalidAttrNumber
-                        * earlier, so no need to reset it if the above
-                        * test fails. - thomas 2000-02-07
-                        */
-                       break;
-                   }
-                   i++;
-               }
-               if (attnum == InvalidAttrNumber)
-                   attnum = specialAttNum(funcname);
-           }
-           else
-               attnum = get_attnum(relid, funcname);
+           retval = qualifiedNameToVar(pstate, refname, funcname, true);
+           if (retval)
+               return retval;
 
-           if (attnum != InvalidAttrNumber)
-           {
-               return (Node *) make_var(pstate,
-                                        relid,
-                                        refname,
-                                        funcname);
-           }
-           /* else drop through - attr is a set */
+           /* else drop through - attr is a set or function */
        }
        else if (ISCOMPLEX(exprType(first_arg)))
        {
@@ -376,10 +309,7 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
                toid = exprType(first_arg);
                rd = heap_openr_nofail(typeidTypeName(toid));
                if (RelationIsValid(rd))
-               {
-                   relname = RelationGetRelationName(rd);
                    heap_close(rd, NoLock);
-               }
                else
                    elog(ERROR, "Type '%s' is not a relation type",
                         typeidTypeName(toid));
@@ -506,17 +436,9 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 
            rte = refnameRangeTableEntry(pstate, refname);
            if (rte == NULL)
-           {
-               rte = addRangeTableEntry(pstate, refname,
-                                        makeAttr(refname, NULL),
-                                        FALSE, FALSE, TRUE);
-               warnAutoRange(pstate, refname);
-           }
-
-           relname = rte->relname;
+               rte = addImplicitRTE(pstate, refname);
 
-           vnum = refnameRangeTablePosn(pstate, rte->eref->relname,
-                                        &sublevels_up);
+           vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
 
            /*
             * for func(relname), the param to the function is the tuple
@@ -525,7 +447,8 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
             * but has varattno == 0 to signal that the whole tuple is the
             * argument.
             */
-           toid = typeTypeId(typenameType(relname));
+           toid = typeTypeId(typenameType(rte->relname));
+
            /* replace it in the arg list */
            lfirst(i) = makeVar(vnum, 0, toid, -1, sublevels_up);
        }
@@ -666,16 +589,6 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
    /* perform the necessary typecasting of arguments */
    make_arguments(pstate, nargs, fargs, oid_array, true_oid_array);
 
-   /*
-    * Special checks to disallow sequence functions with side-effects
-    * in WHERE clauses.  This is pretty much of a hack; why disallow these
-    * when we have no way to check for side-effects of user-defined fns?
-    */
-   if (funcid == F_NEXTVAL && pstate->p_in_where_clause)
-       elog(ERROR, "Sequence function nextval is not allowed in WHERE clauses");
-   if (funcid == F_SETVAL && pstate->p_in_where_clause)
-       elog(ERROR, "Sequence function setval is not allowed in WHERE clauses");
-
    expr = makeNode(Expr);
    expr->typeOid = rettype;
    expr->opType = FUNC_EXPR;
index 5d363ea3e690d085a5545a816d629ee1ae873feb..85a56067bd2e0c227362066b0bf11218cfa42439 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.45 2000/08/24 03:29:05 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.46 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,8 +49,8 @@ make_parsestate(ParseState *parentParseState)
    pstate = palloc(sizeof(ParseState));
    MemSet(pstate, 0, sizeof(ParseState));
 
-   pstate->p_last_resno = 1;
    pstate->parentParseState = parentParseState;
+   pstate->p_last_resno = 1;
 
    return pstate;
 }
@@ -164,35 +164,44 @@ make_op(char *opname, Node *ltree, Node *rtree)
 
 /*
  * make_var
- *     Build a Var node for an attribute identified by name
+ *     Build a Var node for an attribute identified by RTE and attrno
  */
 Var *
-make_var(ParseState *pstate, Oid relid, char *refname,
-        char *attrname)
+make_var(ParseState *pstate, RangeTblEntry *rte, int attrno)
 {
-   HeapTuple   tp;
-   Form_pg_attribute att_tup;
    int         vnum,
-               attid;
+               sublevels_up;
    Oid         vartypeid;
    int32       type_mod;
-   int         sublevels_up;
-
-   vnum = refnameRangeTablePosn(pstate, refname, &sublevels_up);
-
-   tp = SearchSysCacheTuple(ATTNAME,
-                            ObjectIdGetDatum(relid),
-                            PointerGetDatum(attrname),
-                            0, 0);
-   if (!HeapTupleIsValid(tp))
-       elog(ERROR, "Relation %s does not have attribute %s",
-            refname, attrname);
-   att_tup = (Form_pg_attribute) GETSTRUCT(tp);
-   attid = att_tup->attnum;
-   vartypeid = att_tup->atttypid;
-   type_mod = att_tup->atttypmod;
-
-   return makeVar(vnum, attid, vartypeid, type_mod, sublevels_up);
+
+   vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
+
+   if (rte->relid != InvalidOid)
+   {
+       /* Plain relation RTE --- get the attribute's type info */
+       HeapTuple   tp;
+       Form_pg_attribute att_tup;
+
+       tp = SearchSysCacheTuple(ATTNUM,
+                                ObjectIdGetDatum(rte->relid),
+                                Int16GetDatum(attrno),
+                                0, 0);
+       /* this shouldn't happen... */
+       if (!HeapTupleIsValid(tp))
+           elog(ERROR, "Relation %s does not have attribute %d",
+                rte->relname, attrno);
+       att_tup = (Form_pg_attribute) GETSTRUCT(tp);
+       vartypeid = att_tup->atttypid;
+       type_mod = att_tup->atttypmod;
+   }
+   else
+   {
+       /* Subselect RTE --- get type info from subselect's tlist */
+       elog(ERROR, "make_var: subselect in FROM not implemented yet");
+       vartypeid = type_mod = 0;
+   }
+
+   return makeVar(vnum, attrno, vartypeid, type_mod, sublevels_up);
 }
 
 /*
index 802299c8966856265f752fd30d4b80664c679fa7..491cbc5ef08780cfec069a90d487b1e187f34630 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.46 2000/08/08 15:42:04 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.47 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "access/htup.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
+#include "parser/parsetree.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_type.h"
+#include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 
 
+static Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
+                             char *colname);
+static Node *scanJoinForColumn(JoinExpr *join, char *colname,
+                              int sublevels_up);
+static List *expandNamesVars(ParseState *pstate, List *names, List *vars);
+static void warnAutoRange(ParseState *pstate, char *refname);
+
+
 /*
  * Information defining the "system" attributes of every relation.
  */
@@ -65,40 +76,96 @@ static struct
 #define SPECIALS ((int) (sizeof(special_attr)/sizeof(special_attr[0])))
 
 
-#ifdef NOT_USED
-/* refnameRangeTableEntries()
- * Given refname, return a list of range table entries
- * This is possible with JOIN syntax, where tables in a join
- * acquire the same reference name.
- * - thomas 2000-01-20
- * But at the moment we aren't carrying along a full list of
- * table/column aliases, so we don't have the full mechanism
- * to support outer joins in place yet.
- * - thomas 2000-03-04
+/*
+ * refnameRangeOrJoinEntry
+ *   Given a refname, look to see if it matches any RTE or join table.
+ *   If so, return a pointer to the RangeTblEntry or JoinExpr.
+ *   Optionally get its nesting depth (0 = current).   If sublevels_up
+ *   is NULL, only consider items at the current nesting level.
  */
-
-static List *
-refnameRangeTableEntries(ParseState *pstate, char *refname)
+Node *
+refnameRangeOrJoinEntry(ParseState *pstate,
+                       char *refname,
+                       int *sublevels_up)
 {
-   List       *rteList = NULL;
-   List       *temp;
+   if (sublevels_up)
+       *sublevels_up = 0;
 
    while (pstate != NULL)
    {
+       List       *temp;
+       JoinExpr   *join;
+
+       /*
+        * Check the rangetable for RTEs; if no match, recursively scan
+        * the jointree for join tables.  We assume that no duplicate
+        * entries have been made in any one nesting level.
+        */
        foreach(temp, pstate->p_rtable)
        {
            RangeTblEntry *rte = lfirst(temp);
 
            if (strcmp(rte->eref->relname, refname) == 0)
-               rteList = lappend(rteList, rte);
+               return (Node *) rte;
        }
+
+       join = scanJoinTreeForRefname((Node *) pstate->p_jointree, refname);
+       if (join)
+           return (Node *) join;
+
        pstate = pstate->parentParseState;
+       if (sublevels_up)
+           (*sublevels_up)++;
+       else
+           break;
    }
-   return rteList;
+   return NULL;
 }
-#endif
 
-/* given refname, return a pointer to the range table entry */
+/* Recursively search a jointree for a joinexpr with given refname */
+JoinExpr *
+scanJoinTreeForRefname(Node *jtnode, char *refname)
+{
+   JoinExpr   *result = NULL;
+
+   if (jtnode == NULL)
+       return NULL;
+   if (IsA(jtnode, List))
+   {
+       List       *l;
+
+       foreach(l, (List *) jtnode)
+       {
+           result = scanJoinTreeForRefname(lfirst(l), refname);
+           if (result)
+               break;
+       }
+   }
+   else if (IsA(jtnode, RangeTblRef))
+   {
+       /* ignore ... */
+   }
+   else if (IsA(jtnode, JoinExpr))
+   {
+       JoinExpr   *j = (JoinExpr *) jtnode;
+
+       if (j->alias && strcmp(j->alias->relname, refname) == 0)
+           return j;
+       result = scanJoinTreeForRefname(j->larg, refname);
+       if (! result)
+           result = scanJoinTreeForRefname(j->rarg, refname);
+   }
+   else
+       elog(ERROR, "scanJoinTreeForRefname: unexpected node type %d",
+            nodeTag(jtnode));
+   return result;
+}
+
+/*
+ * given refname, return a pointer to the range table entry.
+ *
+ * NOTE that this routine will ONLY find RTEs, not join tables.
+ */
 RangeTblEntry *
 refnameRangeTableEntry(ParseState *pstate, char *refname)
 {
@@ -118,9 +185,13 @@ refnameRangeTableEntry(ParseState *pstate, char *refname)
    return NULL;
 }
 
-/* given refname, return RT index (starting with 1) of the relation,
+/*
+ * given refname, return RT index (starting with 1) of the relation,
  * and optionally get its nesting depth (0 = current). If sublevels_up
  * is NULL, only consider rels at the current nesting level.
+ * A zero result means name not found.
+ *
+ * NOTE that this routine will ONLY find RTEs, not join tables.
  */
 int
 refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up)
@@ -152,114 +223,264 @@ refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up)
 }
 
 /*
- * returns range entry if found, else NULL
+ * given an RTE, return RT index (starting with 1) of the entry,
+ * and optionally get its nesting depth (0 = current). If sublevels_up
+ * is NULL, only consider rels at the current nesting level.
+ * Raises error if RTE not found.
  */
-RangeTblEntry *
-colnameRangeTableEntry(ParseState *pstate, char *colname)
+int
+RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up)
 {
-   List       *et;
-   List       *rtable;
-   RangeTblEntry *rte_result = NULL;
+   int         index;
+   List       *temp;
+
+   if (sublevels_up)
+       *sublevels_up = 0;
 
    while (pstate != NULL)
    {
-       if (pstate->p_is_rule)
-           rtable = lnext(lnext(pstate->p_rtable));
+       index = 1;
+       foreach(temp, pstate->p_rtable)
+       {
+           if (rte == (RangeTblEntry *) lfirst(temp))
+               return index;
+           index++;
+       }
+       pstate = pstate->parentParseState;
+       if (sublevels_up)
+           (*sublevels_up)++;
        else
-           rtable = pstate->p_rtable;
+           break;
+   }
+   elog(ERROR, "RTERangeTablePosn: RTE not found (internal error)");
+   return 0;                   /* keep compiler quiet */
+}
+
+/*
+ * scanRTEForColumn
+ *   Search the column names of a single RTE for the given name.
+ *   If found, return an appropriate Var node, else return NULL.
+ *   If the name proves ambiguous within this RTE, raise error.
+ */
+static Node *
+scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
+{
+   Node       *result = NULL;
+   int         attnum = 0;
+   List       *c;
 
-       foreach(et, rtable)
+   /*
+    * Scan the user column names (or aliases) for a match.
+    * Complain if multiple matches.
+    */
+   foreach(c, rte->eref->attrs)
+   {
+       attnum++;
+       if (strcmp(strVal(lfirst(c)), colname) == 0)
        {
-           RangeTblEntry *rte_candidate = NULL;
-           RangeTblEntry *rte = lfirst(et);
+           if (result)
+               elog(ERROR, "Column reference \"%s\" is ambiguous", colname);
+           result = (Node *) make_var(pstate, rte, attnum);
+       }
+   }
 
-           /* only consider RTEs mentioned in FROM or UPDATE/DELETE */
-           if (!rte->inFromCl && rte != pstate->p_target_rangetblentry)
-               continue;
+   /*
+    * If we have a unique match, return it.  Note that this allows a user
+    * alias to override a system column name (such as OID) without error.
+    */
+   if (result)
+       return result;
 
-           if (rte->eref->attrs != NULL)
-           {
-               List       *c;
-
-               foreach(c, rte->ref->attrs)
-               {
-                   if (strcmp(strVal(lfirst(c)), colname) == 0)
-                   {
-                       if (rte_candidate != NULL)
-                           elog(ERROR, "Column '%s' is ambiguous"
-                                " (internal error)", colname);
-                       rte_candidate = rte;
-                   }
-               }
-           }
+   /*
+    * If the RTE represents a table (not a sub-select), consider system
+    * column names.
+    */
+   if (rte->relid != InvalidOid)
+   {
+       attnum = specialAttNum(colname);
+       if (attnum != InvalidAttrNumber)
+           result = (Node *) make_var(pstate, rte, attnum);
+   }
+
+   return result;
+}
 
+/*
+ * scanJoinForColumn
+ *   Search the column names of a single join table for the given name.
+ *   If found, return an appropriate Var node or expression, else return NULL.
+ *   If the name proves ambiguous within this jointable, raise error.
+ */
+static Node *
+scanJoinForColumn(JoinExpr *join, char *colname, int sublevels_up)
+{
+   Node       *result = NULL;
+   int         attnum = 0;
+   List       *c;
+
+   foreach(c, join->colnames)
+   {
+       attnum++;
+       if (strcmp(strVal(lfirst(c)), colname) == 0)
+       {
+           if (result)
+               elog(ERROR, "Column reference \"%s\" is ambiguous", colname);
+           result = copyObject(nth(attnum-1, join->colvars));
            /*
-            * Even if we have an attribute list in the RTE, look for the
-            * column here anyway. This is the only way we will find
-            * implicit columns like "oid". - thomas 2000-02-07
+            * If referencing an uplevel join item, we must adjust
+            * sublevels settings in the copied expression.
             */
-           if ((rte_candidate == NULL)
-               && (get_attnum(rte->relid, colname) != InvalidAttrNumber))
-               rte_candidate = rte;
+           if (sublevels_up > 0)
+               IncrementVarSublevelsUp(result, sublevels_up, 0);
+       }
+   }
+   return result;
+}
+
+/*
+ * colnameToVar
+ *   Search for an unqualified column name.
+ *   If found, return the appropriate Var node (or expression).
+ *   If not found, return NULL.  If the name proves ambiguous, raise error.
+ */
+Node *
+colnameToVar(ParseState *pstate, char *colname)
+{
+   Node       *result = NULL;
+   ParseState *orig_pstate = pstate;
+   int         levels_up = 0;
 
-           if (rte_candidate == NULL)
-               continue;
+   while (pstate != NULL)
+   {
+       List       *jt;
 
-           if (rte_result != NULL)
+       /*
+        * We want to look only at top-level jointree items, and even for
+        * those, ignore RTEs that are marked as not inFromCl and not
+        * the query's target relation.
+        */
+       foreach(jt, pstate->p_jointree)
+       {
+           Node   *jtnode = (Node *) lfirst(jt);
+           Node   *newresult = NULL;
+
+           if (IsA(jtnode, RangeTblRef))
            {
-               if (!pstate->p_is_insert ||
+               int         varno = ((RangeTblRef *) jtnode)->rtindex;
+               RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
+
+               if (! rte->inFromCl &&
                    rte != pstate->p_target_rangetblentry)
-                   elog(ERROR, "Column '%s' is ambiguous", colname);
+                   continue;
+
+               /* use orig_pstate here to get the right sublevels_up */
+               newresult = scanRTEForColumn(orig_pstate, rte, colname);
+           }
+           else if (IsA(jtnode, JoinExpr))
+           {
+               JoinExpr   *j = (JoinExpr *) jtnode;
+
+               newresult = scanJoinForColumn(j, colname, levels_up);
            }
            else
-               rte_result = rte;
+               elog(ERROR, "colnameToVar: unexpected node type %d",
+                    nodeTag(jtnode));
+
+           if (newresult)
+           {
+               if (result)
+                   elog(ERROR, "Column reference \"%s\" is ambiguous",
+                        colname);
+               result = newresult;
+           }
        }
 
-       if (rte_result != NULL)
+       if (result != NULL)
            break;              /* found */
 
        pstate = pstate->parentParseState;
+       levels_up++;
    }
-   return rte_result;
+
+   return result;
 }
 
 /*
- * put new entry in pstate p_rtable structure, or return pointer
- * if pstate null
+ * qualifiedNameToVar
+ *   Search for a qualified column name (refname + column name).
+ *   If found, return the appropriate Var node (or expression).
+ *   If not found, return NULL.  If the name proves ambiguous, raise error.
+ */
+Node *
+qualifiedNameToVar(ParseState *pstate, char *refname, char *colname,
+                  bool implicitRTEOK)
+{
+   Node       *result;
+   Node       *rteorjoin;
+   int         sublevels_up;
+
+   rteorjoin = refnameRangeOrJoinEntry(pstate, refname, &sublevels_up);
+
+   if (rteorjoin == NULL)
+   {
+       if (! implicitRTEOK)
+           return NULL;
+       rteorjoin = (Node *) addImplicitRTE(pstate, refname);
+       sublevels_up = 0;
+   }
+
+   if (IsA(rteorjoin, RangeTblEntry))
+       result = scanRTEForColumn(pstate, (RangeTblEntry *) rteorjoin,
+                                 colname);
+   else if (IsA(rteorjoin, JoinExpr))
+       result = scanJoinForColumn((JoinExpr *) rteorjoin,
+                                 colname, sublevels_up);
+   else
+   {
+       elog(ERROR, "qualifiedNameToVar: unexpected node type %d",
+            nodeTag(rteorjoin));
+       result = NULL;          /* keep compiler quiet */
+   }
+
+   return result;
+}
+
+/*
+ * Add an entry to the pstate's range table (p_rtable), unless the
+ * specified refname is already present, in which case raise error.
+ *
+ * If pstate is NULL, we just build an RTE and return it without worrying
+ * about membership in an rtable list.
  */
 RangeTblEntry *
 addRangeTableEntry(ParseState *pstate,
                   char *relname,
-                  Attr *ref,
+                  Attr *alias,
                   bool inh,
-                  bool inFromCl,
-                  bool inJoinSet)
+                  bool inFromCl)
 {
+   char       *refname = alias ? alias->relname : relname;
    Relation    rel;
    RangeTblEntry *rte;
    Attr       *eref;
    int         maxattrs;
-   int         sublevels_up;
+   int         numaliases;
    int         varattno;
 
-   /* Look for an existing rte, if available... */
+   /* Check for conflicting RTE or jointable alias (at level 0 only) */
    if (pstate != NULL)
    {
-       int         rt_index = refnameRangeTablePosn(pstate, ref->relname,
-                                                    &sublevels_up);
+       Node   *rteorjoin = refnameRangeOrJoinEntry(pstate, refname, NULL);
 
-       if (rt_index != 0 && (!inFromCl || sublevels_up == 0))
-       {
-           if (!strcmp(ref->relname, "*OLD*") || !strcmp(ref->relname, "*NEW*"))
-               return (RangeTblEntry *) nth(rt_index - 1, pstate->p_rtable);
-           elog(ERROR, "Table name '%s' specified more than once", ref->relname);
-       }
+       if (rteorjoin)
+           elog(ERROR, "Table name \"%s\" specified more than once",
+                refname);
    }
 
    rte = makeNode(RangeTblEntry);
 
    rte->relname = relname;
-   rte->ref = ref;
+   rte->alias = alias;
 
    /*
     * Get the rel's OID.  This access also ensures that we have an
@@ -271,30 +492,34 @@ addRangeTableEntry(ParseState *pstate,
    rte->relid = RelationGetRelid(rel);
    maxattrs = RelationGetNumberOfAttributes(rel);
 
-   eref = copyObject(ref);
-   if (maxattrs < length(eref->attrs))
-       elog(ERROR, "Table '%s' has %d columns available but %d columns specified",
-            relname, maxattrs, length(eref->attrs));
+   eref = alias ? copyObject(alias) : makeAttr(refname, NULL);
+   numaliases = length(eref->attrs);
+
+   if (maxattrs < numaliases)
+       elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified",
+            refname, maxattrs, numaliases);
 
    /* fill in any unspecified alias columns */
-   for (varattno = length(eref->attrs); varattno < maxattrs; varattno++)
+   for (varattno = numaliases; varattno < maxattrs; varattno++)
    {
        char       *attrname;
 
        attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
        eref->attrs = lappend(eref->attrs, makeString(attrname));
    }
-   heap_close(rel, AccessShareLock);
    rte->eref = eref;
 
-   /*
-    * Flags: - this RTE should be expanded to include descendant tables,
-    * - this RTE is in the FROM clause, - this RTE should be included in
-    * the planner's final join.
+   heap_close(rel, AccessShareLock);
+
+   /*----------
+    * Flags:
+    * - this RTE should be expanded to include descendant tables,
+    * - this RTE is in the FROM clause,
+    * - this RTE should not be checked for access rights.
+    *----------
     */
    rte->inh = inh;
    rte->inFromCl = inFromCl;
-   rte->inJoinSet = inJoinSet;
    rte->skipAcl = false;       /* always starts out false */
 
    /*
@@ -306,118 +531,184 @@ addRangeTableEntry(ParseState *pstate,
    return rte;
 }
 
-/* expandTable()
- * Populates an Attr with table name and column names
- * This is similar to expandAll(), but does not create an RTE
- * if it does not already exist.
- * - thomas 2000-01-19
+/*
+ * Add the given RTE as a top-level entry in the pstate's join tree,
+ * unless there already is an entry for it.
  */
-Attr *
-expandTable(ParseState *pstate, char *refname, bool getaliases)
+void
+addRTEtoJoinTree(ParseState *pstate, RangeTblEntry *rte)
+{
+   int         rtindex = RTERangeTablePosn(pstate, rte, NULL);
+   List       *jt;
+   RangeTblRef *rtr;
+
+   foreach(jt, pstate->p_jointree)
+   {
+       Node       *n = (Node *) lfirst(jt);
+
+       if (IsA(n, RangeTblRef))
+       {
+           if (rtindex == ((RangeTblRef *) n)->rtindex)
+               return;         /* it's already being joined to */
+       }
+   }
+
+   /* Not present, so add it */
+   rtr = makeNode(RangeTblRef);
+   rtr->rtindex = rtindex;
+   pstate->p_jointree = lappend(pstate->p_jointree, rtr);
+}
+
+/*
+ * Add a POSTQUEL-style implicit RTE.
+ *
+ * We assume caller has already checked that there is no such RTE now.
+ */
+RangeTblEntry *
+addImplicitRTE(ParseState *pstate, char *relname)
 {
-   Attr       *attr;
    RangeTblEntry *rte;
+
+   rte = addRangeTableEntry(pstate, relname, NULL, false, false);
+   addRTEtoJoinTree(pstate, rte);
+   warnAutoRange(pstate, relname);
+
+   return rte;
+}
+
+/* expandRTE()
+ *
+ * Given a rangetable entry, create lists of its column names (aliases if
+ * provided, else real names) and Vars for each column.  Only user columns
+ * are considered, since this is primarily used to expand '*' and determine
+ * the contents of JOIN tables.
+ *
+ * If only one of the two kinds of output list is needed, pass NULL for the
+ * output pointer for the unwanted one.
+ */
+void
+expandRTE(ParseState *pstate, RangeTblEntry *rte,
+         List **colnames, List **colvars)
+{
    Relation    rel;
    int         varattno,
-               maxattrs;
+               maxattrs,
+               rtindex,
+               sublevels_up;
 
-   rte = refnameRangeTableEntry(pstate, refname);
+   if (colnames)
+       *colnames = NIL;
+   if (colvars)
+       *colvars = NIL;
 
-   if (getaliases && (rte != NULL))
-       return rte->eref;
+   /* Need the RT index of the entry for creating Vars */
+   rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up);
 
-   if (rte != NULL)
-       rel = heap_open(rte->relid, AccessShareLock);
-   else
-       rel = heap_openr(refname, AccessShareLock);
-
-   if (rel == NULL)
-       elog(ERROR, "Relation '%s' not found", refname);
+   rel = heap_open(rte->relid, AccessShareLock);
 
    maxattrs = RelationGetNumberOfAttributes(rel);
 
-   attr = makeAttr(refname, NULL);
-
    for (varattno = 0; varattno < maxattrs; varattno++)
    {
-       char       *attrname;
+       Form_pg_attribute attr = rel->rd_att->attrs[varattno];
 
 #ifdef _DROP_COLUMN_HACK__
-       if (COLUMN_IS_DROPPED(rel->rd_att->attrs[varattno]))
+       if (COLUMN_IS_DROPPED(attr))
            continue;
 #endif  /* _DROP_COLUMN_HACK__ */
-       attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
-       attr->attrs = lappend(attr->attrs, makeString(attrname));
+
+       if (colnames)
+       {
+           char       *label;
+
+           if (varattno < length(rte->eref->attrs))
+               label = strVal(nth(varattno, rte->eref->attrs));
+           else
+               label = NameStr(attr->attname);
+           *colnames = lappend(*colnames, makeString(pstrdup(label)));
+       }
+
+       if (colvars)
+       {
+           Var        *varnode;
+
+           varnode = makeVar(rtindex, attr->attnum,
+                             attr->atttypid, attr->atttypmod,
+                             sublevels_up);
+
+           *colvars = lappend(*colvars, varnode);
+       }
    }
 
    heap_close(rel, AccessShareLock);
-
-   return attr;
 }
 
 /*
- * expandAll -
- *   makes a list of attributes
+ * expandRelAttrs -
+ *   makes a list of TargetEntry nodes for the attributes of the rel
  */
 List *
-expandAll(ParseState *pstate, char *relname, Attr *ref, int *this_resno)
+expandRelAttrs(ParseState *pstate, RangeTblEntry *rte)
 {
-   List       *te_list = NIL;
-   RangeTblEntry *rte;
-   Relation    rel;
-   int         varattno,
-               maxattrs;
+   List       *name_list,
+              *var_list;
 
-   rte = refnameRangeTableEntry(pstate, ref->relname);
-   if (rte == NULL)
-   {
-       rte = addRangeTableEntry(pstate, relname, ref,
-                                FALSE, FALSE, TRUE);
-       warnAutoRange(pstate, ref->relname);
-   }
+   expandRTE(pstate, rte, &name_list, &var_list);
 
-   rel = heap_open(rte->relid, AccessShareLock);
+   return expandNamesVars(pstate, name_list, var_list);
+}
 
-   maxattrs = RelationGetNumberOfAttributes(rel);
+/*
+ * expandJoinAttrs -
+ *   makes a list of TargetEntry nodes for the attributes of the join
+ */
+List *
+expandJoinAttrs(ParseState *pstate, JoinExpr *join, int sublevels_up)
+{
+   List       *vars;
 
-   for (varattno = 0; varattno < maxattrs; varattno++)
-   {
-       char       *attrname;
-       char       *label;
-       Var        *varnode;
-       TargetEntry *te = makeNode(TargetEntry);
+   vars = copyObject(join->colvars);
+   /*
+    * If referencing an uplevel join item, we must adjust
+    * sublevels settings in the copied expression.
+    */
+   if (sublevels_up > 0)
+       IncrementVarSublevelsUp((Node *) vars, sublevels_up, 0);
 
-#ifdef _DROP_COLUMN_HACK__
-       if (COLUMN_IS_DROPPED(rel->rd_att->attrs[varattno]))
-           continue;
-#endif  /* _DROP_COLUMN_HACK__ */
-       attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
+   return expandNamesVars(pstate,
+                          copyObject(join->colnames),
+                          vars);
+}
 
-       /*
-        * varattno is zero-based, so check that length() is always
-        * greater
-        */
-       if (length(rte->eref->attrs) > varattno)
-           label = pstrdup(strVal(nth(varattno, rte->eref->attrs)));
-       else
-           label = attrname;
-       varnode = make_var(pstate, rte->relid, relname, attrname);
+/*
+ * expandNamesVars -
+ *     Workhorse for "*" expansion: produce a list of targetentries
+ *     given lists of column names (as String nodes) and var references.
+ */
+static List *
+expandNamesVars(ParseState *pstate, List *names, List *vars)
+{
+   List       *te_list = NIL;
 
-       /*
-        * Even if the elements making up a set are complex, the set
-        * itself is not.
-        */
+   while (names)
+   {
+       char       *label = strVal(lfirst(names));
+       Node       *varnode = (Node *) lfirst(vars);
+       TargetEntry *te = makeNode(TargetEntry);
 
-       te->resdom = makeResdom((AttrNumber) (*this_resno)++,
-                               varnode->vartype,
-                               varnode->vartypmod,
+       te->resdom = makeResdom((AttrNumber) (pstate->p_last_resno)++,
+                               exprType(varnode),
+                               exprTypmod(varnode),
                                label,
                                false);
-       te->expr = (Node *) varnode;
+       te->expr = varnode;
        te_list = lappend(te_list, te);
+
+       names = lnext(names);
+       vars = lnext(vars);
    }
 
-   heap_close(rel, AccessShareLock);
+   Assert(vars == NIL);        /* lists not same length? */
 
    return te_list;
 }
@@ -531,11 +822,17 @@ attnumTypeId(Relation rd, int attid)
    return rd->rd_att->attrs[attid - 1]->atttypid;
 }
 
-void
+/*
+ * Generate a warning about an implicit RTE, if appropriate.
+ *
+ * Our current theory on this is that we should allow "SELECT foo.*"
+ * but warn about a mixture of explicit and implicit RTEs.
+ */
+static void
 warnAutoRange(ParseState *pstate, char *refname)
 {
-   List       *temp;
    bool        foundInFromCl = false;
+   List       *temp;
 
    foreach(temp, pstate->p_rtable)
    {
@@ -548,8 +845,8 @@ warnAutoRange(ParseState *pstate, char *refname)
        }
    }
    if (foundInFromCl)
-       elog(NOTICE, "Adding missing FROM-clause entry%s for table %s",
-           pstate->parentParseState != NULL ? " in subquery" : "",
-           refname);
+       elog(NOTICE, "Adding missing FROM-clause entry%s for table \"%s\"",
+            pstate->parentParseState != NULL ? " in subquery" : "",
+            refname);
 }
 
index 1564f976b042b0ef13154b5640b51e76ecacbda0..b8e1570985f2cba6283e074fe31ede401d167c61 100644 (file)
@@ -8,13 +8,14 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.61 2000/08/08 15:42:04 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.62 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 #include "nodes/makefuncs.h"
+#include "parser/parsetree.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_func.h"
@@ -104,36 +105,8 @@ transformTargetList(ParseState *pstate, List *targetlist)
                 * Target item is a single '*', expand all tables (eg.
                 * SELECT * FROM emp)
                 */
-               if (pstate->p_shape != NULL)
-               {
-                   List       *s,
-                              *a;
-                   int         i;
-
-                   Assert(length(pstate->p_shape) == length(pstate->p_alias));
-
-                   s = pstate->p_shape;
-                   a = pstate->p_alias;
-                   for (i = 0; i < length(pstate->p_shape); i++)
-                   {
-                       TargetEntry *te;
-                       char       *colname;
-                       Attr       *shape = lfirst(s);
-                       Attr       *alias = lfirst(a);
-
-                       Assert(IsA(shape, Attr) &&IsA(alias, Attr));
-
-                       colname = strVal(lfirst(alias->attrs));
-                       te = transformTargetEntry(pstate, (Node *) shape,
-                                                 NULL, colname, false);
-                       p_target = lappend(p_target, te);
-                       s = lnext(s);
-                       a = lnext(a);
-                   }
-               }
-               else
-                   p_target = nconc(p_target,
-                                    ExpandAllTables(pstate));
+               p_target = nconc(p_target,
+                                ExpandAllTables(pstate));
            }
            else if (att->attrs != NIL &&
                     strcmp(strVal(lfirst(att->attrs)), "*") == 0)
@@ -143,10 +116,30 @@ transformTargetList(ParseState *pstate, List *targetlist)
                 * Target item is relation.*, expand that table (eg.
                 * SELECT emp.*, dname FROM emp, dept)
                 */
-               p_target = nconc(p_target,
-                                expandAll(pstate, att->relname,
-                                          makeAttr(att->relname, NULL),
-                                          &pstate->p_last_resno));
+               Node       *rteorjoin;
+               int         sublevels_up;
+
+               rteorjoin = refnameRangeOrJoinEntry(pstate, att->relname,
+                                                   &sublevels_up);
+
+               if (rteorjoin == NULL)
+               {
+                   rteorjoin = (Node *) addImplicitRTE(pstate, att->relname);
+                   sublevels_up = 0;
+               }
+
+               if (IsA(rteorjoin, RangeTblEntry))
+                   p_target = nconc(p_target,
+                                    expandRelAttrs(pstate,
+                                                   (RangeTblEntry *) rteorjoin));
+               else if (IsA(rteorjoin, JoinExpr))
+                   p_target = nconc(p_target,
+                                    expandJoinAttrs(pstate,
+                                                    (JoinExpr *) rteorjoin,
+                                                    sublevels_up));
+               else
+                   elog(ERROR, "transformTargetList: unexpected node type %d",
+                        nodeTag(rteorjoin));
            }
            else
            {
@@ -219,23 +212,12 @@ updateTargetListEntry(ParseState *pstate,
     */
    if (indirection)
    {
-#ifndef DISABLE_JOIN_SYNTAX
-       Attr       *att = makeAttr(pstrdup(RelationGetRelationName(rd)), colname);
-
-#else
-       Attr       *att = makeNode(Attr);
-
-#endif
+       Attr       *att = makeAttr(pstrdup(RelationGetRelationName(rd)),
+                                  colname);
        Node       *arrayBase;
        ArrayRef   *aref;
 
-#ifdef DISABLE_JOIN_SYNTAX
-       att->relname = pstrdup(RelationGetRelationName(rd));
-       att->attrs = lcons(makeString(colname), NIL);
-#endif
-       arrayBase = ParseNestedFuncOrColumn(pstate, att,
-                                           &pstate->p_last_resno,
-                                           EXPR_COLUMN_FIRST);
+       arrayBase = ParseNestedFuncOrColumn(pstate, att, EXPR_COLUMN_FIRST);
        aref = transformArraySubscripts(pstate, arrayBase,
                                        indirection,
                                        pstate->p_is_insert,
@@ -401,46 +383,54 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
 }
 
 /* ExpandAllTables()
- * Turns '*' (in the target list) into a list of attributes
- * (of all relations in the range table)
+ * Turns '*' (in the target list) into a list of targetlist entries.
+ *
+ * tlist entries are generated for each relation appearing in the FROM list,
+ * which by now has been expanded into a join tree.
  */
 static List *
 ExpandAllTables(ParseState *pstate)
 {
    List       *target = NIL;
-   List       *rt,
-              *rtable;
-
-   rtable = pstate->p_rtable;
-   if (pstate->p_is_rule)
-   {
-
-       /*
-        * skip first two entries, "*new*" and "*current*"
-        */
-       rtable = lnext(lnext(rtable));
-   }
+   List       *jt;
 
    /* SELECT *; */
-   if (rtable == NIL)
+   if (pstate->p_jointree == NIL)
        elog(ERROR, "Wildcard with no tables specified not allowed");
 
-   foreach(rt, rtable)
+   foreach(jt, pstate->p_jointree)
    {
-       RangeTblEntry *rte = lfirst(rt);
+       Node       *n = (Node *) lfirst(jt);
 
-       /*
-        * we only expand those listed in the from clause. (This will also
-        * prevent us from using the wrong table in inserts: eg. tenk2 in
-        * "insert into tenk2 select * from tenk1;")
-        */
-       if (!rte->inFromCl)
-           continue;
+       if (IsA(n, RangeTblRef))
+       {
+           RangeTblEntry *rte;
 
-       target = nconc(target,
-                      expandAll(pstate, rte->eref->relname, rte->eref,
-                                &pstate->p_last_resno));
+           rte = rt_fetch(((RangeTblRef *) n)->rtindex,
+                          pstate->p_rtable);
+
+           /*
+            * Ignore added-on relations that were not listed in the FROM
+            * clause.
+            */
+           if (!rte->inFromCl)
+               continue;
+
+           target = nconc(target, expandRelAttrs(pstate, rte));
+       }
+       else if (IsA(n, JoinExpr))
+       {
+           /* A newfangled join expression */
+           JoinExpr   *j = (JoinExpr *) n;
+
+           /* Currently, a join expr could only have come from FROM. */
+           target = nconc(target, expandJoinAttrs(pstate, j, 0));
+       }
+       else
+           elog(ERROR, "ExpandAllTables: unexpected node (internal error)"
+                "\n\t%s", nodeToString(n));
    }
+
    return target;
 }
 
index a7652407b73252f8fdc98661529e3f9188f73a65..4a6c825498afd76cae0997390787e1c54743a91e 100644 (file)
@@ -1,32 +1,40 @@
 /*-------------------------------------------------------------------------
  *
  * parser.c
+ *     Main entry point/driver for PostgreSQL parser
+ *
  *
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.45 2000/04/12 17:15:27 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.46 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
 #include "parser/analyze.h"
 #include "parser/gramparse.h"
+#include "parser/parse.h"
 #include "parser/parser.h"
 #include "parser/parse_expr.h"
 
+
 #if defined(FLEX_SCANNER)
 extern void DeleteBuffer(void);
-
 #endif  /* FLEX_SCANNER */
 
 char      *parseString;        /* the char* which holds the string to be
                                 * parsed */
 List      *parsetree;          /* result of parsing is left here */
 
+static int lookahead_token;    /* one-token lookahead */
+static bool have_lookahead;        /* lookahead_token set? */
+
 #ifdef SETS_FIXED
 static void fixupsets();
 static void define_sets();
@@ -42,11 +50,11 @@ parser(char *str, Oid *typev, int nargs)
    List       *queryList;
    int         yyresult;
 
-   init_io();
-
-   parseString = pstrdup(str);
+   parseString = str;
    parsetree = NIL;            /* in case parser forgets to set it */
+   have_lookahead = false;
 
+   scanner_init();
    parser_init(typev, nargs);
    parse_expr_init();
 
@@ -83,6 +91,52 @@ parser(char *str, Oid *typev, int nargs)
    return queryList;
 }
 
+
+/*
+ * Intermediate filter between parser and base lexer (base_yylex in scan.l).
+ *
+ * The filter is needed because in some cases SQL92 requires more than one
+ * token lookahead.  We reduce these cases to one-token lookahead by combining
+ * tokens here, in order to keep the grammar LR(1).
+ *
+ * Using a filter is simpler than trying to recognize multiword tokens 
+ * directly in scan.l, because we'd have to allow for comments between the
+ * words ...
+ */
+int
+yylex(void)
+{
+   int         cur_token;
+
+   /* Get next token --- we might already have it */
+   if (have_lookahead)
+   {
+       cur_token = lookahead_token;
+       have_lookahead = false;
+   }
+   else
+       cur_token = base_yylex();
+
+   /* Do we need to look ahead for a possible multiword token? */
+   switch (cur_token)
+   {
+       case UNION:
+           /* UNION JOIN must be reduced to a single UNIONJOIN token */
+           lookahead_token = base_yylex();
+           if (lookahead_token == JOIN)
+               cur_token = UNIONJOIN;
+           else
+               have_lookahead = true;
+           break;
+
+       default:
+           break;
+   }
+
+   return cur_token;
+}
+
+
 #ifdef SETS_FIXED
 static void
 fixupsets(Query *parse)
index f5597d1593ed245c1b96c5bae50e21c5a57ef359..5700915ad94ed27b3322dfc219d178c7df38aa0d 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/scan.l,v 1.76 2000/08/22 13:01:20 ishii Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/scan.l,v 1.77 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -76,7 +76,7 @@ static char      *literalbuf;     /* expandable buffer */
 static int     literallen;     /* actual current length */
 static int     literalalloc;   /* current allocated buffer size */
 
-static int     xcdepth = 0;
+static int     xcdepth = 0;    /* depth of nesting in slash-star comments */
 
 #define startlit()  (literalbuf[0] = '\0', literallen = 0)
 static void addlit(char *ytext, int yleng);
@@ -510,22 +510,24 @@ other         .
 
 %%
 
-void yyerror(const char * message)
+void
+yyerror(const char *message)
 {
    elog(ERROR, "parser: %s at or near \"%s\"", message, yytext);
 }
 
-int yywrap()
+int
+yywrap(void)
 {
    return(1);
 }
 
 /*
init_io:
scanner_init:
    called by postgres before any actual parsing is done
 */
 void
-init_io()
+scanner_init(void)
 {
    /* it's important to set this to NULL
       because input()/myinput() checks the non-nullness of parseCh
index a14e1b4868453d32cdf03bc72c22bc7e22ed0b53..78fdad8960aaef7fdd2a3f10e520801ae97434be 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.31 2000/09/06 14:15:20 petere Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.32 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -56,38 +56,16 @@ thisLockWasTriggered_walker(Node *node,
            return true;
        return false;
    }
-   if (IsA(node, SubLink))
+   if (IsA(node, Query))
    {
+       /* Recurse into subselects */
+       bool        result;
 
-       /*
-        * Standard expression_tree_walker will not recurse into
-        * subselect, but here we must do so.
-        */
-       SubLink    *sub = (SubLink *) node;
-
-       if (thisLockWasTriggered_walker((Node *) (sub->lefthand), context))
-           return true;
        context->sublevels_up++;
-       if (thisLockWasTriggered_walker((Node *) (sub->subselect), context))
-       {
-           context->sublevels_up--;    /* not really necessary */
-           return true;
-       }
+       result = query_tree_walker((Query *) node, thisLockWasTriggered_walker,
+                                  (void *) context);
        context->sublevels_up--;
-       return false;
-   }
-   if (IsA(node, Query))
-   {
-       /* Reach here after recursing down into subselect above... */
-       Query      *qry = (Query *) node;
-
-       if (thisLockWasTriggered_walker((Node *) (qry->targetList), context))
-           return true;
-       if (thisLockWasTriggered_walker((Node *) (qry->qual), context))
-           return true;
-       if (thisLockWasTriggered_walker((Node *) (qry->havingQual), context))
-           return true;
-       return false;
+       return result;
    }
    return expression_tree_walker(node, thisLockWasTriggered_walker,
                                  (void *) context);
@@ -175,7 +153,7 @@ matchLocks(CmdType event,
 
 typedef struct
 {
-   Oid evowner;
+   Oid         evowner;
 } checkLockPerms_context;
 
 static bool
@@ -184,23 +162,8 @@ checkLockPerms_walker(Node *node,
 {
    if (node == NULL)
        return false;
-   if (IsA(node, SubLink))
-   {
-       /*
-        * Standard expression_tree_walker will not recurse into
-        * subselect, but here we must do so.
-        */
-       SubLink    *sub = (SubLink *) node;
-
-       if (checkLockPerms_walker((Node *) (sub->lefthand), context))
-           return true;
-       if (checkLockPerms_walker((Node *) (sub->subselect), context))
-           return true;
-       return false;
-   }
    if (IsA(node, Query))
    {
-       /* Reach here after recursing down into subselect above... */
        Query      *qry = (Query *) node;
        int         rtablength = length(qry->rtable);
        int         i;
@@ -212,13 +175,10 @@ checkLockPerms_walker(Node *node,
            int32       reqperm;
            int32       aclcheck_res;
 
-           if (rte->ref != NULL)
-           {
-               if (strcmp(rte->ref->relname, "*NEW*") == 0)
-                   continue;
-               if (strcmp(rte->ref->relname, "*OLD*") == 0)
-                   continue;
-           }
+           if (strcmp(rte->eref->relname, "*NEW*") == 0)
+               continue;
+           if (strcmp(rte->eref->relname, "*OLD*") == 0)
+               continue;
 
            if (i == qry->resultRelation)
                switch (qry->commandType)
@@ -250,14 +210,8 @@ checkLockPerms_walker(Node *node,
 
        /* If there are sublinks, search for them and check their RTEs */
        if (qry->hasSubLinks)
-       {
-           if (checkLockPerms_walker((Node *) (qry->targetList), context))
-               return true;
-           if (checkLockPerms_walker((Node *) (qry->qual), context))
-               return true;
-           if (checkLockPerms_walker((Node *) (qry->havingQual), context))
-               return true;
-       }
+           return query_tree_walker(qry, checkLockPerms_walker,
+                                    (void *) context);
        return false;
    }
    return expression_tree_walker(node, checkLockPerms_walker,
@@ -278,7 +232,7 @@ checkLockPerms(List *locks, Query *parsetree, int rt_index)
        return;                 /* nothing to check */
 
    /*
-    * Get the usename of the rule's event relation owner
+    * Get the userid of the rule's event relation owner
     */
    rte = rt_fetch(rt_index, parsetree->rtable);
    ev_rel = heap_openr(rte->relname, AccessShareLock);
index 4362687f8b8e177a8305a6ce7a010e9aeba45ef7..49dfae5b905434a4e4086a41755f99519bc278b3 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.79 2000/09/06 14:15:20 petere Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.80 2000/09/12 21:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,12 +51,11 @@ static RewriteInfo *gatherRewriteMeta(Query *parsetree,
                  Node *rule_qual,
                  int rt_index,
                  CmdType event,
-                 bool *instead_flag);
-static bool rangeTableEntry_used(Node *node, int rt_index, int sublevels_up);
-static bool attribute_used(Node *node, int rt_index, int attno,
-              int sublevels_up);
-static bool modifyAggrefChangeVarnodes(Node *node, int rt_index, int new_index,
-                          int sublevels_up, int new_sublevels_up);
+                 bool instead_flag);
+static List *adjustJoinTree(Query *parsetree, int rt_index, bool *found);
+static bool modifyAggrefChangeVarnodes(Query *query,
+                                      int rt_index, int new_index,
+                                      int sublevels_up, int new_sublevels_up);
 static Node *modifyAggrefDropQual(Node *node, Node *targetNode);
 static SubLink *modifyAggrefMakeSublink(Aggref *aggref, Query *parsetree);
 static Node *modifyAggrefQual(Node *node, Query *parsetree);
@@ -80,16 +79,15 @@ gatherRewriteMeta(Query *parsetree,
                  Node *rule_qual,
                  int rt_index,
                  CmdType event,
-                 bool *instead_flag)
+                 bool instead_flag)
 {
    RewriteInfo *info;
    int         rt_length;
-   int         result_reln;
 
    info = (RewriteInfo *) palloc(sizeof(RewriteInfo));
    info->rt_index = rt_index;
    info->event = event;
-   info->instead_flag = *instead_flag;
+   info->instead_flag = instead_flag;
    info->rule_action = (Query *) copyObject(rule_action);
    info->rule_qual = (Node *) copyObject(rule_qual);
    if (info->rule_action == NULL)
@@ -99,21 +97,54 @@ gatherRewriteMeta(Query *parsetree,
        info->nothing = FALSE;
        info->action = info->rule_action->commandType;
        info->current_varno = rt_index;
-       info->rt = parsetree->rtable;
-       rt_length = length(info->rt);
-       info->rt = nconc(info->rt, copyObject(info->rule_action->rtable));
+       rt_length = length(parsetree->rtable);
 
+       /* Adjust rule action and qual to offset its varnos */
        info->new_varno = PRS2_NEW_VARNO + rt_length;
-       OffsetVarNodes(info->rule_action->qual, rt_length, 0);
-       OffsetVarNodes((Node *) info->rule_action->targetList, rt_length, 0);
+       OffsetVarNodes((Node *) info->rule_action, rt_length, 0);
        OffsetVarNodes(info->rule_qual, rt_length, 0);
-       ChangeVarNodes((Node *) info->rule_action->qual,
-                      PRS2_OLD_VARNO + rt_length, rt_index, 0);
-       ChangeVarNodes((Node *) info->rule_action->targetList,
+       /* but its references to *OLD* should point at original rt_index */
+       ChangeVarNodes((Node *) info->rule_action,
                       PRS2_OLD_VARNO + rt_length, rt_index, 0);
        ChangeVarNodes(info->rule_qual,
                       PRS2_OLD_VARNO + rt_length, rt_index, 0);
 
+       /*
+        * We want the main parsetree's rtable to end up as the concatenation
+        * of its original contents plus those of all the relevant rule
+        * actions.  Also store same into all the rule_action rtables.
+        * Some of the entries may be unused after we finish rewriting, but
+        * if we tried to clean those out we'd have a much harder job to
+        * adjust RT indexes in the query's Vars.  It's OK to have unused
+        * RT entries, since planner will ignore them.
+        *
+        * NOTE KLUGY HACK: we assume the parsetree rtable had at least one
+        * entry to begin with (OK enough, else where'd the rule come from?).
+        * Because of this, if multiple rules nconc() their rtable additions
+        * onto parsetree->rtable, they'll all see the same rtable because
+        * they all have the same list head pointer.
+        */
+       parsetree->rtable = nconc(parsetree->rtable,
+                                 info->rule_action->rtable);
+       info->rule_action->rtable = parsetree->rtable;
+
+       /*
+        * Each rule action's jointree should be the main parsetree's jointree
+        * plus that rule's jointree, but *without* the original rtindex
+        * that we're replacing (if present, which it won't be for INSERT).
+        * Note that if the rule refers to OLD, its jointree will add back
+        * a reference to rt_index.
+        *
+        * XXX This might be wrong for subselect-in-FROM?
+        */
+       {
+           bool    found;
+           List   *newjointree = adjustJoinTree(parsetree, rt_index, &found);
+
+           info->rule_action->jointree = nconc(newjointree,
+                                               info->rule_action->jointree);
+       }
+
        /*
         * bug here about replace CURRENT  -- sort of replace current is
         * deprecated now so this code shouldn't really need to be so
@@ -121,7 +152,8 @@ gatherRewriteMeta(Query *parsetree,
         */
        if (info->action != CMD_SELECT)
        {                       /* i.e update XXXXX */
-           int         new_result_reln = 0;
+           int         result_reln;
+           int         new_result_reln;
 
            result_reln = info->rule_action->resultRelation;
            switch (result_reln)
@@ -140,152 +172,31 @@ gatherRewriteMeta(Query *parsetree,
    return info;
 }
 
-
-/*
- * rangeTableEntry_used -
- * we need to process a RTE for RIR rules only if it is
- * referenced somewhere in var nodes of the query.
- */
-
-typedef struct
-{
-   int         rt_index;
-   int         sublevels_up;
-} rangeTableEntry_used_context;
-
-static bool
-rangeTableEntry_used_walker(Node *node,
-                           rangeTableEntry_used_context *context)
-{
-   if (node == NULL)
-       return false;
-   if (IsA(node, Var))
-   {
-       Var        *var = (Var *) node;
-
-       if (var->varlevelsup == context->sublevels_up &&
-           var->varno == context->rt_index)
-           return true;
-       return false;
-   }
-   if (IsA(node, SubLink))
-   {
-
-       /*
-        * Standard expression_tree_walker will not recurse into
-        * subselect, but here we must do so.
-        */
-       SubLink    *sub = (SubLink *) node;
-
-       if (rangeTableEntry_used_walker((Node *) (sub->lefthand), context))
-           return true;
-       if (rangeTableEntry_used((Node *) (sub->subselect),
-                                context->rt_index,
-                                context->sublevels_up + 1))
-           return true;
-       return false;
-   }
-   if (IsA(node, Query))
-   {
-       /* Reach here after recursing down into subselect above... */
-       Query      *qry = (Query *) node;
-
-       if (rangeTableEntry_used_walker((Node *) (qry->targetList), context))
-           return true;
-       if (rangeTableEntry_used_walker((Node *) (qry->qual), context))
-           return true;
-       if (rangeTableEntry_used_walker((Node *) (qry->havingQual), context))
-           return true;
-       return false;
-   }
-   return expression_tree_walker(node, rangeTableEntry_used_walker,
-                                 (void *) context);
-}
-
-static bool
-rangeTableEntry_used(Node *node, int rt_index, int sublevels_up)
-{
-   rangeTableEntry_used_context context;
-
-   context.rt_index = rt_index;
-   context.sublevels_up = sublevels_up;
-   return rangeTableEntry_used_walker(node, &context);
-}
-
-
 /*
- * attribute_used -
- * Check if a specific attribute number of a RTE is used
- * somewhere in the query
+ * Copy the query's jointree list, and attempt to remove any occurrence
+ * of the given rt_index as a top-level join item (we do not look for it
+ * within JoinExprs).  Returns modified jointree list --- original list
+ * is not changed.  *found is set to indicate if we found the rt_index.
  */
-
-typedef struct
-{
-   int         rt_index;
-   int         attno;
-   int         sublevels_up;
-} attribute_used_context;
-
-static bool
-attribute_used_walker(Node *node,
-                     attribute_used_context *context)
+static List *
+adjustJoinTree(Query *parsetree, int rt_index, bool *found)
 {
-   if (node == NULL)
-       return false;
-   if (IsA(node, Var))
-   {
-       Var        *var = (Var *) node;
+   List       *newjointree = listCopy(parsetree->jointree);
+   List       *jjt;
 
-       if (var->varlevelsup == context->sublevels_up &&
-           var->varno == context->rt_index &&
-           var->varattno == context->attno)
-           return true;
-       return false;
-   }
-   if (IsA(node, SubLink))
+   *found = false;
+   foreach(jjt, newjointree)
    {
+       RangeTblRef *rtr = lfirst(jjt);
 
-       /*
-        * Standard expression_tree_walker will not recurse into
-        * subselect, but here we must do so.
-        */
-       SubLink    *sub = (SubLink *) node;
-
-       if (attribute_used_walker((Node *) (sub->lefthand), context))
-           return true;
-       if (attribute_used((Node *) (sub->subselect),
-                          context->rt_index,
-                          context->attno,
-                          context->sublevels_up + 1))
-           return true;
-       return false;
-   }
-   if (IsA(node, Query))
-   {
-       /* Reach here after recursing down into subselect above... */
-       Query      *qry = (Query *) node;
-
-       if (attribute_used_walker((Node *) (qry->targetList), context))
-           return true;
-       if (attribute_used_walker((Node *) (qry->qual), context))
-           return true;
-       if (attribute_used_walker((Node *) (qry->havingQual), context))
-           return true;
-       return false;
+       if (IsA(rtr, RangeTblRef) && rtr->rtindex == rt_index)
+       {
+           newjointree = lremove(rtr, newjointree);
+           *found = true;
+           break;
+       }
    }
-   return expression_tree_walker(node, attribute_used_walker,
-                                 (void *) context);
-}
-
-static bool
-attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
-{
-   attribute_used_context context;
-
-   context.rt_index = rt_index;
-   context.attno = attno;
-   context.sublevels_up = sublevels_up;
-   return attribute_used_walker(node, &context);
+   return newjointree;
 }
 
 
@@ -330,48 +241,26 @@ modifyAggrefChangeVarnodes_walker(Node *node,
        }
        return false;
    }
-   if (IsA(node, SubLink))
-   {
-
-       /*
-        * Standard expression_tree_walker will not recurse into
-        * subselect, but here we must do so.
-        */
-       SubLink    *sub = (SubLink *) node;
-
-       if (modifyAggrefChangeVarnodes_walker((Node *) (sub->lefthand),
-                                             context))
-           return true;
-       if (modifyAggrefChangeVarnodes((Node *) (sub->subselect),
-                                      context->rt_index,
-                                      context->new_index,
-                                      context->sublevels_up + 1,
-                                      context->new_sublevels_up + 1))
-           return true;
-       return false;
-   }
    if (IsA(node, Query))
    {
-       /* Reach here after recursing down into subselect above... */
-       Query      *qry = (Query *) node;
-
-       if (modifyAggrefChangeVarnodes_walker((Node *) (qry->targetList),
-                                             context))
-           return true;
-       if (modifyAggrefChangeVarnodes_walker((Node *) (qry->qual),
-                                             context))
-           return true;
-       if (modifyAggrefChangeVarnodes_walker((Node *) (qry->havingQual),
-                                             context))
-           return true;
-       return false;
+       /* Recurse into subselects */
+       bool        result;
+
+       context->sublevels_up++;
+       context->new_sublevels_up++;
+       result = query_tree_walker((Query *) node,
+                                  modifyAggrefChangeVarnodes_walker,
+                                  (void *) context);
+       context->sublevels_up--;
+       context->new_sublevels_up--;
+       return result;
    }
    return expression_tree_walker(node, modifyAggrefChangeVarnodes_walker,
                                  (void *) context);
 }
 
 static bool
-modifyAggrefChangeVarnodes(Node *node, int rt_index, int new_index,
+modifyAggrefChangeVarnodes(Query *query, int rt_index, int new_index,
                           int sublevels_up, int new_sublevels_up)
 {
    modifyAggrefChangeVarnodes_context context;
@@ -380,7 +269,8 @@ modifyAggrefChangeVarnodes(Node *node, int rt_index, int new_index,
    context.new_index = new_index;
    context.sublevels_up = sublevels_up;
    context.new_sublevels_up = new_sublevels_up;
-   return modifyAggrefChangeVarnodes_walker(node, &context);
+   return query_tree_walker(query, modifyAggrefChangeVarnodes_walker,
+                            (void *) &context);
 }
 
 
@@ -453,6 +343,7 @@ modifyAggrefMakeSublink(Aggref *aggref, Query *parsetree)
    SubLink    *sublink;
    TargetEntry *tle;
    Resdom     *resdom;
+   RangeTblRef *rtr;
 
    aggVarNos = pull_varnos(aggref->target);
    if (length(aggVarNos) != 1)
@@ -492,6 +383,9 @@ modifyAggrefMakeSublink(Aggref *aggref, Query *parsetree)
    subquery->distinctClause = NIL;
    subquery->sortClause = NIL;
    subquery->rtable = lcons(copyObject(rte), NIL);
+   rtr = makeNode(RangeTblRef);
+   rtr->rtindex = 1;
+   subquery->jointree = lcons(rtr, NIL);
    subquery->targetList = lcons(tle, NIL);
    subquery->qual = modifyAggrefDropQual((Node *) parsetree->qual,
                                          (Node *) aggref);
@@ -517,7 +411,7 @@ modifyAggrefMakeSublink(Aggref *aggref, Query *parsetree)
     * Note that because of previous line, these references have
     * varlevelsup = 1, which must be changed to 0.
     */
-   modifyAggrefChangeVarnodes((Node *) subquery,
+   modifyAggrefChangeVarnodes(subquery,
                               lfirsti(aggVarNos), 1,
                               1, 0);
 
@@ -675,6 +569,8 @@ apply_RIR_view_mutator(Node *node,
               apply_RIR_view_mutator, context);
        MUTATE(newnode->havingQual, query->havingQual, Node *,
               apply_RIR_view_mutator, context);
+       MUTATE(newnode->jointree, query->jointree, List *,
+              apply_RIR_view_mutator, context);
        return (Node *) newnode;
    }
    return expression_tree_mutator(node, apply_RIR_view_mutator,
@@ -703,7 +599,7 @@ ApplyRetrieveRule(Query *parsetree,
                  int rt_index,
                  int relation_level,
                  Relation relation,
-                 bool relWasInJoinSet)
+                 bool relIsUsed)
 {
    Query      *rule_action = NULL;
    Node       *rule_qual;
@@ -735,17 +631,34 @@ ApplyRetrieveRule(Query *parsetree,
    addedrtable = copyObject(rule_action->rtable);
 
    /*
-    * If the original rel wasn't in the join set, none of its spawn is.
-    * If it was, then leave the spawn's flags as they are.
+    * If the original rel wasn't in the join set (which'd be the case
+    * for the target of an INSERT, for example), none of its spawn is.
+    * If it was, then the spawn has to be added to the join set.
     */
-   if (!relWasInJoinSet)
+   if (relIsUsed)
    {
-       foreach(l, addedrtable)
-       {
-           RangeTblEntry *rte = lfirst(l);
-
-           rte->inJoinSet = false;
-       }
+       /*
+        * QUICK HACK: this all needs to be replaced, but for now, find
+        * the original rel in the jointree, remove it, and add the rule
+        * action's jointree.  This will not work for views referenced
+        * in JoinExprs!!
+        *
+        * Note: it is possible that the old rel is referenced in the query
+        * but isn't present in the jointree; this should only happen for
+        * *OLD* and *NEW*.  We must not fail if so, but add the rule's
+        * jointree anyway.  (This is a major crock ... should fix rule
+        * representation ...)
+        */
+       bool    found;
+       List   *newjointree = adjustJoinTree(parsetree, rt_index, &found);
+       List   *addedjointree = (List *) copyObject(rule_action->jointree);
+
+       if (!found)
+           elog(DEBUG, "ApplyRetrieveRule: can't find old rel %s (%d) in jointree",
+                rt_fetch(rt_index, rtable)->eref->relname, rt_index);
+       OffsetVarNodes((Node *) addedjointree, rt_length, 0);
+       newjointree = nconc(newjointree, addedjointree);
+       parsetree->jointree = newjointree;
    }
 
    rtable = nconc(rtable, addedrtable);
@@ -845,6 +758,10 @@ ApplyRetrieveRule(Query *parsetree,
  * NOTE: although this has the form of a walker, we cheat and modify the
  * SubLink nodes in-place. It is caller's responsibility to ensure that
  * no unwanted side-effects occur!
+ *
+ * This is unlike most of the other routines that recurse into subselects,
+ * because we must take control at the SubLink node in order to replace
+ * the SubLink's subselect link with the possibly-rewritten subquery.
  */
 static bool
 fireRIRonSubselect(Node *node, void *context)
@@ -854,30 +771,15 @@ fireRIRonSubselect(Node *node, void *context)
    if (IsA(node, SubLink))
    {
        SubLink    *sub = (SubLink *) node;
-       Query      *qry;
 
-       /* Process lefthand args */
-       if (fireRIRonSubselect((Node *) (sub->lefthand), context))
-           return true;
        /* Do what we came for */
-       qry = fireRIRrules((Query *) (sub->subselect));
-       sub->subselect = (Node *) qry;
-       /* Need not recurse into subselect, because fireRIRrules did it */
-       return false;
-   }
-   if (IsA(node, Query))
-   {
-       /* Reach here when called from fireRIRrules */
-       Query      *qry = (Query *) node;
-
-       if (fireRIRonSubselect((Node *) (qry->targetList), context))
-           return true;
-       if (fireRIRonSubselect((Node *) (qry->qual), context))
-           return true;
-       if (fireRIRonSubselect((Node *) (qry->havingQual), context))
-           return true;
-       return false;
+       sub->subselect = (Node *) fireRIRrules((Query *) (sub->subselect));
+       /* Fall through to process lefthand args of SubLink */
    }
+   /*
+    * Do NOT recurse into Query nodes, because fireRIRrules already
+    * processed subselects for us.
+    */
    return expression_tree_walker(node, fireRIRonSubselect,
                                  (void *) context);
 }
@@ -897,7 +799,7 @@ fireRIRrules(Query *parsetree)
    RuleLock   *rules;
    RewriteRule *rule;
    RewriteRule RIRonly;
-   bool        relWasInJoinSet;
+   bool        relIsUsed;
    int         i;
    List       *l;
 
@@ -916,11 +818,12 @@ fireRIRrules(Query *parsetree)
         * If the table is not referenced in the query, then we ignore it.
         * This prevents infinite expansion loop due to new rtable entries
         * inserted by expansion of a rule. A table is referenced if it is
-        * part of the join set (a source table), or is the result table,
-        * or is referenced by any Var nodes.
+        * part of the join set (a source table), or is referenced by any
+        * Var nodes, or is the result table.
         */
-       if (!rte->inJoinSet && rt_index != parsetree->resultRelation &&
-           !rangeTableEntry_used((Node *) parsetree, rt_index, 0))
+       relIsUsed = rangeTableEntry_used((Node *) parsetree, rt_index, 0);
+
+       if (!relIsUsed && rt_index != parsetree->resultRelation)
            continue;
 
        rel = heap_openr(rte->relname, AccessShareLock);
@@ -931,9 +834,6 @@ fireRIRrules(Query *parsetree)
            continue;
        }
 
-       relWasInJoinSet = rte->inJoinSet;       /* save before possibly
-                                                * clearing */
-
        /*
         * Collect the RIR rules that we must apply
         */
@@ -947,22 +847,10 @@ fireRIRrules(Query *parsetree)
            if (rule->attrno > 0)
            {
                /* per-attr rule; do we need it? */
-               if (!attribute_used((Node *) parsetree,
-                                   rt_index,
+               if (!attribute_used((Node *) parsetree, rt_index,
                                    rule->attrno, 0))
                    continue;
            }
-           else
-           {
-
-               /*
-                * Rel-wide ON SELECT DO INSTEAD means this is a view.
-                * Remove the view from the planner's join target set, or
-                * we'll get no rows out because view itself is empty!
-                */
-               if (rule->isInstead)
-                   rte->inJoinSet = false;
-           }
 
            locks = lappend(locks, rule);
        }
@@ -989,7 +877,7 @@ fireRIRrules(Query *parsetree)
                                          rt_index,
                                          RIRonly.attrno == -1,
                                          rel,
-                                         relWasInJoinSet);
+                                         relIsUsed);
        }
 
        heap_close(rel, AccessShareLock);
@@ -999,7 +887,7 @@ fireRIRrules(Query *parsetree)
        parsetree->qual = modifyAggrefQual(parsetree->qual, parsetree);
 
    if (parsetree->hasSubLinks)
-       fireRIRonSubselect((Node *) parsetree, NULL);
+       query_tree_walker(parsetree, fireRIRonSubselect, NULL);
 
    return parsetree;
 }
@@ -1056,13 +944,20 @@ CopyAndAddQual(Query *parsetree,
    {
        List       *rtable;
        int         rt_length;
+       List       *jointree;
 
        rtable = new_tree->rtable;
        rt_length = length(rtable);
        rtable = nconc(rtable, copyObject(rule_action->rtable));
+       /* XXX above possibly wrong for subselect-in-FROM */
        new_tree->rtable = rtable;
        OffsetVarNodes(new_qual, rt_length, 0);
        ChangeVarNodes(new_qual, PRS2_OLD_VARNO + rt_length, rt_index, 0);
+       jointree = copyObject(rule_action->jointree);
+       OffsetVarNodes((Node *) jointree, rt_length, 0);
+       ChangeVarNodes((Node *) jointree, PRS2_OLD_VARNO + rt_length,
+                      rt_index, 0);
+       new_tree->jointree = nconc(new_tree->jointree, jointree);
    }
    /* XXX -- where current doesn't work for instead nothing.... yet */
    AddNotQual(new_tree, new_qual);
@@ -1103,8 +998,7 @@ fireRules(Query *parsetree,
    foreach(i, locks)
    {
        RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
-       Node       *qual,
-                  *event_qual;
+       Node       *event_qual;
        List       *actions;
        List       *r;
 
@@ -1227,7 +1121,7 @@ fireRules(Query *parsetree,
             *--------------------------------------------------
             */
            info = gatherRewriteMeta(parsetree, rule_action, rule_qual,
-                                    rt_index, event, instead_flag);
+                                    rt_index, event, *instead_flag);
 
            /* handle escapable cases, or those handled by other code */
            if (info->nothing)
@@ -1247,11 +1141,9 @@ fireRules(Query *parsetree,
             * splitting into two queries one w/rule_qual, one w/NOT
             * rule_qual. Also add user query qual onto rule action
             */
-           qual = parsetree->qual;
-           AddQual(info->rule_action, qual);
+           AddQual(info->rule_action, parsetree->qual);
 
-           if (info->rule_qual != NULL)
-               AddQual(info->rule_action, info->rule_qual);
+           AddQual(info->rule_action, info->rule_qual);
 
            /*--------------------------------------------------
             * Step 2:
@@ -1264,18 +1156,6 @@ fireRules(Query *parsetree,
 
            /*--------------------------------------------------
             * Step 3:
-            *    rewriting due to retrieve rules
-            *--------------------------------------------------
-            */
-           info->rule_action->rtable = info->rt;
-
-           /*
-            * ProcessRetrieveQuery(info->rule_action, info->rt,
-            * &orig_instead_flag, TRUE);
-            */
-
-           /*--------------------------------------------------
-            * Step 4
             *    Simplify? hey, no algorithm for simplification... let
             *    the planner do it.
             *--------------------------------------------------
@@ -1403,7 +1283,7 @@ deepRewriteQuery(Query *parsetree)
        rewritten = nconc(rewritten, qual_products);
 
    /* ----------
-    * The original query is appended last if not instead
+    * The original query is appended last (if no "instead" rule)
     * because update and delete rule actions might not do
     * anything if they are invoked after the update or
     * delete is performed. The command counter increment
@@ -1471,17 +1351,15 @@ BasicQueryRewrite(Query *parsetree)
         */
        if (query->hasAggs)
        {
-           query->hasAggs =
-               checkExprHasAggs((Node *) (query->targetList)) ||
-               checkExprHasAggs((Node *) (query->havingQual));
-           if (checkExprHasAggs((Node *) (query->qual)))
-               elog(ERROR, "BasicQueryRewrite: failed to remove aggs from qual");
+           query->hasAggs = checkExprHasAggs((Node *) query);
+           if (query->hasAggs)
+               if (checkExprHasAggs(query->qual))
+                   elog(ERROR, "BasicQueryRewrite: failed to remove aggs from qual");
        }
        if (query->hasSubLinks)
-           query->hasSubLinks =
-               checkExprHasSubLink((Node *) (query->targetList)) ||
-               checkExprHasSubLink((Node *) (query->qual)) ||
-               checkExprHasSubLink((Node *) (query->havingQual));
+       {
+           query->hasSubLinks = checkExprHasSubLink((Node *) query);
+       }
        results = lappend(results, query);
    }
 
index a8ec560c741724176edf48535e467abaf23013cb..e83ac05485304210074687d2618a2a5e9a8fbdc0 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.47 2000/05/30 00:49:51 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.48 2000/09/12 21:07:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,7 +42,15 @@ static bool checkExprHasSubLink_walker(Node *node, void *context);
 bool
 checkExprHasAggs(Node *node)
 {
-   return checkExprHasAggs_walker(node, NULL);
+   /*
+    * If a Query is passed, examine it --- but we will not recurse
+    * into sub-Queries.
+    */
+   if (node && IsA(node, Query))
+       return query_tree_walker((Query *) node, checkExprHasAggs_walker,
+                                NULL);
+   else
+       return checkExprHasAggs_walker(node, NULL);
 }
 
 static bool
@@ -64,7 +72,15 @@ checkExprHasAggs_walker(Node *node, void *context)
 bool
 checkExprHasSubLink(Node *node)
 {
-   return checkExprHasSubLink_walker(node, NULL);
+   /*
+    * If a Query is passed, examine it --- but we will not recurse
+    * into sub-Queries.
+    */
+   if (node && IsA(node, Query))
+       return query_tree_walker((Query *) node, checkExprHasSubLink_walker,
+                                NULL);
+   else
+       return checkExprHasSubLink_walker(node, NULL);
 }
 
 static bool
@@ -84,10 +100,11 @@ checkExprHasSubLink_walker(Node *node, void *context)
  *
  * Find all Var nodes in the given tree with varlevelsup == sublevels_up,
  * and increment their varno fields (rangetable indexes) by 'offset'.
- * The varnoold fields are adjusted similarly.
+ * The varnoold fields are adjusted similarly.  Also, RangeTblRef nodes
+ * in join trees are adjusted.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
- * Var nodes in-place. The given expression tree should have been copied
+ * nodes in-place. The given expression tree should have been copied
  * earlier to ensure that no unwanted side-effects occur!
  */
 
@@ -113,38 +130,24 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
        }
        return false;
    }
-   if (IsA(node, SubLink))
+   if (IsA(node, RangeTblRef))
    {
+       RangeTblRef    *rtr = (RangeTblRef *) node;
 
-       /*
-        * Standard expression_tree_walker will not recurse into
-        * subselect, but here we must do so.
-        */
-       SubLink    *sub = (SubLink *) node;
-
-       if (OffsetVarNodes_walker((Node *) (sub->lefthand),
-                                 context))
-           return true;
-       OffsetVarNodes((Node *) (sub->subselect),
-                      context->offset,
-                      context->sublevels_up + 1);
+       if (context->sublevels_up == 0)
+           rtr->rtindex += context->offset;
        return false;
    }
    if (IsA(node, Query))
    {
-       /* Reach here after recursing down into subselect above... */
-       Query      *qry = (Query *) node;
+       /* Recurse into subselects */
+       bool        result;
 
-       if (OffsetVarNodes_walker((Node *) (qry->targetList),
-                                 context))
-           return true;
-       if (OffsetVarNodes_walker((Node *) (qry->qual),
-                                 context))
-           return true;
-       if (OffsetVarNodes_walker((Node *) (qry->havingQual),
-                                 context))
-           return true;
-       return false;
+       context->sublevels_up++;
+       result = query_tree_walker((Query *) node, OffsetVarNodes_walker,
+                                  (void *) context);
+       context->sublevels_up--;
+       return result;
    }
    return expression_tree_walker(node, OffsetVarNodes_walker,
                                  (void *) context);
@@ -157,7 +160,17 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
 
    context.offset = offset;
    context.sublevels_up = sublevels_up;
-   OffsetVarNodes_walker(node, &context);
+
+   /*
+    * Must be prepared to start with a Query or a bare expression tree;
+    * if it's a Query, go straight to query_tree_walker to make sure that
+    * sublevels_up doesn't get incremented prematurely.
+    */
+   if (node && IsA(node, Query))
+       query_tree_walker((Query *) node, OffsetVarNodes_walker,
+                         (void *) &context);
+   else
+       OffsetVarNodes_walker(node, &context);
 }
 
 /*
@@ -165,10 +178,11 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
  *
  * Find all Var nodes in the given tree belonging to a specific relation
  * (identified by sublevels_up and rt_index), and change their varno fields
- * to 'new_index'. The varnoold fields are changed too.
+ * to 'new_index'. The varnoold fields are changed too.  Also, RangeTblRef
+ * nodes in join trees are adjusted.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
- * Var nodes in-place. The given expression tree should have been copied
+ * nodes in-place. The given expression tree should have been copied
  * earlier to ensure that no unwanted side-effects occur!
  */
 
@@ -196,39 +210,25 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
        }
        return false;
    }
-   if (IsA(node, SubLink))
+   if (IsA(node, RangeTblRef))
    {
+       RangeTblRef    *rtr = (RangeTblRef *) node;
 
-       /*
-        * Standard expression_tree_walker will not recurse into
-        * subselect, but here we must do so.
-        */
-       SubLink    *sub = (SubLink *) node;
-
-       if (ChangeVarNodes_walker((Node *) (sub->lefthand),
-                                 context))
-           return true;
-       ChangeVarNodes((Node *) (sub->subselect),
-                      context->rt_index,
-                      context->new_index,
-                      context->sublevels_up + 1);
+       if (context->sublevels_up == 0 &&
+           rtr->rtindex == context->rt_index)
+           rtr->rtindex = context->new_index;
        return false;
    }
    if (IsA(node, Query))
    {
-       /* Reach here after recursing down into subselect above... */
-       Query      *qry = (Query *) node;
+       /* Recurse into subselects */
+       bool        result;
 
-       if (ChangeVarNodes_walker((Node *) (qry->targetList),
-                                 context))
-           return true;
-       if (ChangeVarNodes_walker((Node *) (qry->qual),
-                                 context))
-           return true;
-       if (ChangeVarNodes_walker((Node *) (qry->havingQual),
-                                 context))
-           return true;
-       return false;
+       context->sublevels_up++;
+       result = query_tree_walker((Query *) node, ChangeVarNodes_walker,
+                                  (void *) context);
+       context->sublevels_up--;
+       return result;
    }
    return expression_tree_walker(node, ChangeVarNodes_walker,
                                  (void *) context);
@@ -242,7 +242,17 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
    context.rt_index = rt_index;
    context.new_index = new_index;
    context.sublevels_up = sublevels_up;
-   ChangeVarNodes_walker(node, &context);
+
+   /*
+    * Must be prepared to start with a Query or a bare expression tree;
+    * if it's a Query, go straight to query_tree_walker to make sure that
+    * sublevels_up doesn't get incremented prematurely.
+    */
+   if (node && IsA(node, Query))
+       query_tree_walker((Query *) node, ChangeVarNodes_walker,
+                         (void *) &context);
+   else
+       ChangeVarNodes_walker(node, &context);
 }
 
 /*
@@ -282,54 +292,181 @@ IncrementVarSublevelsUp_walker(Node *node,
            var->varlevelsup += context->delta_sublevels_up;
        return false;
    }
-   if (IsA(node, SubLink))
+   if (IsA(node, Query))
    {
+       /* Recurse into subselects */
+       bool        result;
 
-       /*
-        * Standard expression_tree_walker will not recurse into
-        * subselect, but here we must do so.
-        */
-       SubLink    *sub = (SubLink *) node;
+       context->min_sublevels_up++;
+       result = query_tree_walker((Query *) node,
+                                  IncrementVarSublevelsUp_walker,
+                                  (void *) context);
+       context->min_sublevels_up--;
+       return result;
+   }
+   return expression_tree_walker(node, IncrementVarSublevelsUp_walker,
+                                 (void *) context);
+}
+
+void
+IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
+                       int min_sublevels_up)
+{
+   IncrementVarSublevelsUp_context context;
+
+   context.delta_sublevels_up = delta_sublevels_up;
+   context.min_sublevels_up = min_sublevels_up;
+
+   /*
+    * Must be prepared to start with a Query or a bare expression tree;
+    * if it's a Query, go straight to query_tree_walker to make sure that
+    * sublevels_up doesn't get incremented prematurely.
+    */
+   if (node && IsA(node, Query))
+       query_tree_walker((Query *) node, IncrementVarSublevelsUp_walker,
+                         (void *) &context);
+   else
+       IncrementVarSublevelsUp_walker(node, &context);
+}
+
+
+/*
+ * rangeTableEntry_used - detect whether an RTE is referenced somewhere
+ * in var nodes or jointree nodes of a query or expression.
+ */
+
+typedef struct
+{
+   int         rt_index;
+   int         sublevels_up;
+} rangeTableEntry_used_context;
+
+static bool
+rangeTableEntry_used_walker(Node *node,
+                           rangeTableEntry_used_context *context)
+{
+   if (node == NULL)
+       return false;
+   if (IsA(node, Var))
+   {
+       Var        *var = (Var *) node;
 
-       if (IncrementVarSublevelsUp_walker((Node *) (sub->lefthand),
-                                          context))
+       if (var->varlevelsup == context->sublevels_up &&
+           var->varno == context->rt_index)
            return true;
-       IncrementVarSublevelsUp((Node *) (sub->subselect),
-                               context->delta_sublevels_up,
-                               context->min_sublevels_up + 1);
        return false;
    }
-   if (IsA(node, Query))
+   if (IsA(node, RangeTblRef))
    {
-       /* Reach here after recursing down into subselect above... */
-       Query      *qry = (Query *) node;
+       RangeTblRef *rtr = (RangeTblRef *) node;
 
-       if (IncrementVarSublevelsUp_walker((Node *) (qry->targetList),
-                                          context))
+       if (rtr->rtindex == context->rt_index &&
+           context->sublevels_up == 0)
            return true;
-       if (IncrementVarSublevelsUp_walker((Node *) (qry->qual),
-                                          context))
-           return true;
-       if (IncrementVarSublevelsUp_walker((Node *) (qry->havingQual),
-                                          context))
+       return false;
+   }
+   if (IsA(node, Query))
+   {
+       /* Recurse into subselects */
+       bool        result;
+
+       context->sublevels_up++;
+       result = query_tree_walker((Query *) node, rangeTableEntry_used_walker,
+                                  (void *) context);
+       context->sublevels_up--;
+       return result;
+   }
+   return expression_tree_walker(node, rangeTableEntry_used_walker,
+                                 (void *) context);
+}
+
+bool
+rangeTableEntry_used(Node *node, int rt_index, int sublevels_up)
+{
+   rangeTableEntry_used_context context;
+
+   context.rt_index = rt_index;
+   context.sublevels_up = sublevels_up;
+
+   /*
+    * Must be prepared to start with a Query or a bare expression tree;
+    * if it's a Query, go straight to query_tree_walker to make sure that
+    * sublevels_up doesn't get incremented prematurely.
+    */
+   if (node && IsA(node, Query))
+       return query_tree_walker((Query *) node, rangeTableEntry_used_walker,
+                                (void *) &context);
+   else
+       return rangeTableEntry_used_walker(node, &context);
+}
+
+
+/*
+ * attribute_used -
+ * Check if a specific attribute number of a RTE is used
+ * somewhere in the query or expression.
+ */
+
+typedef struct
+{
+   int         rt_index;
+   int         attno;
+   int         sublevels_up;
+} attribute_used_context;
+
+static bool
+attribute_used_walker(Node *node,
+                     attribute_used_context *context)
+{
+   if (node == NULL)
+       return false;
+   if (IsA(node, Var))
+   {
+       Var        *var = (Var *) node;
+
+       if (var->varlevelsup == context->sublevels_up &&
+           var->varno == context->rt_index &&
+           var->varattno == context->attno)
            return true;
        return false;
    }
-   return expression_tree_walker(node, IncrementVarSublevelsUp_walker,
+   if (IsA(node, Query))
+   {
+       /* Recurse into subselects */
+       bool        result;
+
+       context->sublevels_up++;
+       result = query_tree_walker((Query *) node, attribute_used_walker,
+                                  (void *) context);
+       context->sublevels_up--;
+       return result;
+   }
+   return expression_tree_walker(node, attribute_used_walker,
                                  (void *) context);
 }
 
-void
-IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
-                       int min_sublevels_up)
+bool
+attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
 {
-   IncrementVarSublevelsUp_context context;
+   attribute_used_context context;
 
-   context.delta_sublevels_up = delta_sublevels_up;
-   context.min_sublevels_up = min_sublevels_up;
-   IncrementVarSublevelsUp_walker(node, &context);
+   context.rt_index = rt_index;
+   context.attno = attno;
+   context.sublevels_up = sublevels_up;
+
+   /*
+    * Must be prepared to start with a Query or a bare expression tree;
+    * if it's a Query, go straight to query_tree_walker to make sure that
+    * sublevels_up doesn't get incremented prematurely.
+    */
+   if (node && IsA(node, Query))
+       return query_tree_walker((Query *) node, attribute_used_walker,
+                                (void *) &context);
+   else
+       return attribute_used_walker(node, &context);
 }
 
+
 /*
  * Add the given qualifier condition to the query's WHERE clause
  */
@@ -615,11 +752,6 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
        Query      *query = (Query *) node;
        Query      *newnode;
 
-       /*
-        * XXX original code for ResolveNew only recursed into qual field
-        * of subquery.  I'm assuming that was an oversight ... tgl 9/99
-        */
-
        FLATCOPY(newnode, query, Query);
        MUTATE(newnode->targetList, query->targetList, List *,
               ResolveNew_mutator, context);
@@ -627,6 +759,8 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
               ResolveNew_mutator, context);
        MUTATE(newnode->havingQual, query->havingQual, Node *,
               ResolveNew_mutator, context);
+       MUTATE(newnode->jointree, query->jointree, List *,
+              ResolveNew_mutator, context);
        return (Node *) newnode;
    }
    return expression_tree_mutator(node, ResolveNew_mutator,
@@ -650,13 +784,15 @@ void
 FixNew(RewriteInfo *info, Query *parsetree)
 {
    info->rule_action->targetList = (List *)
-   ResolveNew((Node *) info->rule_action->targetList,
-              info, parsetree->targetList, 0);
+       ResolveNew((Node *) info->rule_action->targetList,
+                  info, parsetree->targetList, 0);
    info->rule_action->qual = ResolveNew(info->rule_action->qual,
                                         info, parsetree->targetList, 0);
-   /* XXX original code didn't fix havingQual; presumably an oversight? */
    info->rule_action->havingQual = ResolveNew(info->rule_action->havingQual,
-                                        info, parsetree->targetList, 0);
+                                              info, parsetree->targetList, 0);
+   info->rule_action->jointree = (List *)
+       ResolveNew((Node *) info->rule_action->jointree,
+                  info, parsetree->targetList, 0);
 }
 
 /*
@@ -758,11 +894,6 @@ HandleRIRAttributeRule_mutator(Node *node,
        Query      *query = (Query *) node;
        Query      *newnode;
 
-       /*
-        * XXX original code for HandleRIRAttributeRule only recursed into
-        * qual field of subquery.  I'm assuming that was an oversight ...
-        */
-
        FLATCOPY(newnode, query, Query);
        MUTATE(newnode->targetList, query->targetList, List *,
               HandleRIRAttributeRule_mutator, context);
@@ -770,6 +901,8 @@ HandleRIRAttributeRule_mutator(Node *node,
               HandleRIRAttributeRule_mutator, context);
        MUTATE(newnode->havingQual, query->havingQual, Node *,
               HandleRIRAttributeRule_mutator, context);
+       MUTATE(newnode->jointree, query->jointree, List *,
+              HandleRIRAttributeRule_mutator, context);
        return (Node *) newnode;
    }
    return expression_tree_mutator(node, HandleRIRAttributeRule_mutator,
@@ -798,9 +931,13 @@ HandleRIRAttributeRule(Query *parsetree,
    parsetree->targetList = (List *)
        HandleRIRAttributeRule_mutator((Node *) parsetree->targetList,
                                       &context);
-   parsetree->qual = HandleRIRAttributeRule_mutator(parsetree->qual,
-                                                    &context);
-   /* XXX original code did not fix havingQual ... oversight? */
-   parsetree->havingQual = HandleRIRAttributeRule_mutator(parsetree->havingQual,
-                                                          &context);
+   parsetree->qual =
+       HandleRIRAttributeRule_mutator(parsetree->qual,
+                                      &context);
+   parsetree->havingQual =
+       HandleRIRAttributeRule_mutator(parsetree->havingQual,
+                                      &context);
+   parsetree->jointree = (List *)
+       HandleRIRAttributeRule_mutator((Node *) parsetree->jointree,
+                                      &context);
 }
index 571854d446c7bfd45c31961d0c057d9c7af05ed2..26ebe21c4a2c17b6bd34c81782e17a90d07c4991 100644 (file)
@@ -1,9 +1,9 @@
 /**********************************************************************
- * get_ruledef.c   - Function to get a rules definition text
- *           out of its tuple
+ * ruleutils.c - Functions to convert stored expressions/querytrees
+ *             back to source text
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.60 2000/09/12 04:15:58 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.61 2000/09/12 21:07:05 tgl Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -43,6 +43,7 @@
 #include "catalog/pg_index.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_shadow.h"
+#include "commands/view.h"
 #include "executor/spi.h"
 #include "lib/stringinfo.h"
 #include "optimizer/clauses.h"
@@ -50,8 +51,8 @@
 #include "parser/keywords.h"
 #include "parser/parse_expr.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-#include "commands/view.h"
 
 
 /* ----------
@@ -65,12 +66,6 @@ typedef struct
    bool        varprefix;      /* TRUE to print prefixes on Vars */
 } deparse_context;
 
-typedef struct
-{
-   Index       rt_index;
-   int         levelsup;
-} check_if_rte_used_context;
-
 
 /* ----------
  * Global data
@@ -108,13 +103,13 @@ static void get_func_expr(Expr *expr, deparse_context *context);
 static void get_tle_expr(TargetEntry *tle, deparse_context *context);
 static void get_const_expr(Const *constval, deparse_context *context);
 static void get_sublink_expr(Node *node, deparse_context *context);
+static void get_from_clause(Query *query, deparse_context *context);
+static void get_from_clause_item(Node *jtnode, Query *query,
+                                deparse_context *context);
 static bool tleIsArrayAssign(TargetEntry *tle);
 static char *quote_identifier(char *ident);
 static char *get_relation_name(Oid relid);
 static char *get_attribute_name(Oid relid, int2 attnum);
-static bool check_if_rte_used(Node *node, Index rt_index, int levelsup);
-static bool check_if_rte_used_walker(Node *node,
-                        check_if_rte_used_context *context);
 
 #define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")
 
@@ -230,13 +225,13 @@ pg_get_viewdef(PG_FUNCTION_ARGS)
    Name        vname = PG_GETARG_NAME(0);
    text       *ruledef;
    Datum       args[1];
-   char        nulls[2];
+   char        nulls[1];
    int         spirc;
    HeapTuple   ruletup;
    TupleDesc   rulettc;
    StringInfoData buf;
    int         len;
-   char        *name;
+   char       *name;
 
    /* ----------
     * We need the view name somewhere deep down
@@ -276,7 +271,6 @@ pg_get_viewdef(PG_FUNCTION_ARGS)
    name = MakeRetrieveViewRuleName(rulename);
    args[0] = PointerGetDatum(name);
    nulls[0] = ' ';
-   nulls[1] = '\0';
    spirc = SPI_execp(plan_getview, args, nulls, 1);
    if (spirc != SPI_OK_SELECT)
        elog(ERROR, "failed to get pg_rewrite tuple for view %s", rulename);
@@ -883,60 +877,8 @@ get_select_query_def(Query *query, deparse_context *context)
 {
    StringInfo  buf = context->buf;
    char       *sep;
-   TargetEntry *tle;
-   RangeTblEntry *rte;
-   bool       *rt_used;
-   int         rt_length;
-   int         rt_numused = 0;
-   bool        rt_constonly = TRUE;
-   int         i;
    List       *l;
 
-   /* ----------
-    * First we need to know which and how many of the
-    * range table entries in the query are used in the target list
-    * or queries qualification
-    * ----------
-    */
-   rt_length = length(query->rtable);
-   rt_used = palloc(sizeof(bool) * rt_length);
-   for (i = 0; i < rt_length; i++)
-   {
-       if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
-           check_if_rte_used(query->qual, i + 1, 0) ||
-           check_if_rte_used(query->havingQual, i + 1, 0))
-       {
-           rt_used[i] = TRUE;
-           rt_numused++;
-       }
-       else
-           rt_used[i] = FALSE;
-   }
-
-   /* ----------
-    * Now check if any of the used rangetable entries is different
-    * from *NEW* and *OLD*. If so we must provide the FROM clause
-    * later.
-    * ----------
-    */
-   i = 0;
-   foreach(l, query->rtable)
-   {
-       if (!rt_used[i++])
-           continue;
-
-       rte = (RangeTblEntry *) lfirst(l);
-       if (rte->ref == NULL)
-           continue;
-       if (strcmp(rte->ref->relname, "*NEW*") == 0)
-           continue;
-       if (strcmp(rte->ref->relname, "*OLD*") == 0)
-           continue;
-
-       rt_constonly = FALSE;
-       break;
-   }
-
    /* ----------
     * Build up the query string - first we say SELECT
     * ----------
@@ -947,9 +889,9 @@ get_select_query_def(Query *query, deparse_context *context)
    sep = " ";
    foreach(l, query->targetList)
    {
+       TargetEntry *tle = (TargetEntry *) lfirst(l);
        bool        tell_as = false;
 
-       tle = (TargetEntry *) lfirst(l);
        appendStringInfo(buf, sep);
        sep = ", ";
 
@@ -962,6 +904,7 @@ get_select_query_def(Query *query, deparse_context *context)
        else
        {
            Var        *var = (Var *) (tle->expr);
+           RangeTblEntry *rte;
            char       *attname;
 
            rte = get_rte_for_var(var, context);
@@ -975,60 +918,8 @@ get_select_query_def(Query *query, deparse_context *context)
                             quote_identifier(tle->resdom->resname));
    }
 
-   /* If we need other tables than *NEW* or *OLD* add the FROM clause */
-   if (!rt_constonly && rt_numused > 0)
-   {
-       sep = " FROM ";
-       i = 0;
-       foreach(l, query->rtable)
-       {
-           if (rt_used[i++])
-           {
-               rte = (RangeTblEntry *) lfirst(l);
-
-               if (rte->ref == NULL)
-                   continue;
-               if (strcmp(rte->ref->relname, "*NEW*") == 0)
-                   continue;
-               if (strcmp(rte->ref->relname, "*OLD*") == 0)
-                   continue;
-
-               appendStringInfo(buf, sep);
-               sep = ", ";
-               appendStringInfo(buf, "%s%s",
-                                only_marker(rte),
-                                quote_identifier(rte->relname));
-
-               /*
-                * NOTE: SQL92 says you can't write column aliases unless
-                * you write a table alias --- so, if there's an alias
-                * list, make sure we emit a table alias even if it's the
-                * same as the table's real name.
-                */
-               if ((rte->ref != NULL)
-                   && ((strcmp(rte->relname, rte->ref->relname) != 0)
-                       || (rte->ref->attrs != NIL)))
-               {
-                   appendStringInfo(buf, " %s",
-                                    quote_identifier(rte->ref->relname));
-                   if (rte->ref->attrs != NIL)
-                   {
-                       List       *col;
-
-                       appendStringInfo(buf, " (");
-                       foreach(col, rte->ref->attrs)
-                       {
-                           if (col != rte->ref->attrs)
-                               appendStringInfo(buf, ", ");
-                           appendStringInfo(buf, "%s",
-                                 quote_identifier(strVal(lfirst(col))));
-                       }
-                       appendStringInfoChar(buf, ')');
-                   }
-               }
-           }
-       }
-   }
+   /* Add the FROM clause if needed */
+   get_from_clause(query, context);
 
    /* Add the WHERE clause if given */
    if (query->qual != NULL)
@@ -1066,52 +957,32 @@ get_insert_query_def(Query *query, deparse_context *context)
 {
    StringInfo  buf = context->buf;
    char       *sep;
-   TargetEntry *tle;
-   RangeTblEntry *rte;
-   bool       *rt_used;
-   int         rt_length;
-   int         rt_numused = 0;
    bool        rt_constonly = TRUE;
+   RangeTblEntry *rte;
    int         i;
    List       *l;
 
    /* ----------
     * We need to know if other tables than *NEW* or *OLD*
     * are used in the query. If not, it's an INSERT ... VALUES,
-    * otherwise an INSERT ... SELECT.
+    * otherwise an INSERT ... SELECT.  (Pretty klugy ... fix this
+    * when we redesign querytrees!)
     * ----------
     */
-   rt_length = length(query->rtable);
-   rt_used = palloc(sizeof(bool) * rt_length);
-   for (i = 0; i < rt_length; i++)
-   {
-       if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
-           check_if_rte_used(query->qual, i + 1, 0) ||
-           check_if_rte_used(query->havingQual, i + 1, 0))
-       {
-           rt_used[i] = TRUE;
-           rt_numused++;
-       }
-       else
-           rt_used[i] = FALSE;
-   }
-
    i = 0;
    foreach(l, query->rtable)
    {
-       if (!rt_used[i++])
-           continue;
-
        rte = (RangeTblEntry *) lfirst(l);
-       if (rte->ref == NULL)
-           continue;
-       if (strcmp(rte->ref->relname, "*NEW*") == 0)
+       i++;
+       if (strcmp(rte->eref->relname, "*NEW*") == 0)
            continue;
-       if (strcmp(rte->ref->relname, "*OLD*") == 0)
+       if (strcmp(rte->eref->relname, "*OLD*") == 0)
            continue;
-
-       rt_constonly = FALSE;
-       break;
+       if (rangeTableEntry_used((Node *) query, i, 0))
+       {
+           rt_constonly = FALSE;
+           break;
+       }
    }
 
    /* ----------
@@ -1122,11 +993,11 @@ get_insert_query_def(Query *query, deparse_context *context)
    appendStringInfo(buf, "INSERT INTO %s",
                     quote_identifier(rte->relname));
 
-   /* Add the target list */
+   /* Add the insert-column-names list */
    sep = " (";
    foreach(l, query->targetList)
    {
-       tle = (TargetEntry *) lfirst(l);
+       TargetEntry *tle = (TargetEntry *) lfirst(l);
 
        appendStringInfo(buf, sep);
        sep = ", ";
@@ -1141,7 +1012,7 @@ get_insert_query_def(Query *query, deparse_context *context)
        sep = "";
        foreach(l, query->targetList)
        {
-           tle = (TargetEntry *) lfirst(l);
+           TargetEntry *tle = (TargetEntry *) lfirst(l);
 
            appendStringInfo(buf, sep);
            sep = ", ";
@@ -1195,6 +1066,9 @@ get_update_query_def(Query *query, deparse_context *context)
        get_tle_expr(tle, context);
    }
 
+   /* Add the FROM clause if needed */
+   get_from_clause(query, context);
+
    /* Finally add a WHERE clause if given */
    if (query->qual != NULL)
    {
@@ -1281,16 +1155,13 @@ get_rule_expr(Node *node, deparse_context *context)
 
                if (context->varprefix)
                {
-                   if (rte->ref == NULL)
-                       appendStringInfo(buf, "%s.",
-                                        quote_identifier(rte->relname));
-                   else if (strcmp(rte->ref->relname, "*NEW*") == 0)
+                   if (strcmp(rte->eref->relname, "*NEW*") == 0)
                        appendStringInfo(buf, "new.");
-                   else if (strcmp(rte->ref->relname, "*OLD*") == 0)
+                   else if (strcmp(rte->eref->relname, "*OLD*") == 0)
                        appendStringInfo(buf, "old.");
                    else
                        appendStringInfo(buf, "%s.",
-                                   quote_identifier(rte->ref->relname));
+                                   quote_identifier(rte->eref->relname));
                }
                appendStringInfo(buf, "%s",
                          quote_identifier(get_attribute_name(rte->relid,
@@ -1860,6 +1731,165 @@ get_sublink_expr(Node *node, deparse_context *context)
        appendStringInfoChar(buf, ')');
 }
 
+
+/* ----------
+ * get_from_clause         - Parse back a FROM clause
+ * ----------
+ */
+static void
+get_from_clause(Query *query, deparse_context *context)
+{
+   StringInfo  buf = context->buf;
+   char       *sep;
+   List       *l;
+
+   /*
+    * We use the query's jointree as a guide to what to print.  However,
+    * we must ignore auto-added RTEs that are marked not inFromCl.
+    * Also ignore the rule pseudo-RTEs for NEW and OLD.
+    */
+   sep = " FROM ";
+
+   foreach(l, query->jointree)
+   {
+       Node   *jtnode = (Node *) lfirst(l);
+
+       if (IsA(jtnode, RangeTblRef))
+       {
+           int         varno = ((RangeTblRef *) jtnode)->rtindex;
+           RangeTblEntry *rte = rt_fetch(varno, query->rtable);
+
+           if (!rte->inFromCl)
+               continue;
+           if (strcmp(rte->eref->relname, "*NEW*") == 0)
+               continue;
+           if (strcmp(rte->eref->relname, "*OLD*") == 0)
+               continue;
+       }
+
+       appendStringInfo(buf, sep);
+       get_from_clause_item(jtnode, query, context);
+       sep = ", ";
+   }
+}
+
+static void
+get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
+{
+   StringInfo  buf = context->buf;
+
+   if (IsA(jtnode, RangeTblRef))
+   {
+       int         varno = ((RangeTblRef *) jtnode)->rtindex;
+       RangeTblEntry *rte = rt_fetch(varno, query->rtable);
+
+       appendStringInfo(buf, "%s%s",
+                        only_marker(rte),
+                        quote_identifier(rte->relname));
+       if (rte->alias != NULL)
+       {
+           appendStringInfo(buf, " %s",
+                            quote_identifier(rte->alias->relname));
+           if (rte->alias->attrs != NIL)
+           {
+               List       *col;
+
+               appendStringInfo(buf, " (");
+               foreach(col, rte->alias->attrs)
+               {
+                   if (col != rte->alias->attrs)
+                       appendStringInfo(buf, ", ");
+                   appendStringInfo(buf, "%s",
+                                    quote_identifier(strVal(lfirst(col))));
+               }
+               appendStringInfoChar(buf, ')');
+           }
+       }
+   }
+   else if (IsA(jtnode, JoinExpr))
+   {
+       JoinExpr   *j = (JoinExpr *) jtnode;
+
+       appendStringInfoChar(buf, '(');
+       get_from_clause_item(j->larg, query, context);
+       if (j->isNatural)
+           appendStringInfo(buf, " NATURAL");
+       switch (j->jointype)
+       {
+           case JOIN_INNER:
+               if (j->quals)
+                   appendStringInfo(buf, " JOIN ");
+               else
+                   appendStringInfo(buf, " CROSS JOIN ");
+               break;
+           case JOIN_LEFT:
+               appendStringInfo(buf, " LEFT JOIN ");
+               break;
+           case JOIN_FULL:
+               appendStringInfo(buf, " FULL JOIN ");
+               break;
+           case JOIN_RIGHT:
+               appendStringInfo(buf, " RIGHT JOIN ");
+               break;
+           case JOIN_UNION:
+               appendStringInfo(buf, " UNION JOIN ");
+               break;
+           default:
+               elog(ERROR, "get_from_clause_item: unknown join type %d",
+                    (int) j->jointype);
+       }
+       get_from_clause_item(j->rarg, query, context);
+       if (! j->isNatural)
+       {
+           if (j->using)
+           {
+               List       *col;
+
+               appendStringInfo(buf, " USING (");
+               foreach(col, j->using)
+               {
+                   if (col != j->using)
+                       appendStringInfo(buf, ", ");
+                   appendStringInfo(buf, "%s",
+                                    quote_identifier(strVal(lfirst(col))));
+               }
+               appendStringInfoChar(buf, ')');
+           }
+           else if (j->quals)
+           {
+               appendStringInfo(buf, " ON (");
+               get_rule_expr(j->quals, context);
+               appendStringInfoChar(buf, ')');
+           }
+       }
+       appendStringInfoChar(buf, ')');
+       /* Yes, it's correct to put alias after the right paren ... */
+       if (j->alias != NULL)
+       {
+           appendStringInfo(buf, " %s",
+                            quote_identifier(j->alias->relname));
+           if (j->alias->attrs != NIL)
+           {
+               List       *col;
+
+               appendStringInfo(buf, " (");
+               foreach(col, j->alias->attrs)
+               {
+                   if (col != j->alias->attrs)
+                       appendStringInfo(buf, ", ");
+                   appendStringInfo(buf, "%s",
+                                    quote_identifier(strVal(lfirst(col))));
+               }
+               appendStringInfoChar(buf, ')');
+           }
+       }
+   }
+   else
+       elog(ERROR, "get_from_clause_item: unexpected node type %d",
+            nodeTag(jtnode));
+}
+
+
 /* ----------
  * tleIsArrayAssign            - check for array assignment
  * ----------
@@ -1990,56 +2020,3 @@ get_attribute_name(Oid relid, int2 attnum)
    attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
    return pstrdup(NameStr(attStruct->attname));
 }
-
-
-/* ----------
- * check_if_rte_used
- *     Check a targetlist or qual to see if a given rangetable entry
- *     is used in it
- * ----------
- */
-static bool
-check_if_rte_used(Node *node, Index rt_index, int levelsup)
-{
-   check_if_rte_used_context context;
-
-   context.rt_index = rt_index;
-   context.levelsup = levelsup;
-   return check_if_rte_used_walker(node, &context);
-}
-
-static bool
-check_if_rte_used_walker(Node *node,
-                        check_if_rte_used_context *context)
-{
-   if (node == NULL)
-       return false;
-   if (IsA(node, Var))
-   {
-       Var        *var = (Var *) node;
-
-       return var->varno == context->rt_index &&
-           var->varlevelsup == context->levelsup;
-   }
-   if (IsA(node, SubLink))
-   {
-       SubLink    *sublink = (SubLink *) node;
-       Query      *query = (Query *) sublink->subselect;
-
-       /* Recurse into subquery; expression_tree_walker will not */
-       if (check_if_rte_used((Node *) (query->targetList),
-                             context->rt_index, context->levelsup + 1) ||
-           check_if_rte_used(query->qual,
-                             context->rt_index, context->levelsup + 1) ||
-           check_if_rte_used(query->havingQual,
-                             context->rt_index, context->levelsup + 1))
-           return true;
-
-       /*
-        * fall through to let expression_tree_walker examine lefthand
-        * args
-        */
-   }
-   return expression_tree_walker(node, check_if_rte_used_walker,
-                                 (void *) context);
-}
index b73ea0e6136e105db47812d3f8c3471aa10fea39..eb067fe50575ff3561b537b2928c7bc12e0617bc 100644 (file)
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.44 2000/09/12 04:49:15 momjian Exp $
+ * $Id: catversion.h,v 1.45 2000/09/12 21:07:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 200009111
+#define CATALOG_VERSION_NO 200009121
 
 #endif
index 7eb6667da03e76b368f7983990184e1292444b74..c4ba3d1f5b6e8be8743d730f6d90e362d2980570 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execdebug.h,v 1.13 2000/06/15 00:52:07 momjian Exp $
+ * $Id: execdebug.h,v 1.14 2000/09/12 21:07:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #undef EXEC_MERGEJOINDEBUG
  */
 
-/* ----------------
- *     EXEC_MERGEJOINPFREE is a flag which causes merge joins
- *     to pfree intermittant tuples (which is the proper thing)
- *     Not defining this means we avoid menory management problems
- *     at the cost of doing deallocation of stuff only at the
- *     end of the transaction
- * ----------------
-#undef EXEC_MERGEJOINPFREE
- */
-
 /* ----------------
  *     EXEC_DEBUGINTERACTIVE is a flag which enables the
  *     user to issue "DEBUG" commands from an interactive
  *           only as necessary -cim 10/26/89
  * ----------------------------------------------------------------
  */
-#define T_OR_F(b)              (b ? "true" : "false")
+#define T_OR_F(b)              ((b) ? "true" : "false")
 #define NULL_OR_TUPLE(slot)        (TupIsNull(slot) ? "null" : "a tuple")
 
 
-/* #define EXEC_TUPLECOUNT - XXX take out for now for executor stubbing -- jolly*/
 /* ----------------
  *     tuple count debugging defines
  * ----------------
@@ -326,28 +315,31 @@ extern int    NIndexTupleInserted;
 #define MJ1_printf(s, p)               printf(s, p)
 #define MJ2_printf(s, p1, p2)          printf(s, p1, p2)
 #define MJ_debugtup(tuple, type)       debugtup(tuple, type, NULL)
-#define MJ_dump(context, state)            ExecMergeTupleDump(econtext, state)
+#define MJ_dump(state)                 ExecMergeTupleDump(state)
 #define MJ_DEBUG_QUAL(clause, res) \
   MJ2_printf("  ExecQual(%s, econtext) returns %s\n", \
             CppAsString(clause), T_OR_F(res));
 
 #define MJ_DEBUG_MERGE_COMPARE(qual, res) \
-  MJ2_printf("  MergeCompare(mergeclauses, %s, ..) returns %s\n", \
+  MJ2_printf("  MergeCompare(mergeclauses, %s, ...) returns %s\n", \
             CppAsString(qual), T_OR_F(res));
 
 #define MJ_DEBUG_PROC_NODE(slot) \
-  MJ2_printf("  %s = ExecProcNode(innerPlan) returns %s\n", \
+  MJ2_printf("  %s = ExecProcNode(...) returns %s\n", \
             CppAsString(slot), NULL_OR_TUPLE(slot));
+
 #else
+
 #define MJ_nodeDisplay(l)
 #define MJ_printf(s)
 #define MJ1_printf(s, p)
 #define MJ2_printf(s, p1, p2)
 #define MJ_debugtup(tuple, type)
-#define MJ_dump(context, state)
+#define MJ_dump(state)
 #define MJ_DEBUG_QUAL(clause, res)
 #define MJ_DEBUG_MERGE_COMPARE(qual, res)
 #define MJ_DEBUG_PROC_NODE(slot)
+
 #endif  /* EXEC_MERGEJOINDEBUG */
 
 /* ----------------------------------------------------------------
index 89fed192cdd6b99cc43a6deb3724467218da87ba..6b9457969b1a054fd13006490063e9285d3a192c 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execdefs.h,v 1.6 2000/01/26 05:58:05 momjian Exp $
+ * $Id: execdefs.h,v 1.7 2000/09/12 21:07:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #define EXEC_MJ_NEXTOUTER              5
 #define EXEC_MJ_TESTOUTER              6
 #define EXEC_MJ_NEXTINNER              7
-#define EXEC_MJ_SKIPINNER              8
-#define EXEC_MJ_SKIPOUTER              9
-#define EXEC_MJ_FILLINNER             10
-#define EXEC_MJ_FILLOUTER             11
+#define EXEC_MJ_SKIPOUTER_BEGIN            8
+#define EXEC_MJ_SKIPOUTER_TEST         9
+#define EXEC_MJ_SKIPOUTER_ADVANCE      10
+#define EXEC_MJ_SKIPINNER_BEGIN            11
+#define EXEC_MJ_SKIPINNER_TEST         12
+#define EXEC_MJ_SKIPINNER_ADVANCE      13
+#define EXEC_MJ_ENDOUTER               14
+#define EXEC_MJ_ENDINNER               15
 
 #endif  /* EXECDEFS_H */
index 5eb7cbb93bae73664ff370c83360d8ed0499e941..5c330915e754454de0b10c9c778e7b3025792729 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: executor.h,v 1.50 2000/08/24 23:34:09 tgl Exp $
+ * $Id: executor.h,v 1.51 2000/09/12 21:07:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -117,7 +117,9 @@ extern void ExecSetSlotDescriptorIsNew(TupleTableSlot *slot, bool isNew);
 extern void ExecInitResultTupleSlot(EState *estate, CommonState *commonstate);
 extern void ExecInitScanTupleSlot(EState *estate,
                      CommonScanState *commonscanstate);
-extern void ExecInitOuterTupleSlot(EState *estate, HashJoinState *hashstate);
+extern TupleTableSlot *ExecInitExtraTupleSlot(EState *estate);
+extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
+                                            TupleDesc tupType);
 
 extern TupleDesc ExecGetTupType(Plan *node);
 extern TupleDesc ExecTypeFromTL(List *targetList);
index 9626dbf8b1c0c658122af32860d5ed3118b0c5b9..83ed6c5234bb3913457e8388480e77d5a65c1971 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.48 2000/08/24 03:29:13 tgl Exp $
+ * $Id: execnodes.h,v 1.49 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -469,11 +469,18 @@ typedef CommonState JoinState;
 
 /* ----------------
  *  NestLoopState information
+ *
+ *     NeedNewOuter       true if need new outer tuple on next call
+ *     MatchedOuter       true if found a join match for current outer tuple
+ *     NullInnerTupleSlot prepared null tuple for left outer joins
  * ----------------
  */
 typedef struct NestLoopState
 {
    JoinState   jstate;         /* its first field is NodeTag */
+   bool        nl_NeedNewOuter;
+   bool        nl_MatchedOuter;
+   TupleTableSlot *nl_NullInnerTupleSlot;
 } NestLoopState;
 
 /* ----------------
@@ -482,7 +489,13 @@ typedef struct NestLoopState
  *     OuterSkipQual      outerKey1 < innerKey1 ...
  *     InnerSkipQual      outerKey1 > innerKey1 ...
  *     JoinState          current "state" of join. see executor.h
+ *     MatchedOuter       true if found a join match for current outer tuple
+ *     MatchedInner       true if found a join match for current inner tuple
+ *     OuterTupleSlot     pointer to slot in tuple table for cur outer tuple
+ *     InnerTupleSlot     pointer to slot in tuple table for cur inner tuple
  *     MarkedTupleSlot    pointer to slot in tuple table for marked tuple
+ *     NullOuterTupleSlot prepared null tuple for right outer joins
+ *     NullInnerTupleSlot prepared null tuple for left outer joins
  * ----------------
  */
 typedef struct MergeJoinState
@@ -491,7 +504,13 @@ typedef struct MergeJoinState
    List       *mj_OuterSkipQual;
    List       *mj_InnerSkipQual;
    int         mj_JoinState;
+   bool        mj_MatchedOuter;
+   bool        mj_MatchedInner;
+   TupleTableSlot *mj_OuterTupleSlot;
+   TupleTableSlot *mj_InnerTupleSlot;
    TupleTableSlot *mj_MarkedTupleSlot;
+   TupleTableSlot *mj_NullOuterTupleSlot;
+   TupleTableSlot *mj_NullInnerTupleSlot;
 } MergeJoinState;
 
 /* ----------------
@@ -506,6 +525,10 @@ typedef struct MergeJoinState
  *     hj_InnerHashKey         the inner hash key in the hashjoin condition
  *     hj_OuterTupleSlot       tuple slot for outer tuples
  *     hj_HashTupleSlot        tuple slot for hashed tuples
+ *     hj_NullInnerTupleSlot   prepared null tuple for left outer joins
+ *     hj_NeedNewOuter         true if need new outer tuple on next call
+ *     hj_MatchedOuter         true if found a join match for current outer
+ *     hj_hashdone             true if hash-table-build phase is done
  * ----------------
  */
 typedef struct HashJoinState
@@ -517,6 +540,10 @@ typedef struct HashJoinState
    Node       *hj_InnerHashKey;
    TupleTableSlot *hj_OuterTupleSlot;
    TupleTableSlot *hj_HashTupleSlot;
+   TupleTableSlot *hj_NullInnerTupleSlot;
+   bool        hj_NeedNewOuter;
+   bool        hj_MatchedOuter;
+   bool        hj_hashdone;
 } HashJoinState;
 
 
index d825c8fe395bcd8f64f4d191277884213608104e..f3929d8b2c67e27560feab1082075adea1ec3e0c 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nodes.h,v 1.75 2000/08/24 03:29:13 tgl Exp $
+ * $Id: nodes.h,v 1.76 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -68,6 +68,8 @@ typedef enum NodeTag
    T_ArrayRef,
    T_Iter,
    T_RelabelType,
+   T_RangeTblRef,
+   T_JoinExpr,
 
    /*---------------------
     * TAGS FOR PLANNER NODES (relation.h)
@@ -204,7 +206,7 @@ typedef enum NodeTag
    T_A_Indices,
    T_ResTarget,
    T_TypeCast,
-   T_RelExpr,
+   T_RangeSubselect,
    T_SortGroupBy,
    T_RangeVar,
    T_TypeName,
@@ -217,14 +219,14 @@ typedef enum NodeTag
    T_SortClause,
    T_GroupClause,
    T_SubSelectXXX,             /* not used anymore; this tag# is available */
-   T_JoinExpr,
+   T_oldJoinExprXXX,           /* not used anymore; this tag# is available */
    T_CaseExpr,
    T_CaseWhen,
    T_RowMark,
    T_FkConstraint,
 
    /*---------------------
-    * TAGS FOR FUNCTION-CALL CONTEXT AND RESULTINFO NODES (cf. fmgr.h)
+    * TAGS FOR FUNCTION-CALL CONTEXT AND RESULTINFO NODES (see fmgr.h)
     *---------------------
     */
    T_TriggerData = 800,        /* in commands/trigger.h */
@@ -310,7 +312,7 @@ typedef double Cost;            /* execution cost (in page-access units) */
 
 /*
  * CmdType -
- *   enums for type of operation to aid debugging
+ *   enums for type of operation represented by a Query
  *
  * ??? could have put this in parsenodes.h but many files not in the
  *   optimizer also need this...
@@ -329,4 +331,40 @@ typedef enum CmdType
 } CmdType;
 
 
+/*
+ * JoinType -
+ *   enums for types of relation joins
+ *
+ * JoinType determines the exact semantics of joining two relations using
+ * a matching qualification.  For example, it tells what to do with a tuple
+ * that has no match in the other relation.
+ *
+ * This is needed in both parsenodes.h and plannodes.h, so put it here...
+ */
+typedef enum JoinType
+{
+   /*
+    * The canonical kinds of joins
+    */
+   JOIN_INNER,                 /* matching tuple pairs only */
+   JOIN_LEFT,                  /* pairs + unmatched outer tuples */
+   JOIN_FULL,                  /* pairs + unmatched outer + unmatched inner */
+   JOIN_RIGHT,                 /* pairs + unmatched inner tuples */
+   /*
+    * SQL92 considers UNION JOIN to be a kind of join, so list it here for
+    * parser convenience, even though it's not implemented like a join in
+    * the executor.  (The planner must convert it to an Append plan.)
+    */
+   JOIN_UNION
+   /*
+    * Eventually we will have some additional join types for efficient
+    * support of queries like WHERE foo IN (SELECT bar FROM ...).
+    */
+} JoinType;
+
+#define IS_OUTER_JOIN(jointype) \
+   ((jointype) == JOIN_LEFT || \
+    (jointype) == JOIN_FULL || \
+    (jointype) == JOIN_RIGHT)
+
 #endif  /* NODES_H */
index f6c75c197810f96634539b9173be7b38cf14da24..440a7609d83fddffee462e716dcbc1b018a5a9e4 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.112 2000/09/12 05:09:50 momjian Exp $
+ * $Id: parsenodes.h,v 1.113 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,7 +40,7 @@ typedef struct Query
    Node       *utilityStmt;    /* non-null if this is a non-optimizable
                                 * statement */
 
-   int         resultRelation; /* target relation (index to rtable) */
+   int         resultRelation; /* target relation (index into rtable) */
    char       *into;           /* portal (cursor) name */
    bool        isPortal;       /* is this a retrieve into portal? */
    bool        isBinary;       /* binary portal? */
@@ -50,6 +50,8 @@ typedef struct Query
    bool        hasSubLinks;    /* has subquery SubLink */
 
    List       *rtable;         /* list of range table entries */
+   List       *jointree;       /* table join tree (from the FROM clause) */
+
    List       *targetList;     /* target list (of TargetEntry) */
    Node       *qual;           /* qualifications applied to tuples */
    List       *rowMark;        /* list of RowMark entries */
@@ -1057,16 +1059,6 @@ typedef struct ResTarget
                                 * assign */
 } ResTarget;
 
-/*
- * RelExpr - relation expressions
- */
-typedef struct RelExpr
-{
-   NodeTag     type;
-   char       *relname;        /* the relation name */
-   bool        inh;            /* inheritance query */
-} RelExpr;
-
 /*
  * SortGroupBy - for ORDER BY clause
  */
@@ -1083,10 +1075,21 @@ typedef struct SortGroupBy
 typedef struct RangeVar
 {
    NodeTag     type;
-   RelExpr    *relExpr;        /* the relation expression */
-   Attr       *name;           /* the name to be referenced (optional) */
+   char       *relname;        /* the relation name */
+   bool        inh;            /* expand rel by inheritance? */
+   Attr       *name;           /* optional table alias & column aliases */
 } RangeVar;
 
+/*
+ * RangeSubselect - subquery appearing in a FROM clause
+ */
+typedef struct RangeSubselect
+{
+   NodeTag     type;
+   Node       *subquery;       /* the untransformed sub-select clause */
+   Attr       *name;           /* optional table alias & column aliases */
+} RangeSubselect;
+
 /*
  * IndexElem - index parameters (used in CREATE INDEX)
  *
@@ -1114,20 +1117,6 @@ typedef struct DefElem
    Node       *arg;            /* a (Value *) or a (TypeName *) */
 } DefElem;
 
-/*
- * JoinExpr - for JOIN expressions
- */
-typedef struct JoinExpr
-{
-   NodeTag     type;
-   int         jointype;
-   bool        isNatural;      /* Natural join? Will need to shape table */
-   Node       *larg;           /* RangeVar or join expression */
-   Node       *rarg;           /* RangeVar or join expression */
-   Attr       *alias;          /* table and column aliases, if any */
-   List       *quals;          /* qualifiers on join, if any */
-} JoinExpr;
-
 
 /****************************************************************************
  * Nodes for a Query tree
@@ -1155,11 +1144,12 @@ typedef struct TargetEntry
  *   Some of the following are only used in one of
  *   the parsing, optimizing, execution stages.
  *
- *   eref is the expanded table name and columns for the underlying
- *   relation. Note that for outer join syntax, allowed reference names
- *   could be modified as one evaluates the nested clauses (e.g.
- *   "SELECT ... FROM t1 NATURAL JOIN t2 WHERE ..." forbids explicit mention
- *   of a table name in any reference to the join column.
+ *   alias is an Attr node representing the AS alias-clause attached to the
+ *   FROM expression, or NULL if no clause.
+ *
+ *   eref is the table reference name and column reference names (either
+ *   real or aliases).  This is filled in during parse analysis.  Note that
+ *   system columns (OID etc) are not included in the column list.
  *
  *   inFromCl marks those range variables that are listed in the FROM clause.
  *   In SQL, the query can only refer to range variables listed in the
@@ -1170,29 +1160,17 @@ typedef struct TargetEntry
  *   implicitly-added RTE shouldn't change the namespace for unqualified
  *   column names processed later, and it also shouldn't affect the
  *   expansion of '*'.
- *
- *   inJoinSet marks those range variables that the planner should join
- *   over even if they aren't explicitly referred to in the query.  For
- *   example, "SELECT COUNT(1) FROM tx" should produce the number of rows
- *   in tx.  A more subtle example uses a POSTQUEL implicit RTE:
- *         SELECT COUNT(1) FROM tx WHERE TRUE OR (tx.f1 = ty.f2)
- *   Here we should get the product of the sizes of tx and ty.  However,
- *   the query optimizer can simplify the WHERE clause to "TRUE", so
- *   ty will no longer be referred to explicitly; without a flag forcing
- *   it to be included in the join, we will get the wrong answer.  So,
- *   a POSTQUEL implicit RTE must be marked inJoinSet but not inFromCl.
  *--------------------
  */
 typedef struct RangeTblEntry
 {
    NodeTag     type;
    char       *relname;        /* real name of the relation */
-   Attr       *ref;            /* reference names (given in FROM clause) */
-   Attr       *eref;           /* expanded reference names */
    Oid         relid;          /* OID of the relation */
+   Attr       *alias;          /* user-written alias clause, if any */
+   Attr       *eref;           /* expanded reference names */
    bool        inh;            /* inheritance requested? */
    bool        inFromCl;       /* present in FROM clause */
-   bool        inJoinSet;      /* planner must include this rel */
    bool        skipAcl;        /* skip ACL check in executor */
 } RangeTblEntry;
 
index a0e9881dc87235481202aa5479bc7ad16e4e62a7..4e0bcfc70539c4ec87208d9439353c45e70f8113 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_list.h,v 1.18 2000/06/09 01:44:26 momjian Exp $
+ * $Id: pg_list.h,v 1.19 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -101,6 +101,7 @@ extern List *nconc(List *list1, List *list2);
 extern List *lcons(void *datum, List *list);
 extern List *lconsi(int datum, List *list);
 extern bool member(void *datum, List *list);
+extern bool ptrMember(void *datum, List *list);
 extern bool intMember(int datum, List *list);
 extern Value *makeInteger(long i);
 extern Value *makeFloat(char *numericStr);
index e348d25b2baca11299f0c2ad06ca057f019d172d..cf93b9dee17e979288e05800deec63c50f59e8ca 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: plannodes.h,v 1.41 2000/07/12 02:37:33 tgl Exp $
+ * $Id: plannodes.h,v 1.42 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -82,7 +82,7 @@ typedef struct Plan
                                 * individual nodes point to one EState
                                 * for the whole top-level plan */
    List       *targetlist;
-   List       *qual;           /* Node* or List* ?? */
+   List       *qual;           /* implicitly-ANDed qual conditions */
    struct Plan *lefttree;
    struct Plan *righttree;
    List       *extParam;       /* indices of _all_ _external_ PARAM_EXEC
@@ -210,9 +210,26 @@ typedef struct TidScan
 
 /* ----------------
  *     Join node
+ *
+ * jointype:   rule for joining tuples from left and right subtrees
+ * joinqual:   qual conditions that came from JOIN/ON or JOIN/USING
+ *             (plan.qual contains conditions that came from WHERE)
+ *
+ * When jointype is INNER, joinqual and plan.qual are semantically
+ * interchangeable.  For OUTER jointypes, the two are *not* interchangeable;
+ * only joinqual is used to determine whether a match has been found for
+ * the purpose of deciding whether to generate null-extended tuples.
+ * (But plan.qual is still applied before actually returning a tuple.)
+ * For an outer join, only joinquals are allowed to be used as the merge
+ * or hash condition of a merge or hash join.
  * ----------------
  */
-typedef Plan Join;
+typedef struct Join
+{
+   Plan        plan;
+   JoinType    jointype;
+   List       *joinqual;       /* JOIN quals (in addition to plan.qual) */
+} Join;
 
 /* ----------------
  *     nest loop join node
@@ -245,7 +262,6 @@ typedef struct HashJoin
    List       *hashclauses;
    Oid         hashjoinop;
    HashJoinState *hashjoinstate;
-   bool        hashdone;
 } HashJoin;
 
 /* ---------------
index 0ef350687dc41a6466dc0192260b875c20424aba..bc17773642c276561ae50cc757f1eec167938b4a 100644 (file)
@@ -1,13 +1,16 @@
 /*-------------------------------------------------------------------------
  *
  * primnodes.h
- *   Definitions for parse tree/query tree ("primitive") nodes.
+ *   Definitions for "primitive" node types, those that are used in more
+ *   than one of the parse/plan/execute stages of the query pipeline.
+ *   Currently, these are mostly nodes for executable expressions
+ *   and join trees.
  *
  *
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: primnodes.h,v 1.47 2000/08/24 03:29:13 tgl Exp $
+ * $Id: primnodes.h,v 1.48 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -98,6 +101,12 @@ typedef struct Fjoin
    BoolPtr     fj_alwaysDone;
 } Fjoin;
 
+
+/* ----------------------------------------------------------------
+ *                 node types for executable expressions
+ * ----------------------------------------------------------------
+ */
+
 /* ----------------
  * Expr
  *     typeOid         - oid of the type of this expression
@@ -155,7 +164,7 @@ typedef struct Var
    AttrNumber  varattno;
    Oid         vartype;
    int32       vartypmod;
-   Index       varlevelsup;    /* erased by upper optimizer */
+   Index       varlevelsup;
    Index       varnoold;       /* mainly for debugging --- see above */
    AttrNumber  varoattno;
 } Var;
@@ -480,4 +489,76 @@ typedef struct RelabelType
    int32       resulttypmod;
 } RelabelType;
 
+
+/* ----------------------------------------------------------------
+ *                 node types for join trees
+ *
+ * The leaves of a join tree structure are RangeTblRef nodes.  Above
+ * these, JoinExpr nodes can appear to denote a specific kind of join
+ * or qualified join.  A join tree can also contain List nodes --- a list
+ * implies an unqualified cross-product join of its members.  The planner
+ * is allowed to combine the elements of a list using whatever join order
+ * seems good to it.  At present, JoinExpr nodes are always joined in
+ * exactly the order implied by the tree structure (except the planner
+ * may choose to swap inner and outer members of a join pair).
+ *
+ * NOTE: currently, the planner only supports a List at the top level of
+ * a join tree.  Should generalize this to allow Lists at lower levels.
+ *
+ * NOTE: the qualification expressions present in JoinExpr nodes are
+ * *in addition to* the query's main WHERE clause.  For outer joins there
+ * is a real semantic difference between a join qual and a WHERE clause,
+ * though if all joins are inner joins they are interchangeable.
+ *
+ * NOTE: in the raw output of gram.y, a join tree contains RangeVar and
+ * RangeSubselect nodes, which are both replaced by RangeTblRef nodes
+ * during the parse analysis phase.
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * RangeTblRef - reference to an entry in the query's rangetable
+ *
+ * We could use direct pointers to the RT entries and skip having these
+ * nodes, but multiple pointers to the same node in a querytree cause
+ * lots of headaches, so it seems better to store an index into the RT.
+ */
+typedef struct RangeTblRef
+{
+   NodeTag     type;
+   int         rtindex;
+} RangeTblRef;
+
+/*----------
+ * JoinExpr - for SQL JOIN expressions
+ *
+ * isNatural, using, and quals are interdependent.  The user can write only
+ * one of NATURAL, USING(), or ON() (this is enforced by the grammar).
+ * If he writes NATURAL then parse analysis generates the equivalent USING()
+ * list, and from that fills in "quals" with the right equality comparisons.
+ * If he writes USING() then "quals" is filled with equality comparisons.
+ * If he writes ON() then only "quals" is set.  Note that NATURAL/USING
+ * are not equivalent to ON() since they also affect the output column list.
+ *
+ * alias is an Attr node representing the AS alias-clause attached to the
+ * join expression, or NULL if no clause.  During parse analysis, colnames
+ * is filled with a list of String nodes giving the column names (real or
+ * alias) of the output of the join, and colvars is filled with a list of
+ * expressions that can be copied to reference the output columns.
+ *----------
+ */
+typedef struct JoinExpr
+{
+   NodeTag     type;
+   JoinType    jointype;       /* type of join */
+   bool        isNatural;      /* Natural join? Will need to shape table */
+   Node       *larg;           /* left subtree */
+   Node       *rarg;           /* right subtree */
+   List       *using;          /* USING clause, if any (list of String) */
+   Node       *quals;          /* qualifiers on join, if any */
+   struct Attr *alias;         /* user-written alias clause, if any */
+   List       *colnames;       /* output column names (list of String) */
+   List       *colvars;        /* output column nodes (list of expressions) */
+} JoinExpr;
+
 #endif  /* PRIMNODES_H */
index b7d651310669e0e342ad13614ead91eeb737c76e..767e2e114e0502a44c174c28487e9358bce0661b 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: relation.h,v 1.47 2000/04/12 17:16:40 momjian Exp $
+ * $Id: relation.h,v 1.48 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -73,6 +73,9 @@ typedef enum CostSelector
  *                 participates (only used for base rels)
  *     baserestrictcost - Estimated cost of evaluating the baserestrictinfo
  *                 clauses at a single tuple (only used for base rels)
+ *     outerjoinset - If the rel appears within the nullable side of an outer
+ *                 join, the list of all relids participating in the highest
+ *                 such outer join; else NIL (only used for base rels)
  *     joininfo  - List of JoinInfo nodes, containing info about each join
  *                 clause in which this relation participates
  *     innerjoin - List of Path nodes that represent indices that may be used
@@ -94,6 +97,10 @@ typedef enum CostSelector
  * We store baserestrictcost in the RelOptInfo (for base relations) because
  * we know we will need it at least once (to price the sequential scan)
  * and may need it multiple times to price index scans.
+ *
+ * outerjoinset is used to ensure correct placement of WHERE clauses that
+ * apply to outer-joined relations; we must not apply such WHERE clauses
+ * until after the outer join is performed.
  */
 
 typedef struct RelOptInfo
@@ -124,6 +131,7 @@ typedef struct RelOptInfo
    List       *baserestrictinfo;       /* RestrictInfo structures (if
                                         * base rel) */
    Cost        baserestrictcost;       /* cost of evaluating the above */
+   Relids      outerjoinset;           /* integer list of base relids */
    List       *joininfo;       /* JoinInfo structures */
    List       *innerjoin;      /* potential indexscans for nestloop joins */
 
@@ -263,6 +271,9 @@ typedef struct Path
  * that refer to values of other rels, so those other rels must be
  * included in the outer joinrel in order to make a usable join.
  *
+ * 'alljoinquals' is also used only for inner paths of nestloop joins.
+ * This flag is TRUE iff all the indexquals came from JOIN/ON conditions.
+ *
  * 'rows' is the estimated result tuple count for the indexscan.  This
  * is the same as path.parent->rows for a simple indexscan, but it is
  * different for a nestloop inner path, because the additional indexquals
@@ -277,6 +288,7 @@ typedef struct IndexPath
    List       *indexqual;
    ScanDirection indexscandir;
    Relids      joinrelids;     /* other rels mentioned in indexqual */
+   bool        alljoinquals;   /* all indexquals derived from JOIN conds? */
    double      rows;           /* estimated number of result tuples */
 } IndexPath;
 
@@ -295,8 +307,11 @@ typedef struct JoinPath
 {
    Path        path;
 
+   JoinType    jointype;
+
    Path       *outerjoinpath;  /* path for the outer side of the join */
    Path       *innerjoinpath;  /* path for the inner side of the join */
+
    List       *joinrestrictinfo;       /* RestrictInfos to apply to join */
 
    /*
@@ -375,11 +390,12 @@ typedef struct HashPath
  * The clause cannot actually be applied until we have built a join rel
  * containing all the base rels it references, however.
  *
- * When we construct a join rel that describes exactly the set of base rels
- * referenced in a multi-relation restriction clause, we place that clause
- * into the joinrestrictinfo lists of paths for the join rel.  It will be
- * applied at that join level, and will not propagate any further up the
- * join tree.  (Note: the "predicate migration" code was once intended to
+ * When we construct a join rel that includes all the base rels referenced
+ * in a multi-relation restriction clause, we place that clause into the
+ * joinrestrictinfo lists of paths for the join rel, if neither left nor
+ * right sub-path includes all base rels referenced in the clause.  The clause
+ * will be applied at that join level, and will not propagate any further up
+ * the join tree.  (Note: the "predicate migration" code was once intended to
  * push restriction clauses up and down the plan tree based on evaluation
  * costs, but it's dead code and is unlikely to be resurrected in the
  * foreseeable future.)
@@ -394,18 +410,30 @@ typedef struct HashPath
  * or hashjoin clauses are fairly limited --- the code for each kind of
  * path is responsible for identifying the restrict clauses it can use
  * and ignoring the rest.  Clauses not implemented by an indexscan,
- * mergejoin, or hashjoin will be placed in the qpqual field of the
- * final Plan node, where they will be enforced by general-purpose
+ * mergejoin, or hashjoin will be placed in the plan qual or joinqual field
+ * of the final Plan node, where they will be enforced by general-purpose
  * qual-expression-evaluation code.  (But we are still entitled to count
  * their selectivity when estimating the result tuple count, if we
  * can guess what it is...)
+ *
+ * When dealing with outer joins we must distinguish between qual clauses
+ * that came from WHERE and those that came from JOIN/ON or JOIN/USING.
+ * (For inner joins there's no semantic difference and we can treat the
+ * clauses interchangeably.)  Both kinds of quals are stored as RestrictInfo
+ * nodes during planning, but there's a flag to indicate where they came from.
+ * Note also that when outer joins are present, a qual clause may be treated
+ * as referencing more rels than it really does.  This trick ensures that the
+ * qual will be evaluated at the right level of the join tree --- we don't
+ * want quals from WHERE to be evaluated until after the outer join is done.
  */
 
 typedef struct RestrictInfo
 {
    NodeTag     type;
 
-   Expr       *clause;         /* the represented clause of WHERE cond */
+   Expr       *clause;         /* the represented clause of WHERE or JOIN */
+
+   bool        isjoinqual;     /* TRUE if clause came from JOIN/ON */
 
    /* only used if clause is an OR clause: */
    List       *subclauseindices;       /* indexes matching subclauses */
@@ -437,7 +465,7 @@ typedef struct RestrictInfo
 typedef struct JoinInfo
 {
    NodeTag     type;
-   Relids      unjoined_relids;/* some rels not yet part of my RelOptInfo */
+   Relids      unjoined_relids; /* some rels not yet part of my RelOptInfo */
    List       *jinfo_restrictinfo;     /* relevant RestrictInfos */
 } JoinInfo;
 
index 1b2bcd920551f0d8850f2761548ecc15d97121d1..62bb401193d5250d620fd498e7143ad2bc41a0b6 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: clauses.h,v 1.38 2000/08/13 02:50:26 tgl Exp $
+ * $Id: clauses.h,v 1.39 2000/09/12 21:07:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -73,9 +73,11 @@ extern void CommuteClause(Expr *clause);
 extern Node *eval_const_expressions(Node *node);
 
 extern bool expression_tree_walker(Node *node, bool (*walker) (),
-                                              void *context);
+                                  void *context);
 extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (),
-                                                void *context);
+                                    void *context);
+extern bool query_tree_walker(Query *query, bool (*walker) (),
+                             void *context);
 
 #define is_subplan(clause) ((clause) != NULL && \
                             IsA(clause, Expr) && \
index b8788851e2b4dd151f613a7e7957f930020691eb..0bf57ef0cc5cb6bcfb828b6cd81dffadfd93bd82 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pathnode.h,v 1.27 2000/04/12 17:16:42 momjian Exp $
+ * $Id: pathnode.h,v 1.28 2000/09/12 21:07:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,26 +34,29 @@ extern IndexPath *create_index_path(Query *root, RelOptInfo *rel,
 extern TidPath *create_tidscan_path(RelOptInfo *rel, List *tideval);
 
 extern NestPath *create_nestloop_path(RelOptInfo *joinrel,
-                    Path *outer_path,
-                    Path *inner_path,
-                    List *restrict_clauses,
-                    List *pathkeys);
+                                     JoinType jointype,
+                                     Path *outer_path,
+                                     Path *inner_path,
+                                     List *restrict_clauses,
+                                     List *pathkeys);
 
 extern MergePath *create_mergejoin_path(RelOptInfo *joinrel,
-                     Path *outer_path,
-                     Path *inner_path,
-                     List *restrict_clauses,
-                     List *pathkeys,
-                     List *mergeclauses,
-                     List *outersortkeys,
-                     List *innersortkeys);
+                                       JoinType jointype,
+                                       Path *outer_path,
+                                       Path *inner_path,
+                                       List *restrict_clauses,
+                                       List *pathkeys,
+                                       List *mergeclauses,
+                                       List *outersortkeys,
+                                       List *innersortkeys);
 
 extern HashPath *create_hashjoin_path(RelOptInfo *joinrel,
-                    Path *outer_path,
-                    Path *inner_path,
-                    List *restrict_clauses,
-                    List *hashclauses,
-                    Selectivity innerdisbursion);
+                                     JoinType jointype,
+                                     Path *outer_path,
+                                     Path *inner_path,
+                                     List *restrict_clauses,
+                                     List *hashclauses,
+                                     Selectivity innerdisbursion);
 
 /*
  * prototypes for relnode.c
index 66520d6a897bfd0953293ed30ceeeb7527538d59..35eb3190f1c8d9a1839666b9843d8036f1a30e44 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: paths.h,v 1.46 2000/07/24 03:10:54 tgl Exp $
+ * $Id: paths.h,v 1.47 2000/09/12 21:07:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,21 +61,23 @@ extern void create_tidscan_paths(Query *root, RelOptInfo *rel);
  *    routines to create join paths
  */
 extern void add_paths_to_joinrel(Query *root, RelOptInfo *joinrel,
-                    RelOptInfo *outerrel,
-                    RelOptInfo *innerrel,
-                    List *restrictlist);
+                                RelOptInfo *outerrel,
+                                RelOptInfo *innerrel,
+                                JoinType jointype,
+                                List *restrictlist);
 
 /*
  * joinrels.c
  *   routines to determine which relations to join
  */
-extern void make_rels_by_joins(Query *root, int level);
-extern RelOptInfo *make_rels_by_clause_joins(Query *root,
-                         RelOptInfo *old_rel,
-                         List *other_rels);
-extern RelOptInfo *make_rels_by_clauseless_joins(Query *root,
-                             RelOptInfo *old_rel,
-                             List *other_rels);
+extern List *make_rels_by_joins(Query *root, int level, List **joinrels);
+extern List *make_rels_by_clause_joins(Query *root,
+                                      RelOptInfo *old_rel,
+                                      List *other_rels);
+extern List *make_rels_by_clauseless_joins(Query *root,
+                                          RelOptInfo *old_rel,
+                                          List *other_rels);
+extern RelOptInfo *make_rel_from_jointree(Query *root, Node *jtnode);
 
 /*
  * pathkeys.c
@@ -110,7 +112,7 @@ extern List *make_pathkeys_for_sortclauses(List *sortclauses,
 extern List *find_mergeclauses_for_pathkeys(List *pathkeys,
                               List *restrictinfos);
 extern List *make_pathkeys_for_mergeclauses(Query *root,
-                              List *mergeclauses,
-                              List *tlist);
+                                           List *mergeclauses,
+                                           RelOptInfo *rel);
 
 #endif  /* PATHS_H */
index 723543c437c5a35835ac3fc32d37658c2dde1905..43c93978cdfff4a8c2095daa6d744754b13cee54 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: planmain.h,v 1.43 2000/07/24 03:10:54 tgl Exp $
+ * $Id: planmain.h,v 1.44 2000/09/12 21:07:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,8 +20,7 @@
 /*
  * prototypes for plan/planmain.c
  */
-extern Plan *query_planner(Query *root, List *tlist, List *qual,
-             double tuple_fraction);
+extern Plan *query_planner(Query *root, List *tlist, double tuple_fraction);
 
 /*
  * prototypes for plan/createplan.c
@@ -40,9 +39,10 @@ extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
 /*
  * prototypes for plan/initsplan.c
  */
-extern void make_var_only_tlist(Query *root, List *tlist);
+extern void build_base_rel_tlists(Query *root, List *tlist);
+extern Relids add_join_quals_to_rels(Query *root, Node *jtnode);
 extern void add_restrict_and_join_to_rels(Query *root, List *clauses);
-extern void add_missing_rels_to_query(Query *root);
+extern List *add_missing_rels_to_query(Query *root, Node *jtnode);
 extern void process_implied_equality(Query *root, Node *item1, Node *item2,
                                     Oid sortop1, Oid sortop2);
 
@@ -58,6 +58,7 @@ extern void fix_opids(Node *node);
  * prep/prepkeyset.c
  */
 extern bool _use_keyset_query_optimizer;
+
 extern void transformKeySetQuery(Query *origNode);
 
 #endif  /* PLANMAIN_H */
index 3d94854e03b6b01e1412a591730a310c24505203..2e1d4d66f999424a8742f53d6ab02c857f5ad8c1 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: restrictinfo.h,v 1.8 2000/01/26 05:58:21 momjian Exp $
+ * $Id: restrictinfo.h,v 1.9 2000/09/12 21:07:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,5 +18,7 @@
 
 extern bool restriction_is_or_clause(RestrictInfo *restrictinfo);
 extern List *get_actual_clauses(List *restrictinfo_list);
+extern void get_actual_join_clauses(List *restrictinfo_list,
+                                   List **joinquals, List **otherquals);
 
 #endif  /* RESTRICTINFO_H */
index 02c95745fee356d10881bbdee69f11d4aa971c02..54d6e869ad9a57eee4f7ac3bdd80113293b73835 100644 (file)
@@ -1,28 +1,31 @@
 /*-------------------------------------------------------------------------
  *
  * gramparse.h
- *   scanner support routines.  used by both the bootstrap lexer
- * as well as the normal lexer
+ *   Declarations for routines exported from lexer and parser files.
+ *
  *
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: gramparse.h,v 1.12 2000/04/12 17:16:44 momjian Exp $
+ * $Id: gramparse.h,v 1.13 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #ifndef GRAMPARSE_H
-#define GRAMPARSE_H                /* include once only */
+#define GRAMPARSE_H
 
-/* from scan.l */
-extern void init_io(void);
+/* from parser.c */
 extern int yylex(void);
+
+/* from scan.l */
+extern void scanner_init(void);
+extern int base_yylex(void);
 extern void yyerror(const char *message);
 
 /* from gram.y */
-extern Oid param_type(int t);
 extern void parser_init(Oid *typev, int nargs);
+extern Oid param_type(int t);
 extern int yyparse(void);
 
 #endif  /* GRAMPARSE_H */
index b9a868d442030310c1a1c9b98d2c6a5c11267c60..fd1cfdb3604efe82a6a4181b4e9972daf592156f 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_clause.h,v 1.18 2000/06/09 01:44:29 momjian Exp $
+ * $Id: parse_clause.h,v 1.19 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +17,8 @@
 #include "parser/parse_node.h"
 
 extern void makeRangeTable(ParseState *pstate, List *frmList);
-extern void setTargetTable(ParseState *pstate, char *relname, bool inh);
+extern void setTargetTable(ParseState *pstate, char *relname,
+                          bool inh, bool inJoinSet);
 extern Node *transformWhereClause(ParseState *pstate, Node *where);
 extern List *transformGroupClause(ParseState *pstate, List *grouplist,
                     List *targetlist);
index be96652cfb21810e4a89ff3b6d6f9261922dc39d..d221c600c8ee555897cfcf4d12338ca9ea2aa264 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_func.h,v 1.26 2000/08/20 00:44:17 tgl Exp $
+ * $Id: parse_func.h,v 1.27 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,11 +39,11 @@ typedef struct _CandidateList
 }         *CandidateList;
 
 extern Node *ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr,
-                       int *curr_resno, int precedence);
+                                    int precedence);
 extern Node *ParseFuncOrColumn(ParseState *pstate,
-                 char *funcname, List *fargs,
-                 bool agg_star, bool agg_distinct,
-                 int *curr_resno, int precedence);
+                              char *funcname, List *fargs,
+                              bool agg_star, bool agg_distinct,
+                              int precedence);
 
 extern bool func_get_detail(char *funcname, int nargs, Oid *argtypes,
                            Oid *funcid, Oid *rettype,
index d4231e8819d5eceed9344e360e4f67c9de147e8b..002391d65308e541beb2187cdb7bdc004c89bf08 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_node.h,v 1.20 2000/05/12 01:33:52 tgl Exp $
+ * $Id: parse_node.h,v 1.21 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "nodes/parsenodes.h"
 #include "utils/rel.h"
 
-/* State information used during parse analysis
- * p_join_quals is a list of untransformed qualification expressions
- * (implicitly ANDed together) found in the FROM clause.
- * Needs to be available later to merge with other qualifiers from the
- * WHERE clause.
+/*
+ * State information used during parse analysis
  */
 typedef struct ParseState
 {
-   int         p_last_resno;
-   List       *p_rtable;
-   struct ParseState *parentParseState;
+   struct ParseState *parentParseState; /* stack link */
+   List       *p_rtable;       /* range table so far */
+   List       *p_jointree;     /* join tree so far */
+   int         p_last_resno;   /* last targetlist resno assigned */
    bool        p_hasAggs;
    bool        p_hasSubLinks;
    bool        p_is_insert;
    bool        p_is_update;
-   bool        p_is_rule;
-   bool        p_in_where_clause;
    Relation    p_target_relation;
    RangeTblEntry *p_target_rangetblentry;
-   List       *p_shape;
-   List       *p_alias;
-   List       *p_join_quals;
 } ParseState;
 
 extern ParseState *make_parsestate(ParseState *parentParseState);
 extern Expr *make_op(char *opname, Node *ltree, Node *rtree);
 extern Node *make_operand(char *opname, Node *tree,
             Oid orig_typeId, Oid target_typeId);
-extern Var *make_var(ParseState *pstate, Oid relid, char *refname,
-        char *attrname);
+extern Var *make_var(ParseState *pstate, RangeTblEntry *rte, int attrno);
 extern ArrayRef *transformArraySubscripts(ParseState *pstate,
                         Node *arrayBase,
                         List *indirection,
index 4f89bcc65c344ca7e28c09e42457346e672c483d..7c7a04844e4226cffba7f21965cc389ee796c348 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_relation.h,v 1.18 2000/06/08 22:37:53 momjian Exp $
+ * $Id: parse_relation.h,v 1.19 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "parser/parse_node.h"
 
-extern RangeTblEntry *refnameRangeTableEntry(ParseState *pstate, char *refname);
+extern Node *refnameRangeOrJoinEntry(ParseState *pstate,
+                                    char *refname,
+                                    int *sublevels_up);
+extern RangeTblEntry *refnameRangeTableEntry(ParseState *pstate,
+                                            char *refname);
 extern int refnameRangeTablePosn(ParseState *pstate,
-                     char *refname,
-                     int *sublevels_up);
-extern RangeTblEntry *colnameRangeTableEntry(ParseState *pstate, char *colname);
+                                char *refname,
+                                int *sublevels_up);
+extern int RTERangeTablePosn(ParseState *pstate,
+                            RangeTblEntry *rte,
+                            int *sublevels_up);
+extern JoinExpr *scanJoinTreeForRefname(Node *jtnode, char *refname);
+extern Node *colnameToVar(ParseState *pstate, char *colname);
+extern Node *qualifiedNameToVar(ParseState *pstate, char *refname,
+                               char *colname, bool implicitRTEOK);
 extern RangeTblEntry *addRangeTableEntry(ParseState *pstate,
-                  char *relname,
-                  Attr *ref,
-                  bool inh,
-                  bool inFromCl,
-                  bool inJoinSet);
-extern Attr *expandTable(ParseState *pstate, char *refname, bool getaliases);
-extern List *expandAll(ParseState *pstate, char *relname, Attr *ref,
-         int *this_resno);
+                                        char *relname,
+                                        Attr *alias,
+                                        bool inh,
+                                        bool inFromCl);
+extern void addRTEtoJoinTree(ParseState *pstate, RangeTblEntry *rte);
+extern RangeTblEntry *addImplicitRTE(ParseState *pstate, char *relname);
+extern void expandRTE(ParseState *pstate, RangeTblEntry *rte,
+                     List **colnames, List **colvars);
+extern List *expandRelAttrs(ParseState *pstate, RangeTblEntry *rte);
+extern List *expandJoinAttrs(ParseState *pstate, JoinExpr *join,
+                            int sublevels_up);
 extern int attnameAttNum(Relation rd, char *a);
 extern int specialAttNum(char *a);
 extern Oid attnumTypeId(Relation rd, int attid);
-extern void warnAutoRange(ParseState *pstate, char *refname);
 
 #endif  /* PARSE_RELATION_H */
index 277bc32a504883f50adbd4a2294523f9c57f1aa5..ff727cfd07a6229f26852d09f7ed3125b393deb4 100644 (file)
@@ -8,41 +8,26 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsetree.h,v 1.10 2000/06/12 19:40:51 momjian Exp $
+ * $Id: parsetree.h,v 1.11 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef PARSETREE_H
-#define PARSETREE_H                /* include once only */
+#define PARSETREE_H
 
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
 
 /* ----------------
- *     need pg_list.h for definitions of CAR(), etc. macros
+ *     need pg_list.h for definitions of nth(), etc.
  * ----------------
  */
 
 /* ----------------
  *     range table macros
- *
- * parse tree:
- *     (root targetlist qual)
- *      ^^^^
- * parse root:
- *     (numlevels cmdtype resrel rangetable priority ruleinfo nestdotinfo)
- *                               ^^^^^^^^^^
- * range table:
- *     (rtentry ...)
- * rtentry:
  * ----------------
  */
 
-#define rt_relname(rt_entry) \
-     ((!strcmp(((rt_entry)->ref->relname),"*OLD*") ||\
-       !strcmp(((rt_entry)->ref->relname),"*NEW*")) ? ((rt_entry)->ref->relname) : \
-       ((char *)(rt_entry)->relname))
-
 /*
  *     rt_fetch
  *     rt_store
  *
  */
 #define rt_fetch(rangetable_index, rangetable) \
-   ((RangeTblEntry*)nth((rangetable_index)-1, rangetable))
+   ((RangeTblEntry*) nth((rangetable_index)-1, rangetable))
 
 #define rt_store(rangetable_index, rangetable, rt) \
    set_nth(rangetable, (rangetable_index)-1, rt)
 
 /*
  *     getrelid
- *     getrelname
  *
  *     Given the range index of a relation, return the corresponding
- *     relation id or relation name.
+ *     relation OID.
  */
 #define getrelid(rangeindex,rangetable) \
-   ((RangeTblEntry*)nth((rangeindex)-1, rangetable))->relid
-
-#define getrelname(rangeindex, rangetable) \
-   rt_relname((RangeTblEntry*)nth((rangeindex)-1, rangetable))
+   (rt_fetch(rangeindex, rangetable)->relid)
 
 #endif  /* PARSETREE_H */
index 7af0f3932ec28337b80f5e5d9cc187f5db298e97..5271d78717c6b153ed59c5892087683c109ba79f 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: rewriteHandler.h,v 1.12 2000/01/26 05:58:30 momjian Exp $
+ * $Id: rewriteHandler.h,v 1.13 2000/09/12 21:07:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,9 +16,8 @@
 
 #include "nodes/parsenodes.h"
 
-struct _rewrite_meta_knowledge
+typedef struct RewriteInfo
 {
-   List       *rt;
    int         rt_index;
    bool        instead_flag;
    int         event;
@@ -28,9 +27,7 @@ struct _rewrite_meta_knowledge
    Query      *rule_action;
    Node       *rule_qual;
    bool        nothing;
-};
-
-typedef struct _rewrite_meta_knowledge RewriteInfo;
+} RewriteInfo;
 
 
 extern List *QueryRewrite(Query *parsetree);
index af052a65510849b42cd891e634c420623604fb26..c41519acb8c9b9ce6393f7f8d0c394e3912e14c6 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: rewriteManip.h,v 1.21 2000/04/12 17:16:50 momjian Exp $
+ * $Id: rewriteManip.h,v 1.22 2000/09/12 21:07:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,6 +22,12 @@ extern void ChangeVarNodes(Node *node, int old_varno, int new_varno,
               int sublevels_up);
 extern void IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
                        int min_sublevels_up);
+
+extern bool rangeTableEntry_used(Node *node, int rt_index,
+                                int sublevels_up);
+extern bool attribute_used(Node *node, int rt_index, int attno,
+                          int sublevels_up);
+
 extern void AddQual(Query *parsetree, Node *qual);
 extern void AddHavingQual(Query *parsetree, Node *havingQual);
 extern void AddNotQual(Query *parsetree, Node *qual);
index 0ad8be9eec48f9b6b7c1b6cc73e566f724de88ec..2b53bc699dc8a03b70fed206c45ffbe082b71f8b 100644 (file)
@@ -157,28 +157,28 @@ SELECT COALESCE(a.f, b.i, b.j)
  case  
 -------
   10.1
-  20.2
- -30.3
-     1
   10.1
-  20.2
- -30.3
-     2
   10.1
-  20.2
- -30.3
-     3
   10.1
-  20.2
- -30.3
-     2
   10.1
-  20.2
- -30.3
-     1
   10.1
   20.2
+  20.2
+  20.2
+  20.2
+  20.2
+  20.2
+ -30.3
+ -30.3
+ -30.3
  -30.3
+ -30.3
+ -30.3
+     1
+     2
+     3
+     2
+     1
     -6
 (24 rows)
 
@@ -197,28 +197,28 @@ SELECT '' AS Five, NULLIF(a.i,b.i) AS "NULLIF(a.i,b.i)",
  five | NULLIF(a.i,b.i) | NULLIF(b.i,4) 
 ------+-----------------+---------------
       |                 |             1
-      |               2 |             1
-      |               3 |             1
-      |               4 |             1
       |               1 |             2
-      |                 |             2
-      |               3 |             2
-      |               4 |             2
       |               1 |             3
-      |               2 |             3
-      |                 |             3
-      |               4 |             3
       |               1 |             2
-      |                 |             2
-      |               3 |             2
-      |               4 |             2
       |                 |             1
-      |               2 |             1
-      |               3 |             1
-      |               4 |             1
       |               1 |              
+      |               2 |             1
+      |                 |             2
+      |               2 |             3
+      |                 |             2
+      |               2 |             1
       |               2 |              
+      |               3 |             1
+      |               3 |             2
+      |                 |             3
+      |               3 |             2
+      |               3 |             1
       |               3 |              
+      |               4 |             1
+      |               4 |             2
+      |               4 |             3
+      |               4 |             2
+      |               4 |             1
       |               4 |              
 (24 rows)
 
index 4e0651e46cd66a0a50f77294f25bac162f1a249a..67ab299c39eab7973a8258bb50ed0f8831b0d822 100644 (file)
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
index c2e689dea96a30b8af897908b948b2236f6e6d93..6ad08de4154ea60b09ffab9d0691f7e5116e2bae 100644 (file)
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
index 169709e37bc03e9aa2432f587036f6392be806ed..78940c084687df17c802cbed507ff31bfb9c6175 100644 (file)
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
index 113733d9420ebbf76bd12118166a33ae9c127545..914d9e775282c8b62820e1d3c499162ddd8ebd15 100644 (file)
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
index 07017606bad897d05d30a48092064661ca38e725..f7ab9e6509692280ffe39dd8254ec02d99028830 100644 (file)
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
index 1b0714b69522fa6888d6ca1daebee53ad2c2df30..3c2e4557ffb3f14e543da5978761702cad3dccde 100644 (file)
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
index 4e0651e46cd66a0a50f77294f25bac162f1a249a..67ab299c39eab7973a8258bb50ed0f8831b0d822 100644 (file)
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
index 9dfe6081eb167cbfd688725a79c24b522242c718..dcefbf78f54a0b5c2c90a9e59d85aba2fd82a38c 100644 (file)
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
index 2b4f1b655aa2f4fa0ccd6e6842ea9a7e48b015e2..3c156ee46c14d7e577eedcf04c6df9aff18d7edc 100644 (file)
@@ -1,6 +1,6 @@
 --
 -- JOIN
--- Test join clauses
+-- Test JOIN clauses
 --
 CREATE TABLE J1_TBL (
   i integer,
@@ -28,6 +28,7 @@ INSERT INTO J2_TBL VALUES (1, -1);
 INSERT INTO J2_TBL VALUES (2, 2);
 INSERT INTO J2_TBL VALUES (3, -3);
 INSERT INTO J2_TBL VALUES (2, 4);
+INSERT INTO J2_TBL VALUES (5, -5);
 --
 -- CORRELATION NAMES
 -- Make sure that table/column aliases are supported
@@ -78,22 +79,26 @@ SELECT '' AS "xxx", *
  xxx | a | b |   c   | d | e  
 -----+---+---+-------+---+----
      | 1 | 3 | one   | 1 | -1
-     | 2 | 2 | two   | 1 | -1
-     | 3 | 1 | three | 1 | -1
-     | 4 | 0 | four  | 1 | -1
      | 1 | 3 | one   | 2 |  2
-     | 2 | 2 | two   | 2 |  2
-     | 3 | 1 | three | 2 |  2
-     | 4 | 0 | four  | 2 |  2
      | 1 | 3 | one   | 3 | -3
-     | 2 | 2 | two   | 3 | -3
-     | 3 | 1 | three | 3 | -3
-     | 4 | 0 | four  | 3 | -3
      | 1 | 3 | one   | 2 |  4
+     | 1 | 3 | one   | 5 | -5
+     | 2 | 2 | two   | 1 | -1
+     | 2 | 2 | two   | 2 |  2
+     | 2 | 2 | two   | 3 | -3
      | 2 | 2 | two   | 2 |  4
+     | 2 | 2 | two   | 5 | -5
+     | 3 | 1 | three | 1 | -1
+     | 3 | 1 | three | 2 |  2
+     | 3 | 1 | three | 3 | -3
      | 3 | 1 | three | 2 |  4
+     | 3 | 1 | three | 5 | -5
+     | 4 | 0 | four  | 1 | -1
+     | 4 | 0 | four  | 2 |  2
+     | 4 | 0 | four  | 3 | -3
      | 4 | 0 | four  | 2 |  4
-(16 rows)
+     | 4 | 0 | four  | 5 | -5
+(20 rows)
 
 SELECT '' AS "xxx", t1.a, t2.e
   FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e)
@@ -116,58 +121,218 @@ SELECT '' AS "xxx", *
  xxx | i | j |   t   | i | k  
 -----+---+---+-------+---+----
      | 1 | 3 | one   | 1 | -1
-     | 2 | 2 | two   | 1 | -1
-     | 3 | 1 | three | 1 | -1
-     | 4 | 0 | four  | 1 | -1
      | 1 | 3 | one   | 2 |  2
-     | 2 | 2 | two   | 2 |  2
-     | 3 | 1 | three | 2 |  2
-     | 4 | 0 | four  | 2 |  2
      | 1 | 3 | one   | 3 | -3
-     | 2 | 2 | two   | 3 | -3
-     | 3 | 1 | three | 3 | -3
-     | 4 | 0 | four  | 3 | -3
      | 1 | 3 | one   | 2 |  4
+     | 1 | 3 | one   | 5 | -5
+     | 2 | 2 | two   | 1 | -1
+     | 2 | 2 | two   | 2 |  2
+     | 2 | 2 | two   | 3 | -3
      | 2 | 2 | two   | 2 |  4
+     | 2 | 2 | two   | 5 | -5
+     | 3 | 1 | three | 1 | -1
+     | 3 | 1 | three | 2 |  2
+     | 3 | 1 | three | 3 | -3
      | 3 | 1 | three | 2 |  4
+     | 3 | 1 | three | 5 | -5
+     | 4 | 0 | four  | 1 | -1
+     | 4 | 0 | four  | 2 |  2
+     | 4 | 0 | four  | 3 | -3
      | 4 | 0 | four  | 2 |  4
-(16 rows)
+     | 4 | 0 | four  | 5 | -5
+(20 rows)
 
 -- ambiguous column
 SELECT '' AS "xxx", i, k, t
   FROM J1_TBL CROSS JOIN J2_TBL;
-ERROR:  Column 'i' is ambiguous
+ERROR:  Column reference "i" is ambiguous
 -- resolve previous ambiguity by specifying the table name
 SELECT '' AS "xxx", t1.i, k, t
   FROM J1_TBL t1 CROSS JOIN J2_TBL t2;
  xxx | i | k  |   t   
 -----+---+----+-------
      | 1 | -1 | one
-     | 2 | -1 | two
-     | 3 | -1 | three
-     | 4 | -1 | four
      | 1 |  2 | one
-     | 2 |  2 | two
-     | 3 |  2 | three
-     | 4 |  2 | four
      | 1 | -3 | one
-     | 2 | -3 | two
-     | 3 | -3 | three
-     | 4 | -3 | four
      | 1 |  4 | one
+     | 1 | -5 | one
+     | 2 | -1 | two
+     | 2 |  2 | two
+     | 2 | -3 | two
      | 2 |  4 | two
+     | 2 | -5 | two
+     | 3 | -1 | three
+     | 3 |  2 | three
+     | 3 | -3 | three
      | 3 |  4 | three
+     | 3 | -5 | three
+     | 4 | -1 | four
+     | 4 |  2 | four
+     | 4 | -3 | four
      | 4 |  4 | four
-(16 rows)
+     | 4 | -5 | four
+(20 rows)
 
 SELECT '' AS "xxx", ii, tt, kk
   FROM (J1_TBL CROSS JOIN J2_TBL)
     AS tx (ii, jj, tt, ii2, kk);
-ERROR:  JOIN table aliases are not supported
+ xxx | ii |  tt   | kk 
+-----+----+-------+----
+     |  1 | one   | -1
+     |  1 | one   |  2
+     |  1 | one   | -3
+     |  1 | one   |  4
+     |  1 | one   | -5
+     |  2 | two   | -1
+     |  2 | two   |  2
+     |  2 | two   | -3
+     |  2 | two   |  4
+     |  2 | two   | -5
+     |  3 | three | -1
+     |  3 | three |  2
+     |  3 | three | -3
+     |  3 | three |  4
+     |  3 | three | -5
+     |  4 | four  | -1
+     |  4 | four  |  2
+     |  4 | four  | -3
+     |  4 | four  |  4
+     |  4 | four  | -5
+(20 rows)
+
 SELECT '' AS "xxx", tx.ii, tx.jj, tx.kk
   FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e))
     AS tx (ii, jj, tt, ii2, kk);
-ERROR:  JOIN table aliases are not supported
+ xxx | ii | jj | kk 
+-----+----+----+----
+     |  1 |  3 | -1
+     |  1 |  3 |  2
+     |  1 |  3 | -3
+     |  1 |  3 |  4
+     |  1 |  3 | -5
+     |  2 |  2 | -1
+     |  2 |  2 |  2
+     |  2 |  2 | -3
+     |  2 |  2 |  4
+     |  2 |  2 | -5
+     |  3 |  1 | -1
+     |  3 |  1 |  2
+     |  3 |  1 | -3
+     |  3 |  1 |  4
+     |  3 |  1 | -5
+     |  4 |  0 | -1
+     |  4 |  0 |  2
+     |  4 |  0 | -3
+     |  4 |  0 |  4
+     |  4 |  0 | -5
+(20 rows)
+
+SELECT '' AS "xxx", *
+  FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b;
+ xxx | i | j |   t   | i | k  | i | k  
+-----+---+---+-------+---+----+---+----
+     | 1 | 3 | one   | 1 | -1 | 1 | -1
+     | 1 | 3 | one   | 1 | -1 | 2 |  2
+     | 1 | 3 | one   | 1 | -1 | 3 | -3
+     | 1 | 3 | one   | 1 | -1 | 2 |  4
+     | 1 | 3 | one   | 1 | -1 | 5 | -5
+     | 1 | 3 | one   | 2 |  2 | 1 | -1
+     | 1 | 3 | one   | 2 |  2 | 2 |  2
+     | 1 | 3 | one   | 2 |  2 | 3 | -3
+     | 1 | 3 | one   | 2 |  2 | 2 |  4
+     | 1 | 3 | one   | 2 |  2 | 5 | -5
+     | 1 | 3 | one   | 3 | -3 | 1 | -1
+     | 1 | 3 | one   | 3 | -3 | 2 |  2
+     | 1 | 3 | one   | 3 | -3 | 3 | -3
+     | 1 | 3 | one   | 3 | -3 | 2 |  4
+     | 1 | 3 | one   | 3 | -3 | 5 | -5
+     | 1 | 3 | one   | 2 |  4 | 1 | -1
+     | 1 | 3 | one   | 2 |  4 | 2 |  2
+     | 1 | 3 | one   | 2 |  4 | 3 | -3
+     | 1 | 3 | one   | 2 |  4 | 2 |  4
+     | 1 | 3 | one   | 2 |  4 | 5 | -5
+     | 1 | 3 | one   | 5 | -5 | 1 | -1
+     | 1 | 3 | one   | 5 | -5 | 2 |  2
+     | 1 | 3 | one   | 5 | -5 | 3 | -3
+     | 1 | 3 | one   | 5 | -5 | 2 |  4
+     | 1 | 3 | one   | 5 | -5 | 5 | -5
+     | 2 | 2 | two   | 1 | -1 | 1 | -1
+     | 2 | 2 | two   | 1 | -1 | 2 |  2
+     | 2 | 2 | two   | 1 | -1 | 3 | -3
+     | 2 | 2 | two   | 1 | -1 | 2 |  4
+     | 2 | 2 | two   | 1 | -1 | 5 | -5
+     | 2 | 2 | two   | 2 |  2 | 1 | -1
+     | 2 | 2 | two   | 2 |  2 | 2 |  2
+     | 2 | 2 | two   | 2 |  2 | 3 | -3
+     | 2 | 2 | two   | 2 |  2 | 2 |  4
+     | 2 | 2 | two   | 2 |  2 | 5 | -5
+     | 2 | 2 | two   | 3 | -3 | 1 | -1
+     | 2 | 2 | two   | 3 | -3 | 2 |  2
+     | 2 | 2 | two   | 3 | -3 | 3 | -3
+     | 2 | 2 | two   | 3 | -3 | 2 |  4
+     | 2 | 2 | two   | 3 | -3 | 5 | -5
+     | 2 | 2 | two   | 2 |  4 | 1 | -1
+     | 2 | 2 | two   | 2 |  4 | 2 |  2
+     | 2 | 2 | two   | 2 |  4 | 3 | -3
+     | 2 | 2 | two   | 2 |  4 | 2 |  4
+     | 2 | 2 | two   | 2 |  4 | 5 | -5
+     | 2 | 2 | two   | 5 | -5 | 1 | -1
+     | 2 | 2 | two   | 5 | -5 | 2 |  2
+     | 2 | 2 | two   | 5 | -5 | 3 | -3
+     | 2 | 2 | two   | 5 | -5 | 2 |  4
+     | 2 | 2 | two   | 5 | -5 | 5 | -5
+     | 3 | 1 | three | 1 | -1 | 1 | -1
+     | 3 | 1 | three | 1 | -1 | 2 |  2
+     | 3 | 1 | three | 1 | -1 | 3 | -3
+     | 3 | 1 | three | 1 | -1 | 2 |  4
+     | 3 | 1 | three | 1 | -1 | 5 | -5
+     | 3 | 1 | three | 2 |  2 | 1 | -1
+     | 3 | 1 | three | 2 |  2 | 2 |  2
+     | 3 | 1 | three | 2 |  2 | 3 | -3
+     | 3 | 1 | three | 2 |  2 | 2 |  4
+     | 3 | 1 | three | 2 |  2 | 5 | -5
+     | 3 | 1 | three | 3 | -3 | 1 | -1
+     | 3 | 1 | three | 3 | -3 | 2 |  2
+     | 3 | 1 | three | 3 | -3 | 3 | -3
+     | 3 | 1 | three | 3 | -3 | 2 |  4
+     | 3 | 1 | three | 3 | -3 | 5 | -5
+     | 3 | 1 | three | 2 |  4 | 1 | -1
+     | 3 | 1 | three | 2 |  4 | 2 |  2
+     | 3 | 1 | three | 2 |  4 | 3 | -3
+     | 3 | 1 | three | 2 |  4 | 2 |  4
+     | 3 | 1 | three | 2 |  4 | 5 | -5
+     | 3 | 1 | three | 5 | -5 | 1 | -1
+     | 3 | 1 | three | 5 | -5 | 2 |  2
+     | 3 | 1 | three | 5 | -5 | 3 | -3
+     | 3 | 1 | three | 5 | -5 | 2 |  4
+     | 3 | 1 | three | 5 | -5 | 5 | -5
+     | 4 | 0 | four  | 1 | -1 | 1 | -1
+     | 4 | 0 | four  | 1 | -1 | 2 |  2
+     | 4 | 0 | four  | 1 | -1 | 3 | -3
+     | 4 | 0 | four  | 1 | -1 | 2 |  4
+     | 4 | 0 | four  | 1 | -1 | 5 | -5
+     | 4 | 0 | four  | 2 |  2 | 1 | -1
+     | 4 | 0 | four  | 2 |  2 | 2 |  2
+     | 4 | 0 | four  | 2 |  2 | 3 | -3
+     | 4 | 0 | four  | 2 |  2 | 2 |  4
+     | 4 | 0 | four  | 2 |  2 | 5 | -5
+     | 4 | 0 | four  | 3 | -3 | 1 | -1
+     | 4 | 0 | four  | 3 | -3 | 2 |  2
+     | 4 | 0 | four  | 3 | -3 | 3 | -3
+     | 4 | 0 | four  | 3 | -3 | 2 |  4
+     | 4 | 0 | four  | 3 | -3 | 5 | -5
+     | 4 | 0 | four  | 2 |  4 | 1 | -1
+     | 4 | 0 | four  | 2 |  4 | 2 |  2
+     | 4 | 0 | four  | 2 |  4 | 3 | -3
+     | 4 | 0 | four  | 2 |  4 | 2 |  4
+     | 4 | 0 | four  | 2 |  4 | 5 | -5
+     | 4 | 0 | four  | 5 | -5 | 1 | -1
+     | 4 | 0 | four  | 5 | -5 | 2 |  2
+     | 4 | 0 | four  | 5 | -5 | 3 | -3
+     | 4 | 0 | four  | 5 | -5 | 2 |  4
+     | 4 | 0 | four  | 5 | -5 | 5 | -5
+(100 rows)
+
 --
 --
 -- Inner joins (equi-joins)
@@ -249,14 +414,6 @@ SELECT '' AS "xxx", *
      | 4 | 0 | four | 2
 (2 rows)
 
-SELECT '' AS "xxx", *
-  FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
- xxx | a | b |  c   | d 
------+---+---+------+---
-     | 2 | 2 | two  | 2
-     | 4 | 0 | four | 2
-(2 rows)
-
 -- mismatch number of columns
 -- currently, Postgres will fill in with underlying names
 SELECT '' AS "xxx", *
@@ -290,28 +447,6 @@ SELECT '' AS "xxx", *
      | 4 | 0 | four | 2 | 4
 (2 rows)
 
-SELECT '' AS "xxx", *
-  FROM J1_TBL CROSS JOIN J2_TBL;
- xxx | i | j |   t   | i | k  
------+---+---+-------+---+----
-     | 1 | 3 | one   | 1 | -1
-     | 2 | 2 | two   | 1 | -1
-     | 3 | 1 | three | 1 | -1
-     | 4 | 0 | four  | 1 | -1
-     | 1 | 3 | one   | 2 |  2
-     | 2 | 2 | two   | 2 |  2
-     | 3 | 1 | three | 2 |  2
-     | 4 | 0 | four  | 2 |  2
-     | 1 | 3 | one   | 3 | -3
-     | 2 | 2 | two   | 3 | -3
-     | 3 | 1 | three | 3 | -3
-     | 4 | 0 | four  | 3 | -3
-     | 1 | 3 | one   | 2 |  4
-     | 2 | 2 | two   | 2 |  4
-     | 3 | 1 | three | 2 |  4
-     | 4 | 0 | four  | 2 |  4
-(16 rows)
-
 --
 -- Non-equi-joins
 --
@@ -320,8 +455,8 @@ SELECT '' AS "xxx", *
  xxx | i | j |   t   | i | k 
 -----+---+---+-------+---+---
      | 1 | 3 | one   | 2 | 2
-     | 2 | 2 | two   | 2 | 2
      | 1 | 3 | one   | 2 | 4
+     | 2 | 2 | two   | 2 | 2
      | 2 | 2 | two   | 2 | 4
      | 3 | 1 | three | 2 | 4
      | 4 | 0 | four  | 2 | 4
@@ -330,21 +465,48 @@ SELECT '' AS "xxx", *
 --
 -- Outer joins
 --
-SELECT '' AS "xxx", *
-  FROM J1_TBL OUTER JOIN J2_TBL USING (i);
-ERROR:  OUTER JOIN is not yet supported
 SELECT '' AS "xxx", *
   FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i);
-ERROR:  OUTER JOIN is not yet supported
+ xxx | i | j |   t   | k  
+-----+---+---+-------+----
+     | 1 | 3 | one   | -1
+     | 2 | 2 | two   |  2
+     | 2 | 2 | two   |  4
+     | 3 | 1 | three | -3
+     | 4 | 0 | four  |   
+(5 rows)
+
 SELECT '' AS "xxx", *
   FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i);
-ERROR:  OUTER JOIN is not yet supported
+ xxx | i | j |   t   | k  
+-----+---+---+-------+----
+     | 1 | 3 | one   | -1
+     | 2 | 2 | two   |  2
+     | 2 | 2 | two   |  4
+     | 3 | 1 | three | -3
+     | 5 |   |       | -5
+(5 rows)
+
+-- Note that OUTER is a noise word
 SELECT '' AS "xxx", *
-  FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i);
-ERROR:  OUTER JOIN is not yet supported
+  FROM J1_TBL FULL JOIN J2_TBL USING (i);
+ xxx | i | j |   t   | k  
+-----+---+---+-------+----
+     | 1 | 3 | one   | -1
+     | 2 | 2 | two   |  2
+     | 2 | 2 | two   |  4
+     | 3 | 1 | three | -3
+     | 4 | 0 | four  |   
+     | 5 |   |       | -5
+(6 rows)
+
 --
 -- More complicated constructs
 --
+-- UNION JOIN isn't implemented yet
+SELECT '' AS "xxx", *
+  FROM J1_TBL UNION JOIN J2_TBL;
+ERROR:  UNION JOIN is not implemented yet
 --
 -- Clean up
 --
index 9f347bce7971dec79843370953efff113aba5fb7..3eab1c5b5d523efb2d9df3585935179de9a202e3 100644 (file)
@@ -154,36 +154,36 @@ SELECT '' AS thirty, p1.f1 AS point1, p2.f1 AS point2
    WHERE (p1.f1 <-> p2.f1) > 3;
  thirty |   point1   |   point2   
 --------+------------+------------
-        | (-10,0)    | (0,0)
-        | (-3,4)     | (0,0)
-        | (5.1,34.5) | (0,0)
-        | (-5,-12)   | (0,0)
-        | (10,10)    | (0,0)
         | (0,0)      | (-10,0)
-        | (-3,4)     | (-10,0)
-        | (5.1,34.5) | (-10,0)
-        | (-5,-12)   | (-10,0)
-        | (10,10)    | (-10,0)
         | (0,0)      | (-3,4)
-        | (-10,0)    | (-3,4)
-        | (5.1,34.5) | (-3,4)
-        | (-5,-12)   | (-3,4)
-        | (10,10)    | (-3,4)
         | (0,0)      | (5.1,34.5)
-        | (-10,0)    | (5.1,34.5)
-        | (-3,4)     | (5.1,34.5)
-        | (-5,-12)   | (5.1,34.5)
-        | (10,10)    | (5.1,34.5)
         | (0,0)      | (-5,-12)
-        | (-10,0)    | (-5,-12)
-        | (-3,4)     | (-5,-12)
-        | (5.1,34.5) | (-5,-12)
-        | (10,10)    | (-5,-12)
         | (0,0)      | (10,10)
+        | (-10,0)    | (0,0)
+        | (-10,0)    | (-3,4)
+        | (-10,0)    | (5.1,34.5)
+        | (-10,0)    | (-5,-12)
         | (-10,0)    | (10,10)
+        | (-3,4)     | (0,0)
+        | (-3,4)     | (-10,0)
+        | (-3,4)     | (5.1,34.5)
+        | (-3,4)     | (-5,-12)
         | (-3,4)     | (10,10)
+        | (5.1,34.5) | (0,0)
+        | (5.1,34.5) | (-10,0)
+        | (5.1,34.5) | (-3,4)
+        | (5.1,34.5) | (-5,-12)
         | (5.1,34.5) | (10,10)
+        | (-5,-12)   | (0,0)
+        | (-5,-12)   | (-10,0)
+        | (-5,-12)   | (-3,4)
+        | (-5,-12)   | (5.1,34.5)
         | (-5,-12)   | (10,10)
+        | (10,10)    | (0,0)
+        | (10,10)    | (-10,0)
+        | (10,10)    | (-3,4)
+        | (10,10)    | (5.1,34.5)
+        | (10,10)    | (-5,-12)
 (30 rows)
 
 -- put distance result into output to allow sorting with GEQ optimizer - tgl 97/05/10
index 34059b8a6e7ab0bcfc60b2727b7cf5bbf9ad9008..eb19122cbcea0ea1cabc2c24a2a616e137fa7835 100644 (file)
@@ -1158,6 +1158,47 @@ SELECT count(*) FROM shoe;
      4
 (1 row)
 
+--
+-- Simple test of qualified ON INSERT ... this did not work in 7.0 ...
+--
+create table foo (f1 int);
+create table foo2 (f1 int);
+create rule foorule as on insert to foo where f1 < 100
+do instead nothing;
+insert into foo values(1);
+insert into foo values(1001);
+select * from foo;
+  f1  
+------
+ 1001
+(1 row)
+
+drop rule foorule;
+-- this should fail because f1 is not exposed for unqualified reference:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (f1);
+ERROR:  Attribute 'f1' not found
+-- this is the correct way:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (new.f1);
+insert into foo values(2);
+insert into foo values(100);
+select * from foo;
+  f1  
+------
+ 1001
+  100
+(2 rows)
+
+select * from foo2;
+ f1 
+----
+  2
+(1 row)
+
+drop rule foorule;
+drop table foo;
+drop table foo2;
 --
 -- Check that ruleutils are working
 --
@@ -1200,7 +1241,7 @@ SELECT tablename, rulename, definition FROM pg_rules
  rtest_order1  | rtest_order_r1  | CREATE RULE rtest_order_r1 AS ON INSERT TO rtest_order1 DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 1 - this should run 3rd or 4th'::text);
  rtest_order1  | rtest_order_r2  | CREATE RULE rtest_order_r2 AS ON INSERT TO rtest_order1 DO INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 2 - this should run 1st'::text);
  rtest_order1  | rtest_order_r3  | CREATE RULE rtest_order_r3 AS ON INSERT TO rtest_order1 DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 3 - this should run 3rd or 4th'::text);
- rtest_order1  | rtest_order_r4  | CREATE RULE rtest_order_r4 AS ON INSERT TO rtest_order1 WHERE (rtest_order2.a < 100) DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 4 - this should run 2nd'::text);
+ rtest_order1  | rtest_order_r4  | CREATE RULE rtest_order_r4 AS ON INSERT TO rtest_order1 WHERE (new.a < 100) DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 4 - this should run 2nd'::text);
  rtest_person  | rtest_pers_del  | CREATE RULE rtest_pers_del AS ON DELETE TO rtest_person DO DELETE FROM rtest_admin WHERE (rtest_admin.pname = old.pname);
  rtest_person  | rtest_pers_upd  | CREATE RULE rtest_pers_upd AS ON UPDATE TO rtest_person DO UPDATE rtest_admin SET pname = new.pname WHERE (rtest_admin.pname = old.pname);
  rtest_system  | rtest_sys_del   | CREATE RULE rtest_sys_del AS ON DELETE TO rtest_system DO (DELETE FROM rtest_interface WHERE (rtest_interface.sysname = old.sysname); DELETE FROM rtest_admin WHERE (rtest_admin.sysname = old.sysname); );
index adf0f794774857848b78cbcac4969dcd5857807c..e3d74e5daba3f7227348819f8e275602f6fb7ec9 100644 (file)
@@ -120,7 +120,7 @@ ERROR:  GROUP BY position 3 is not in target list
 SELECT count(*) FROM test_missing_target x, test_missing_target y 
    WHERE x.a = y.a
    GROUP BY b ORDER BY b;
-ERROR:  Column 'b' is ambiguous
+ERROR:  Column reference "b" is ambiguous
 --   order w/ target under ambiguous condition
 --   failure NOT expected
 SELECT a, a FROM test_missing_target
@@ -282,7 +282,7 @@ SELECT count(b) FROM test_missing_target
 SELECT count(x.a) FROM test_missing_target x, test_missing_target y 
    WHERE x.a = y.a
    GROUP BY b/2 ORDER BY b/2;
-ERROR:  Column 'b' is ambiguous
+ERROR:  Column reference "b" is ambiguous
 --   group w/ existing GROUP BY target under ambiguous condition
 SELECT x.b/2, count(x.b) FROM test_missing_target x, test_missing_target y 
    WHERE x.a = y.a
@@ -299,7 +299,7 @@ SELECT x.b/2, count(x.b) FROM test_missing_target x, test_missing_target y
 SELECT count(b) FROM test_missing_target x, test_missing_target y 
    WHERE x.a = y.a
    GROUP BY x.b/2;
-ERROR:  Column 'b' is ambiguous
+ERROR:  Column reference "b" is ambiguous
 --   group w/o existing GROUP BY target under ambiguous condition
 --   into a table
 SELECT count(x.b) INTO TABLE test_missing_target3 
index c63bd0596fb8d3767b5c1bcc074f2ae4690456a5..88972de5ea90d179eb4c272614a97b8d3cbefec8 100644 (file)
@@ -1,6 +1,6 @@
 --
 -- JOIN
--- Test join clauses
+-- Test JOIN clauses
 --
 
 CREATE TABLE J1_TBL (
@@ -34,6 +34,7 @@ INSERT INTO J2_TBL VALUES (1, -1);
 INSERT INTO J2_TBL VALUES (2, 2);
 INSERT INTO J2_TBL VALUES (3, -3);
 INSERT INTO J2_TBL VALUES (2, 4);
+INSERT INTO J2_TBL VALUES (5, -5);
 
 --
 -- CORRELATION NAMES
@@ -86,6 +87,9 @@ SELECT '' AS "xxx", tx.ii, tx.jj, tx.kk
   FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e))
     AS tx (ii, jj, tt, ii2, kk);
 
+SELECT '' AS "xxx", *
+  FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b;
+
 
 --
 --
@@ -128,9 +132,6 @@ SELECT '' AS "xxx", *
 SELECT '' AS "xxx", *
   FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
 
-SELECT '' AS "xxx", *
-  FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
-
 -- mismatch number of columns
 -- currently, Postgres will fill in with underlying names
 SELECT '' AS "xxx", *
@@ -147,9 +148,6 @@ SELECT '' AS "xxx", *
 SELECT '' AS "xxx", *
   FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k);
 
-SELECT '' AS "xxx", *
-  FROM J1_TBL CROSS JOIN J2_TBL;
-
 
 --
 -- Non-equi-joins
@@ -163,23 +161,25 @@ SELECT '' AS "xxx", *
 -- Outer joins
 --
 
-SELECT '' AS "xxx", *
-  FROM J1_TBL OUTER JOIN J2_TBL USING (i);
-
 SELECT '' AS "xxx", *
   FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i);
 
 SELECT '' AS "xxx", *
   FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i);
 
+-- Note that OUTER is a noise word
 SELECT '' AS "xxx", *
-  FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i);
+  FROM J1_TBL FULL JOIN J2_TBL USING (i);
 
 
 --
 -- More complicated constructs
 --
 
+-- UNION JOIN isn't implemented yet
+SELECT '' AS "xxx", *
+  FROM J1_TBL UNION JOIN J2_TBL;
+
 --
 -- Clean up
 --
index 2b7aa33a8cf2d8dcf658c5758b1f96f3649b9602..2c99f2a3ccbe6bfd7427b59a82141562f239ce1d 100644 (file)
@@ -686,6 +686,39 @@ SELECT * FROM shoe ORDER BY shoename;
 SELECT count(*) FROM shoe;
 
 
+--
+-- Simple test of qualified ON INSERT ... this did not work in 7.0 ...
+--
+create table foo (f1 int);
+create table foo2 (f1 int);
+
+create rule foorule as on insert to foo where f1 < 100
+do instead nothing;
+
+insert into foo values(1);
+insert into foo values(1001);
+select * from foo;
+
+drop rule foorule;
+
+-- this should fail because f1 is not exposed for unqualified reference:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (f1);
+-- this is the correct way:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (new.f1);
+
+insert into foo values(2);
+insert into foo values(100);
+
+select * from foo;
+select * from foo2;
+
+drop rule foorule;
+drop table foo;
+drop table foo2;
+
+
 --
 -- Check that ruleutils are working
 --