diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2000-09-12 21:07:18 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2000-09-12 21:07:18 +0000 |
commit | ed5003c58401e5727fcdd970505972394c95febb (patch) | |
tree | 53c25d5c65d6f7275f110503f51ab370e55af6ea /src | |
parent | b5c0ab278bc67bc7f363da7d828a08ce7c4d28c2 (diff) | |
download | postgresql-ed5003c58401e5727fcdd970505972394c95febb.tar.gz postgresql-ed5003c58401e5727fcdd970505972394c95febb.zip |
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!
Diffstat (limited to 'src')
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 $ * *------------------------------------------------------------------------- */ @@ -257,6 +257,26 @@ _equalSubLink(SubLink *a, SubLink *b) } 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) { if (!equal(a->arg, b->arg)) @@ -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; @@ -1645,25 +1688,6 @@ _equalRowMark(RowMark *a, RowMark *b) } 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) { if (!equalstr(a->constr_name, b->constr_name)) @@ -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 @@ -758,32 +765,6 @@ _outSubLink(StringInfo str, SubLink *node) } /* - * 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 */ static void @@ -847,6 +828,66 @@ _outParam(StringInfo str, Param *node) } /* + * 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); } } @@ -126,19 +139,46 @@ 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,19 +590,37 @@ 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 * accept only var-op-var clauses as hashjoinable, we need only check @@ -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,7 +297,9 @@ 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; @@ -294,16 +307,62 @@ make_rels_by_clauseless_joins(Query *root, /* + * 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 * the two given rels, and add to it path information for paths * 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; } @@ -135,10 +167,144 @@ 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,6 +16,7 @@ #include "postgres.h" +#include "nodes/plannodes.h" #include "optimizer/clauses.h" #include "optimizer/var.h" @@ -23,10 +24,17 @@ 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,29 +97,63 @@ 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 * clutzy but..... */ 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,61 +877,9 @@ 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 $ * *------------------------------------------------------------------------- */ @@ -129,16 +129,6 @@ */ /* ---------------- - * 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 * backend. @@ -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 */ @@ -1058,16 +1060,6 @@ typedef struct ResTarget } ResTarget; /* - * RelExpr - relation expressions - */ -typedef struct RelExpr -{ - NodeTag type; - char *relname; /* the relation name */ - bool inh; /* inheritance query */ -} RelExpr; - -/* * SortGroupBy - for ORDER BY clause */ typedef struct SortGroupBy @@ -1083,11 +1075,22 @@ 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) * * For a plain index, each 'name' is an attribute name in the heap relation, @@ -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) @@ -224,28 +224,28 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation 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) @@ -224,28 +224,28 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation 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) @@ -224,28 +224,28 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation 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) @@ -224,28 +224,28 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation 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) @@ -224,28 +224,28 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation 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) @@ -224,28 +224,28 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation 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) @@ -224,28 +224,28 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation 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) @@ -224,28 +224,28 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation 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 @@ -331,20 +466,47 @@ 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 @@ -1159,6 +1159,47 @@ SELECT count(*) FROM shoe; (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 -- SELECT viewname, definition FROM pg_views ORDER BY viewname; @@ -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 @@ -164,22 +162,24 @@ SELECT '' AS "xxx", * -- 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 @@ -687,6 +687,39 @@ 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 -- SELECT viewname, definition FROM pg_views ORDER BY viewname; |