* 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;
}