* 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.8 1998/02/10 04:01:57 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.101 2003/05/06 00:20:32 tgl Exp $
*
*-------------------------------------------------------------------------
*/
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
#include "postgres.h"
-#include "catalog/pg_type.h"
+
+#include "miscadmin.h"
#include "nodes/makefuncs.h"
-#include "nodes/primnodes.h"
+#include "parser/parsetree.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
-#include "parser/parse_node.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
+#include "parser/parse_type.h"
#include "utils/builtins.h"
-#include "utils/lsyscache.h"
-static List *expandAllTables(ParseState *pstate);
-static char *figureColname(Node *expr, Node *resval);
-static TargetEntry *make_targetlist_expr(ParseState *pstate,
- char *colname,
+
+static void markTargetListOrigin(ParseState *pstate, Resdom *res, Var *var);
+static List *ExpandAllTables(ParseState *pstate);
+static char *FigureColname(Node *node);
+static int FigureColnameInternal(Node *node, char **name);
+
+
+/*
+ * 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.
+ *
+ * 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 *
+transformTargetEntry(ParseState *pstate,
+ Node *node,
Node *expr,
- List *arrayRef);
+ char *colname,
+ bool resjunk)
+{
+ Oid type_id;
+ int32 type_mod;
+ Resdom *resnode;
+
+ /* Transform the node if caller didn't do it already */
+ if (expr == NULL)
+ 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
+ * explicit 'AS ColumnName' clause.
+ */
+ colname = FigureColname(node);
+ }
+
+ resnode = makeResdom((AttrNumber) pstate->p_next_resno++,
+ type_id,
+ type_mod,
+ colname,
+ resjunk);
+
+ return makeTargetEntry(resnode, (Expr *) expr);
+}
+
/*
- * transformTargetList -
- * turns a list of ResTarget's into a list of TargetEntry's
+ * 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.
*/
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 = makeNode(TargetEntry);
- switch (nodeTag(res->val))
+ if (IsA(res->val, ColumnRef))
{
- case T_Ident:
- {
- Node *expr;
- Oid type_id;
- int type_mod;
- char *identname;
- char *resname;
-
- identname = ((Ident *) res->val)->name;
- handleTargetColname(pstate, &res->name, NULL, identname);
-
- /*
- * here we want to look for column names only, not relation
- * names (even though they can be stored in Ident nodes, too)
- */
- expr = transformIdent(pstate, (Node *) res->val, EXPR_COLUMN_FIRST);
- type_id = exprType(expr);
- if (nodeTag(expr) == T_Var)
- type_mod = ((Var *)expr)->vartypmod;
- else
- type_mod = -1;
- resname = (res->name) ? res->name : identname;
- tent->resdom = makeResdom((AttrNumber) pstate->p_last_resno++,
- (Oid) type_id,
- type_mod,
- resname,
- (Index) 0,
- (Oid) 0,
- 0);
-
- tent->expr = expr;
- break;
- }
- case T_ParamNo:
- case T_FuncCall:
- case T_A_Const:
- case T_A_Expr:
- {
- 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, "yyparse: string constant expected");
-
- 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");
- 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;
- }
- else
- {
- lindx[i] = 1;
- }
- if (lindx[i] > uindx[i])
- elog(ERROR, "yyparse: lower index cannot be greater than upper index");
- sprintf(str, "[%d:%d]", lindx[i], uindx[i]);
- str += strlen(str);
- i++;
- }
- 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, "yyparse: array dimensions do not match");
- constval = makeNode(Value);
- constval->type = T_String;
- constval->val.str = save_str;
- tent = make_targetlist_expr(pstate, res->name,
- (Node *) make_const(constval),
- NULL);
- pfree(save_str);
- }
- else
- {
- char *colname = res->name;
+ ColumnRef *cref = (ColumnRef *) res->val;
+ List *fields = cref->fields;
+ int numnames = length(fields);
- /* this is not an array assignment */
- if (colname == NULL)
+ 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));
+ }
+ else if (strcmp(strVal(nth(numnames - 1, fields)), "*") == 0)
+ {
+ /*
+ * 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));
/*
- * if you're wondering why this is here, look
- * at the yacc grammar for why a name can be
- * missing. -ay
+ * We check the catalog name and then ignore
+ * it.
*/
- colname = figureColname(expr, res->val);
+ if (strcmp(name1, DatabaseName) != 0)
+ elog(ERROR, "Cross-database references are not implemented");
+ schemaname = strVal(lsecond(fields));
+ relname = strVal(lthird(fields));
+ break;
}
- 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;
- tent = make_targetlist_expr(pstate, res->name, expr,
- res->indirection);
- }
- break;
+ default:
+ elog(ERROR, "Invalid qualified name syntax (too many names)");
+ schemaname = NULL; /* keep compiler quiet */
+ relname = NULL;
+ break;
}
- case T_Attr:
- {
- Oid type_id;
- int type_mod;
- Attr *att = (Attr *) res->val;
- Node *result;
- char *attrname;
- char *resname;
- Resdom *resnode;
- List *attrs = att->attrs;
-
- /*
- * 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);
-
- 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;
- }
- /*
- * 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, "*"))
- {
+ rte = refnameRangeTblEntry(pstate, schemaname, relname,
+ &sublevels_up);
+ if (rte == NULL)
+ rte = addImplicitRTE(pstate, makeRangeVar(schemaname,
+ relname));
- /*
- * 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);
- while (lnext(tail_p_target) != NIL)
- /* make sure we point to the last target entry */
- tail_p_target = lnext(tail_p_target);
-
- /*
- * skip the rest of the while loop
- */
- targetlist = lnext(targetlist);
- continue;
- }
-
-
- /*
- * 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));
- resnode = makeResdom((AttrNumber) pstate->p_last_resno++,
- (Oid) type_id,
- type_mod,
- resname,
- (Index) 0,
- (Oid) 0,
- 0);
- tent->resdom = resnode;
- tent->expr = result;
- break;
- }
- default:
- /* internal error */
- elog(ERROR,
- "internal error: do not know how to transform targetlist");
- break;
+ p_target = nconc(p_target,
+ expandRelAttrs(pstate, rte));
+ }
+ else
+ {
+ /* Plain ColumnRef node, treat it as an expression */
+ p_target = lappend(p_target,
+ transformTargetEntry(pstate,
+ res->val,
+ NULL,
+ res->name,
+ false));
+ }
}
-
- if (p_target == NIL)
+ else if (IsA(res->val, InsertDefault))
{
- p_target = tail_p_target = lcons(tent, NIL);
+ 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
{
- lnext(tail_p_target) = lcons(tent, NIL);
- tail_p_target = lnext(tail_p_target);
+ /* Everything else but ColumnRef and InsertDefault */
+ p_target = lappend(p_target,
+ transformTargetEntry(pstate,
+ res->val,
+ NULL,
+ res->name,
+ false));
}
+
targetlist = lnext(targetlist);
}
/*
- * make_targetlist_expr -
- * make a TargetEntry from an expression
+ * markTargetListOrigins()
+ * Mark targetlist columns that are simple Vars with the source
+ * table's OID and column number.
*
- * arrayRef is a list of transformed A_Indices
+ * 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 *
-make_targetlist_expr(ParseState *pstate,
- char *colname,
- Node *expr,
- List *arrayRef)
+void
+markTargetListOrigins(ParseState *pstate, List *targetlist)
{
- Oid type_id,
- attrtype;
- int type_mod,
- attrtypmod;
- int resdomno;
- Relation rd;
- bool attrisset;
- TargetEntry *tent;
- Resdom *resnode;
+ List *l;
- if (expr == NULL)
- elog(ERROR, "make_targetlist_expr: invalid use of NULL expression");
-
- type_id = exprType(expr);
- if (nodeTag(expr) == T_Var)
- type_mod = ((Var *)expr)->vartypmod;
- else
- type_mod = -1;
-
- /* Processes target columns that will be receiving results */
- if (pstate->p_is_insert || pstate->p_is_update)
+ foreach(l, targetlist)
{
+ 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, 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;
-#if 0
- if (Input_is_string && Typecast_ok)
- {
- Datum val;
+ markTargetListOrigin(pstate, tle->resdom, (Var *) tle->expr);
+ }
+}
- if (type_id == typeTypeId(type("unknown")))
- {
- val = (Datum) textout((struct varlena *)
- ((Const) lnext(expr))->constvalue);
- }
- else
- {
- val = ((Const) lnext(expr))->constvalue;
- }
- if (attrisset)
- {
- lnext(expr) = makeConst(attrtype,
- attrlen,
- val,
- false,
- true,
- true, /* is set */
- false);
- }
- else
- {
- lnext(expr) =
- makeConst(attrtype,
- attrlen,
- (Datum) fmgr(typeidRetinfunc(attrtype),
- val, typeidTypElem(attrtype), -1),
- false,
- true /* Maybe correct-- 80% chance */ ,
- false, /* is not a set */
- false);
- }
- }
- else if ((Typecast_ok) && (attrtype != type_id))
- {
- lnext(expr) =
- parser_typecast2(expr, typeidType(attrtype));
- }
- else if (attrtype != type_id)
- {
- if ((attrtype == INT2OID) && (type_id == INT4OID))
- lfirst(expr) = lispInteger(INT2OID); /* handle CASHOID too */
- else if ((attrtype == FLOAT4OID) && (type_id == FLOAT8OID))
- lfirst(expr) = lispInteger(FLOAT4OID);
- else
- elog(ERROR, "unequal type in tlist : %s \n", colname);
- }
+/*
+ * 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;
- Input_is_string = false;
- Input_is_integer = false;
- Typecast_ok = true;
-#endif
+ 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;
- if (attrtype != type_id)
- {
- if (IsA(expr, Const))
+ 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:
{
- /* try to cast the constant */
- if (arrayRef && !(((A_Indices *) lfirst(arrayRef))->lidx))
+ /* Subselect-in-FROM: copy up from the subselect */
+ List *subtl;
+
+ foreach(subtl, rte->subquery->targetList)
{
- /* updating a single item */
- Oid typelem = typeidTypElem(attrtype);
+ TargetEntry *subte = (TargetEntry *) lfirst(subtl);
- expr = (Node *) parser_typecast2(expr,
- type_id,
- typeidType(typelem),
- attrtypmod);
+ if (subte->resdom->resjunk ||
+ subte->resdom->resno != attnum)
+ continue;
+ res->resorigtbl = subte->resdom->resorigtbl;
+ res->resorigcol = subte->resdom->resorigcol;
+ break;
}
- else
- expr = (Node *) parser_typecast2(expr,
- type_id,
- typeidType(attrtype),
- attrtypmod);
+ /* falling off end of list shouldn't happen... */
+ if (subtl == NIL)
+ elog(ERROR, "Subquery %s does not have attribute %d",
+ rte->eref->aliasname, attnum);
}
- else
+ break;
+ case RTE_JOIN:
{
- /* currently, we can't handle casting of expressions */
- elog(ERROR, "parser: attribute '%s' is of type '%s' but expression is of type '%s'",
- colname,
- typeidTypeName(attrtype),
- typeidTypeName(type_id));
+ /* 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;
+ }
+}
- 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))
- {
+/*
+ * 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)
+{
+ 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;
- /*
- * XXX assume all lowerIndexpr is non-null in this
- * case
- */
- lowerIndexpr = lappend(lowerIndexpr, ind->lidx);
- }
- upperIndexpr = lappend(upperIndexpr, ind->uidx);
- ar = lnext(ar);
- }
+ /*
+ * 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)
+ {
+ Node *arrayBase;
+ ArrayRef *aref;
- expr = (Node *) make_array_set(target_expr,
- upperIndexpr,
- lowerIndexpr,
- (Expr *) expr);
- attrtype = attnumTypeId(rd, resdomno);
- attrtypmod = get_atttypmod(rd->rd_id, resdomno);
+ 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
+ {
+ /*
+ * 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
{
- resdomno = pstate->p_last_resno++;
- attrtype = type_id;
- attrtypmod = type_mod;
+ /*
+ * 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));
+ }
}
- tent = makeNode(TargetEntry);
-
- resnode = makeResdom((AttrNumber) resdomno,
- (Oid) attrtype,
- attrtypmod,
- colname,
- (Index) 0,
- (Oid) 0,
- 0);
-
- tent->resdom = resnode;
- tent->expr = expr;
- return tent;
+ /*
+ * 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;
+ /*
+ * 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;
- AttributeTupleForm *attr = pstate->p_target_relation->rd_att->attrs;
- 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)
+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));
- }
+ bool found_table = false;
+ List *ns;
- /* this should not happen */
- if (rtable == NULL)
- elog(ERROR, "cannot expand: null p_rtable");
-
- /*
- * go through the range table and make a list of range table entries
- * which we will expand.
- */
- foreach(rt, rtable)
+ foreach(ns, pstate->p_namespace)
{
- 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 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 -
+ * 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)
{
- switch (nodeTag(expr))
+ 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_Aggreg:
- return (char *) ((Aggreg *) 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_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;
+ 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;
}