aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/analyze.c345
-rw-r--r--src/backend/parser/parse_clause.c349
-rw-r--r--src/backend/parser/parse_expr.c79
-rw-r--r--src/backend/parser/parse_node.c245
-rw-r--r--src/backend/parser/parse_relation.c134
-rw-r--r--src/backend/parser/parse_target.c874
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;
}