diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 1999-07-19 00:26:20 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 1999-07-19 00:26:20 +0000 |
commit | 7f76eab140e703b7847b107245a669e2010886c0 (patch) | |
tree | 8c9d01c654aa8de0e14c0d446817ba60c33d0199 /src/backend/parser/parse_target.c | |
parent | c9814427722798751fa5bf254f597d722d76b5e3 (diff) | |
download | postgresql-7f76eab140e703b7847b107245a669e2010886c0.tar.gz postgresql-7f76eab140e703b7847b107245a669e2010886c0.zip |
Rewrite parser's handling of INSERT ... SELECT so that processing
of the SELECT part of the statement is just like a plain SELECT. All
INSERT-specific processing happens after the SELECT parsing is done.
This eliminates many problems, e.g. INSERT ... SELECT ... GROUP BY using
the wrong column labels. Ensure that DEFAULT clauses are coerced to
the target column type, whether or not stored clause produces the right
type. Substantial cleanup of parser's array support.
Diffstat (limited to 'src/backend/parser/parse_target.c')
-rw-r--r-- | src/backend/parser/parse_target.c | 874 |
1 files changed, 244 insertions, 630 deletions
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; } |