* parse_target.c
* handle target lists
*
- * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
+ * 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.57 2000/03/14 23:06:33 thomas 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 char *FigureColname(Node *node);
+static int FigureColnameInternal(Node *node, char **name);
/*
/* Transform the node if caller didn't do it already */
if (expr == NULL)
- expr = transformExpr(pstate, node, EXPR_COLUMN_FIRST);
+ expr = transformExpr(pstate, node);
+
+ if (IsA(expr, RangeVar))
+ elog(ERROR, "You can't use relation names alone in the target list, try relation.*.");
type_id = exprType(expr);
type_mod = exprTypmod(expr);
if (colname == NULL)
{
- /* Generate a suitable column name for a column without any
+ /*
+ * Generate a suitable column name for a column without any
* explicit 'AS ColumnName' clause.
*/
- colname = FigureColname(expr, node);
+ colname = FigureColname(node);
}
- resnode = makeResdom((AttrNumber) pstate->p_last_resno++,
+ resnode = makeResdom((AttrNumber) pstate->p_next_resno++,
type_id,
type_mod,
colname,
- (Index) 0,
- (Oid) InvalidOid,
resjunk);
- return makeTargetEntry(resnode, expr);
+ return makeTargetEntry(resnode, (Expr *) expr);
}
{
ResTarget *res = (ResTarget *) lfirst(targetlist);
- if (IsA(res->val, Attr))
+ if (IsA(res->val, ColumnRef))
{
- Attr *att = (Attr *) res->val;
+ ColumnRef *cref = (ColumnRef *) res->val;
+ List *fields = cref->fields;
+ int numnames = length(fields);
- if (att->relname != NULL && strcmp(att->relname, "*") == 0)
+ if (numnames == 1 && strcmp(strVal(lfirst(fields)), "*") == 0)
{
/*
- * Target item is a single '*', expand all tables
- * (eg. SELECT * FROM emp)
+ * Target item is a single '*', expand all tables (eg.
+ * SELECT * FROM emp)
*/
- if (pstate->p_shape != NULL)
- {
- List *s, *a;
- int i;
-
- Assert(length(pstate->p_shape) == length(pstate->p_alias));
-
- s = pstate->p_shape;
- a = pstate->p_alias;
- for (i = 0; i < length(pstate->p_shape); i++)
- {
- TargetEntry *te;
- char *colname;
- Attr *shape = lfirst(s);
- Attr *alias = lfirst(a);
-
- Assert(IsA(shape, Attr) && IsA(alias, Attr));
-
- colname = strVal(lfirst(alias->attrs));
- te = transformTargetEntry(pstate, (Node *) shape,
- NULL, colname, false);
- p_target = lappend(p_target, te);
- s = lnext(s);
- a = lnext(a);
- }
- }
- else
- p_target = nconc(p_target,
- ExpandAllTables(pstate));
+ p_target = nconc(p_target,
+ ExpandAllTables(pstate));
}
- else if (att->attrs != NIL &&
- strcmp(strVal(lfirst(att->attrs)), "*") == 0)
+ else if (strcmp(strVal(nth(numnames - 1, fields)), "*") == 0)
{
/*
- * Target item is relation.*, expand that table
- * (eg. SELECT emp.*, dname FROM emp, dept)
+ * 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));
+
+ /*
+ * 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;
+ }
+
+ rte = refnameRangeTblEntry(pstate, schemaname, relname,
+ &sublevels_up);
+ if (rte == NULL)
+ rte = addImplicitRTE(pstate, makeRangeVar(schemaname,
+ relname));
+
p_target = nconc(p_target,
- expandAll(pstate, att->relname,
- makeAttr(att->relname, NULL),
- &pstate->p_last_resno));
+ expandRelAttrs(pstate, rte));
}
else
{
- /* Plain Attr node, treat it as an expression */
+ /* Plain ColumnRef node, treat it as an expression */
p_target = lappend(p_target,
transformTargetEntry(pstate,
res->val,
false));
}
}
+ else if (IsA(res->val, InsertDefault))
+ {
+ InsertDefault *newnode = makeNode(InsertDefault);
+
+ /*
+ * If this is a DEFAULT element, we make a junk entry which
+ * will get dropped on return to transformInsertStmt().
+ */
+ p_target = lappend(p_target, newnode);
+ }
else
{
- /* Everything else but Attr */
+ /* Everything else but ColumnRef and InsertDefault */
p_target = lappend(p_target,
transformTargetEntry(pstate,
res->val,
}
+/*
+ * 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.
+ */
+void
+markTargetListOrigins(ParseState *pstate, List *targetlist)
+{
+ List *l;
+
+ foreach(l, targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ markTargetListOrigin(pstate, tle->resdom, (Var *) tle->expr);
+ }
+}
+
+/*
+ * 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.
+ */
+static void
+markTargetListOrigin(ParseState *pstate, Resdom *res, Var *var)
+{
+ RangeTblEntry *rte;
+ AttrNumber attnum;
+
+ 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 (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;
+
+ foreach(subtl, rte->subquery->targetList)
+ {
+ TargetEntry *subte = (TargetEntry *) lfirst(subtl);
+
+ if (subte->resdom->resjunk ||
+ subte->resdom->resno != attnum)
+ continue;
+ res->resorigtbl = subte->resdom->resorigtbl;
+ res->resorigcol = subte->resdom->resorigcol;
+ 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;
+
+ 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;
+ }
+}
+
+
/*
* updateTargetListEntry()
- * This is used in INSERT and UPDATE statements only. It prepares a
+ * 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
int attrno,
List *indirection)
{
- Oid type_id = exprType(tle->expr); /* type of value provided */
+ Oid type_id = exprType((Node *) tle->expr); /* type of value provided */
Oid attrtype; /* type of target column */
int32 attrtypmod;
Resdom *resnode = tle->resdom;
attrtypmod = rd->rd_att->attrs[attrno - 1]->atttypmod;
/*
- * If there are subscripts on the target column, prepare an
- * array assignment expression. This will generate an array value
- * that the source value has been inserted into, which can then
- * be placed in the new tuple constructed by INSERT or UPDATE.
- * Note that transformArraySubscripts takes care of type coercion.
+ * If there are subscripts on the target column, prepare an array
+ * assignment expression. This will generate an array value that the
+ * source value has been inserted into, which can then be placed in
+ * the new tuple constructed by INSERT or UPDATE. Note that
+ * transformArraySubscripts takes care of type coercion.
*/
if (indirection)
{
-#ifndef DISABLE_JOIN_SYNTAX
- Attr *att = makeAttr(pstrdup(RelationGetRelationName(rd)), colname);
-#else
- Attr *att = makeNode(Attr);
-#endif
Node *arrayBase;
ArrayRef *aref;
-#ifdef DISABLE_JOIN_SYNTAX
- att->relname = pstrdup(RelationGetRelationName(rd));
- att->attrs = lcons(makeString(colname), NIL);
-#endif
- arrayBase = ParseNestedFuncOrColumn(pstate, att,
- &pstate->p_last_resno,
- EXPR_COLUMN_FIRST);
- aref = transformArraySubscripts(pstate, arrayBase,
- indirection,
- pstate->p_is_insert,
- tle->expr);
if (pstate->p_is_insert)
{
/*
* The command is INSERT INTO table (arraycol[subscripts]) ...
* so there is not really a source array value to work with.
- * Let the executor do something reasonable, if it can.
- * Notice that we forced transformArraySubscripts to treat
- * the subscripting op as an array-slice op above, so the
- * source data will have been coerced to array type.
+ * 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.
*/
- aref->refexpr = NULL; /* signal there is no source array */
+ arrayBase = NULL; /* signal there is no source array */
}
- tle->expr = (Node *) aref;
+ else
+ {
+ /*
+ * 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
{
/*
- * For normal non-subscripted target column, do type checking
- * and coercion. But accept InvalidOid, which indicates the
- * source is a NULL constant.
+ * 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)
{
- if (type_id != attrtype)
- {
- tle->expr = CoerceTargetExpr(pstate, tle->expr, type_id,
- attrtype, attrtypmod);
- 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.
- */
- tle->expr = coerce_type_typmod(pstate, tle->expr,
- attrtype, attrtypmod);
+ 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));
}
}
/*
- * 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!
+ * 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;
}
-Node *
-CoerceTargetExpr(ParseState *pstate,
- Node *expr,
- Oid type_id,
- Oid attrtype,
- int32 attrtypmod)
-{
- if (can_coerce_type(1, &type_id, &attrtype))
- expr = coerce_type(pstate, expr, type_id, attrtype, attrtypmod);
-
-#ifndef DISABLE_STRING_HACKS
-
- /*
- * string hacks to get transparent conversions w/o explicit
- * conversions
- */
- else if ((attrtype == BPCHAROID) || (attrtype == VARCHAROID))
- {
- Oid text_id = TEXTOID;
-
- if (type_id == TEXTOID)
- {
- }
- else if (can_coerce_type(1, &type_id, &text_id))
- expr = coerce_type(pstate, expr, type_id, text_id, attrtypmod);
- else
- expr = NULL;
- }
-#endif
-
- else
- expr = NULL;
-
- return expr;
-}
-
-
/*
* checkInsertTargets -
- * generate a list of column names if not supplied or
+ * 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.
- * (used exclusively for inserts)
*/
List *
checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
for (i = 0; i < numcol; i++)
{
- Ident *id = makeNode(Ident);
+ ResTarget *col;
-#ifdef _DROP_COLUMN_HACK__
- if (COLUMN_IS_DROPPED(attr[i]))
- {
+ if (attr[i]->attisdropped)
continue;
- }
-#endif /* _DROP_COLUMN_HACK__ */
- id->name = palloc(NAMEDATALEN);
- StrNCpy(id->name, NameStr(attr[i]->attname), NAMEDATALEN);
- id->indirection = NIL;
- id->isRel = false;
- cols = lappend(cols, id);
- *attrnos = lappendi(*attrnos, i+1);
+
+ 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
foreach(tl, cols)
{
- char *name = ((Ident *) lfirst(tl))->name;
+ char *name = ((ResTarget *) lfirst(tl))->name;
int attrno;
/* Lookup column name, elog on failure */
- attrno = attnameAttNum(pstate->p_target_relation, name);
+ attrno = attnameAttNum(pstate->p_target_relation, name, false);
/* Check for duplicates */
if (intMember(attrno, *attrnos))
elog(ERROR, "Attribute '%s' specified more than once", name);
}
/* ExpandAllTables()
- * Turns '*' (in the target list) into a list of attributes
- * (of all relations in the range table)
+ * 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 *rt,
- *rtable;
+ bool found_table = false;
+ List *ns;
- rtable = pstate->p_rtable;
- if (pstate->p_is_rule)
+ foreach(ns, pstate->p_namespace)
{
- /*
- * skip first two entries, "*new*" and "*current*"
- */
- rtable = lnext(lnext(rtable));
- }
-
- /* SELECT *; */
- if (rtable == NIL)
- elog(ERROR, "Wildcard with no tables specified not allowed");
-
- foreach(rt, rtable)
- {
- RangeTblEntry *rte = lfirst(rt);
+ Node *n = (Node *) lfirst(ns);
+ RangeTblEntry *rte;
+
+ 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 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;")
+ * Ignore added-on relations that were not listed in the FROM
+ * clause.
*/
if (!rte->inFromCl)
continue;
- target = nconc(target,
- expandAll(pstate, rte->eref->relname, rte->eref,
- &pstate->p_last_resno));
+ found_table = true;
+ target = nconc(target, expandRelAttrs(pstate, rte));
}
+
+ /* Check for SELECT *; */
+ if (!found_table)
+ elog(ERROR, "Wildcard with no tables specified not allowed");
+
return target;
}
* 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)
{
- /* Some of these are easiest to do with the untransformed node */
- switch (nodeTag(resval))
+ 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)
+{
+ int strength = 0;
+
+ if (node == NULL)
+ return strength;
+
+ switch (nodeTag(node))
{
- case T_Ident:
- return ((Ident *) resval)->name;
- case T_Attr:
+ case T_ColumnRef:
{
- List *attrs = ((Attr *) resval)->attrs;
- if (attrs)
+ char *cname = strVal(llast(((ColumnRef *) node)->fields));
+
+ if (strcmp(cname, "*") != 0)
{
- while (lnext(attrs) != NIL)
- attrs = lnext(attrs);
- return strVal(lfirst(attrs));
+ *name = cname;
+ return 2;
}
}
break;
- default:
+ case T_ExprFieldSelect:
+ {
+ ExprFieldSelect *efs = (ExprFieldSelect *) node;
+
+ if (efs->fields)
+ {
+ char *fname = strVal(llast(efs->fields));
+
+ if (strcmp(fname, "*") != 0)
+ {
+ *name = fname;
+ return 2;
+ }
+ }
+ return FigureColnameInternal(efs->arg, name);
+ }
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;
+ 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_Aggref:
- return ((Aggref *) expr)->aggname;
case T_CaseExpr:
+ strength = FigureColnameInternal((Node *) ((CaseExpr *) node)->defresult,
+ name);
+ if (strength <= 1)
{
- char *name;
-
- name = FigureColname(((CaseExpr *) expr)->defresult, resval);
- if (strcmp(name, "?column?") == 0)
- name = "case";
- return name;
+ *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;
}