From 7f76eab140e703b7847b107245a669e2010886c0 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 19 Jul 1999 00:26:20 +0000 Subject: [PATCH] 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. --- src/backend/executor/execQual.c | 85 ++- src/backend/parser/analyze.c | 345 ++++++----- src/backend/parser/parse_clause.c | 349 +++++------ src/backend/parser/parse_expr.c | 79 +-- src/backend/parser/parse_node.c | 251 ++++---- src/backend/parser/parse_relation.c | 134 +---- src/backend/parser/parse_target.c | 904 ++++++++-------------------- src/include/parser/parse_clause.h | 8 +- src/include/parser/parse_expr.h | 8 +- src/include/parser/parse_node.h | 14 +- src/include/parser/parse_relation.h | 14 +- src/include/parser/parse_target.h | 25 +- 12 files changed, 877 insertions(+), 1339 deletions(-) diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 649c189ea9..a3eb943ab9 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.57 1999/07/17 20:16:57 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.58 1999/07/19 00:26:15 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -86,31 +86,44 @@ ExecEvalArrayRef(ArrayRef *arrayRef, bool *isNull, bool *isDone) { - bool dummy; + ArrayType *array_scanner; + List *elt; int i = 0, j = 0; - ArrayType *array_scanner; - List *upperIndexpr, - *lowerIndexpr; - Node *assgnexpr; - List *elt; IntArray upper, lower; int *lIndex; - char *dataPtr; + bool dummy; *isNull = false; - array_scanner = (ArrayType *) ExecEvalExpr(arrayRef->refexpr, - econtext, - isNull, - isDone); - if (*isNull) - return (Datum) NULL; - upperIndexpr = arrayRef->refupperindexpr; - - foreach(elt, upperIndexpr) + if (arrayRef->refexpr != NULL) { + array_scanner = (ArrayType *) ExecEvalExpr(arrayRef->refexpr, + econtext, + isNull, + isDone); + if (*isNull) + return (Datum) NULL; + } + else + { + /* Null refexpr indicates we are doing an INSERT into an array column. + * For now, we just take the refassgnexpr (which the parser will have + * ensured is an array value) and return it as-is, ignoring any + * subscripts that may have been supplied in the INSERT column list. + * This is a kluge, but it's not real clear what the semantics ought + * to be... + */ + array_scanner = NULL; + } + + foreach(elt, arrayRef->refupperindexpr) + { + if (i >= MAXDIM) + elog(ERROR, "ExecEvalArrayRef: can only handle %d dimensions", + MAXDIM); + upper.indx[i++] = (int32) ExecEvalExpr((Node *) lfirst(elt), econtext, isNull, @@ -119,12 +132,14 @@ ExecEvalArrayRef(ArrayRef *arrayRef, return (Datum) NULL; } - lowerIndexpr = arrayRef->reflowerindexpr; - lIndex = NULL; - if (lowerIndexpr != NIL) + if (arrayRef->reflowerindexpr != NIL) { - foreach(elt, lowerIndexpr) + foreach(elt, arrayRef->reflowerindexpr) { + if (j >= MAXDIM) + elog(ERROR, "ExecEvalArrayRef: can only handle %d dimensions", + MAXDIM); + lower.indx[j++] = (int32) ExecEvalExpr((Node *) lfirst(elt), econtext, isNull, @@ -137,30 +152,42 @@ ExecEvalArrayRef(ArrayRef *arrayRef, "ExecEvalArrayRef: upper and lower indices mismatch"); lIndex = lower.indx; } - - assgnexpr = arrayRef->refassgnexpr; - if (assgnexpr != NULL) + else { - dataPtr = (char *) ExecEvalExpr((Node *) - assgnexpr, econtext, - isNull, &dummy); + lIndex = NULL; + } + + if (arrayRef->refassgnexpr != NULL) + { + Datum sourceData = ExecEvalExpr(arrayRef->refassgnexpr, + econtext, + isNull, + &dummy); if (*isNull) return (Datum) NULL; + execConstByVal = arrayRef->refelembyval; execConstLen = arrayRef->refelemlength; + + if (array_scanner == NULL) + return sourceData; /* XXX do something else? */ + if (lIndex == NULL) - return (Datum) array_set(array_scanner, i, upper.indx, dataPtr, + return (Datum) array_set(array_scanner, i, upper.indx, + (char *) sourceData, arrayRef->refelembyval, arrayRef->refelemlength, arrayRef->refattrlength, isNull); return (Datum) array_assgn(array_scanner, i, upper.indx, lower.indx, - (ArrayType *) dataPtr, + (ArrayType *) sourceData, arrayRef->refelembyval, arrayRef->refelemlength, isNull); } + execConstByVal = arrayRef->refelembyval; execConstLen = arrayRef->refelemlength; + if (lIndex == NULL) return (Datum) array_ref(array_scanner, i, upper.indx, arrayRef->refelembyval, diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 40a66b4873..d4a1ef57ef 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...) + *---------- + */ + + /* set up a range table --- note INSERT target is not in it yet */ + makeRangeTable(pstate, stmt->fromClause, &fromQual); qry->uniqueFlag = stmt->unique; - /* fix the target list */ - icolumns = pstate->p_insert_columns = makeTargetNames(pstate, stmt->cols); - 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; + qry->qual = transformWhereClause(pstate, stmt->whereClause, fromQual); - /* - * 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; - } - - 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.) */ - if ((qry->hasAggs == false) && (qry->havingQual != NULL)) + 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) { - elog(ERROR, "SELECT/HAVING requires aggregates to be valid"); - return (Query *) NIL; + 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? + */ + rd_att = pstate->p_target_relation->rd_att; + if (rd_att->constr && rd_att->constr->num_defval > 0) + { + 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,15 +1023,20 @@ 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 ce9583c174..f8dc7777b0 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,35 +30,40 @@ 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) +{ + /* Currently, nothing to do except this: */ + parseFromClause(pstate, frmList, qual); +} + +/* + * 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; - parseFromClause(pstate, frmList, qual); - - if (relname == NULL) - return; - if ((refnameRangeTablePosn(pstate, relname, &sublevels_up) == 0) || (sublevels_up != 0)) rte = addRangeTableEntry(pstate, relname, relname, FALSE, FALSE); @@ -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; + List *tl; + Node *expr; - /* Pull out some values before looping thru target list */ - switch (nodeTag(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. + *---------- + */ + if (IsA(node, Ident) && ((Ident *) node)->indirection == NIL) { - 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; + char *name = ((Ident *) node)->name; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Resdom *resnode = tle->resdom; - case T_Ident: - name = ((Ident *) node)->name; - relCnt = length(pstate->p_rtable); - break; + if (!resnode->resjunk && + strcmp(resnode->resname, name) == 0) + { + if (target_result != NULL) + { + if (! equal(target_result->expr, tle->expr)) + elog(ERROR, "%s BY '%s' is ambiguous", + clauseText[clause], name); + } + 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; - case T_A_Const: - val = &((A_Const *) node)->val; + 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; - 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)); + 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); } /* - * Loop through target entries and try to match to node + * 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. */ - foreach(l, tlist) + expr = transformExpr(pstate, node, EXPR_COLUMN_FIRST); + + foreach(tl, tlist) { - TargetEntry *target = (TargetEntry *) lfirst(l); - Resdom *resnode = target->resdom; - Var *var = (Var *) target->expr; - char *resname = resnode->resname; - int test_rtable_pos = var->varno; + TargetEntry *tle = (TargetEntry *) lfirst(tl); - ++targetlist_pos; - - switch (nodeTag(node)) - { - 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) - { - - /* - * 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) - { - /* Can't be ambigious and we got what we came for */ - return target; - } - break; - - case T_FuncCall: - case T_A_Expr: - if (equal(expr, target->expr)) - { - - /* - * Check for only 1 table & ORDER BY -ambiguity does - * not matter here - */ - if (clause == ORDER_CLAUSE) - return target; - - if (target_result != NULL) - elog(ERROR, "GROUP BY has ambiguous expression"); - else - target_result = target; - } - break; - - 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) + + /* avoid making duplicate grouplist entries */ + foreach(othergl, glist) { - int groupref = length(glist) + 1; + GroupClause *gcl = (GroupClause *) lfirst(othergl); - restarget->resdom->resgroupref = groupref; - grpcl->tleGroupref = groupref; - - gl = glist = lcons(grpcl, NIL); + if (equal(get_groupclause_expr(gcl, targetlist), + restarget->expr)) + break; } - else + + if (othergl == NIL) /* not in grouplist already */ { - List *i; + GroupClause *grpcl = makeNode(GroupClause); - foreach(i, glist) - { - GroupClause *gcl = (GroupClause *) lfirst(i); + grpcl->tleGroupref = nextgroupref++; + resdom->resgroupref = grpcl->tleGroupref; - if (equal(get_groupclause_expr(gcl, targetlist), - restarget->expr)) - break; - } - if (i == NIL) /* not in grouplist already */ - { - int groupref = length(glist) + 1; + grpcl->grpOpoid = oprid(oper("<", + resdom->restype, + resdom->restype, false)); - restarget->resdom->resgroupref = groupref; - grpcl->tleGroupref = groupref; - - 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 a936554ad9..e9a6745334 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 53c756670c..80a8543d5a 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_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); - } - 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; - else -/* request to clip a part of the array, the result is another array */ - reftype = typearray; - /* - * we change it to reflect the true type; since the original - * refelemtype doesn't seem to get used anywhere. - ay 10/94 + * 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. */ - aref->refelemtype = reftype; + if (! isSlice) + { + foreach (idx, indirection) + { + A_Indices *ai = (A_Indices *) lfirst(idx); + if (ai->lidx != NULL) + { + isSlice = true; + break; + } + } + } - return aref; -} + /* 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 + typeresult = typeelement; + /* + * Transform the subscript expressions. + */ + foreach (idx, indirection) + { + A_Indices *ai = (A_Indices *) lfirst(idx); + Node *subexpr; -/* 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; + 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); + } - typearray = exprType((Node *) target_expr); + /* + * 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_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); - - 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 (!HeapTupleIsValid(type_tuple)) - elog(ERROR, "make_array_ref: Cache lookup failed for type %u\n", - typearray); - - 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 d3662aea82..79b08635f0 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 35d064b098..48973f67d9 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. +/* + * 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. * - * resjunk = TRUE will hide the target entry in the final result tuple. - * daveh@insightdist.com 5/20/98 - * - * 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. - */ -static TargetEntry * -MakeTargetEntryComplex(ParseState *pstate, - ResTarget *res) -{ - Node *expr = transformExpr(pstate, (Node *) res->val, EXPR_COLUMN_FIRST); - - handleTargetColname(pstate, &res->name, NULL, NULL); - /* note indirection has not been transformed */ - if (pstate->p_is_insert && res->indirection != 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); - - aind->uidx = transformExpr(pstate, aind->uidx, EXPR_COLUMN_FIRST); - if (!IsA(aind->uidx, Const)) - elog(ERROR, "Array Index for Append should be a constant"); - - uindx[i] = ((Const *) aind->uidx)->constvalue; - if (aind->lidx != NULL) - { - 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; - } - 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++; - } - 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) - { - - /* - * 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); - } - 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); - } -} - -/* - * MakeTargetEntryAttr() - * Make a TargetEntry from a complex node. - */ -static TargetEntry * -MakeTargetEntryAttr(ParseState *pstate, - ResTarget *res) -{ - Oid type_id; - int32 type_mod; - Attr *att = (Attr *) res->val; - Node *result; - char *attrname; - char *resname; - Resdom *resnode; - int resdomno; - List *attrs = att->attrs; - TargetEntry *tent; - - attrname = strVal(lfirst(att->attrs)); - - /* - * Target item is fully specified: ie. relation.attribute - */ - result = ParseNestedFuncOrColumn(pstate, att, &pstate->p_last_resno, EXPR_COLUMN_FIRST); - handleTargetColname(pstate, &res->name, att->relname, attrname); - if (att->indirection != NIL) - { - List *ilist = att->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); - } - result = (Node *) make_array_ref(result, att->indirection); - } - 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 - */ - 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() +/* + * 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. */ 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)) + if (IsA(res->val, Attr)) { - case T_Ident: - { - char *identname; + Attr *att = (Attr *) res->val; - 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 (att->relname != NULL && strcmp(att->relname, "*") == 0) + { + /* + * 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 + { + /* Plain Attr node, treat it as an expression */ + p_target = lappend(p_target, + transformTargetEntry(pstate, + res->val, + NULL, + res->name, + false)); + } } - - if (p_target == NIL) - p_target = tail_p_target = lcons(tent, NIL); else { - lnext(tail_p_target) = lcons(tent, NIL); - tail_p_target = lnext(tail_p_target); + /* Everything else but Attr */ + p_target = lappend(p_target, + transformTargetEntry(pstate, + res->val, + NULL, + res->name, + false)); } + targetlist = lnext(targetlist); } return p_target; -} /* transformTargetList() */ +} + + +/* + * 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 + */ +void +updateTargetListEntry(ParseState *pstate, + TargetEntry *tle, + char *colname, + List *indirection) +{ + 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; + + 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; + + /* + * 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. + */ + if (indirection) + { + 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) + { + /* + * 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 */ + } + tle->expr = (Node *) aref; + } + else + { + /* + * For normal non-subscripted target column, do type checking + * and coercion. But accept InvalidOid, which indicates the + * source is a NULL constant. + */ + if (type_id != InvalidOid) + { + 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); + } + } + + /* + * 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; } diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h index a50ab4e039..3b1aafa07d 100644 --- a/src/include/parser/parse_clause.h +++ b/src/include/parser/parse_clause.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: parse_clause.h,v 1.11 1999/07/15 23:04:01 momjian Exp $ + * $Id: parse_clause.h,v 1.12 1999/07/19 00:26:16 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -15,8 +15,10 @@ #include "parser/parse_node.h" -extern void makeRangeTable(ParseState *pstate, char *relname, List *frmList, Node **qual); -extern Node *transformWhereClause(ParseState *pstate, Node *where, Node *using); +extern void makeRangeTable(ParseState *pstate, List *frmList, Node **qual); +extern void setTargetTable(ParseState *pstate, char *relname); +extern Node *transformWhereClause(ParseState *pstate, Node *where, + Node *using); extern List *transformGroupClause(ParseState *pstate, List *grouplist, List *targetlist); extern List *transformSortClause(ParseState *pstate, diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h index cbc4095e33..3bd4d3fb47 100644 --- a/src/include/parser/parse_expr.h +++ b/src/include/parser/parse_expr.h @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * - * parse_exer.h + * parse_expr.h * * * * Copyright (c) 1994, Regents of the University of California * - * $Id: parse_expr.h,v 1.13 1999/07/15 23:04:02 momjian Exp $ + * $Id: parse_expr.h,v 1.14 1999/07/19 00:26:16 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,8 +16,12 @@ #include "parser/parse_node.h" #include "parser/parse_type.h" +#define EXPR_COLUMN_FIRST 1 +#define EXPR_RELATION_FIRST 2 + extern Node *transformExpr(ParseState *pstate, Node *expr, int precedence); extern Oid exprType(Node *expr); +extern int32 exprTypmod(Node *expr); extern Node *parser_typecast2(Node *expr, Oid exprType, Type tp, int32 attypmod); #endif /* PARSE_EXPR_H */ diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index 3dd2257db5..1591743e06 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -5,7 +5,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: parse_node.h,v 1.14 1999/07/15 23:04:02 momjian Exp $ + * $Id: parse_node.h,v 1.15 1999/07/19 00:26:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,7 +20,6 @@ typedef struct ParseState { int p_last_resno; List *p_rtable; - List *p_insert_columns; struct ParseState *parentParseState; bool p_hasAggs; bool p_hasSubLinks; @@ -36,12 +35,11 @@ extern ParseState *make_parsestate(ParseState *parentParseState); extern Expr *make_op(char *opname, Node *ltree, Node *rtree); extern Var *make_var(ParseState *pstate, Oid relid, char *refname, char *attrname); -extern ArrayRef *make_array_ref(Node *expr, - List *indirection); -extern ArrayRef *make_array_set(Expr *target_expr, - List *upperIndexpr, - List *lowerIndexpr, - Expr *expr); +extern ArrayRef *transformArraySubscripts(ParseState *pstate, + Node *arrayBase, + List *indirection, + bool forceSlice, + Node *assignFrom); extern Const *make_const(Value *value); #endif /* PARSE_NODE_H */ diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index e44f89c79b..68e5ac7bf1 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -1,17 +1,17 @@ /*------------------------------------------------------------------------- * - * parse_query.h - * prototypes for parse_query.c. + * parse_relation.h + * prototypes for parse_relation.c. * * * Copyright (c) 1994, Regents of the University of California * - * $Id: parse_relation.h,v 1.11 1999/07/15 23:04:03 momjian Exp $ + * $Id: parse_relation.h,v 1.12 1999/07/19 00:26:17 tgl Exp $ * *------------------------------------------------------------------------- */ -#ifndef PARSE_QUERY_H -#define PARSE_RANGE_H +#ifndef PARSE_RELATION_H +#define PARSE_RELATION_H #include "parser/parse_node.h" @@ -30,7 +30,5 @@ extern int attnameAttNum(Relation rd, char *a); extern bool attnameIsSet(Relation rd, char *name); extern int attnumAttNelems(Relation rd, int attid); extern Oid attnumTypeId(Relation rd, int attid); -extern void handleTargetColname(ParseState *pstate, char **resname, - char *refname, char *colname); -#endif /* PARSE_RANGE_H */ +#endif /* PARSE_RELATION_H */ diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h index bd520d1bab..c2babecb76 100644 --- a/src/include/parser/parse_target.h +++ b/src/include/parser/parse_target.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: parse_target.h,v 1.14 1999/07/15 23:04:03 momjian Exp $ + * $Id: parse_target.h,v 1.15 1999/07/19 00:26:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -15,23 +15,14 @@ #include "parser/parse_node.h" -#define EXPR_COLUMN_FIRST 1 -#define EXPR_RELATION_FIRST 2 - extern List *transformTargetList(ParseState *pstate, List *targetlist); -extern List *makeTargetNames(ParseState *pstate, List *cols); -extern TargetEntry *MakeTargetEntryIdent(ParseState *pstate, - Node *node, - char **resname, - char *refname, - char *colname, - bool resjunk); +extern TargetEntry *transformTargetEntry(ParseState *pstate, + Node *node, Node *expr, + char *colname, bool resjunk); +extern void updateTargetListEntry(ParseState *pstate, TargetEntry *tle, + char *colname, List *indirection); extern Node *CoerceTargetExpr(ParseState *pstate, Node *expr, - Oid type_id, Oid attrtype); -TargetEntry *MakeTargetEntryExpr(ParseState *pstate, - char *colname, - Node *expr, - List *arrayRef, - bool resjunk); + Oid type_id, Oid attrtype); +extern List *makeTargetNames(ParseState *pstate, List *cols); #endif /* PARSE_TARGET_H */