diff options
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/analyze.c | 345 | ||||
-rw-r--r-- | src/backend/parser/parse_clause.c | 349 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 79 | ||||
-rw-r--r-- | src/backend/parser/parse_node.c | 245 | ||||
-rw-r--r-- | src/backend/parser/parse_relation.c | 134 | ||||
-rw-r--r-- | src/backend/parser/parse_target.c | 874 |
6 files changed, 772 insertions, 1254 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; } diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index ce9583c174a..f8dc7777b06 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -7,12 +7,13 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.41 1999/07/17 20:17:23 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.42 1999/07/19 00:26:19 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" + #include "access/heapam.h" #include "nodes/relation.h" #include "parse.h" @@ -29,34 +30,39 @@ static char *clauseText[] = {"ORDER", "GROUP"}; -static TargetEntry * - findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause); - +static TargetEntry *findTargetlistEntry(ParseState *pstate, Node *node, + List *tlist, int clause); static void parseFromClause(ParseState *pstate, List *frmList, Node **qual); +static char *transformTableEntry(ParseState *pstate, RangeVar *r); #ifdef ENABLE_OUTER_JOINS -Node *transformUsingClause(ParseState *pstate, List *onList, char *lname, char *rname); - +static Node *transformUsingClause(ParseState *pstate, List *onList, + char *lname, char *rname); #endif -static char *transformTableEntry(ParseState *pstate, RangeVar *r); /* * makeRangeTable - - * make a range table with the specified relation (optional) and the - * from_clause. + * Build the initial range table from the FROM clause. */ void -makeRangeTable(ParseState *pstate, char *relname, List *frmList, Node **qual) +makeRangeTable(ParseState *pstate, List *frmList, Node **qual) { - RangeTblEntry *rte; - int sublevels_up; - + /* Currently, nothing to do except this: */ parseFromClause(pstate, frmList, qual); +} - if (relname == NULL) - return; +/* + * setTargetTable + * Add the target relation of INSERT or UPDATE to the range table, + * and make the special links to it in the ParseState. + */ +void +setTargetTable(ParseState *pstate, char *relname) +{ + RangeTblEntry *rte; + int sublevels_up; if ((refnameRangeTablePosn(pstate, relname, &sublevels_up) == 0) || (sublevels_up != 0)) @@ -136,7 +142,7 @@ makeAttr(char *relname, char *attname) /* transformUsingClause() * Take an ON or USING clause from a join expression and expand if necessary. */ -Node * +static Node * transformUsingClause(ParseState *pstate, List *onList, char *lname, char *rname) { A_Expr *expr = NULL; @@ -295,7 +301,8 @@ parseFromClause(ParseState *pstate, List *frmList, Node **qual) if (IsA(j->quals, List)) j->quals = lcons(transformUsingClause(pstate, (List *) j->quals, lname, rname), NIL); - Assert(qual != NULL); + if (qual == NULL) + elog(ERROR, "JOIN/ON not supported in this context"); if (*qual == NULL) *qual = lfirst(j->quals); @@ -329,145 +336,111 @@ parseFromClause(ParseState *pstate, List *frmList, Node **qual) } } + /* * findTargetlistEntry - - * returns the Resdom in the target list matching the specified varname - * and range. If none exist one is created. - * - * Rewritten for ver 6.4 to handle expressions in the GROUP/ORDER BY clauses. - * - daveh@insightdist.com 1998-07-31 + * Returns the targetlist entry matching the given (untransformed) node. + * If no matching entry exists, one is created and appended to the target + * list as a "resjunk" node. * + * node the ORDER BY or GROUP BY expression to be matched + * tlist the existing target list (NB: this cannot be NIL, which is a + * good thing since we'd be unable to append to it...) + * clause identifies clause type for error messages. */ static TargetEntry * findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause) { - List *l; - int rtable_pos = 0, - target_pos = 0, - targetlist_pos = 0; TargetEntry *target_result = NULL; - Value *val = NULL; - char *relname = NULL; - char *name = NULL; - Node *expr = NULL; - int relCnt = 0; - - /* Pull out some values before looping thru target list */ - switch (nodeTag(node)) - { - case T_Attr: - relname = ((Attr *) node)->relname; - val = (Value *) lfirst(((Attr *) node)->attrs); - name = strVal(val); - rtable_pos = refnameRangeTablePosn(pstate, relname, NULL); - relCnt = length(pstate->p_rtable); - break; - - case T_Ident: - name = ((Ident *) node)->name; - relCnt = length(pstate->p_rtable); - break; - - case T_A_Const: - val = &((A_Const *) node)->val; - - if (nodeTag(val) != T_Integer) - elog(ERROR, "Illegal Constant in %s BY", clauseText[clause]); - target_pos = intVal(val); - break; - - case T_FuncCall: - case T_A_Expr: - expr = transformExpr(pstate, node, EXPR_COLUMN_FIRST); - break; - - default: - elog(ERROR, "Illegal %s BY node = %d", clauseText[clause], nodeTag(node)); - } + List *tl; + Node *expr; - /* - * Loop through target entries and try to match to node + /*---------- + * Handle two special cases as mandated by the SQL92 spec: + * + * 1. ORDER/GROUP BY ColumnName + * For a bare identifier, we search for a matching column name + * in the existing target list. Multiple matches are an error + * unless they refer to identical values; for example, + * we allow SELECT a, a FROM table ORDER BY a + * but not SELECT a AS b, b FROM table ORDER BY b + * If no match is found, we fall through and treat the identifier + * as an expression. + * + * 2. ORDER/GROUP BY IntegerConstant + * This means to use the n'th item in the existing target list. + * Note that it would make no sense to order/group by an actual + * constant, so this does not create a conflict with our extension + * to order/group by an expression. + * + * Note that pre-existing resjunk targets must not be used in either case. + *---------- */ - foreach(l, tlist) + if (IsA(node, Ident) && ((Ident *) node)->indirection == NIL) { - TargetEntry *target = (TargetEntry *) lfirst(l); - Resdom *resnode = target->resdom; - Var *var = (Var *) target->expr; - char *resname = resnode->resname; - int test_rtable_pos = var->varno; - - ++targetlist_pos; - - switch (nodeTag(node)) + char *name = ((Ident *) node)->name; + foreach(tl, tlist) { - case T_Attr: - if (strcmp(resname, name) == 0 && rtable_pos == test_rtable_pos) - { - - /* - * Check for only 1 table & ORDER BY -ambiguity does - * not matter here - */ - if (clause == ORDER_CLAUSE && relCnt == 1) - return target; - - if (target_result != NULL) - elog(ERROR, "%s BY '%s' is ambiguous", clauseText[clause], name); - else - target_result = target; - /* Stay in loop to check for ambiguity */ - } - break; - - case T_Ident: - if (strcmp(resname, name) == 0) - { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Resdom *resnode = tle->resdom; - /* - * Check for only 1 table & ORDER BY -ambiguity does - * not matter here - */ - if (clause == ORDER_CLAUSE && relCnt == 1) - return target; - - if (target_result != NULL) - elog(ERROR, "%s BY '%s' is ambiguous", clauseText[clause], name); - else - target_result = target; - /* Stay in loop to check for ambiguity */ - } - break; - - case T_A_Const: - if (target_pos == targetlist_pos) + if (!resnode->resjunk && + strcmp(resnode->resname, name) == 0) + { + if (target_result != NULL) { - /* Can't be ambigious and we got what we came for */ - return target; + if (! equal(target_result->expr, tle->expr)) + elog(ERROR, "%s BY '%s' is ambiguous", + clauseText[clause], name); } - break; + else + target_result = tle; + /* Stay in loop to check for ambiguity */ + } + } + if (target_result != NULL) + return target_result; /* return the first match */ + } + if (IsA(node, A_Const)) + { + Value *val = &((A_Const *) node)->val; + int targetlist_pos = 0; + int target_pos; + + if (nodeTag(val) != T_Integer) + elog(ERROR, "Non-integer constant in %s BY", clauseText[clause]); + target_pos = intVal(val); + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Resdom *resnode = tle->resdom; - case T_FuncCall: - case T_A_Expr: - if (equal(expr, target->expr)) - { + if (!resnode->resjunk) + { + if (++targetlist_pos == target_pos) + return tle; /* return the unique match */ + } + } + elog(ERROR, "%s BY position %d is not in target list", + clauseText[clause], target_pos); + } - /* - * Check for only 1 table & ORDER BY -ambiguity does - * not matter here - */ - if (clause == ORDER_CLAUSE) - return target; + /* + * Otherwise, we have an expression (this is a Postgres extension + * not found in SQL92). Convert the untransformed node to a + * transformed expression, and search for a match in the tlist. + * NOTE: it doesn't really matter whether there is more than one + * match. Also, we are willing to match a resjunk target here, + * though the above cases must ignore resjunk targets. + */ + expr = transformExpr(pstate, node, EXPR_COLUMN_FIRST); - if (target_result != NULL) - elog(ERROR, "GROUP BY has ambiguous expression"); - else - target_result = target; - } - break; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); - default: - elog(ERROR, "Illegal %s BY node = %d", clauseText[clause], nodeTag(node)); - } + if (equal(expr, tle->expr)) + return tle; } /* @@ -475,51 +448,13 @@ findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause) * the end of the target list. This target is set to be resjunk = * TRUE so that it will not be projected into the final tuple. */ - if (target_result == NULL) - { - switch (nodeTag(node)) - { - case T_Attr: - target_result = MakeTargetEntryIdent(pstate, node, - &((Attr *) node)->relname, NULL, - ((Attr *) node)->relname, true); - lappend(tlist, target_result); - break; - - case T_Ident: - target_result = MakeTargetEntryIdent(pstate, node, - &((Ident *) node)->name, NULL, - ((Ident *) node)->name, true); - lappend(tlist, target_result); - break; - - case T_A_Const: - - /* - * If we got this far, then must have been an out-of-range - * column number - */ - elog(ERROR, "%s BY position %d is not in target list", clauseText[clause], target_pos); - break; - - case T_FuncCall: - case T_A_Expr: - target_result = MakeTargetEntryExpr(pstate, "resjunk", expr, false, true); - lappend(tlist, target_result); - break; - - default: - elog(ERROR, "Illegal %s BY node = %d", clauseText[clause], nodeTag(node)); - break; - } - } + target_result = transformTargetEntry(pstate, node, expr, NULL, true); + lappend(tlist, target_result); return target_result; } - - /* * transformGroupClause - * transform a Group By clause @@ -529,55 +464,42 @@ List * transformGroupClause(ParseState *pstate, List *grouplist, List *targetlist) { List *glist = NIL, - *gl = NIL; + *gl, + *othergl; + int nextgroupref = 1; - while (grouplist != NIL) + foreach(gl, grouplist) { - GroupClause *grpcl = makeNode(GroupClause); TargetEntry *restarget; Resdom *resdom; - restarget = findTargetlistEntry(pstate, lfirst(grouplist), targetlist, GROUP_CLAUSE); - + restarget = findTargetlistEntry(pstate, lfirst(gl), + targetlist, GROUP_CLAUSE); resdom = restarget->resdom; - grpcl->grpOpoid = oprid(oper("<", - resdom->restype, - resdom->restype, false)); - if (glist == NIL) - { - int groupref = length(glist) + 1; - restarget->resdom->resgroupref = groupref; - grpcl->tleGroupref = groupref; + /* avoid making duplicate grouplist entries */ + foreach(othergl, glist) + { + GroupClause *gcl = (GroupClause *) lfirst(othergl); - gl = glist = lcons(grpcl, NIL); + if (equal(get_groupclause_expr(gcl, targetlist), + restarget->expr)) + break; } - else - { - List *i; - foreach(i, glist) - { - GroupClause *gcl = (GroupClause *) lfirst(i); + if (othergl == NIL) /* not in grouplist already */ + { + GroupClause *grpcl = makeNode(GroupClause); - if (equal(get_groupclause_expr(gcl, targetlist), - restarget->expr)) - break; - } - if (i == NIL) /* not in grouplist already */ - { - int groupref = length(glist) + 1; + grpcl->tleGroupref = nextgroupref++; + resdom->resgroupref = grpcl->tleGroupref; - restarget->resdom->resgroupref = groupref; - grpcl->tleGroupref = groupref; + grpcl->grpOpoid = oprid(oper("<", + resdom->restype, + resdom->restype, false)); - lnext(gl) = lcons(grpcl, NIL); - gl = lnext(gl); - } - else - pfree(grpcl); /* get rid of this */ + glist = lappend(glist, grpcl); } - grouplist = lnext(grouplist); } return glist; @@ -604,7 +526,8 @@ transformSortClause(ParseState *pstate, TargetEntry *restarget; Resdom *resdom; - restarget = findTargetlistEntry(pstate, sortby->node, targetlist, ORDER_CLAUSE); + restarget = findTargetlistEntry(pstate, sortby->node, + targetlist, ORDER_CLAUSE); sortcl->resdom = resdom = restarget->resdom; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index a936554ad9d..e9a67453342 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.54 1999/07/17 20:17:23 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.55 1999/07/19 00:26:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -30,7 +30,7 @@ static Node *parser_typecast(Value *expr, TypeName *typename, int32 atttypmod); static Node *transformAttr(ParseState *pstate, Attr *att, int precedence); static Node *transformIdent(ParseState *pstate, Ident *ident, int precedence); static Node *transformIndirection(ParseState *pstate, Node *basenode, - List *indirection, int precedence); + List *indirection); /* * transformExpr - @@ -81,7 +81,7 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) param->paramtype = (Oid) toid; param->param_tlist = (List *) NULL; result = transformIndirection(pstate, (Node *) param, - pno->indirection, precedence); + pno->indirection); break; } case T_A_Expr: @@ -467,37 +467,12 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) } static Node * -transformIndirection(ParseState *pstate, Node *basenode, - List *indirection, int precedence) +transformIndirection(ParseState *pstate, Node *basenode, List *indirection) { - List *idx; - if (indirection == NIL) return basenode; - foreach (idx, indirection) - { - A_Indices *ai = (A_Indices *) lfirst(idx); - Node *lexpr = NULL, - *uexpr; - - /* uidx is always present, but lidx might be null */ - if (ai->lidx != NULL) - { - lexpr = transformExpr(pstate, ai->lidx, precedence); - if (exprType(lexpr) != INT4OID) - elog(ERROR, "array index expressions must be int4's"); - } - uexpr = transformExpr(pstate, ai->uidx, precedence); - if (exprType(uexpr) != INT4OID) - elog(ERROR, "array index expressions must be int4's"); - ai->lidx = lexpr; - ai->uidx = uexpr; - /* - * note we reuse the list of A_Indices nodes, make sure - * we don't free them! Otherwise, make a new list here - */ - } - return (Node *) make_array_ref(basenode, indirection); + return (Node *) transformArraySubscripts(pstate, basenode, + indirection, false, NULL); } static Node * @@ -505,11 +480,9 @@ transformAttr(ParseState *pstate, Attr *att, int precedence) { Node *basenode; - /* what if att->attrs == "*"? */ basenode = ParseNestedFuncOrColumn(pstate, att, &pstate->p_last_resno, precedence); - return transformIndirection(pstate, basenode, - att->indirection, precedence); + return transformIndirection(pstate, basenode, att->indirection); } static Node * @@ -555,7 +528,7 @@ transformIdent(ParseState *pstate, Ident *ident, int precedence) Oid exprType(Node *expr) { - Oid type = (Oid) 0; + Oid type = (Oid) InvalidOid; if (!expr) return type; @@ -622,6 +595,42 @@ exprType(Node *expr) return type; } +/* + * exprTypmod - + * returns the type-specific attrmod of the expression, if it can be + * determined. In most cases, it can't and we return -1. + */ +int32 +exprTypmod(Node *expr) +{ + if (!expr) + return -1; + + switch (nodeTag(expr)) + { + case T_Var: + return ((Var *) expr)->vartypmod; + case T_Const: + { + /* Be smart about string constants... */ + Const *con = (Const *) expr; + switch (con->consttype) + { + case BPCHAROID: + if (! con->constisnull) + return VARSIZE(DatumGetPointer(con->constvalue)); + break; + default: + break; + } + } + break; + default: + break; + } + return -1; +} + static Node * parser_typecast(Value *expr, TypeName *typename, int32 atttypmod) { diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index 53c756670c3..80a8543d5a5 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.28 1999/07/17 20:17:24 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.29 1999/07/19 00:26:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -24,6 +24,7 @@ #include "parser/parse_node.h" #include "parser/parse_oper.h" #include "parser/parse_relation.h" +#include "parser/parse_target.h" #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -222,164 +223,184 @@ make_var(ParseState *pstate, Oid relid, char *refname, } /* - * make_array_ref() -- Make an array reference node. + * transformArraySubscripts() + * Transform array subscripting. This is used for both + * array fetch and array assignment. * - * Array references can hang off of arbitrary nested dot (or - * function invocation) expressions. This routine takes a - * tree generated by ParseFunc() and an array index and - * generates a new array reference tree. We do some simple - * typechecking to be sure the dereference is valid in the - * type system, but we don't do any bounds checking here. + * In an array fetch, we are given a source array value and we produce an + * expression that represents the result of extracting a single array element + * or an array slice. * - * indirection is a list of A_Indices + * In an array assignment, we are given a destination array value plus a + * source value that is to be assigned to a single element or a slice of + * that array. We produce an expression that represents the new array value + * with the source data inserted into the right part of the array. + * + * pstate Parse state + * arrayBase Already-transformed expression for the array as a whole + * indirection Untransformed list of subscripts (must not be NIL) + * forceSlice If true, treat subscript as array slice in all cases + * assignFrom NULL for array fetch, else transformed expression for source. */ -ArrayRef * -make_array_ref(Node *expr, - List *indirection) +ArrayRef * +transformArraySubscripts(ParseState *pstate, + Node *arrayBase, + List *indirection, + bool forceSlice, + Node *assignFrom) { - Oid typearray; + Oid typearray, + typeelement, + typeresult; HeapTuple type_tuple; Form_pg_type type_struct_array, type_struct_element; - ArrayRef *aref; - Oid reftype; + bool isSlice = forceSlice; List *upperIndexpr = NIL; List *lowerIndexpr = NIL; + List *idx; + ArrayRef *aref; - typearray = exprType(expr); + /* Get the type tuple for the array */ + typearray = exprType(arrayBase); type_tuple = SearchSysCacheTuple(TYPOID, ObjectIdGetDatum(typearray), 0, 0, 0); - if (!HeapTupleIsValid(type_tuple)) - elog(ERROR, "make_array_ref: Cache lookup failed for type %u\n", + elog(ERROR, "transformArraySubscripts: Cache lookup failed for array type %u", typearray); - - /* get the array type struct from the type tuple */ type_struct_array = (Form_pg_type) GETSTRUCT(type_tuple); - if (type_struct_array->typelem == InvalidOid) - elog(ERROR, "make_array_ref: type %s is not an array", + typeelement = type_struct_array->typelem; + if (typeelement == InvalidOid) + elog(ERROR, "transformArraySubscripts: type %s is not an array", type_struct_array->typname); - /* get the type tuple for the element type */ + /* Get the type tuple for the array element type */ type_tuple = SearchSysCacheTuple(TYPOID, - ObjectIdGetDatum(type_struct_array->typelem), + ObjectIdGetDatum(typeelement), 0, 0, 0); if (!HeapTupleIsValid(type_tuple)) - elog(ERROR, "make_array_ref: Cache lookup failed for type %u\n", - typearray); - + elog(ERROR, "transformArraySubscripts: Cache lookup failed for array element type %u", + typeelement); type_struct_element = (Form_pg_type) GETSTRUCT(type_tuple); - while (indirection != NIL) + /* + * A list containing only single subscripts refers to a single array + * element. If any of the items are double subscripts (lower:upper), + * then the subscript expression means an array slice operation. + * In this case, we supply a default lower bound of 1 for any items + * that contain only a single subscript. + * The forceSlice parameter forces us to treat the operation as a + * slice, even if no lower bounds are mentioned. Otherwise, + * we have to prescan the indirection list to see if there are any + * double subscripts. + */ + if (! isSlice) { - A_Indices *ind = lfirst(indirection); - - if (ind->lidx) - - /* - * XXX assumes all lower indices non null in this case - */ - lowerIndexpr = lappend(lowerIndexpr, ind->lidx); - - upperIndexpr = lappend(upperIndexpr, ind->uidx); - indirection = lnext(indirection); + foreach (idx, indirection) + { + A_Indices *ai = (A_Indices *) lfirst(idx); + if (ai->lidx != NULL) + { + isSlice = true; + break; + } + } } - aref = makeNode(ArrayRef); - aref->refattrlength = type_struct_array->typlen; - aref->refelemlength = type_struct_element->typlen; - aref->refelemtype = type_struct_array->typelem; - aref->refelembyval = type_struct_element->typbyval; - aref->refupperindexpr = upperIndexpr; - aref->reflowerindexpr = lowerIndexpr; - aref->refexpr = expr; - aref->refassgnexpr = NULL; - if (lowerIndexpr == NIL) /* accessing a single array element */ - reftype = aref->refelemtype; + /* The type represented by the subscript expression is the element type + * if we are fetching a single element, but it is the same as the array + * type if we are fetching a slice or storing. + */ + if (isSlice || assignFrom != NULL) + typeresult = typearray; else -/* request to clip a part of the array, the result is another array */ - reftype = typearray; + typeresult = typeelement; /* - * we change it to reflect the true type; since the original - * refelemtype doesn't seem to get used anywhere. - ay 10/94 + * Transform the subscript expressions. */ - aref->refelemtype = reftype; - - return aref; -} - - -/* make_array_set() - */ -ArrayRef * -make_array_set(Expr *target_expr, - List *upperIndexpr, - List *lowerIndexpr, - Expr *expr) -{ - Oid typearray; - HeapTuple type_tuple; - Form_pg_type type_struct_array; - Form_pg_type type_struct_element; - ArrayRef *aref; - Oid reftype; - - typearray = exprType((Node *) target_expr); - - type_tuple = SearchSysCacheTuple(TYPOID, - ObjectIdGetDatum(typearray), - 0, 0, 0); - - if (!HeapTupleIsValid(type_tuple)) - elog(ERROR, "make_array_ref: Cache lookup failed for type %u\n", - typearray); - - /* get the array type struct from the type tuple */ - type_struct_array = (Form_pg_type) GETSTRUCT(type_tuple); + foreach (idx, indirection) + { + A_Indices *ai = (A_Indices *) lfirst(idx); + Node *subexpr; - if (type_struct_array->typelem == InvalidOid) - elog(ERROR, "make_array_ref: type %s is not an array", - type_struct_array->typname); - /* get the type tuple for the element type */ - type_tuple = SearchSysCacheTuple(TYPOID, - ObjectIdGetDatum(type_struct_array->typelem), - 0, 0, 0); + if (isSlice) + { + if (ai->lidx) + { + subexpr = transformExpr(pstate, ai->lidx, EXPR_COLUMN_FIRST); + /* If it's not int4 already, try to coerce */ + subexpr = CoerceTargetExpr(pstate, subexpr, + exprType(subexpr), INT4OID); + if (subexpr == NULL) + elog(ERROR, "array index expressions must be integers"); + } + else + { + /* Make a constant 1 */ + subexpr = (Node *) makeConst(INT4OID, + sizeof(int32), + Int32GetDatum(1), + false, + true, /* pass by value */ + false, + false); + } + lowerIndexpr = lappend(lowerIndexpr, subexpr); + } + subexpr = transformExpr(pstate, ai->uidx, EXPR_COLUMN_FIRST); + /* If it's not int4 already, try to coerce */ + subexpr = CoerceTargetExpr(pstate, subexpr, + exprType(subexpr), INT4OID); + if (subexpr == NULL) + elog(ERROR, "array index expressions must be integers"); + upperIndexpr = lappend(upperIndexpr, subexpr); + } - if (!HeapTupleIsValid(type_tuple)) - elog(ERROR, "make_array_ref: Cache lookup failed for type %u\n", - typearray); + /* + * If doing an array store, coerce the source value to the right type. + */ + if (assignFrom != NULL) + { + Oid typesource = exprType(assignFrom); + Oid typeneeded = isSlice ? typearray : typeelement; - type_struct_element = (Form_pg_type) GETSTRUCT(type_tuple); + if (typesource != InvalidOid) + { + if (typesource != typeneeded) + { + assignFrom = CoerceTargetExpr(pstate, assignFrom, + typesource, typeneeded); + if (assignFrom == NULL) + elog(ERROR, "Array assignment requires type '%s'" + " but expression is of type '%s'" + "\n\tYou will need to rewrite or cast the expression", + typeidTypeName(typeneeded), + typeidTypeName(typesource)); + } + } + } + /* + * Ready to build the ArrayRef node. + */ aref = makeNode(ArrayRef); aref->refattrlength = type_struct_array->typlen; aref->refelemlength = type_struct_element->typlen; - aref->refelemtype = type_struct_array->typelem; + aref->refelemtype = typeresult; /* XXX should save element type too */ aref->refelembyval = type_struct_element->typbyval; aref->refupperindexpr = upperIndexpr; aref->reflowerindexpr = lowerIndexpr; - aref->refexpr = (Node *) target_expr; - aref->refassgnexpr = (Node *) expr; - - /* accessing a single array element? */ - if (lowerIndexpr == NIL) - reftype = aref->refelemtype; - - /* otherwise, request to set a part of the array, by another array */ - else - reftype = typearray; - - aref->refelemtype = reftype; + aref->refexpr = arrayBase; + aref->refassgnexpr = assignFrom; return aref; } /* - * * make_const - * * - takes a lispvalue, (as returned to the yacc routine by the lexer) diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index d3662aea826..79b08635f02 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.25 1999/07/17 20:17:25 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.26 1999/07/19 00:26:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,10 +25,8 @@ #include "utils/builtins.h" #include "utils/lsyscache.h" -static void checkTargetTypes(ParseState *pstate, char *target_colname, - char *refname, char *colname); -struct +static struct { char *field; int code; @@ -97,7 +95,6 @@ refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up) int index; List *temp; - if (sublevels_up) *sublevels_up = 0; @@ -175,7 +172,7 @@ colnameRangeTableEntry(ParseState *pstate, char *colname) /* * put new entry in pstate p_rtable structure, or return pointer * if pstate null -*/ + */ RangeTblEntry * addRangeTableEntry(ParseState *pstate, char *relname, @@ -239,38 +236,31 @@ addRangeTableEntry(ParseState *pstate, List * expandAll(ParseState *pstate, char *relname, char *refname, int *this_resno) { + List *te_list = NIL; + RangeTblEntry *rte; Relation rel; - List *te_tail = NIL, - *te_head = NIL; - Var *varnode; int varattno, maxattrs; - RangeTblEntry *rte; rte = refnameRangeTableEntry(pstate, refname); if (rte == NULL) rte = addRangeTableEntry(pstate, relname, refname, FALSE, FALSE); rel = heap_open(rte->relid); - if (rel == NULL) elog(ERROR, "Unable to expand all -- heap_open failed on %s", rte->refname); maxattrs = RelationGetNumberOfAttributes(rel); - for (varattno = 0; varattno <= maxattrs - 1; varattno++) + for (varattno = 0; varattno < maxattrs; varattno++) { char *attrname; - char *resname = NULL; + Var *varnode; TargetEntry *te = makeNode(TargetEntry); - attrname = pstrdup((rel->rd_att->attrs[varattno]->attname).data); - varnode = (Var *) make_var(pstate, rte->relid, refname, attrname); - - handleTargetColname(pstate, &resname, refname, attrname); - if (resname != NULL) - attrname = resname; + attrname = pstrdup(rel->rd_att->attrs[varattno]->attname.data); + varnode = make_var(pstate, rte->relid, refname, attrname); /* * Even if the elements making up a set are complex, the set @@ -285,15 +275,12 @@ expandAll(ParseState *pstate, char *relname, char *refname, int *this_resno) (Oid) 0, false); te->expr = (Node *) varnode; - if (te_head == NIL) - te_head = te_tail = lcons(te, NIL); - else - te_tail = lappend(te_tail, te); + te_list = lappend(te_list, te); } heap_close(rel); - return te_head; + return te_list; } /* @@ -378,102 +365,3 @@ attnumTypeId(Relation rd, int attid) */ return rd->rd_att->attrs[attid - 1]->atttypid; } - -/* handleTargetColname() - * Use column names from insert. - */ -void -handleTargetColname(ParseState *pstate, char **resname, - char *refname, char *colname) -{ - if (pstate->p_is_insert) - { - if (pstate->p_insert_columns != NIL) - { - Ident *id = lfirst(pstate->p_insert_columns); - - *resname = id->name; - pstate->p_insert_columns = lnext(pstate->p_insert_columns); - } - else - elog(ERROR, "INSERT has more expressions than target columns"); - } - if (pstate->p_is_insert || pstate->p_is_update) - checkTargetTypes(pstate, *resname, refname, colname); -} - -/* checkTargetTypes() - * Checks value and target column types. - */ -static void -checkTargetTypes(ParseState *pstate, char *target_colname, - char *refname, char *colname) -{ - Oid attrtype_id, - attrtype_target; - int resdomno_id, - resdomno_target; - RangeTblEntry *rte; - - if (target_colname == NULL || colname == NULL) - return; - - if (refname != NULL) - rte = refnameRangeTableEntry(pstate, refname); - else - { - rte = colnameRangeTableEntry(pstate, colname); - if (rte == (RangeTblEntry *) NULL) - elog(ERROR, "Attribute %s not found", colname); - refname = rte->refname; - } - -/* - if (pstate->p_is_insert && rte == pstate->p_target_rangetblentry) - elog(ERROR, "'%s' not available in this context", colname); -*/ - resdomno_id = get_attnum(rte->relid, colname); - attrtype_id = get_atttype(rte->relid, resdomno_id); - - resdomno_target = attnameAttNum(pstate->p_target_relation, target_colname); - attrtype_target = attnumTypeId(pstate->p_target_relation, resdomno_target); - -#ifdef NOT_USED - if ((attrtype_id != attrtype_target) - || (get_atttypmod(rte->relid, resdomno_id) != - get_atttypmod(pstate->p_target_relation->rd_id, resdomno_target))) - { - if (can_coerce_type(1, &attrtype_id, &attrtype_target)) - { - Node *expr = coerce_type(pstate, expr, attrtype_id, - attrtype_target, - get_atttypmod(pstate->p_target_relation->rd_id, resdomno_target)); - - elog(ERROR, "Type %s(%d) can be coerced to match target column %s(%d)", - colname, get_atttypmod(rte->relid, resdomno_id), - target_colname, get_atttypmod(pstate->p_target_relation->rd_id, resdomno_target)); - } - else - { - elog(ERROR, "Type or size of %s(%d) does not match target column %s(%d)", - colname, get_atttypmod(rte->relid, resdomno_id), - target_colname, get_atttypmod(pstate->p_target_relation->rd_id, resdomno_target)); - } - } -#else - if (attrtype_id != attrtype_target) - elog(ERROR, "Type of '%s' does not match target column '%s'", - colname, target_colname); - - if (attrtype_id == BPCHAROID && - get_atttypmod(rte->relid, resdomno_id) != - get_atttypmod(pstate->p_target_relation->rd_id, resdomno_target)) - elog(ERROR, "Length of '%s' is not equal to the length of target column '%s'", - colname, target_colname); - if (attrtype_id == VARCHAROID && - get_atttypmod(rte->relid, resdomno_id) > - get_atttypmod(pstate->p_target_relation->rd_id, resdomno_target)) - elog(ERROR, "Length of '%s' is longer than length of target column '%s'", - colname, target_colname); -#endif -} diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 35d064b0980..48973f67d9b 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.45 1999/07/17 20:17:26 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.46 1999/07/19 00:26:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -24,636 +24,244 @@ #include "utils/syscache.h" +static Node *SizeTargetExpr(ParseState *pstate, Node *expr, + Oid attrtype, int32 attrtypmod); static List *ExpandAllTables(ParseState *pstate); static char *FigureColname(Node *expr, Node *resval); -static Node *SizeTargetExpr(ParseState *pstate, - Node *expr, - Oid attrtype, - int32 attrtypmod); -/* MakeTargetEntryIdent() - * Transforms an Ident Node to a Target Entry - * Created this function to allow the ORDER/GROUP BY clause to be able - * to construct a TargetEntry from an Ident. - * - * resjunk = TRUE will hide the target entry in the final result tuple. - * daveh@insightdist.com 5/20/98 +/* + * transformTargetEntry() + * Transform any ordinary "expression-type" node into a targetlist entry. + * This is exported so that parse_clause.c can generate targetlist entries + * for ORDER/GROUP BY items that are not already in the targetlist. * - * Added more conversion logic to match up types from source to target. - * - thomas 1998-06-02 + * node the (untransformed) parse tree for the value expression. + * expr the transformed expression, or NULL if caller didn't do it yet. + * colname the column name to be assigned, or NULL if none yet set. + * resjunk true if the target should be marked resjunk, ie, it is not + * wanted in the final projected tuple. */ TargetEntry * -MakeTargetEntryIdent(ParseState *pstate, +transformTargetEntry(ParseState *pstate, Node *node, - char **resname, - char *refname, + Node *expr, char *colname, bool resjunk) { - Node *expr = NULL; - Oid attrtype_target; - TargetEntry *tent = makeNode(TargetEntry); - - if (pstate->p_is_insert && !resjunk) - { - - /* - * Assign column name of destination column to the new TLE. XXX - * this is probably WRONG in INSERT ... SELECT case, since - * handling of GROUP BY and so forth probably should use the - * source table's names not the destination's names. - */ - if (pstate->p_insert_columns != NIL) - { - Ident *id = lfirst(pstate->p_insert_columns); - - *resname = id->name; - pstate->p_insert_columns = lnext(pstate->p_insert_columns); - } - else - elog(ERROR, "INSERT has more expressions than target columns"); - } - - if ((pstate->p_is_insert || pstate->p_is_update) && !resjunk) - { - Oid attrtype_id; - int resdomno_id, - resdomno_target; - RangeTblEntry *rte; - char *target_colname; - int32 attrtypmod, - attrtypmod_target; - - target_colname = *resname; - - /* - * this looks strange to me, returning an empty TargetEntry bjm - * 1998/08/24 - */ - if (target_colname == NULL || colname == NULL) - return tent; - - if (refname != NULL) - rte = refnameRangeTableEntry(pstate, refname); - else - { - rte = colnameRangeTableEntry(pstate, colname); - if (rte == (RangeTblEntry *) NULL) - elog(ERROR, "Attribute %s not found", colname); - refname = rte->refname; - } - - resdomno_id = get_attnum(rte->relid, colname); - attrtype_id = get_atttype(rte->relid, resdomno_id); - attrtypmod = get_atttypmod(rte->relid, resdomno_id); - - resdomno_target = attnameAttNum(pstate->p_target_relation, target_colname); - attrtype_target = attnumTypeId(pstate->p_target_relation, resdomno_target); - attrtypmod_target = get_atttypmod(pstate->p_target_relation->rd_id, resdomno_target); - - if ((attrtype_id != attrtype_target) - || ((attrtypmod_target >= 0) && (attrtypmod_target != attrtypmod))) - { - if (can_coerce_type(1, &attrtype_id, &attrtype_target)) - { - expr = coerce_type(pstate, node, attrtype_id, - attrtype_target, - get_atttypmod(pstate->p_target_relation->rd_id, resdomno_target)); - expr = transformExpr(pstate, expr, EXPR_COLUMN_FIRST); - tent = MakeTargetEntryExpr(pstate, *resname, expr, false, false); - expr = tent->expr; - } - else - { - elog(ERROR, "Unable to convert %s to %s for column %s", - typeidTypeName(attrtype_id), typeidTypeName(attrtype_target), - target_colname); - } - } - } - - /* - * here we want to look for column names only, not relation names - * (even though they can be stored in Ident nodes, too) - */ - if (expr == NULL) - { - char *name; - int32 type_mod; - - name = ((*resname != NULL) ? *resname : colname); - - expr = transformExpr(pstate, node, EXPR_COLUMN_FIRST); - - attrtype_target = exprType(expr); - if (nodeTag(expr) == T_Var) - type_mod = ((Var *) expr)->vartypmod; - else - type_mod = -1; - - tent->resdom = makeResdom((AttrNumber) pstate->p_last_resno++, - (Oid) attrtype_target, - type_mod, - name, - (Index) 0, - (Oid) 0, - resjunk); - tent->expr = expr; - } - - return tent; -} /* MakeTargetEntryIdent() */ - - -/* MakeTargetEntryExpr() - * Make a TargetEntry from an expression. - * arrayRef is a list of transformed A_Indices. - * - * For type mismatches between expressions and targets, use the same - * techniques as for function and operator type coersion. - * - thomas 1998-05-08 - * - * Added resjunk flag and made extern so that it can be use by GROUP/ - * ORDER BY a function or expression not in the target_list - * - daveh@insightdist.com 1998-07-31 - */ -TargetEntry * -MakeTargetEntryExpr(ParseState *pstate, - char *colname, - Node *expr, - List *arrayRef, - bool resjunk) -{ - Oid type_id, - attrtype; - int32 type_mod, - attrtypmod; - int resdomno; - Relation rd; - bool attrisset; + Oid type_id; + int32 type_mod; Resdom *resnode; + /* Transform the node if caller didn't do it already */ if (expr == NULL) - elog(ERROR, "Invalid use of NULL expression (internal error)"); + expr = transformExpr(pstate, node, EXPR_COLUMN_FIRST); type_id = exprType(expr); - if (nodeTag(expr) == T_Var) - type_mod = ((Var *) expr)->vartypmod; - else - type_mod = -1; + type_mod = exprTypmod(expr); - /* Process target columns that will be receiving results */ - if ((pstate->p_is_insert || pstate->p_is_update) && !resjunk) + if (colname == NULL) { - - /* - * insert or update query -- insert, update work only on one - * relation, so multiple occurence of same resdomno is bogus + /* Generate a suitable column name for a column without any + * explicit 'AS ColumnName' clause. */ - rd = pstate->p_target_relation; - Assert(rd != NULL); - resdomno = attnameAttNum(rd, colname); - if (resdomno <= 0) - elog(ERROR, "Cannot assign to system attribute '%s'", colname); - attrisset = attnameIsSet(rd, colname); - attrtype = attnumTypeId(rd, resdomno); - if ((arrayRef != NIL) && (lfirst(arrayRef) == NIL)) - attrtype = GetArrayElementType(attrtype); - attrtypmod = rd->rd_att->attrs[resdomno - 1]->atttypmod; - - /* - * Check for InvalidOid since that seems to indicate a NULL - * constant... - */ - if (type_id != InvalidOid) - { - /* Mismatch on types? then try to coerce to target... */ - if (attrtype != type_id) - { - Oid typelem; - - if (arrayRef && !(((A_Indices *) lfirst(arrayRef))->lidx)) - typelem = typeTypElem(typeidType(attrtype)); - else - typelem = attrtype; - - expr = CoerceTargetExpr(pstate, expr, type_id, typelem); - - if (!HeapTupleIsValid(expr)) - elog(ERROR, "Attribute '%s' is of type '%s'" - " but expression is of type '%s'" - "\n\tYou will need to rewrite or cast the expression", - colname, - typeidTypeName(attrtype), - typeidTypeName(type_id)); - } - - /* - * Apparently going to a fixed-length string? Then explicitly - * size for storage... - */ - if (attrtypmod > 0) - expr = SizeTargetExpr(pstate, expr, attrtype, attrtypmod); - } - - if (arrayRef != NIL) - { - Expr *target_expr; - Attr *att = makeNode(Attr); - List *ar = arrayRef; - List *upperIndexpr = NIL; - List *lowerIndexpr = NIL; - - att->relname = pstrdup(RelationGetRelationName(rd)->data); - att->attrs = lcons(makeString(colname), NIL); - target_expr = (Expr *) ParseNestedFuncOrColumn(pstate, att, - &pstate->p_last_resno, - EXPR_COLUMN_FIRST); - while (ar != NIL) - { - A_Indices *ind = lfirst(ar); - - if (lowerIndexpr || (!upperIndexpr && ind->lidx)) - { - - /* - * XXX assume all lowerIndexpr is non-null in this - * case - */ - lowerIndexpr = lappend(lowerIndexpr, ind->lidx); - } - upperIndexpr = lappend(upperIndexpr, ind->uidx); - ar = lnext(ar); - } - - expr = (Node *) make_array_set(target_expr, - upperIndexpr, - lowerIndexpr, - (Expr *) expr); - attrtype = attnumTypeId(rd, resdomno); - attrtypmod = get_atttypmod(RelationGetRelid(rd), resdomno); - } - } - else - { - resdomno = pstate->p_last_resno++; - attrtype = type_id; - attrtypmod = type_mod; + colname = FigureColname(expr, node); } - resnode = makeResdom((AttrNumber) resdomno, - (Oid) attrtype, - attrtypmod, + resnode = makeResdom((AttrNumber) pstate->p_last_resno++, + type_id, + type_mod, colname, (Index) 0, - (Oid) 0, + (Oid) InvalidOid, resjunk); return makeTargetEntry(resnode, expr); -} /* MakeTargetEntryExpr() */ - -/* - * MakeTargetEntryCase() - * Make a TargetEntry from a case node. - */ -static TargetEntry * -MakeTargetEntryCase(ParseState *pstate, - ResTarget *res) -{ - TargetEntry *tent; - CaseExpr *expr; - Resdom *resnode; - int resdomno; - Oid type_id; - int32 type_mod; - - expr = (CaseExpr *) transformExpr(pstate, (Node *) res->val, EXPR_COLUMN_FIRST); - - type_id = expr->casetype; - type_mod = -1; - handleTargetColname(pstate, &res->name, NULL, NULL); - if (res->name == NULL) - res->name = FigureColname((Node *) expr, res->val); - - resdomno = pstate->p_last_resno++; - resnode = makeResdom((AttrNumber) resdomno, - (Oid) type_id, - type_mod, - res->name, - (Index) 0, - (Oid) 0, - false); - - tent = makeNode(TargetEntry); - tent->resdom = resnode; - tent->expr = (Node *) expr; +} - return tent; -} /* MakeTargetEntryCase() */ /* - * MakeTargetEntryComplex() - * Make a TargetEntry from a complex node. + * transformTargetList() + * Turns a list of ResTarget's into a list of TargetEntry's. + * + * At this point, we don't care whether we are doing SELECT, INSERT, + * or UPDATE; we just transform the given expressions. */ -static TargetEntry * -MakeTargetEntryComplex(ParseState *pstate, - ResTarget *res) +List * +transformTargetList(ParseState *pstate, List *targetlist) { - Node *expr = transformExpr(pstate, (Node *) res->val, EXPR_COLUMN_FIRST); + List *p_target = NIL; - handleTargetColname(pstate, &res->name, NULL, NULL); - /* note indirection has not been transformed */ - if (pstate->p_is_insert && res->indirection != NIL) + while (targetlist != NIL) { - /* this is an array assignment */ - char *val; - char *str, - *save_str; - List *elt; - int i = 0, - ndims; - int lindx[MAXDIM], - uindx[MAXDIM]; - int resdomno; - Relation rd; - Value *constval; - - if (exprType(expr) != UNKNOWNOID || !IsA(expr, Const)) - elog(ERROR, "String constant expected (internal error)"); - - val = (char *) textout((struct varlena *) - ((Const *) expr)->constvalue); - str = save_str = (char *) palloc(strlen(val) + MAXDIM * 25 + 2); - foreach(elt, res->indirection) - { - A_Indices *aind = (A_Indices *) lfirst(elt); + ResTarget *res = (ResTarget *) lfirst(targetlist); - aind->uidx = transformExpr(pstate, aind->uidx, EXPR_COLUMN_FIRST); - if (!IsA(aind->uidx, Const)) - elog(ERROR, "Array Index for Append should be a constant"); + if (IsA(res->val, Attr)) + { + Attr *att = (Attr *) res->val; - uindx[i] = ((Const *) aind->uidx)->constvalue; - if (aind->lidx != NULL) + if (att->relname != NULL && strcmp(att->relname, "*") == 0) { - aind->lidx = transformExpr(pstate, aind->lidx, EXPR_COLUMN_FIRST); - if (!IsA(aind->lidx, Const)) - elog(ERROR, "Array Index for Append should be a constant"); - - lindx[i] = ((Const *) aind->lidx)->constvalue; + /* + * Target item is a single '*', expand all tables + * (eg. SELECT * FROM emp) + */ + p_target = nconc(p_target, + ExpandAllTables(pstate)); + } + else if (att->attrs != NIL && + strcmp(strVal(lfirst(att->attrs)), "*") == 0) + { + /* + * Target item is relation.*, expand that table + * (eg. SELECT emp.*, dname FROM emp, dept) + */ + p_target = nconc(p_target, + expandAll(pstate, + att->relname, + att->relname, + &pstate->p_last_resno)); } else - lindx[i] = 1; - if (lindx[i] > uindx[i]) - elog(ERROR, "Lower index cannot be greater than upper index"); - - sprintf(str, "[%d:%d]", lindx[i], uindx[i]); - str += strlen(str); - i++; + { + /* Plain Attr node, treat it as an expression */ + p_target = lappend(p_target, + transformTargetEntry(pstate, + res->val, + NULL, + res->name, + false)); + } } - sprintf(str, "=%s", val); - rd = pstate->p_target_relation; - Assert(rd != NULL); - resdomno = attnameAttNum(rd, res->name); - ndims = attnumAttNelems(rd, resdomno); - if (i != ndims) - elog(ERROR, "Array dimensions do not match"); - - constval = makeNode(Value); - constval->type = T_String; - constval->val.str = save_str; - return MakeTargetEntryExpr(pstate, res->name, - (Node *) make_const(constval), - NULL, false); - pfree(save_str); - } - else - { - /* this is not an array assignment */ - char *colname = res->name; - - if (colname == NULL) + else { - - /* - * if you're wondering why this is here, look at the yacc - * grammar for why a name can be missing. -ay - */ - colname = FigureColname(expr, res->val); + /* Everything else but Attr */ + p_target = lappend(p_target, + transformTargetEntry(pstate, + res->val, + NULL, + res->name, + false)); } - if (res->indirection) - { - List *ilist = res->indirection; - - while (ilist != NIL) - { - A_Indices *ind = lfirst(ilist); - ind->lidx = transformExpr(pstate, ind->lidx, EXPR_COLUMN_FIRST); - ind->uidx = transformExpr(pstate, ind->uidx, EXPR_COLUMN_FIRST); - ilist = lnext(ilist); - } - } - res->name = colname; - return MakeTargetEntryExpr(pstate, res->name, expr, - res->indirection, false); + targetlist = lnext(targetlist); } + + return p_target; } + /* - * MakeTargetEntryAttr() - * Make a TargetEntry from a complex node. + * updateTargetListEntry() + * This is used in INSERT and UPDATE statements only. It prepares a + * TargetEntry for assignment to a column of the target table. + * This includes coercing the given value to the target column's type + * (if necessary), and dealing with any subscripts attached to the target + * column itself. + * + * pstate parse state + * tle target list entry to be modified + * colname target column name (ie, name of attribute to be assigned to) + * indirection subscripts for target column, if any */ -static TargetEntry * -MakeTargetEntryAttr(ParseState *pstate, - ResTarget *res) +void +updateTargetListEntry(ParseState *pstate, + TargetEntry *tle, + char *colname, + List *indirection) { - Oid type_id; - int32 type_mod; - Attr *att = (Attr *) res->val; - Node *result; - char *attrname; - char *resname; - Resdom *resnode; + Oid type_id = exprType(tle->expr); /* type of value provided */ + Oid attrtype; /* type of target column */ + int32 attrtypmod; + Resdom *resnode = tle->resdom; + Relation rd = pstate->p_target_relation; int resdomno; - List *attrs = att->attrs; - TargetEntry *tent; - attrname = strVal(lfirst(att->attrs)); + Assert(rd != NULL); + resdomno = attnameAttNum(rd, colname); + if (resdomno <= 0) + elog(ERROR, "Cannot assign to system attribute '%s'", colname); + attrtype = attnumTypeId(rd, resdomno); + attrtypmod = rd->rd_att->attrs[resdomno - 1]->atttypmod; /* - * Target item is fully specified: ie. relation.attribute + * If there are subscripts on the target column, prepare an + * array assignment expression. This will generate an array value + * that the source value has been inserted into, which can then + * be placed in the new tuple constructed by INSERT or UPDATE. + * Note that transformArraySubscripts takes care of type coercion. */ - result = ParseNestedFuncOrColumn(pstate, att, &pstate->p_last_resno, EXPR_COLUMN_FIRST); - handleTargetColname(pstate, &res->name, att->relname, attrname); - if (att->indirection != NIL) + if (indirection) { - List *ilist = att->indirection; - - while (ilist != NIL) + Attr *att = makeNode(Attr); + Node *arrayBase; + ArrayRef *aref; + + att->relname = pstrdup(RelationGetRelationName(rd)->data); + att->attrs = lcons(makeString(colname), NIL); + arrayBase = ParseNestedFuncOrColumn(pstate, att, + &pstate->p_last_resno, + EXPR_COLUMN_FIRST); + aref = transformArraySubscripts(pstate, arrayBase, + indirection, + pstate->p_is_insert, + tle->expr); + if (pstate->p_is_insert) { - A_Indices *ind = lfirst(ilist); - - ind->lidx = transformExpr(pstate, ind->lidx, EXPR_COLUMN_FIRST); - ind->uidx = transformExpr(pstate, ind->uidx, EXPR_COLUMN_FIRST); - ilist = lnext(ilist); + /* + * The command is INSERT INTO table (arraycol[subscripts]) ... + * so there is not really a source array value to work with. + * Let the executor do something reasonable, if it can. + * Notice that we forced transformArraySubscripts to treat + * the subscripting op as an array-slice op above, so the + * source data will have been coerced to array type. + */ + aref->refexpr = NULL; /* signal there is no source array */ } - result = (Node *) make_array_ref(result, att->indirection); + tle->expr = (Node *) aref; } - type_id = exprType(result); - if (nodeTag(result) == T_Var) - type_mod = ((Var *) result)->vartypmod; else - type_mod = -1; - /* move to last entry */ - while (lnext(attrs) != NIL) - attrs = lnext(attrs); - resname = (res->name) ? res->name : strVal(lfirst(attrs)); - if (pstate->p_is_insert || pstate->p_is_update) { - Relation rd; - /* - * insert or update query -- insert, update work only on one - * relation, so multiple occurence of same resdomno is bogus + * For normal non-subscripted target column, do type checking + * and coercion. But accept InvalidOid, which indicates the + * source is a NULL constant. */ - rd = pstate->p_target_relation; - Assert(rd != NULL); - resdomno = attnameAttNum(rd, res->name); - } - else - resdomno = pstate->p_last_resno++; - resnode = makeResdom((AttrNumber) resdomno, - (Oid) type_id, - type_mod, - resname, - (Index) 0, - (Oid) 0, - false); - tent = makeNode(TargetEntry); - tent->resdom = resnode; - tent->expr = result; - return tent; -} - - -/* transformTargetList() - * Turns a list of ResTarget's into a list of TargetEntry's. - */ -List * -transformTargetList(ParseState *pstate, List *targetlist) -{ - List *p_target = NIL; - List *tail_p_target = NIL; - - while (targetlist != NIL) - { - ResTarget *res = (ResTarget *) lfirst(targetlist); - TargetEntry *tent = NULL; - - switch (nodeTag(res->val)) - { - case T_Ident: - { - char *identname; - - identname = ((Ident *) res->val)->name; - tent = MakeTargetEntryIdent(pstate, - (Node *) res->val, &res->name, NULL, identname, false); - break; - } - case T_ParamNo: - case T_FuncCall: - case T_A_Const: - case T_A_Expr: - { - tent = MakeTargetEntryComplex(pstate, res); - break; - } - case T_CaseExpr: - { - tent = MakeTargetEntryCase(pstate, res); - break; - } - case T_Attr: - { - bool expand_star = false; - char *attrname; - Attr *att = (Attr *) res->val; - - /* - * Target item is a single '*', expand all tables (eg. - * SELECT * FROM emp) - */ - if (att->relname != NULL && !strcmp(att->relname, "*")) - { - if (tail_p_target == NIL) - p_target = tail_p_target = ExpandAllTables(pstate); - else - lnext(tail_p_target) = ExpandAllTables(pstate); - expand_star = true; - } - else - { - - /* - * Target item is relation.*, expand the table - * (eg. SELECT emp.*, dname FROM emp, dept) - */ - attrname = strVal(lfirst(att->attrs)); - if (att->attrs != NIL && !strcmp(attrname, "*")) - { - - /* - * tail_p_target is the target list we're - * building in the while loop. Make sure we - * fix it after appending more nodes. - */ - if (tail_p_target == NIL) - p_target = tail_p_target = expandAll(pstate, att->relname, - att->relname, &pstate->p_last_resno); - else - lnext(tail_p_target) = expandAll(pstate, att->relname, att->relname, - &pstate->p_last_resno); - expand_star = true; - } - } - if (expand_star) - { - while (lnext(tail_p_target) != NIL) - /* make sure we point to the last target entry */ - tail_p_target = lnext(tail_p_target); - - /* - * skip rest of while loop - */ - targetlist = lnext(targetlist); - continue; - } - else - { - tent = MakeTargetEntryAttr(pstate, res); - break; - } - } - default: - /* internal error */ - elog(ERROR, "Unable to transform targetlist (internal error)"); - break; - } - - if (p_target == NIL) - p_target = tail_p_target = lcons(tent, NIL); - else + if (type_id != InvalidOid) { - lnext(tail_p_target) = lcons(tent, NIL); - tail_p_target = lnext(tail_p_target); + if (type_id != attrtype) + { + tle->expr = CoerceTargetExpr(pstate, tle->expr, + type_id, attrtype); + if (tle->expr == NULL) + elog(ERROR, "Attribute '%s' is of type '%s'" + " but expression is of type '%s'" + "\n\tYou will need to rewrite or cast the expression", + colname, + typeidTypeName(attrtype), + typeidTypeName(type_id)); + } + /* + * If the target is a fixed-length type, it may need a length + * coercion as well as a type coercion. + */ + if (attrtypmod > 0 && + attrtypmod != exprTypmod(tle->expr)) + tle->expr = SizeTargetExpr(pstate, tle->expr, + attrtype, attrtypmod); } - targetlist = lnext(targetlist); } - return p_target; -} /* transformTargetList() */ + /* + * The result of the target expression should now match the destination + * column's type. Also, reset the resname and resno to identify + * the destination column --- rewriter and planner depend on that! + */ + resnode->restype = attrtype; + resnode->restypmod = attrtypmod; + resnode->resname = colname; + resnode->resno = (AttrNumber) resdomno; +} Node * @@ -689,12 +297,19 @@ CoerceTargetExpr(ParseState *pstate, expr = NULL; return expr; -} /* CoerceTargetExpr() */ +} -/* SizeTargetExpr() - * Apparently going to a fixed-length string? - * Then explicitly size for storage... +/* + * SizeTargetExpr() + * + * If the target column type possesses a function named for the type + * and having parameter signature (columntype, int4), we assume that + * the type requires coercion to its own length and that the said + * function should be invoked to do that. + * + * Currently, "bpchar" (ie, char(N)) is the only such type, but try + * to be more general than a hard-wired test... */ static Node * SizeTargetExpr(ParseState *pstate, @@ -702,13 +317,10 @@ SizeTargetExpr(ParseState *pstate, Oid attrtype, int32 attrtypmod) { - int i; - HeapTuple ftup; char *funcname; Oid oid_array[MAXFARGS]; - - FuncCall *func; - A_Const *cons; + HeapTuple ftup; + int i; funcname = typeidTypeName(attrtype); oid_array[0] = attrtype; @@ -725,6 +337,9 @@ SizeTargetExpr(ParseState *pstate, if (HeapTupleIsValid(ftup)) { + FuncCall *func; + A_Const *cons; + func = makeNode(FuncCall); func->funcname = funcname; @@ -737,29 +352,27 @@ SizeTargetExpr(ParseState *pstate, } return expr; -} /* SizeTargetExpr() */ +} /* * makeTargetNames - * generate a list of column names if not supplied or - * test supplied column names to make sure they are in target table + * test supplied column names to make sure they are in target table. * (used exclusively for inserts) */ List * makeTargetNames(ParseState *pstate, List *cols) { - List *tl = NULL; - - /* Generate ResTarget if not supplied */ - if (cols == NIL) { - int numcol; - int i; + /* + * Generate default column list for INSERT. + */ Form_pg_attribute *attr = pstate->p_target_relation->rd_att->attrs; + int numcol = pstate->p_target_relation->rd_rel->relnatts; + int i; - numcol = pstate->p_target_relation->rd_rel->relnatts; for (i = 0; i < numcol; i++) { Ident *id = makeNode(Ident); @@ -768,27 +381,30 @@ makeTargetNames(ParseState *pstate, List *cols) StrNCpy(id->name, attr[i]->attname.data, NAMEDATALEN); id->indirection = NIL; id->isRel = false; - if (tl == NIL) - cols = tl = lcons(id, NIL); - else - { - lnext(tl) = lcons(id, NIL); - tl = lnext(tl); - } + cols = lappend(cols, id); } } else { + /* + * Do initial validation of user-supplied INSERT column list. + */ + List *tl; + foreach(tl, cols) { - List *nxt; char *name = ((Ident *) lfirst(tl))->name; + List *nxt; - /* elog on failure */ + /* Lookup column name, elog on failure */ attnameAttNum(pstate->p_target_relation, name); + /* Check for duplicates */ foreach(nxt, lnext(tl)) - if (!strcmp(name, ((Ident *) lfirst(nxt))->name)) - elog(ERROR, "Attribute '%s' should be specified only once", name); + { + if (strcmp(name, ((Ident *) lfirst(nxt))->name) == 0) + elog(ERROR, "Attribute '%s' specified more than once", + name); + } } } @@ -804,57 +420,37 @@ static List * ExpandAllTables(ParseState *pstate) { List *target = NIL; - List *legit_rtable = NIL; List *rt, *rtable; rtable = pstate->p_rtable; if (pstate->p_is_rule) { - /* * skip first two entries, "*new*" and "*current*" */ - rtable = lnext(lnext(pstate->p_rtable)); + rtable = lnext(lnext(rtable)); } /* SELECT *; */ - if (rtable == NULL) + if (rtable == NIL) elog(ERROR, "Wildcard with no tables specified."); - /* - * go through the range table and make a list of range table entries - * which we will expand. - */ foreach(rt, rtable) { RangeTblEntry *rte = lfirst(rt); /* - * we only expand those specify in the from clause. (This will + * 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; - legit_rtable = lappend(legit_rtable, rte); - } - - foreach(rt, legit_rtable) - { - RangeTblEntry *rte = lfirst(rt); - List *temp = target; - if (temp == NIL) - target = expandAll(pstate, rte->relname, rte->refname, - &pstate->p_last_resno); - else - { - while (temp != NIL && lnext(temp) != NIL) - temp = lnext(temp); - lnext(temp) = expandAll(pstate, rte->relname, rte->refname, - &pstate->p_last_resno); - } + target = nconc(target, + expandAll(pstate, rte->relname, rte->refname, + &pstate->p_last_resno)); } return target; } @@ -862,29 +458,47 @@ ExpandAllTables(ParseState *pstate) /* * FigureColname - * if the name of the resulting column is not specified in the target - * list, we have to guess. + * list, we have to guess a suitable name. The SQL spec provides some + * guidance, but not much... * */ static char * FigureColname(Node *expr, Node *resval) { - switch (nodeTag(expr)) + /* Some of these are easiest to do with the untransformed node */ + switch (nodeTag(resval)) { - case T_Aggref: - return (char *) ((Aggref *) expr)->aggname; - case T_Expr: - if (((Expr *) expr)->opType == FUNC_EXPR) + case T_Ident: + return ((Ident *) resval)->name; + case T_Attr: { - if (nodeTag(resval) == T_FuncCall) - return ((FuncCall *) resval)->funcname; + List *attrs = ((Attr *) resval)->attrs; + if (attrs) + { + while (lnext(attrs) != NIL) + attrs = lnext(attrs); + return strVal(lfirst(attrs)); + } } break; + default: + break; + } + /* Otherwise, work with the transformed node */ + switch (nodeTag(expr)) + { + case T_Expr: + if (((Expr *) expr)->opType == FUNC_EXPR && IsA(resval, FuncCall)) + return ((FuncCall *) resval)->funcname; + break; + case T_Aggref: + return ((Aggref *) expr)->aggname; case T_CaseExpr: { char *name; name = FigureColname(((CaseExpr *) expr)->defresult, resval); - if (!strcmp(name, "?column?")) + if (strcmp(name, "?column?") == 0) name = "case"; return name; } |