From ed5003c58401e5727fcdd970505972394c95febb Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Tue, 12 Sep 2000 21:07:18 +0000 Subject: [PATCH] First cut at full support for OUTER JOINs. There are still a few loose ends to clean up (see my message of same date to pghackers), but mostly it works. INITDB REQUIRED! --- src/backend/catalog/heap.c | 18 +- src/backend/commands/command.c | 610 +++++----- src/backend/commands/creatinh.c | 14 +- src/backend/commands/explain.c | 14 +- src/backend/commands/view.c | 43 +- src/backend/executor/execMain.c | 27 +- src/backend/executor/execTuples.c | 73 +- src/backend/executor/execUtils.c | 46 +- src/backend/executor/nodeHashjoin.c | 144 ++- src/backend/executor/nodeMergejoin.c | 1010 +++++++++++------ src/backend/executor/nodeNestloop.c | 194 ++-- src/backend/nodes/copyfuncs.c | 114 +- src/backend/nodes/equalfuncs.c | 123 +- src/backend/nodes/list.c | 21 +- src/backend/nodes/outfuncs.c | 163 ++- src/backend/nodes/print.c | 11 +- src/backend/nodes/readfuncs.c | 148 ++- src/backend/optimizer/README | 28 +- src/backend/optimizer/geqo/geqo_eval.c | 26 +- src/backend/optimizer/path/allpaths.c | 101 +- src/backend/optimizer/path/indxpath.c | 19 +- src/backend/optimizer/path/joinpath.c | 235 +++- src/backend/optimizer/path/joinrels.c | 324 ++++-- src/backend/optimizer/path/orindxpath.c | 3 +- src/backend/optimizer/path/pathkeys.c | 41 +- src/backend/optimizer/plan/createplan.c | 279 +++-- src/backend/optimizer/plan/initsplan.c | 317 +++++- src/backend/optimizer/plan/planmain.c | 51 +- src/backend/optimizer/plan/planner.c | 60 +- src/backend/optimizer/plan/setrefs.c | 17 +- src/backend/optimizer/plan/subselect.c | 12 +- src/backend/optimizer/prep/prepkeyset.c | 1 + src/backend/optimizer/prep/prepunion.c | 46 +- src/backend/optimizer/util/clauses.c | 143 ++- src/backend/optimizer/util/pathnode.c | 16 +- src/backend/optimizer/util/relnode.c | 7 +- src/backend/optimizer/util/restrictinfo.c | 28 +- src/backend/optimizer/util/var.c | 81 +- src/backend/parser/Makefile | 4 +- src/backend/parser/analyze.c | 242 ++-- src/backend/parser/gram.y | 383 +++---- src/backend/parser/parse_agg.c | 7 +- src/backend/parser/parse_clause.c | 993 ++++++++-------- src/backend/parser/parse_expr.c | 61 +- src/backend/parser/parse_func.c | 117 +- src/backend/parser/parse_node.c | 59 +- src/backend/parser/parse_relation.c | 637 ++++++++--- src/backend/parser/parse_target.c | 142 ++- src/backend/parser/parser.c | 66 +- src/backend/parser/scan.l | 14 +- src/backend/rewrite/locks.c | 76 +- src/backend/rewrite/rewriteHandler.c | 432 +++---- src/backend/rewrite/rewriteManip.c | 353 ++++-- src/backend/utils/adt/ruleutils.c | 409 ++++--- src/include/catalog/catversion.h | 4 +- src/include/executor/execdebug.h | 26 +- src/include/executor/execdefs.h | 14 +- src/include/executor/executor.h | 6 +- src/include/nodes/execnodes.h | 29 +- src/include/nodes/nodes.h | 48 +- src/include/nodes/parsenodes.h | 72 +- src/include/nodes/pg_list.h | 3 +- src/include/nodes/plannodes.h | 24 +- src/include/nodes/primnodes.h | 87 +- src/include/nodes/relation.h | 48 +- src/include/optimizer/clauses.h | 8 +- src/include/optimizer/pathnode.h | 37 +- src/include/optimizer/paths.h | 28 +- src/include/optimizer/planmain.h | 11 +- src/include/optimizer/restrictinfo.h | 4 +- src/include/parser/gramparse.h | 17 +- src/include/parser/parse_clause.h | 5 +- src/include/parser/parse_func.h | 10 +- src/include/parser/parse_node.h | 24 +- src/include/parser/parse_relation.h | 40 +- src/include/parser/parsetree.h | 31 +- src/include/rewrite/rewriteHandler.h | 9 +- src/include/rewrite/rewriteManip.h | 8 +- src/test/regress/expected/case.out | 60 +- .../expected/geometry-cygwin-precision.out | 90 +- .../regress/expected/geometry-i86-gnulibc.out | 90 +- .../expected/geometry-positive-zeros-bsd.out | 90 +- .../expected/geometry-positive-zeros.out | 90 +- .../expected/geometry-powerpc-aix4.out | 90 +- .../geometry-powerpc-linux-gnulibc1.out | 90 +- .../expected/geometry-solaris-precision.out | 90 +- src/test/regress/expected/geometry.out | 90 +- src/test/regress/expected/join.out | 306 +++-- src/test/regress/expected/point.out | 42 +- src/test/regress/expected/rules.out | 43 +- src/test/regress/expected/select_implicit.out | 6 +- src/test/regress/sql/join.sql | 22 +- src/test/regress/sql/rules.sql | 33 + 93 files changed, 6276 insertions(+), 4152 deletions(-) diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 68bb8276981..44728bf9c9a 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -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. diff --git a/src/backend/commands/command.c b/src/backend/commands/command.c index 9535e197417..841806810e4 100644 --- a/src/backend/commands/command.c +++ b/src/backend/commands/command.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.102 2000/09/12 05:09:43 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.103 2000/09/12 21:06:47 tgl Exp $ * * NOTES * The PerformAddAttribute() code, like most of the relation @@ -61,8 +61,6 @@ static bool is_viewr(char *relname); static bool is_view(Relation rel); - - /* -------------------------------- * PortalCleanup * -------------------------------- @@ -536,7 +534,6 @@ AlterTableAlterColumn(const char *relationName, rel = heap_openr(relationName, AccessExclusiveLock); if ( rel->rd_rel->relkind == RELKIND_VIEW ) elog(ERROR, "ALTER TABLE: %s is a view", relationName); - myrelid = RelationGetRelid(rel); heap_close(rel, NoLock); @@ -782,7 +779,7 @@ systable_getnext(void *scan) * find a specified attribute in a node entry */ static bool -find_attribute_walker(Node *node, int attnum) +find_attribute_walker(Node *node, int *attnump) { if (node == NULL) return false; @@ -791,16 +788,17 @@ find_attribute_walker(Node *node, int attnum) Var *var = (Var *) node; if (var->varlevelsup == 0 && var->varno == 1 && - var->varattno == attnum) + var->varattno == *attnump) return true; } - return expression_tree_walker(node, find_attribute_walker, (void *) attnum); + return expression_tree_walker(node, find_attribute_walker, + (void *) attnump); } static bool find_attribute_in_node(Node *node, int attnum) { - return expression_tree_walker(node, find_attribute_walker, (void *) attnum); + return find_attribute_walker(node, &attnum); } /* @@ -1096,7 +1094,6 @@ void AlterTableAddConstraint(char *relationName, bool inh, Node *newConstraint) { - if (newConstraint == NULL) elog(ERROR, "ALTER TABLE / ADD CONSTRAINT passed invalid constraint."); @@ -1108,328 +1105,330 @@ AlterTableAddConstraint(char *relationName, /* check to see if the table to be constrained is a view. */ if (is_viewr(relationName)) elog(ERROR, "ALTER TABLE: Cannot add constraints to views."); - + switch (nodeTag(newConstraint)) { case T_Constraint: + { + Constraint *constr = (Constraint *) newConstraint; + + switch (constr->contype) { - Constraint *constr=(Constraint *)newConstraint; - switch (constr->contype) { - case CONSTR_CHECK: + case CONSTR_CHECK: + { + ParseState *pstate; + bool successful = TRUE; + HeapScanDesc scan; + ExprContext *econtext; + TupleTableSlot *slot = makeNode(TupleTableSlot); + HeapTuple tuple; + RangeTblEntry *rte; + List *rtlist; + List *qual; + List *constlist; + Relation rel; + Node *expr; + char *name; + + if (constr->name) + name=constr->name; + else + name="<unnamed>"; + + constlist=lcons(constr, NIL); + + rel = heap_openr(relationName, AccessExclusiveLock); + + /* make sure it is not a view */ + if (rel->rd_rel->relkind == RELKIND_VIEW) + elog(ERROR, "ALTER TABLE: cannot add constraint to a view"); + + /* + * Scan all of the rows, looking for a false match + */ + scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); + AssertState(scan != NULL); + + /* + * We need to make a parse state and range table to allow + * us to transformExpr and fix_opids to get a version of + * the expression we can pass to ExecQual + */ + pstate = make_parsestate(NULL); + makeRangeTable(pstate, NULL); + rte = addRangeTableEntry(pstate, relationName, NULL, + false, true); + addRTEtoJoinTree(pstate, rte); + + /* Convert the A_EXPR in raw_expr into an EXPR */ + expr = transformExpr(pstate, constr->raw_expr, EXPR_COLUMN_FIRST); + + /* + * Make sure it yields a boolean result. + */ + if (exprType(expr) != BOOLOID) + elog(ERROR, "CHECK '%s' does not yield boolean result", + name); + + /* + * Make sure no outside relations are referred to. + */ + if (length(pstate->p_rtable) != 1) + elog(ERROR, "Only relation '%s' can be referenced in CHECK", + relationName); + + /* + * Might as well try to reduce any constant expressions. + */ + expr = eval_const_expressions(expr); + + /* And fix the opids */ + fix_opids(expr); + + qual = lcons(expr, NIL); + + rte = makeNode(RangeTblEntry); + rte->relname = relationName; + rte->relid = RelationGetRelid(rel); + rte->eref = makeNode(Attr); + rte->eref->relname = relationName; + rtlist = lcons(rte, NIL); + + /* + * Scan through the rows now, making the necessary things + * for ExecQual, and then call it to evaluate the + * expression. + */ + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) { - ParseState *pstate; - bool successful=TRUE; - HeapScanDesc scan; - ExprContext *econtext; - TupleTableSlot *slot = makeNode(TupleTableSlot); - HeapTuple tuple; - RangeTblEntry *rte = makeNode(RangeTblEntry); - List *rtlist; - List *qual; - List *constlist; - Relation rel; - Node *expr; - char *name; - if (constr->name) - name=constr->name; - else - name="<unnamed>"; - - rel = heap_openr(relationName, AccessExclusiveLock); - - /* make sure it is not a view */ - if (rel->rd_rel->relkind == RELKIND_VIEW) - elog(ERROR, "ALTER TABLE: cannot add constraint to a view"); - - /* - * Scan all of the rows, looking for a false match - */ - scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); - AssertState(scan != NULL); - - /* - *We need to make a parse state and range table to allow us - * to transformExpr and fix_opids to get a version of the - * expression we can pass to ExecQual - */ - pstate = make_parsestate(NULL); - makeRangeTable(pstate, NULL); - addRangeTableEntry(pstate, relationName, - makeAttr(relationName, NULL), false, true,true); - constlist=lcons(constr, NIL); - - /* Convert the A_EXPR in raw_expr into an EXPR */ - expr = transformExpr(pstate, constr->raw_expr, EXPR_COLUMN_FIRST); - - /* - * Make sure it yields a boolean result. - */ - if (exprType(expr) != BOOLOID) - elog(ERROR, "CHECK '%s' does not yield boolean result", - name); - - /* - * Make sure no outside relations are referred to. - */ - if (length(pstate->p_rtable) != 1) - elog(ERROR, "Only relation '%s' can be referenced in CHECK", - relationName); - - /* - * Might as well try to reduce any constant expressions. - */ - expr = eval_const_expressions(expr); - - /* And fix the opids */ - fix_opids(expr); - - qual = lcons(expr, NIL); - rte->relname = relationName; - rte->ref = makeNode(Attr); - rte->ref->relname = rte->relname; - rte->relid = RelationGetRelid(rel); - rtlist = lcons(rte, NIL); - - /* - * Scan through the rows now, making the necessary things for - * ExecQual, and then call it to evaluate the expression. - */ - while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) + slot->val = tuple; + slot->ttc_shouldFree = false; + slot->ttc_descIsNew = true; + slot->ttc_tupleDescriptor = rel->rd_att; + slot->ttc_buffer = InvalidBuffer; + slot->ttc_whichplan = -1; + + econtext = MakeExprContext(slot, CurrentMemoryContext); + econtext->ecxt_range_table = rtlist; /* range table */ + if (!ExecQual(qual, econtext, true)) { - slot->val = tuple; - slot->ttc_shouldFree = false; - slot->ttc_descIsNew = true; - slot->ttc_tupleDescriptor = rel->rd_att; - slot->ttc_buffer = InvalidBuffer; - slot->ttc_whichplan = -1; - - econtext = MakeExprContext(slot, CurrentMemoryContext); - econtext->ecxt_range_table = rtlist; /* range table */ - if (!ExecQual(qual, econtext, true)) { - successful=false; - break; - } - FreeExprContext(econtext); + successful=false; + break; } + FreeExprContext(econtext); + } - pfree(slot); - pfree(rtlist); - pfree(rte); + pfree(slot); + pfree(rtlist); + pfree(rte); - heap_endscan(scan); - heap_close(rel, NoLock); + heap_endscan(scan); + heap_close(rel, NoLock); - if (!successful) - { - elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name); - } - /* - * Call AddRelationRawConstraints to do the real adding -- It duplicates some - * of the above, but does not check the validity of the constraint against - * tuples already in the table. - */ - AddRelationRawConstraints(rel, NIL, constlist); - pfree(constlist); - - break; + if (!successful) + { + elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name); } - default: - elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type."); + /* + * Call AddRelationRawConstraints to do the real adding -- + * It duplicates some of the above, but does not check the + * validity of the constraint against tuples already in + * the table. + */ + AddRelationRawConstraints(rel, NIL, constlist); + pfree(constlist); + + break; } + default: + elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type."); } break; + } case T_FkConstraint: - { - FkConstraint *fkconstraint = (FkConstraint *) newConstraint; - Relation rel, pkrel; - HeapScanDesc scan; - HeapTuple tuple; - Trigger trig; - List *list; - int count; - List *indexoidlist, - *indexoidscan; - Form_pg_index indexStruct = NULL; - Form_pg_attribute *rel_attrs = NULL; - int i; - int found=0; - - if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL && - get_temp_rel_by_username(relationName)==NULL) { - elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint."); - } + { + FkConstraint *fkconstraint = (FkConstraint *) newConstraint; + Relation rel, pkrel; + HeapScanDesc scan; + HeapTuple tuple; + Trigger trig; + List *list; + int count; + List *indexoidlist, + *indexoidscan; + Form_pg_index indexStruct = NULL; + Form_pg_attribute *rel_attrs = NULL; + int i; + int found=0; + + if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL && + get_temp_rel_by_username(relationName)==NULL) { + elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint."); + } + + /* + * Grab an exclusive lock on the pk table, so that someone + * doesn't delete rows out from under us. + */ + + pkrel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock); + if (pkrel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "referenced table \"%s\" not a relation", + fkconstraint->pktable_name); + + /* + * Grab an exclusive lock on the fk table, and then scan + * through each tuple, calling the RI_FKey_Match_Ins + * (insert trigger) as if that tuple had just been + * inserted. If any of those fail, it should elog(ERROR) + * and that's that. + */ + rel = heap_openr(relationName, AccessExclusiveLock); + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "referencing table \"%s\" not a relation", + relationName); + + /* First we check for limited correctness of the constraint */ + + rel_attrs = pkrel->rd_att->attrs; + indexoidlist = RelationGetIndexList(pkrel); - /* - * Grab an exclusive lock on the pk table, so that someone - * doesn't delete rows out from under us. - */ - - pkrel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock); - if (pkrel == NULL) - elog(ERROR, "referenced table \"%s\" not found", - fkconstraint->pktable_name); - - if (pkrel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "referenced table \"%s\" not a relation", - fkconstraint->pktable_name); - - - /* - * Grab an exclusive lock on the fk table, and then scan - * through each tuple, calling the RI_FKey_Match_Ins - * (insert trigger) as if that tuple had just been - * inserted. If any of those fail, it should elog(ERROR) - * and that's that. - */ - rel = heap_openr(relationName, AccessExclusiveLock); - if (rel == NULL) - elog(ERROR, "table \"%s\" not found", - relationName); - - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "referencing table \"%s\" not a relation", relationName); - - /* First we check for limited correctness of the constraint */ - - rel_attrs = pkrel->rd_att->attrs; - indexoidlist = RelationGetIndexList(pkrel); - - foreach(indexoidscan, indexoidlist) - { - Oid indexoid = lfirsti(indexoidscan); - HeapTuple indexTuple; - List *attrl; - indexTuple = SearchSysCacheTuple(INDEXRELID, - ObjectIdGetDatum(indexoid), - 0, 0, 0); - if (!HeapTupleIsValid(indexTuple)) - elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found", - indexoid); - indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); - - if (indexStruct->indisunique) { - /* go through the fkconstraint->pk_attrs list */ - foreach(attrl, fkconstraint->pk_attrs) { - Ident *attr=lfirst(attrl); - found=0; - for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++) - { - int pkattno = indexStruct->indkey[i]; + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirsti(indexoidscan); + HeapTuple indexTuple; + List *attrl; + indexTuple = SearchSysCacheTuple(INDEXRELID, + ObjectIdGetDatum(indexoid), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) + elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found", + indexoid); + indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); + + if (indexStruct->indisunique) { + /* go through the fkconstraint->pk_attrs list */ + foreach(attrl, fkconstraint->pk_attrs) { + Ident *attr=lfirst(attrl); + found=0; + for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++) + { + int pkattno = indexStruct->indkey[i]; if (pkattno>0) { char *name = NameStr(rel_attrs[pkattno-1]->attname); - if (strcmp(name, attr->name)==0) { - found=1; - break; - } + if (strcmp(name, attr->name)==0) { + found=1; + break; + } } - } - if (!found) - break; - } - } - if (found) - break; - indexStruct = NULL; - } - if (!found) - elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found", - fkconstraint->pktable_name); - - freeList(indexoidlist); - heap_close(pkrel, NoLock); - - rel_attrs = rel->rd_att->attrs; - if (fkconstraint->fk_attrs!=NIL) { - int found=0; - List *fkattrs; - Ident *fkattr; - foreach(fkattrs, fkconstraint->fk_attrs) { - int count=0; - found=0; - fkattr=lfirst(fkattrs); - for (; count < rel->rd_att->natts; count++) { - char *name = NameStr(rel->rd_att->attrs[count]->attname); - if (strcmp(name, fkattr->name)==0) { - found=1; - break; - } - } - if (!found) - break; - } - if (!found) - elog(ERROR, "columns referenced in foreign key constraint not found."); - } - - trig.tgoid = 0; - if (fkconstraint->constr_name) - trig.tgname = fkconstraint->constr_name; - else - trig.tgname = "<unknown>"; - trig.tgfoid = 0; - trig.tgtype = 0; - trig.tgenabled = TRUE; - trig.tgisconstraint = TRUE; - trig.tginitdeferred = FALSE; - trig.tgdeferrable = FALSE; - - trig.tgargs = (char **) palloc( - sizeof(char *) * (4 + length(fkconstraint->fk_attrs) - + length(fkconstraint->pk_attrs))); - - if (fkconstraint->constr_name) - trig.tgargs[0] = fkconstraint->constr_name; - else - trig.tgargs[0] = "<unknown>"; - trig.tgargs[1] = (char *) relationName; - trig.tgargs[2] = fkconstraint->pktable_name; - trig.tgargs[3] = fkconstraint->match_type; - count = 4; - foreach(list, fkconstraint->fk_attrs) + } + if (!found) + break; + } + } + if (found) + break; + indexStruct = NULL; + } + if (!found) + elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found", + fkconstraint->pktable_name); + + freeList(indexoidlist); + heap_close(pkrel, NoLock); + + rel_attrs = rel->rd_att->attrs; + if (fkconstraint->fk_attrs!=NIL) { + int found=0; + List *fkattrs; + Ident *fkattr; + foreach(fkattrs, fkconstraint->fk_attrs) { + int count=0; + found=0; + fkattr=lfirst(fkattrs); + for (; count < rel->rd_att->natts; count++) { + char *name = NameStr(rel->rd_att->attrs[count]->attname); + if (strcmp(name, fkattr->name)==0) { + found=1; + break; + } + } + if (!found) + break; + } + if (!found) + elog(ERROR, "columns referenced in foreign key constraint not found."); + } + + trig.tgoid = 0; + if (fkconstraint->constr_name) + trig.tgname = fkconstraint->constr_name; + else + trig.tgname = "<unknown>"; + trig.tgfoid = 0; + trig.tgtype = 0; + trig.tgenabled = TRUE; + trig.tgisconstraint = TRUE; + trig.tginitdeferred = FALSE; + trig.tgdeferrable = FALSE; + + trig.tgargs = (char **) palloc( + sizeof(char *) * (4 + length(fkconstraint->fk_attrs) + + length(fkconstraint->pk_attrs))); + + if (fkconstraint->constr_name) + trig.tgargs[0] = fkconstraint->constr_name; + else + trig.tgargs[0] = "<unknown>"; + trig.tgargs[1] = (char *) relationName; + trig.tgargs[2] = fkconstraint->pktable_name; + trig.tgargs[3] = fkconstraint->match_type; + count = 4; + foreach(list, fkconstraint->fk_attrs) { Ident *fk_at = lfirst(list); trig.tgargs[count++] = fk_at->name; } - foreach(list, fkconstraint->pk_attrs) + foreach(list, fkconstraint->pk_attrs) { Ident *pk_at = lfirst(list); trig.tgargs[count++] = pk_at->name; } - trig.tgnargs = count; + trig.tgnargs = count; - scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); - AssertState(scan != NULL); + scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); + AssertState(scan != NULL); - while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) - { - /* Make a call to the check function */ - /* No parameters are passed, but we do set a context */ - FunctionCallInfoData fcinfo; - TriggerData trigdata; - - MemSet(&fcinfo, 0, sizeof(fcinfo)); - /* We assume RI_FKey_check_ins won't look at flinfo... */ + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) + { + /* Make a call to the check function */ + /* No parameters are passed, but we do set a context */ + FunctionCallInfoData fcinfo; + TriggerData trigdata; - trigdata.type = T_TriggerData; - trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW; - trigdata.tg_relation = rel; - trigdata.tg_trigtuple = tuple; - trigdata.tg_newtuple = NULL; - trigdata.tg_trigger = &trig; + MemSet(&fcinfo, 0, sizeof(fcinfo)); + /* We assume RI_FKey_check_ins won't look at flinfo... */ - fcinfo.context = (Node *) &trigdata; + trigdata.type = T_TriggerData; + trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW; + trigdata.tg_relation = rel; + trigdata.tg_trigtuple = tuple; + trigdata.tg_newtuple = NULL; + trigdata.tg_trigger = &trig; - RI_FKey_check_ins(&fcinfo); - } - heap_endscan(scan); - heap_close(rel, NoLock); /* close rel but keep - * lock! */ + fcinfo.context = (Node *) &trigdata; - pfree(trig.tgargs); + RI_FKey_check_ins(&fcinfo); } + heap_endscan(scan); + heap_close(rel, NoLock); /* close rel but keep + * lock! */ + + pfree(trig.tgargs); break; + } default: elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed"); } @@ -1449,7 +1448,6 @@ AlterTableDropConstraint(const char *relationName, } - /* * ALTER TABLE OWNER */ @@ -1464,14 +1462,14 @@ AlterTableOwner(const char *relationName, const char *newOwnerName) /* * first check that we are a superuser */ - if (! superuser() ) + if (! superuser()) elog(ERROR, "ALTER TABLE: permission denied"); /* * look up the new owner in pg_shadow and get the sysid */ tuple = SearchSysCacheTuple(SHADOWNAME, PointerGetDatum(newOwnerName), - 0, 0, 0); + 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "ALTER TABLE: user \"%s\" not found", newOwnerName); @@ -1510,10 +1508,9 @@ AlterTableOwner(const char *relationName, const char *newOwnerName) */ heap_freetuple(tuple); heap_close(class_rel, RowExclusiveLock); - - return; } + /* * ALTER TABLE CREATE TOAST TABLE */ @@ -1579,6 +1576,7 @@ AlterTableCreateToastTable(const char *relationName, bool silent) * allow to create TOAST tables for views. But why not - someone * can insert into a view, so it shouldn't be impossible to hide * huge data there :-) + * * Not any more. */ if (((Form_pg_class) GETSTRUCT(reltup))->relkind != RELKIND_RELATION) @@ -1799,8 +1797,7 @@ LockTableCommand(LockStmt *lockstmt) } -static -bool +static bool is_viewr(char *name) { Relation rel = heap_openr(name, NoLock); @@ -1812,18 +1809,15 @@ is_viewr(char *name) return retval; } -static -bool -is_view (Relation rel) +static bool +is_view(Relation rel) { Relation RewriteRelation; HeapScanDesc scanDesc; ScanKeyData scanKeyData; HeapTuple tuple; Form_pg_rewrite data; - - - bool retval = 0; + bool retval = false; /* * Open the pg_rewrite relation. @@ -1849,7 +1843,7 @@ is_view (Relation rel) data = (Form_pg_rewrite) GETSTRUCT(tuple); if (data->ev_type == '1') { - retval = 1; + retval = true; break; } } diff --git a/src/backend/commands/creatinh.c b/src/backend/commands/creatinh.c index e39c24f8dfa..b6485850eb3 100644 --- a/src/backend/commands/creatinh.c +++ b/src/backend/commands/creatinh.c @@ -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. diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 25915fe42bd..2b3d8b85726 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -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) { diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index af10805b71f..d1d63000999 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -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); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index d25530b44fb..d46e0d30f55 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -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); diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 37b092fc20f..05474bc64bc 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -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 $ * *------------------------------------------------------------------------- */ @@ -46,11 +46,10 @@ * 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) diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 63c1e9e157f..39ae7dff10a 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -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); } /* ---------------- diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index 4b3b4a82505..d0eef4380b7 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -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 diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c index 5a2f45028a0..9d4ca0a8d54 100644 --- a/src/backend/executor/nodeMergejoin.c +++ b/src/backend/executor/nodeMergejoin.c @@ -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 $ * *------------------------------------------------------------------------- */ @@ -16,11 +16,10 @@ * 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 @@ -42,19 +41,19 @@ * } - * } - * - * 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 diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c index 3685232c7e4..5abd4ffc3a1 100644 --- a/src/backend/executor/nodeNestloop.c +++ b/src/backend/executor/nodeNestloop.c @@ -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; } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 7270d3116d8..77f17ee0a60 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.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; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index b059e5cd5f0..51a7a03fc1b 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -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; diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c index 45f42dc5024..e94b357d24b 100644 --- a/src/backend/nodes/list.c +++ b/src/backend/nodes/list.c @@ -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) { diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 14f2ab106c7..8b24b82122f 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -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; diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c index 104735cf6f6..7bf78e134bc 100644 --- a/src/backend/nodes/print.c +++ b/src/backend/nodes/print.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.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; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 17e0396e5fe..00a6407db8b 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -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) diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README index a867cd885ed..38901ede1fd 100644 --- a/src/backend/optimizer/README +++ b/src/backend/optimizer/README @@ -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 diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c index 7b9542cb1b2..f32b0d64eeb 100644 --- a/src/backend/optimizer/geqo/geqo_eval.c +++ b/src/backend/optimizer/geqo/geqo_eval.c @@ -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); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 999364d5637..605b60b5845 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -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; } diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 05f32d25972..3156a951314 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.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 diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index c2ca38490e3..367e1ac9767 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.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 $ * *------------------------------------------------------------------------- */ @@ -25,27 +25,32 @@ #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 */ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 741efe928c2..3cab2daba5c 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -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 $ * *------------------------------------------------------------------------- */ @@ -19,62 +19,52 @@ 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; } diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c index 62a02836fec..2a76f63eb7c 100644 --- a/src/backend/optimizer/path/orindxpath.c +++ b/src/backend/optimizer/path/orindxpath.c @@ -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, diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index 6d7b67bee3d..c6eccddab19 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -11,7 +11,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.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); } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c049f5d86b6..96dc3327b7f 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.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; } diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 8ffd35c9bb0..bf728ca1bdc 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.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 $ * *------------------------------------------------------------------------- */ @@ -26,13 +26,18 @@ #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); } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index abb468aa8d1..1fcbe64e888 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.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 */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 4be9b05bb90..7ffbb4666d9 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.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); /* diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index d8a09c017dd..d30636c185e 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.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); } /* diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index b0772b83f1c..24a0aae55cd 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -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: diff --git a/src/backend/optimizer/prep/prepkeyset.c b/src/backend/optimizer/prep/prepkeyset.c index fc192e6f28b..a28e329e537 100644 --- a/src/backend/optimizer/prep/prepkeyset.c +++ b/src/backend/optimizer/prep/prepkeyset.c @@ -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); diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index f069cafdf66..d284dd51e02 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -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); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index cf0b6dd703c..36c7abd85b5 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.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)); diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 5588e91e5b7..fc73bb2b664 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -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; diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 070fabf7669..87e87597d11 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.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); diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index 3ce924de1bb..adbfd884c36 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -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); + } +} diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index bed7be7f08a..ec9f9dafd0b 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.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 $ * *------------------------------------------------------------------------- */ @@ -16,17 +16,25 @@ #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); } /* diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile index 6ef084d04b6..68fed79d382 100644 --- a/src/backend/parser/Makefile +++ b/src/backend/parser/Makefile @@ -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 diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index fe21804a2c4..0165ef15c21 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -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); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 7e970ab1871..301be9eb9b9 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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 <ctype.h> #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 <boolean> opt_table %type <boolean> opt_chain, opt_trans -%type <jexpr> from_expr, join_clause, join_expr -%type <jexpr> join_clause_with_union, join_expr_with_union %type <node> join_outer, join_qual -%type <ival> join_type -%type <list> using_list -%type <ident> using_expr -/*** -#ifdef ENABLE_ORACLE_JOIN_SYNTAX -%type <list> oracle_list -%type <jexpr> oracle_expr -%type <boolean> oracle_outer -#endif -***/ +%type <jtype> join_type %type <list> extract_list, position_list %type <list> substr_list, substr_from, substr_for, trim_list @@ -246,8 +231,9 @@ static void doNegateFloat(Value *v); %type <attr> event_object, attr, alias_clause %type <sortgroupby> sortby %type <ielem> index_elem, func_index -%type <range> table_expr -%type <relexp> relation_expr +%type <node> table_ref +%type <jexpr> joined_table +%type <range> relation_expr %type <target> target_el, update_target_el %type <paramno> 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 <str> IDENT, FCONST, SCONST, Op %token <ival> 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 <name> 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]; } diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index bbc8f5c7076..955be022e4e 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -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. diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 3f874cc9643..c35b41b911b 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -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" @@ -33,57 +35,81 @@ 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; } diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 7976f5e7795..a033ff4be22 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -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) diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index bad9401c609..1f19b1b949e 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -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; diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index 5d363ea3e69..85a56067bd2 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -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); } /* diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 802299c8966..491cbc5ef08 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -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 $ * *------------------------------------------------------------------------- */ @@ -20,14 +20,25 @@ #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); } diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 1564f976b04..b8e1570985f 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -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; } diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index a7652407b73..4a6c825498a 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -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) diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l index f5597d1593e..5700915ad94 100644 --- a/src/backend/parser/scan.l +++ b/src/backend/parser/scan.l @@ -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 diff --git a/src/backend/rewrite/locks.c b/src/backend/rewrite/locks.c index a14e1b48684..78fdad8960a 100644 --- a/src/backend/rewrite/locks.c +++ b/src/backend/rewrite/locks.c @@ -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); diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 4362687f8b8..49dfae5b905 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -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); } diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index a8ec560c741..e83ac054853 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -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); } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 571854d446c..26ebe21c4a2 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -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); -} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index b73ea0e6136..eb067fe5057 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -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 diff --git a/src/include/executor/execdebug.h b/src/include/executor/execdebug.h index 7eb6667da03..c4ba3d1f5b6 100644 --- a/src/include/executor/execdebug.h +++ b/src/include/executor/execdebug.h @@ -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 $ * *------------------------------------------------------------------------- */ @@ -128,16 +128,6 @@ #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 @@ -170,11 +160,10 @@ * 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 */ /* ---------------------------------------------------------------- diff --git a/src/include/executor/execdefs.h b/src/include/executor/execdefs.h index 89fed192cdd..6b9457969b1 100644 --- a/src/include/executor/execdefs.h +++ b/src/include/executor/execdefs.h @@ -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 $ * *------------------------------------------------------------------------- */ @@ -42,9 +42,13 @@ #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 */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 5eb7cbb93ba..5c330915e75 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -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); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 9626dbf8b1c..83ed6c5234b 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -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; diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index d825c8fe395..f3929d8b2c6 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -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 */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index f6c75c19781..440a7609d83 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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; diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h index a0e9881dc87..4e0bcfc7053 100644 --- a/src/include/nodes/pg_list.h +++ b/src/include/nodes/pg_list.h @@ -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); diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index e348d25b2ba..cf93b9dee17 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -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; /* --------------- diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 0ef350687dc..bc17773642c 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -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 */ diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index b7d65131066..767e2e114e0 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -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; diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h index 1b2bcd92055..62bb401193d 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -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) && \ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index b8788851e2b..0bf57ef0cc5 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -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 diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 66520d6a897..35eb3190f1c 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -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 */ diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 723543c437c..43c93978cdf 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -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 */ diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index 3d94854e03b..2e1d4d66f99 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -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 */ diff --git a/src/include/parser/gramparse.h b/src/include/parser/gramparse.h index 02c95745fee..54d6e869ad9 100644 --- a/src/include/parser/gramparse.h +++ b/src/include/parser/gramparse.h @@ -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 */ diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h index b9a868d4420..fd1cfdb3604 100644 --- a/src/include/parser/parse_clause.h +++ b/src/include/parser/parse_clause.h @@ -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); diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h index be96652cfb2..d221c600c8e 100644 --- a/src/include/parser/parse_func.h +++ b/src/include/parser/parse_func.h @@ -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, diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index d4231e8819d..002391d6530 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -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 $ * *------------------------------------------------------------------------- */ @@ -16,36 +16,28 @@ #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, diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 4f89bcc65c3..7c7a04844e4 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -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 $ * *------------------------------------------------------------------------- */ @@ -16,23 +16,35 @@ #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 */ diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h index 277bc32a504..ff727cfd07a 100644 --- a/src/include/parser/parsetree.h +++ b/src/include/parser/parsetree.h @@ -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 @@ -51,22 +36,18 @@ * */ #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 */ diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h index 7af0f3932ec..5271d78717c 100644 --- a/src/include/rewrite/rewriteHandler.h +++ b/src/include/rewrite/rewriteHandler.h @@ -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); diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h index af052a65510..c41519acb8c 100644 --- a/src/include/rewrite/rewriteManip.h +++ b/src/include/rewrite/rewriteManip.h @@ -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); diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out index 0ad8be9eec4..2b53bc699dc 100644 --- a/src/test/regress/expected/case.out +++ b/src/test/regress/expected/case.out @@ -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) diff --git a/src/test/regress/expected/geometry-cygwin-precision.out b/src/test/regress/expected/geometry-cygwin-precision.out index 4e0651e46cd..67ab299c39e 100644 --- a/src/test/regress/expected/geometry-cygwin-precision.out +++ b/src/test/regress/expected/geometry-cygwin-precision.out @@ -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) diff --git a/src/test/regress/expected/geometry-i86-gnulibc.out b/src/test/regress/expected/geometry-i86-gnulibc.out index c2e689dea96..6ad08de4154 100644 --- a/src/test/regress/expected/geometry-i86-gnulibc.out +++ b/src/test/regress/expected/geometry-i86-gnulibc.out @@ -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) diff --git a/src/test/regress/expected/geometry-positive-zeros-bsd.out b/src/test/regress/expected/geometry-positive-zeros-bsd.out index 169709e37bc..78940c08468 100644 --- a/src/test/regress/expected/geometry-positive-zeros-bsd.out +++ b/src/test/regress/expected/geometry-positive-zeros-bsd.out @@ -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) diff --git a/src/test/regress/expected/geometry-positive-zeros.out b/src/test/regress/expected/geometry-positive-zeros.out index 113733d9420..914d9e77528 100644 --- a/src/test/regress/expected/geometry-positive-zeros.out +++ b/src/test/regress/expected/geometry-positive-zeros.out @@ -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) diff --git a/src/test/regress/expected/geometry-powerpc-aix4.out b/src/test/regress/expected/geometry-powerpc-aix4.out index 07017606bad..f7ab9e65096 100644 --- a/src/test/regress/expected/geometry-powerpc-aix4.out +++ b/src/test/regress/expected/geometry-powerpc-aix4.out @@ -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) diff --git a/src/test/regress/expected/geometry-powerpc-linux-gnulibc1.out b/src/test/regress/expected/geometry-powerpc-linux-gnulibc1.out index 1b0714b6952..3c2e4557ffb 100644 --- a/src/test/regress/expected/geometry-powerpc-linux-gnulibc1.out +++ b/src/test/regress/expected/geometry-powerpc-linux-gnulibc1.out @@ -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) diff --git a/src/test/regress/expected/geometry-solaris-precision.out b/src/test/regress/expected/geometry-solaris-precision.out index 4e0651e46cd..67ab299c39e 100644 --- a/src/test/regress/expected/geometry-solaris-precision.out +++ b/src/test/regress/expected/geometry-solaris-precision.out @@ -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) diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out index 9dfe6081eb1..dcefbf78f54 100644 --- a/src/test/regress/expected/geometry.out +++ b/src/test/regress/expected/geometry.out @@ -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) diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 2b4f1b655aa..3c156ee46c1 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -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 -- diff --git a/src/test/regress/expected/point.out b/src/test/regress/expected/point.out index 9f347bce797..3eab1c5b5d5 100644 --- a/src/test/regress/expected/point.out +++ b/src/test/regress/expected/point.out @@ -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 diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 34059b8a6e7..eb19122cbce 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -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); ); diff --git a/src/test/regress/expected/select_implicit.out b/src/test/regress/expected/select_implicit.out index adf0f794774..e3d74e5daba 100644 --- a/src/test/regress/expected/select_implicit.out +++ b/src/test/regress/expected/select_implicit.out @@ -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 diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index c63bd0596fb..88972de5ea9 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -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 -- diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql index 2b7aa33a8cf..2c99f2a3ccb 100644 --- a/src/test/regress/sql/rules.sql +++ b/src/test/regress/sql/rules.sql @@ -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 -- -- GitLab