X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Fparser%2Fparse_target.c;h=10892bc292d73014c9645a36e2b2b25ad30150d9;hb=2cf57c8f8d060711c1ad7e1dd6cc1115a2839b47;hp=35d064b09808d5c44a8a26afb7a00fb7a9c2c053;hpb=3406901a2928627477374769416a32b01a08b131;p=postgresql diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 35d064b098..10892bc292 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -3,895 +3,628 @@ * parse_target.c * handle target lists * - * Copyright (c) 1994, Regents of the University of California + * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California * * * 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.101 2003/05/06 00:20:32 tgl Exp $ * *------------------------------------------------------------------------- */ - #include "postgres.h" + +#include "miscadmin.h" #include "nodes/makefuncs.h" +#include "parser/parsetree.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_func.h" #include "parser/parse_relation.h" #include "parser/parse_target.h" +#include "parser/parse_type.h" #include "utils/builtins.h" -#include "utils/lsyscache.h" -#include "utils/syscache.h" +static void markTargetListOrigin(ParseState *pstate, Resdom *res, Var *var); static List *ExpandAllTables(ParseState *pstate); -static char *FigureColname(Node *expr, Node *resval); -static Node *SizeTargetExpr(ParseState *pstate, - Node *expr, - Oid attrtype, - int32 attrtypmod); +static char *FigureColname(Node *node); +static int FigureColnameInternal(Node *node, char **name); -/* 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); + Oid type_id; + int32 type_mod; + Resdom *resnode; - if (pstate->p_is_insert && !resjunk) - { + /* Transform the node if caller didn't do it already */ + if (expr == NULL) + expr = transformExpr(pstate, node); - /* - * 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); + if (IsA(expr, RangeVar)) + elog(ERROR, "You can't use relation names alone in the target list, try relation.*."); - *resname = id->name; - pstate->p_insert_columns = lnext(pstate->p_insert_columns); - } - else - elog(ERROR, "INSERT has more expressions than target columns"); - } + type_id = exprType(expr); + type_mod = exprTypmod(expr); - if ((pstate->p_is_insert || pstate->p_is_update) && !resjunk) + if (colname == NULL) { - 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 + * Generate a suitable column name for a column without any + * explicit 'AS ColumnName' clause. */ - 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); - } - } + colname = FigureColname(node); } - /* - * 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; - } + resnode = makeResdom((AttrNumber) pstate->p_next_resno++, + type_id, + type_mod, + colname, + resjunk); - return tent; -} /* MakeTargetEntryIdent() */ + return makeTargetEntry(resnode, (Expr *) expr); +} -/* 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 +/* + * transformTargetList() + * Turns a list of ResTarget's into a list of TargetEntry's. * - * 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 + * At this point, we don't care whether we are doing SELECT, INSERT, + * or UPDATE; we just transform the given expressions. */ -TargetEntry * -MakeTargetEntryExpr(ParseState *pstate, - char *colname, - Node *expr, - List *arrayRef, - bool resjunk) +List * +transformTargetList(ParseState *pstate, List *targetlist) { - Oid type_id, - attrtype; - int32 type_mod, - attrtypmod; - int resdomno; - Relation rd; - bool attrisset; - Resdom *resnode; - - if (expr == NULL) - elog(ERROR, "Invalid use of NULL expression (internal error)"); - - type_id = exprType(expr); - if (nodeTag(expr) == T_Var) - type_mod = ((Var *) expr)->vartypmod; - else - type_mod = -1; + List *p_target = NIL; - /* Process target columns that will be receiving results */ - if ((pstate->p_is_insert || pstate->p_is_update) && !resjunk) + while (targetlist != NIL) { + ResTarget *res = (ResTarget *) lfirst(targetlist); - /* - * insert or update query -- insert, update work only on one - * relation, so multiple occurence of same resdomno is bogus - */ - rd = pstate->p_target_relation; - Assert(rd != NULL); - resdomno = attnameAttNum(rd, 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) + if (IsA(res->val, ColumnRef)) { - /* 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; + ColumnRef *cref = (ColumnRef *) res->val; + List *fields = cref->fields; + int numnames = length(fields); - 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)); + if (numnames == 1 && strcmp(strVal(lfirst(fields)), "*") == 0) + { + /* + * Target item is a single '*', expand all tables (eg. + * SELECT * FROM emp) + */ + p_target = nconc(p_target, + ExpandAllTables(pstate)); } - - /* - * 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) + else if (strcmp(strVal(nth(numnames - 1, fields)), "*") == 0) { - A_Indices *ind = lfirst(ar); - - if (lowerIndexpr || (!upperIndexpr && ind->lidx)) + /* + * Target item is relation.*, expand that table (eg. + * SELECT emp.*, dname FROM emp, dept) + */ + char *schemaname; + char *relname; + RangeTblEntry *rte; + int sublevels_up; + + switch (numnames) { + case 2: + schemaname = NULL; + relname = strVal(lfirst(fields)); + break; + case 3: + schemaname = strVal(lfirst(fields)); + relname = strVal(lsecond(fields)); + break; + case 4: + { + char *name1 = strVal(lfirst(fields)); - /* - * XXX assume all lowerIndexpr is non-null in this - * case - */ - lowerIndexpr = lappend(lowerIndexpr, ind->lidx); + /* + * We check the catalog name and then ignore + * it. + */ + if (strcmp(name1, DatabaseName) != 0) + elog(ERROR, "Cross-database references are not implemented"); + schemaname = strVal(lsecond(fields)); + relname = strVal(lthird(fields)); + break; + } + default: + elog(ERROR, "Invalid qualified name syntax (too many names)"); + schemaname = NULL; /* keep compiler quiet */ + relname = NULL; + break; } - 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; - } - - resnode = makeResdom((AttrNumber) resdomno, - (Oid) attrtype, - attrtypmod, - colname, - (Index) 0, - (Oid) 0, - resjunk); - - return makeTargetEntry(resnode, expr); -} /* MakeTargetEntryExpr() */ - -/* - * MakeTargetEntryCase() - * Make a TargetEntry from a case node. - */ -static TargetEntry * -MakeTargetEntryCase(ParseState *pstate, - ResTarget *res) -{ - TargetEntry *tent; - CaseExpr *expr; - Resdom *resnode; - int resdomno; - Oid type_id; - int32 type_mod; - - expr = (CaseExpr *) transformExpr(pstate, (Node *) res->val, EXPR_COLUMN_FIRST); - - type_id = expr->casetype; - type_mod = -1; - handleTargetColname(pstate, &res->name, NULL, NULL); - if (res->name == NULL) - res->name = FigureColname((Node *) expr, res->val); - - resdomno = pstate->p_last_resno++; - resnode = makeResdom((AttrNumber) resdomno, - (Oid) type_id, - type_mod, - res->name, - (Index) 0, - (Oid) 0, - false); - - tent = makeNode(TargetEntry); - tent->resdom = resnode; - tent->expr = (Node *) expr; - - return tent; -} /* MakeTargetEntryCase() */ - -/* - * MakeTargetEntryComplex() - * Make a TargetEntry from a complex node. - */ -static TargetEntry * -MakeTargetEntryComplex(ParseState *pstate, - ResTarget *res) -{ - Node *expr = transformExpr(pstate, (Node *) res->val, EXPR_COLUMN_FIRST); - - handleTargetColname(pstate, &res->name, NULL, NULL); - /* note indirection has not been transformed */ - if (pstate->p_is_insert && res->indirection != NIL) - { - /* this is an array assignment */ - char *val; - char *str, - *save_str; - List *elt; - int i = 0, - ndims; - int lindx[MAXDIM], - uindx[MAXDIM]; - int resdomno; - Relation rd; - Value *constval; - - if (exprType(expr) != UNKNOWNOID || !IsA(expr, Const)) - elog(ERROR, "String constant expected (internal error)"); - - val = (char *) textout((struct varlena *) - ((Const *) expr)->constvalue); - str = save_str = (char *) palloc(strlen(val) + MAXDIM * 25 + 2); - foreach(elt, res->indirection) - { - A_Indices *aind = (A_Indices *) lfirst(elt); - aind->uidx = transformExpr(pstate, aind->uidx, EXPR_COLUMN_FIRST); - if (!IsA(aind->uidx, Const)) - elog(ERROR, "Array Index for Append should be a constant"); + rte = refnameRangeTblEntry(pstate, schemaname, relname, + &sublevels_up); + if (rte == NULL) + rte = addImplicitRTE(pstate, makeRangeVar(schemaname, + relname)); - uindx[i] = ((Const *) aind->uidx)->constvalue; - if (aind->lidx != NULL) - { - aind->lidx = transformExpr(pstate, aind->lidx, EXPR_COLUMN_FIRST); - if (!IsA(aind->lidx, Const)) - elog(ERROR, "Array Index for Append should be a constant"); - - lindx[i] = ((Const *) aind->lidx)->constvalue; + p_target = nconc(p_target, + expandRelAttrs(pstate, rte)); } 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 ColumnRef 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 (IsA(res->val, InsertDefault)) { + InsertDefault *newnode = makeNode(InsertDefault); /* - * if you're wondering why this is here, look at the yacc - * grammar for why a name can be missing. -ay + * If this is a DEFAULT element, we make a junk entry which + * will get dropped on return to transformInsertStmt(). */ - colname = FigureColname(expr, res->val); + p_target = lappend(p_target, newnode); } - if (res->indirection) + else { - 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); - } + /* Everything else but ColumnRef and InsertDefault */ + p_target = lappend(p_target, + transformTargetEntry(pstate, + res->val, + NULL, + res->name, + false)); } - 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. + * markTargetListOrigins() + * Mark targetlist columns that are simple Vars with the source + * table's OID and column number. + * + * Currently, this is done only for SELECT targetlists, since we only + * need the info if we are going to send it to the frontend. */ -static TargetEntry * -MakeTargetEntryAttr(ParseState *pstate, - ResTarget *res) +void +markTargetListOrigins(ParseState *pstate, List *targetlist) { - Oid type_id; - int32 type_mod; - Attr *att = (Attr *) res->val; - Node *result; - char *attrname; - char *resname; - Resdom *resnode; - int resdomno; - List *attrs = att->attrs; - TargetEntry *tent; + List *l; - attrname = strVal(lfirst(att->attrs)); - - /* - * Target item is fully specified: ie. relation.attribute - */ - result = ParseNestedFuncOrColumn(pstate, att, &pstate->p_last_resno, EXPR_COLUMN_FIRST); - handleTargetColname(pstate, &res->name, att->relname, attrname); - if (att->indirection != NIL) - { - List *ilist = att->indirection; - - while (ilist != NIL) - { - A_Indices *ind = lfirst(ilist); - - ind->lidx = transformExpr(pstate, ind->lidx, EXPR_COLUMN_FIRST); - ind->uidx = transformExpr(pstate, ind->uidx, EXPR_COLUMN_FIRST); - ilist = lnext(ilist); - } - result = (Node *) make_array_ref(result, att->indirection); - } - type_id = exprType(result); - if (nodeTag(result) == T_Var) - type_mod = ((Var *) result)->vartypmod; - else - type_mod = -1; - /* move to last entry */ - while (lnext(attrs) != NIL) - attrs = lnext(attrs); - resname = (res->name) ? res->name : strVal(lfirst(attrs)); - if (pstate->p_is_insert || pstate->p_is_update) + foreach(l, targetlist) { - Relation rd; + TargetEntry *tle = (TargetEntry *) lfirst(l); - /* - * insert or update query -- insert, update work only on one - * relation, so multiple occurence of same resdomno is bogus - */ - rd = pstate->p_target_relation; - Assert(rd != NULL); - resdomno = attnameAttNum(rd, res->name); + markTargetListOrigin(pstate, tle->resdom, (Var *) tle->expr); } - 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. +/* + * markTargetListOrigin() + * If 'var' is a Var of a plain relation, mark 'res' with its origin + * + * This is split out so it can recurse for join references. Note that we + * do not drill down into views, but report the view as the column owner. */ -List * -transformTargetList(ParseState *pstate, List *targetlist) +static void +markTargetListOrigin(ParseState *pstate, Resdom *res, Var *var) { - List *p_target = NIL; - List *tail_p_target = NIL; + RangeTblEntry *rte; + AttrNumber attnum; - while (targetlist != NIL) - { - ResTarget *res = (ResTarget *) lfirst(targetlist); - TargetEntry *tent = NULL; + if (var == NULL || !IsA(var, Var)) + return; + Assert(var->varno > 0 && + (int) var->varno <= length(pstate->p_rtable)); + rte = rt_fetch(var->varno, pstate->p_rtable); + attnum = var->varattno; - switch (nodeTag(res->val)) - { - case T_Ident: - { - char *identname; + switch (rte->rtekind) + { + case RTE_RELATION: + /* It's a table or view, report it */ + res->resorigtbl = rte->relid; + res->resorigcol = attnum; + break; + case RTE_SUBQUERY: + { + /* Subselect-in-FROM: copy up from the subselect */ + List *subtl; - 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: + foreach(subtl, rte->subquery->targetList) { - 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 - { + TargetEntry *subte = (TargetEntry *) lfirst(subtl); - /* - * 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); + if (subte->resdom->resjunk || + subte->resdom->resno != attnum) continue; - } - else - { - tent = MakeTargetEntryAttr(pstate, res); - break; - } + res->resorigtbl = subte->resdom->resorigtbl; + res->resorigcol = subte->resdom->resorigcol; + break; } - default: - /* internal error */ - elog(ERROR, "Unable to transform targetlist (internal error)"); - break; - } + /* falling off end of list shouldn't happen... */ + if (subtl == NIL) + elog(ERROR, "Subquery %s does not have attribute %d", + rte->eref->aliasname, attnum); + } + break; + case RTE_JOIN: + { + /* Join RTE --- recursively inspect the alias variable */ + Var *aliasvar; - if (p_target == NIL) - p_target = tail_p_target = lcons(tent, NIL); - else - { - lnext(tail_p_target) = lcons(tent, NIL); - tail_p_target = lnext(tail_p_target); - } - targetlist = lnext(targetlist); + Assert(attnum > 0 && attnum <= length(rte->joinaliasvars)); + aliasvar = (Var *) nth(attnum - 1, rte->joinaliasvars); + markTargetListOrigin(pstate, res, aliasvar); + } + break; + case RTE_SPECIAL: + case RTE_FUNCTION: + /* not a simple relation, leave it unmarked */ + break; } - - return p_target; -} /* transformTargetList() */ +} -Node * -CoerceTargetExpr(ParseState *pstate, - Node *expr, - Oid type_id, - Oid attrtype) +/* + * 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) + * attrno target attribute number + * indirection subscripts for target column, if any + */ +void +updateTargetListEntry(ParseState *pstate, + TargetEntry *tle, + char *colname, + int attrno, + List *indirection) { - if (can_coerce_type(1, &type_id, &attrtype)) - expr = coerce_type(pstate, expr, type_id, attrtype, -1); - -#ifndef DISABLE_STRING_HACKS + Oid type_id = exprType((Node *) tle->expr); /* type of value provided */ + Oid attrtype; /* type of target column */ + int32 attrtypmod; + Resdom *resnode = tle->resdom; + Relation rd = pstate->p_target_relation; + + Assert(rd != NULL); + if (attrno <= 0) + elog(ERROR, "Cannot assign to system attribute '%s'", colname); + attrtype = attnumTypeId(rd, attrno); + attrtypmod = rd->rd_att->attrs[attrno - 1]->atttypmod; /* - * string hacks to get transparent conversions w/o explicit - * conversions + * 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. */ - else if ((attrtype == BPCHAROID) || (attrtype == VARCHAROID)) + if (indirection) { - Oid text_id = TEXTOID; + Node *arrayBase; + ArrayRef *aref; - if (type_id == TEXTOID) + if (pstate->p_is_insert) { + /* + * The command is INSERT INTO table (arraycol[subscripts]) ... + * so there is not really a source array value to work with. + * Let the executor do something reasonable, if it can. Notice + * that we force transformArraySubscripts to treat the + * subscripting op as an array-slice op below, so the source + * data will have been coerced to the array type. + */ + arrayBase = NULL; /* signal there is no source array */ } - else if (can_coerce_type(1, &type_id, &text_id)) - expr = coerce_type(pstate, expr, type_id, text_id, -1); else - expr = NULL; - } -#endif + { + /* + * Build a Var for the array to be updated. + */ + arrayBase = (Node *) make_var(pstate, + pstate->p_target_rangetblentry, + attrno); + } + aref = transformArraySubscripts(pstate, + arrayBase, + attrtype, + attrtypmod, + indirection, + pstate->p_is_insert, + (Node *) tle->expr); + tle->expr = (Expr *) aref; + } else - expr = NULL; - - return expr; -} /* CoerceTargetExpr() */ - - -/* SizeTargetExpr() - * Apparently going to a fixed-length string? - * Then explicitly size for storage... - */ -static Node * -SizeTargetExpr(ParseState *pstate, - Node *expr, - Oid attrtype, - int32 attrtypmod) -{ - int i; - HeapTuple ftup; - char *funcname; - Oid oid_array[MAXFARGS]; - - FuncCall *func; - A_Const *cons; - - funcname = typeidTypeName(attrtype); - oid_array[0] = attrtype; - oid_array[1] = INT4OID; - for (i = 2; i < MAXFARGS; i++) - oid_array[i] = InvalidOid; - - /* attempt to find with arguments exactly as specified... */ - ftup = SearchSysCacheTuple(PRONAME, - PointerGetDatum(funcname), - Int32GetDatum(2), - PointerGetDatum(oid_array), - 0); - - if (HeapTupleIsValid(ftup)) { - func = makeNode(FuncCall); - func->funcname = funcname; - - cons = makeNode(A_Const); - cons->val.type = T_Integer; - cons->val.val.ival = attrtypmod; - func->args = lappend(lcons(expr, NIL), cons); - - expr = transformExpr(pstate, (Node *) func, EXPR_COLUMN_FIRST); + /* + * For normal non-subscripted target column, do type checking and + * coercion. But accept InvalidOid, which indicates the source is + * a NULL constant. (XXX is that still true?) + */ + if (type_id != InvalidOid) + { + tle->expr = (Expr *) + coerce_to_target_type(pstate, + (Node *) tle->expr, type_id, + attrtype, attrtypmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST); + if (tle->expr == NULL) + elog(ERROR, "column \"%s\" is of type %s" + " but expression is of type %s" + "\n\tYou will need to rewrite or cast the expression", + colname, + format_type_be(attrtype), + format_type_be(type_id)); + } } - return expr; -} /* SizeTargetExpr() */ + /* + * 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) attrno; +} /* - * makeTargetNames - - * generate a list of column names if not supplied or - * test supplied column names to make sure they are in target table - * (used exclusively for inserts) + * checkInsertTargets - + * generate a list of INSERT column targets if not supplied, or + * test supplied column names to make sure they are in target table. + * Also return an integer list of the columns' attribute numbers. */ List * -makeTargetNames(ParseState *pstate, List *cols) +checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) { - List *tl = NULL; - - /* Generate ResTarget if not supplied */ + *attrnos = NIL; 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); - - id->name = palloc(NAMEDATALEN); - 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); - } + ResTarget *col; + + if (attr[i]->attisdropped) + continue; + + col = makeNode(ResTarget); + col->name = pstrdup(NameStr(attr[i]->attname)); + col->indirection = NIL; + col->val = NULL; + cols = lappend(cols, col); + *attrnos = lappendi(*attrnos, i + 1); } } else { + /* + * Do initial validation of user-supplied INSERT column list. + */ + List *tl; + foreach(tl, cols) { - List *nxt; - char *name = ((Ident *) lfirst(tl))->name; - - /* elog on failure */ - attnameAttNum(pstate->p_target_relation, name); - foreach(nxt, lnext(tl)) - if (!strcmp(name, ((Ident *) lfirst(nxt))->name)) - elog(ERROR, "Attribute '%s' should be specified only once", name); + char *name = ((ResTarget *) lfirst(tl))->name; + int attrno; + + /* Lookup column name, elog on failure */ + attrno = attnameAttNum(pstate->p_target_relation, name, false); + /* Check for duplicates */ + if (intMember(attrno, *attrnos)) + elog(ERROR, "Attribute '%s' specified more than once", name); + *attrnos = lappendi(*attrnos, attrno); } } return cols; } -/* - * ExpandAllTables - - * turns '*' (in the target list) into a list of attributes - * (of all relations in the range table) +/* ExpandAllTables() + * Turns '*' (in the target list) into a list of targetlist entries. + * + * tlist entries are generated for each relation appearing at the top level + * of the query's namespace, except for RTEs marked not inFromCl. (These + * may include NEW/OLD pseudo-entries, implicit RTEs, etc.) */ static List * ExpandAllTables(ParseState *pstate) { List *target = NIL; - List *legit_rtable = NIL; - List *rt, - *rtable; + bool found_table = false; + List *ns; - rtable = pstate->p_rtable; - if (pstate->p_is_rule) + foreach(ns, pstate->p_namespace) { + Node *n = (Node *) lfirst(ns); + RangeTblEntry *rte; - /* - * skip first two entries, "*new*" and "*current*" - */ - rtable = lnext(lnext(pstate->p_rtable)); - } - - /* SELECT *; */ - if (rtable == NULL) - 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); + if (IsA(n, RangeTblRef)) + rte = rt_fetch(((RangeTblRef *) n)->rtindex, + pstate->p_rtable); + else if (IsA(n, JoinExpr)) + rte = rt_fetch(((JoinExpr *) n)->rtindex, + pstate->p_rtable); + else + { + elog(ERROR, "ExpandAllTables: unexpected node (internal error)" + "\n\t%s", nodeToString(n)); + rte = NULL; /* keep compiler quiet */ + } /* - * we only expand those specify 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;") + * Ignore added-on relations that were not listed in the FROM + * clause. */ if (!rte->inFromCl) continue; - legit_rtable = lappend(legit_rtable, rte); + + found_table = true; + target = nconc(target, expandRelAttrs(pstate, rte)); } - foreach(rt, legit_rtable) - { - RangeTblEntry *rte = lfirst(rt); - List *temp = target; + /* Check for SELECT *; */ + if (!found_table) + elog(ERROR, "Wildcard with no tables specified not allowed"); - 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); - } - } return target; } /* * 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... * + * Note that the argument is the *untransformed* parse tree for the target + * item. This is a shade easier to work with than the transformed tree. */ static char * -FigureColname(Node *expr, Node *resval) +FigureColname(Node *node) +{ + char *name = NULL; + + FigureColnameInternal(node, &name); + if (name != NULL) + return name; + /* default result if we can't guess anything */ + return "?column?"; +} + +static int +FigureColnameInternal(Node *node, char **name) { - switch (nodeTag(expr)) + int strength = 0; + + if (node == NULL) + return strength; + + switch (nodeTag(node)) { - case T_Aggref: - return (char *) ((Aggref *) expr)->aggname; - case T_Expr: - if (((Expr *) expr)->opType == FUNC_EXPR) + case T_ColumnRef: { - if (nodeTag(resval) == T_FuncCall) - return ((FuncCall *) resval)->funcname; + char *cname = strVal(llast(((ColumnRef *) node)->fields)); + + if (strcmp(cname, "*") != 0) + { + *name = cname; + return 2; + } } break; - case T_CaseExpr: + case T_ExprFieldSelect: { - char *name; + ExprFieldSelect *efs = (ExprFieldSelect *) node; + + if (efs->fields) + { + char *fname = strVal(llast(efs->fields)); - name = FigureColname(((CaseExpr *) expr)->defresult, resval); - if (!strcmp(name, "?column?")) - name = "case"; - return name; + if (strcmp(fname, "*") != 0) + { + *name = fname; + return 2; + } + } + return FigureColnameInternal(efs->arg, name); + } + break; + case T_FuncCall: + *name = strVal(llast(((FuncCall *) node)->funcname)); + return 2; + case T_A_Expr: + /* make nullif() act like a regular function */ + if (((A_Expr *) node)->kind == AEXPR_NULLIF) + { + *name = "nullif"; + return 2; + } + break; + case T_A_Const: + if (((A_Const *) node)->typename != NULL) + { + *name = strVal(llast(((A_Const *) node)->typename->names)); + return 1; + } + break; + case T_TypeCast: + strength = FigureColnameInternal(((TypeCast *) node)->arg, + name); + if (strength <= 1) + { + if (((TypeCast *) node)->typename != NULL) + { + *name = strVal(llast(((TypeCast *) node)->typename->names)); + return 1; + } } break; + case T_CaseExpr: + strength = FigureColnameInternal((Node *) ((CaseExpr *) node)->defresult, + name); + if (strength <= 1) + { + *name = "case"; + return 1; + } + break; + case T_ArrayExpr: + /* make ARRAY[] act like a function */ + *name = "array"; + return 2; + case T_CoalesceExpr: + /* make coalesce() act like a regular function */ + *name = "coalesce"; + return 2; default: break; } - return "?column?"; + return strength; }