diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 1999-07-19 00:26:20 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 1999-07-19 00:26:20 +0000 |
commit | 7f76eab140e703b7847b107245a669e2010886c0 (patch) | |
tree | 8c9d01c654aa8de0e14c0d446817ba60c33d0199 /src/backend/parser/analyze.c | |
parent | c9814427722798751fa5bf254f597d722d76b5e3 (diff) | |
download | postgresql-7f76eab140e703b7847b107245a669e2010886c0.tar.gz postgresql-7f76eab140e703b7847b107245a669e2010886c0.zip |
Rewrite parser's handling of INSERT ... SELECT so that processing
of the SELECT part of the statement is just like a plain SELECT. All
INSERT-specific processing happens after the SELECT parsing is done.
This eliminates many problems, e.g. INSERT ... SELECT ... GROUP BY using
the wrong column labels. Ensure that DEFAULT clauses are coerced to
the target column type, whether or not stored clause produces the right
type. Substantial cleanup of parser's array support.
Diffstat (limited to 'src/backend/parser/analyze.c')
-rw-r--r-- | src/backend/parser/analyze.c | 345 |
1 files changed, 204 insertions, 141 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 40a66b48738..d4a1ef57ef8 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -5,12 +5,11 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: analyze.c,v 1.115 1999/07/17 20:17:19 momjian Exp $ + * $Id: analyze.c,v 1.116 1999/07/19 00:26:18 tgl Exp $ * *------------------------------------------------------------------------- */ - #include "postgres.h" #include "access/heapam.h" @@ -38,8 +37,10 @@ static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt); static void transformForUpdate(Query *qry, List *forUpdate); void CheckSelectForUpdate(Query *qry); -List *extras_before = NIL; -List *extras_after = NIL; +/* kluge to return extra info from transformCreateStmt() */ +static List *extras_before; +static List *extras_after; + /* * parse_analyze - @@ -58,17 +59,23 @@ parse_analyze(List *pl, ParseState *parentParseState) while (pl != NIL) { + 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); + pstate->p_target_relation = NULL; + pstate->p_target_rangetblentry = NULL; while (extras_before != NIL) { result = lappend(result, - transformStmt(pstate, lfirst(extras_before))); + transformStmt(pstate, lfirst(extras_before))); if (pstate->p_target_relation != NULL) heap_close(pstate->p_target_relation); + pstate->p_target_relation = NULL; + pstate->p_target_rangetblentry = NULL; extras_before = lnext(extras_before); } @@ -80,11 +87,13 @@ parse_analyze(List *pl, ParseState *parentParseState) transformStmt(pstate, lfirst(extras_after))); if (pstate->p_target_relation != NULL) heap_close(pstate->p_target_relation); + pstate->p_target_relation = NULL; + pstate->p_target_rangetblentry = NULL; extras_after = lnext(extras_after); } - pl = lnext(pl); pfree(pstate); + pl = lnext(pl); } return result; @@ -148,9 +157,9 @@ transformStmt(ParseState *pstate, Node *parseTree) result->commandType = CMD_UTILITY; result->utilityStmt = (Node *) parseTree; MemoryContextSwitchTo(oldcontext); - break; - } + break; + case T_ExplainStmt: { ExplainStmt *n = (ExplainStmt *) parseTree; @@ -215,7 +224,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qry->commandType = CMD_DELETE; /* set up a range table */ - makeRangeTable(pstate, stmt->relname, NULL, NULL); + makeRangeTable(pstate, NULL, NULL); + setTargetTable(pstate, stmt->relname); qry->uniqueFlag = NULL; @@ -240,135 +250,70 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) static Query * transformInsertStmt(ParseState *pstate, InsertStmt *stmt) { - Query *qry = makeNode(Query); /* make a new query tree */ + Query *qry = makeNode(Query); + Node *fromQual; List *icolumns; + List *tl; + TupleDesc rd_att; qry->commandType = CMD_INSERT; pstate->p_is_insert = true; - /* set up a range table */ - makeRangeTable(pstate, stmt->relname, stmt->fromClause, NULL); + /*---------- + * Initial processing steps are just like SELECT, which should not + * be surprising, since we may be handling an INSERT ... SELECT. + * It is important that we finish processing all the SELECT subclauses + * before we start doing any INSERT-specific processing; otherwise + * the behavior of SELECT within INSERT might be different from a + * stand-alone SELECT. (Indeed, Postgres up through 6.5 had bugs of + * just that nature...) + *---------- + */ - qry->uniqueFlag = stmt->unique; + /* set up a range table --- note INSERT target is not in it yet */ + makeRangeTable(pstate, stmt->fromClause, &fromQual); - /* fix the target list */ - icolumns = pstate->p_insert_columns = makeTargetNames(pstate, stmt->cols); + qry->uniqueFlag = stmt->unique; qry->targetList = transformTargetList(pstate, stmt->targetList); - /* DEFAULT handling */ - if (length(qry->targetList) < pstate->p_target_relation->rd_att->natts && - pstate->p_target_relation->rd_att->constr && - pstate->p_target_relation->rd_att->constr->num_defval > 0) - { - Form_pg_attribute *att = pstate->p_target_relation->rd_att->attrs; - AttrDefault *defval = pstate->p_target_relation->rd_att->constr->defval; - int ndef = pstate->p_target_relation->rd_att->constr->num_defval; - - /* - * if stmt->cols == NIL then makeTargetNames returns list of all - * attrs. May have to shorten icolumns list... - */ - if (stmt->cols == NIL) - { - List *extrl; - int i = length(qry->targetList); - - foreach(extrl, icolumns) - { - - /* - * decrements first, so if we started with zero items it - * will now be negative - */ - if (--i <= 0) - break; - } - - /* - * this an index into the targetList, so make sure we had one - * to start... - */ - if (i >= 0) - { - freeList(lnext(extrl)); - lnext(extrl) = NIL; - } - else - icolumns = NIL; - } + qry->qual = transformWhereClause(pstate, stmt->whereClause, fromQual); - while (ndef-- > 0) - { - List *tl; - Ident *id; - TargetEntry *te; - - foreach(tl, icolumns) - { - id = (Ident *) lfirst(tl); - if (namestrcmp(&(att[defval[ndef].adnum - 1]->attname), id->name) == 0) - break; - } - if (tl != NIL) /* something given for this attr */ - continue; - - /* - * Nothing given for this attr with DEFAULT expr, so add new - * TargetEntry to qry->targetList. Note, that we set resno to - * defval[ndef].adnum: it's what - * transformTargetList()->make_targetlist_expr() does for - * INSERT ... SELECT. But for INSERT ... VALUES - * pstate->p_last_resno is used. It doesn't matter for - * "normal" using (planner creates proper target list in - * preptlist.c), but may break RULEs in some way. It seems - * better to create proper target list here... - */ - te = makeTargetEntry(makeResdom(defval[ndef].adnum, - att[defval[ndef].adnum - 1]->atttypid, - att[defval[ndef].adnum - 1]->atttypmod, - pstrdup(nameout(&(att[defval[ndef].adnum - 1]->attname))), - 0, 0, false), - (Node *) stringToNode(defval[ndef].adbin)); - qry->targetList = lappend(qry->targetList, te); - } - } - - /* fix where clause */ - qry->qual = transformWhereClause(pstate, stmt->whereClause, NULL); - - /* - * The havingQual has a similar meaning as "qual" in the where - * statement. So we can easily use the code from the "where clause" - * with some additional traversals done in - * .../optimizer/plan/planner.c + /* Initial processing of HAVING clause is just like WHERE clause. + * Additional work will be done in optimizer/plan/planner.c. */ qry->havingQual = transformWhereClause(pstate, stmt->havingClause, NULL); - qry->hasSubLinks = pstate->p_hasSubLinks; - - /* now the range table will not change */ - qry->rtable = pstate->p_rtable; - qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL); - qry->groupClause = transformGroupClause(pstate, stmt->groupClause, qry->targetList); - /* fix order clause */ + /* An InsertStmt has no sortClause, but we still call + * transformSortClause because it also handles uniqueFlag. + */ qry->sortClause = transformSortClause(pstate, NIL, NIL, qry->targetList, qry->uniqueFlag); + qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasAggs = pstate->p_hasAggs; if (pstate->p_hasAggs || qry->groupClause) parseCheckAggregates(pstate, qry); /* + * If there is a havingQual but there are no aggregates, then there is + * something wrong with the query because HAVING must contain + * aggregates in its expressions! Otherwise the query could have been + * formulated using the WHERE clause. + */ + if (qry->havingQual && ! qry->hasAggs) + elog(ERROR, "SELECT/HAVING requires aggregates to be valid"); + + /* * The INSERT INTO ... SELECT ... could have a UNION in child, so - * unionClause may be false , + * unionClause may be false */ qry->unionall = stmt->unionall; @@ -380,15 +325,98 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) qry->intersectClause = stmt->intersectClause; /* - * If there is a havingQual but there are no aggregates, then there is - * something wrong with the query because having must contain - * aggregates in its expressions! Otherwise the query could have been - * formulated using the where clause. + * Now we are done with SELECT-like processing, and can get on with + * transforming the target list to match the INSERT target columns. + * + * 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.) + */ + setTargetTable(pstate, stmt->relname); + + /* now the range table will not change */ + qry->rtable = pstate->p_rtable; + qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL); + + /* Prepare to assign non-conflicting resnos to resjunk attributes */ + if (pstate->p_last_resno <= pstate->p_target_relation->rd_rel->relnatts) + pstate->p_last_resno = pstate->p_target_relation->rd_rel->relnatts + 1; + + /* Validate stmt->cols list, or build default list if no list given */ + icolumns = makeTargetNames(pstate, stmt->cols); + + /* Prepare non-junk columns for assignment to target table */ + foreach(tl, qry->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Resdom *resnode = tle->resdom; + Ident *id; + + if (resnode->resjunk) + { + /* Resjunk nodes need no additional processing, but be sure they + * have names and resnos that do not match any target columns; + * else rewriter or planner might get confused. + */ + resnode->resname = "?resjunk?"; + resnode->resno = (AttrNumber) pstate->p_last_resno++; + continue; + } + if (icolumns == NIL) + elog(ERROR, "INSERT has more expressions than target columns"); + id = (Ident *) lfirst(icolumns); + updateTargetListEntry(pstate, tle, id->name, id->indirection); + icolumns = lnext(icolumns); + } + + /* + * Add targetlist items to assign DEFAULT values to any columns that + * have defaults and were not assigned to by the user. + * XXX wouldn't it make more sense to do this further downstream, + * after the rule rewriter? */ - if ((qry->hasAggs == false) && (qry->havingQual != NULL)) + rd_att = pstate->p_target_relation->rd_att; + if (rd_att->constr && rd_att->constr->num_defval > 0) { - elog(ERROR, "SELECT/HAVING requires aggregates to be valid"); - return (Query *) NIL; + Form_pg_attribute *att = rd_att->attrs; + AttrDefault *defval = rd_att->constr->defval; + int ndef = rd_att->constr->num_defval; + + while (ndef-- > 0) + { + Form_pg_attribute thisatt = att[defval[ndef].adnum - 1]; + TargetEntry *te; + + foreach(tl, qry->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Resdom *resnode = tle->resdom; + + if (resnode->resjunk) + continue; /* ignore resjunk nodes */ + if (namestrcmp(&(thisatt->attname), resnode->resname) == 0) + break; + } + if (tl != NIL) /* something given for this attr */ + continue; + /* + * No user-supplied value, so add a targetentry with DEFAULT expr + * and correct data for the target column. + */ + te = makeTargetEntry( + makeResdom(defval[ndef].adnum, + thisatt->atttypid, + thisatt->atttypmod, + pstrdup(nameout(&(thisatt->attname))), + 0, 0, false), + stringToNode(defval[ndef].adbin)); + qry->targetList = lappend(qry->targetList, te); + /* + * Make sure the value is coerced to the target column type + * (might not be right type if it's not a constant!) + */ + updateTargetListEntry(pstate, te, te->resdom->resname, NIL); + } } if (stmt->forUpdate != NULL) @@ -963,12 +991,12 @@ static Query * transformSelectStmt(ParseState *pstate, SelectStmt *stmt) { Query *qry = makeNode(Query); - Node *qual; + Node *fromQual; qry->commandType = CMD_SELECT; /* set up a range table */ - makeRangeTable(pstate, NULL, stmt->fromClause, &qual); + makeRangeTable(pstate, stmt->fromClause, &fromQual); qry->uniqueFlag = stmt->unique; @@ -978,16 +1006,16 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) qry->targetList = transformTargetList(pstate, stmt->targetList); - qry->qual = transformWhereClause(pstate, stmt->whereClause, qual); + qry->qual = transformWhereClause(pstate, stmt->whereClause, fromQual); - /* - * The havingQual has a similar meaning as "qual" in the where - * statement. So we can easily use the code from the "where clause" - * with some additional traversals done in optimizer/plan/planner.c + /* Initial processing of HAVING clause is just like WHERE clause. + * Additional work will be done in optimizer/plan/planner.c. */ qry->havingQual = transformWhereClause(pstate, stmt->havingClause, NULL); - qry->hasSubLinks = pstate->p_hasSubLinks; + qry->groupClause = transformGroupClause(pstate, + stmt->groupClause, + qry->targetList); qry->sortClause = transformSortClause(pstate, stmt->sortClause, @@ -995,16 +1023,21 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) qry->targetList, qry->uniqueFlag); - qry->groupClause = transformGroupClause(pstate, - stmt->groupClause, - qry->targetList); - qry->rtable = pstate->p_rtable; - + qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasAggs = pstate->p_hasAggs; if (pstate->p_hasAggs || qry->groupClause) parseCheckAggregates(pstate, qry); /* + * If there is a havingQual but there are no aggregates, then there is + * something wrong with the query because HAVING must contain + * aggregates in its expressions! Otherwise the query could have been + * formulated using the WHERE clause. + */ + if (qry->havingQual && ! qry->hasAggs) + elog(ERROR, "SELECT/HAVING requires aggregates to be valid"); + + /* * The INSERT INTO ... SELECT ... could have a UNION in child, so * unionClause may be false */ @@ -1017,17 +1050,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) qry->unionClause = stmt->unionClause; qry->intersectClause = stmt->intersectClause; - /* - * If there is a havingQual but there are no aggregates, then there is - * something wrong with the query because having must contain - * aggregates in its expressions! Otherwise the query could have been - * formulated using the where clause. - */ - if ((qry->hasAggs == false) && (qry->havingQual != NULL)) - { - elog(ERROR, "SELECT/HAVING requires aggregates to be valid"); - return (Query *) NIL; - } + qry->rtable = pstate->p_rtable; if (stmt->forUpdate != NULL) transformForUpdate(qry, stmt->forUpdate); @@ -1044,6 +1067,8 @@ static Query * transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) { Query *qry = makeNode(Query); + List *origTargetList; + List *tl; qry->commandType = CMD_UPDATE; pstate->p_is_update = true; @@ -1052,21 +1077,59 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) * the FROM clause is non-standard SQL syntax. We used to be able to * do this with REPLACE in POSTQUEL so we keep the feature. */ - makeRangeTable(pstate, stmt->relname, stmt->fromClause, NULL); + makeRangeTable(pstate, stmt->fromClause, NULL); + setTargetTable(pstate, stmt->relname); qry->targetList = transformTargetList(pstate, stmt->targetList); qry->qual = transformWhereClause(pstate, stmt->whereClause, NULL); + qry->hasSubLinks = pstate->p_hasSubLinks; qry->rtable = pstate->p_rtable; - qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL); qry->hasAggs = pstate->p_hasAggs; if (pstate->p_hasAggs) parseCheckAggregates(pstate, qry); + /* + * Now we are done with SELECT-like processing, and can get on with + * transforming the target list to match the UPDATE target columns. + */ + + /* Prepare to assign non-conflicting resnos to resjunk attributes */ + if (pstate->p_last_resno <= pstate->p_target_relation->rd_rel->relnatts) + pstate->p_last_resno = pstate->p_target_relation->rd_rel->relnatts + 1; + + /* Prepare non-junk columns for assignment to target table */ + origTargetList = stmt->targetList; + foreach(tl, qry->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Resdom *resnode = tle->resdom; + ResTarget *origTarget; + + if (resnode->resjunk) + { + /* Resjunk nodes need no additional processing, but be sure they + * have names and resnos that do not match any target columns; + * else rewriter or planner might get confused. + */ + resnode->resname = "?resjunk?"; + resnode->resno = (AttrNumber) pstate->p_last_resno++; + continue; + } + if (origTargetList == NIL) + elog(ERROR, "UPDATE target count mismatch --- internal error"); + origTarget = (ResTarget *) lfirst(origTargetList); + updateTargetListEntry(pstate, tle, + origTarget->name, origTarget->indirection); + origTargetList = lnext(origTargetList); + } + if (origTargetList != NIL) + elog(ERROR, "UPDATE target count mismatch --- internal error"); + return (Query *) qry; } |