* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.51 2002/04/02 08:51:51 inoue Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.52 2002/04/05 05:47:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "nodes/makefuncs.h"
#include "optimizer/prep.h"
#include "parser/parsetree.h"
-#include "parser/parse_coerce.h"
-#include "parser/parse_expr.h"
-#include "parser/parse_target.h"
-#include "utils/builtins.h"
-#include "utils/lsyscache.h"
static List *expand_targetlist(List *tlist, int command_type,
Index result_relation, List *range_table);
-static TargetEntry *process_matched_tle(TargetEntry *src_tle,
- TargetEntry *prior_tle,
- int attrno);
-static Node *build_column_default(Relation rel, int attrno);
/*
/*
* expand_targetlist
* Given a target list as generated by the parser and a result relation,
- * add targetlist entries for any missing attributes, and order the
- * non-junk attributes in proper field order.
+ * add targetlist entries for any missing attributes, and ensure the
+ * non-junk attributes appear in proper field order.
+ *
+ * NOTE: if you are tempted to put more processing here, consider whether
+ * it shouldn't go in the rewriter's rewriteTargetList() instead.
*/
static List *
expand_targetlist(List *tlist, int command_type,
Index result_relation, List *range_table)
{
- int old_tlist_len = length(tlist);
List *new_tlist = NIL;
- bool *tlistentry_used;
Relation rel;
int attrno,
- numattrs,
- old_tlist_index;
- List *temp;
+ numattrs;
/*
- * Keep a map of which tlist items we have transferred to new list.
+ * The rewriter should have already ensured that the TLEs are in
+ * correct order; but we have to insert TLEs for any missing attributes.
*
- * +1 here just keeps palloc from complaining if old_tlist_len==0.
- */
- tlistentry_used = (bool *) palloc((old_tlist_len + 1) * sizeof(bool));
- memset(tlistentry_used, 0, (old_tlist_len + 1) * sizeof(bool));
-
- /*
* Scan the tuple description in the relation's relcache entry to make
* sure we have all the user attributes in the right order.
*/
for (attrno = 1; attrno <= numattrs; attrno++)
{
Form_pg_attribute att_tup = rel->rd_att->attrs[attrno - 1];
- char *attrname = NameStr(att_tup->attname);
TargetEntry *new_tle = NULL;
- /*
- * We match targetlist entries to attributes using the resname.
- * Junk attributes are not candidates to be matched.
- */
- old_tlist_index = 0;
- foreach(temp, tlist)
+ if (tlist != NIL)
{
- TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
+ TargetEntry *old_tle = (TargetEntry *) lfirst(tlist);
Resdom *resdom = old_tle->resdom;
- if (!tlistentry_used[old_tlist_index] &&
- !resdom->resjunk &&
- strcmp(resdom->resname, attrname) == 0)
+ if (!resdom->resjunk && resdom->resno == attrno)
{
- new_tle = process_matched_tle(old_tle, new_tle, attrno);
- tlistentry_used[old_tlist_index] = true;
- /* keep scanning to detect multiple assignments to attr */
+ Assert(strcmp(resdom->resname,
+ NameStr(att_tup->attname)) == 0);
+ new_tle = old_tle;
+ tlist = lnext(tlist);
}
- old_tlist_index++;
}
if (new_tle == NULL)
/*
* Didn't find a matching tlist entry, so make one.
*
- * For INSERT, generate an appropriate default value.
+ * For INSERT, generate a NULL constant. (We assume the
+ * rewriter would have inserted any available default value.)
*
* For UPDATE, generate a Var reference to the existing value of
* the attribute, so that it gets copied to the new tuple.
switch (command_type)
{
case CMD_INSERT:
- new_expr = build_column_default(rel, attrno);
+ new_expr = (Node *) makeConst(atttype,
+ att_tup->attlen,
+ (Datum) 0,
+ true, /* isnull */
+ att_tup->attbyval,
+ false, /* not a set */
+ false);
break;
case CMD_UPDATE:
- new_expr = (Node *) makeVar(result_relation,
- attrno,
- atttype,
- atttypmod,
- 0);
+ new_expr = (Node *) makeVar(result_relation,
+ attrno,
+ atttype,
+ atttypmod,
+ 0);
break;
default:
elog(ERROR, "expand_targetlist: unexpected command_type");
new_tle = makeTargetEntry(makeResdom(attrno,
atttype,
atttypmod,
- pstrdup(attrname),
+ pstrdup(NameStr(att_tup->attname)),
false),
new_expr);
}
}
/*
- * Copy all unprocessed tlist entries to the end of the new tlist,
- * making sure they are marked resjunk = true. Typical junk entries
- * include ORDER BY or GROUP BY expressions (are these actually
- * possible in an INSERT or UPDATE?), system attribute references,
- * etc.
+ * The remaining tlist entries should be resjunk; append them all to
+ * the end of the new tlist, making sure they have resnos higher than
+ * the last real attribute. (Note: although the rewriter already did
+ * such renumbering, we have to do it again here in case we are doing
+ * an UPDATE in an inheritance child table with more columns.)
*/
- old_tlist_index = 0;
- foreach(temp, tlist)
+ while (tlist)
{
- TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
+ TargetEntry *old_tle = (TargetEntry *) lfirst(tlist);
+ Resdom *resdom = old_tle->resdom;
- if (!tlistentry_used[old_tlist_index])
+ if (!resdom->resjunk)
+ elog(ERROR, "expand_targetlist: targetlist is not sorted correctly");
+ /* Get the resno right, but don't copy unnecessarily */
+ if (resdom->resno != attrno)
{
- Resdom *resdom = old_tle->resdom;
-
- if (!resdom->resjunk)
- elog(ERROR, "Unexpected assignment to attribute \"%s\"",
- resdom->resname);
- /* Get the resno right, but don't copy unnecessarily */
- if (resdom->resno != attrno)
- {
- resdom = (Resdom *) copyObject((Node *) resdom);
- resdom->resno = attrno;
- old_tle = makeTargetEntry(resdom, old_tle->expr);
- }
- new_tlist = lappend(new_tlist, old_tle);
- attrno++;
+ resdom = (Resdom *) copyObject((Node *) resdom);
+ resdom->resno = attrno;
+ old_tle = makeTargetEntry(resdom, old_tle->expr);
}
- old_tlist_index++;
+ new_tlist = lappend(new_tlist, old_tle);
+ attrno++;
+ tlist = lnext(tlist);
}
heap_close(rel, AccessShareLock);
- pfree(tlistentry_used);
-
return new_tlist;
}
-
-
-/*
- * Convert a matched TLE from the original tlist into a correct new TLE.
- *
- * This routine checks for multiple assignments to the same target attribute,
- * such as "UPDATE table SET foo = 42, foo = 43". This is OK only if they
- * are array assignments, ie, "UPDATE table SET foo[2] = 42, foo[4] = 43".
- * If so, we need to merge the operations into a single assignment op.
- * Essentially, the expression we want to produce in this case is like
- * foo = array_set(array_set(foo, 2, 42), 4, 43)
- */
-static TargetEntry *
-process_matched_tle(TargetEntry *src_tle,
- TargetEntry *prior_tle,
- int attrno)
-{
- Resdom *resdom = src_tle->resdom;
- Node *priorbottom;
- ArrayRef *newexpr;
-
- if (prior_tle == NULL)
- {
- /*
- * Normal case where this is the first assignment to the
- * attribute.
- *
- * We can recycle the old TLE+resdom if right resno; else make a new
- * one to avoid modifying the old tlist structure. (Is preserving
- * old tlist actually necessary? Not sure, be safe.)
- */
- if (resdom->resno == attrno)
- return src_tle;
- resdom = (Resdom *) copyObject((Node *) resdom);
- resdom->resno = attrno;
- return makeTargetEntry(resdom, src_tle->expr);
- }
-
- /*
- * Multiple assignments to same attribute. Allow only if all are
- * array-assign operators with same bottom array object.
- */
- if (src_tle->expr == NULL || !IsA(src_tle->expr, ArrayRef) ||
- ((ArrayRef *) src_tle->expr)->refassgnexpr == NULL ||
- prior_tle->expr == NULL || !IsA(prior_tle->expr, ArrayRef) ||
- ((ArrayRef *) prior_tle->expr)->refassgnexpr == NULL ||
- ((ArrayRef *) src_tle->expr)->refelemtype !=
- ((ArrayRef *) prior_tle->expr)->refelemtype)
- elog(ERROR, "Multiple assignments to same attribute \"%s\"",
- resdom->resname);
-
- /*
- * Prior TLE could be a nest of ArrayRefs if we do this more than
- * once.
- */
- priorbottom = ((ArrayRef *) prior_tle->expr)->refexpr;
- while (priorbottom != NULL && IsA(priorbottom, ArrayRef) &&
- ((ArrayRef *) priorbottom)->refassgnexpr != NULL)
- priorbottom = ((ArrayRef *) priorbottom)->refexpr;
- if (!equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr))
- elog(ERROR, "Multiple assignments to same attribute \"%s\"",
- resdom->resname);
-
- /*
- * Looks OK to nest 'em.
- */
- newexpr = makeNode(ArrayRef);
- memcpy(newexpr, src_tle->expr, sizeof(ArrayRef));
- newexpr->refexpr = prior_tle->expr;
-
- resdom = (Resdom *) copyObject((Node *) resdom);
- resdom->resno = attrno;
- return makeTargetEntry(resdom, (Node *) newexpr);
-}
-
-
-/*
- * Make an expression tree for the default value for a column.
- *
- * This is used to fill in missing attributes in an INSERT targetlist.
- * We look first to see if the column has a default value expression.
- * If not, generate a constant of the default value for the attribute type,
- * or a NULL if the type has no default value either.
- */
-static Node *
-build_column_default(Relation rel, int attrno)
-{
- TupleDesc rd_att = rel->rd_att;
- Form_pg_attribute att_tup = rd_att->attrs[attrno - 1];
- Oid atttype = att_tup->atttypid;
- int32 atttypmod = att_tup->atttypmod;
- int16 typlen = att_tup->attlen;
- bool typbyval = att_tup->attbyval;
- Node *expr = NULL;
-
- /*
- * Scan to see if relation has a default for this column.
- */
- if (rd_att->constr && rd_att->constr->num_defval > 0)
- {
- AttrDefault *defval = rd_att->constr->defval;
- int ndef = rd_att->constr->num_defval;
-
- while (--ndef >= 0)
- {
- if (attrno == defval[ndef].adnum)
- {
- /*
- * Found it, convert string representation to node tree.
- */
- expr = stringToNode(defval[ndef].adbin);
- break;
- }
- }
- }
-
- if (expr == NULL)
- {
- /*
- * No per-column default, so look for a default for the type itself.
- */
- if (att_tup->attisset)
- {
- /*
- * Set attributes are represented as OIDs no matter what the set
- * element type is, and the element type's default is irrelevant
- * too.
- */
- typlen = sizeof(Oid);
- typbyval = true;
- }
- else
- {
- expr = get_typdefault(atttype);
- }
- }
-
- if (expr == NULL)
- {
- /*
- * No default anywhere, so generate a NULL constant.
- */
- expr = (Node *) makeConst(atttype,
- typlen,
- (Datum) 0,
- true, /* isnull */
- typbyval,
- false, /* not a set */
- false);
- }
- else
- {
- Oid exprtype;
-
- /*
- * Make sure the value is coerced to the target column
- * type (might not be right type yet if it's not a
- * constant!) This should match the parser's processing of
- * non-defaulted expressions --- see
- * updateTargetListEntry().
- */
- exprtype = exprType(expr);
-
- if (exprtype != atttype)
- {
- expr = CoerceTargetExpr(NULL, expr, exprtype,
- atttype, atttypmod);
-
- /*
- * This really shouldn't fail; should have checked the
- * default's type when it was created ...
- */
- if (expr == NULL)
- elog(ERROR, "Column \"%s\" is of type %s"
- " but default expression is of type %s"
- "\n\tYou will need to rewrite or cast the expression",
- NameStr(att_tup->attname),
- format_type_be(atttype),
- format_type_be(exprtype));
- }
-
- /*
- * If the column is a fixed-length type, it may need a
- * length coercion as well as a type coercion.
- */
- expr = coerce_type_typmod(NULL, expr, atttype, atttypmod);
- }
-
- return expr;
-}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.100 2002/03/22 02:56:34 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.101 2002/04/05 05:47:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "optimizer/prep.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "parser/parse_target.h"
-#include "parser/parsetree.h"
#include "parser/parse_type.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
#include "utils/lsyscache.h"
int rt_index,
CmdType event);
static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index);
+static void rewriteTargetList(Query *parsetree, Relation target_relation);
+static TargetEntry *process_matched_tle(TargetEntry *src_tle,
+ TargetEntry *prior_tle);
+static Node *build_column_default(Relation rel, int attrno);
static void markQueryForUpdate(Query *qry, bool skipOldNew);
static List *matchLocks(CmdType event, RuleLock *rulelocks,
int varno, Query *parsetree);
/*
+ * rewriteTargetList - rewrite INSERT/UPDATE targetlist into standard form
+ *
+ * This has the following responsibilities:
+ *
+ * 1. For an INSERT, add tlist entries to compute default values for any
+ * attributes that have defaults and are not assigned to in the given tlist.
+ * (We do not insert anything for default-less attributes, however. The
+ * planner will later insert NULLs for them, but there's no reason to slow
+ * down rewriter processing with extra tlist nodes.)
+ *
+ * 2. Merge multiple entries for the same target attribute, or declare error
+ * if we can't. Presently, multiple entries are only allowed for UPDATE of
+ * an array field, for example "UPDATE table SET foo[2] = 42, foo[4] = 43".
+ * We can merge such operations into a single assignment op. Essentially,
+ * the expression we want to produce in this case is like
+ * foo = array_set(array_set(foo, 2, 42), 4, 43)
+ *
+ * 3. Sort the tlist into standard order: non-junk fields in order by resno,
+ * then junk fields (these in no particular order).
+ *
+ * We must do items 1 and 2 before firing rewrite rules, else rewritten
+ * references to NEW.foo will produce wrong or incomplete results. Item 3
+ * is not needed for rewriting, but will be needed by the planner, and we
+ * can do it essentially for free while handling items 1 and 2.
+ */
+static void
+rewriteTargetList(Query *parsetree, Relation target_relation)
+{
+ CmdType commandType = parsetree->commandType;
+ List *tlist = parsetree->targetList;
+ List *new_tlist = NIL;
+ int attrno,
+ numattrs;
+ List *temp;
+
+ /*
+ * Scan the tuple description in the relation's relcache entry to make
+ * sure we have all the user attributes in the right order.
+ */
+ numattrs = RelationGetNumberOfAttributes(target_relation);
+
+ for (attrno = 1; attrno <= numattrs; attrno++)
+ {
+ Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno-1];
+ TargetEntry *new_tle = NULL;
+
+ /*
+ * Look for targetlist entries matching this attr. We match by
+ * resno, but the resname should match too.
+ *
+ * Junk attributes are not candidates to be matched.
+ */
+ foreach(temp, tlist)
+ {
+ TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
+ Resdom *resdom = old_tle->resdom;
+
+ if (!resdom->resjunk && resdom->resno == attrno)
+ {
+ Assert(strcmp(resdom->resname,
+ NameStr(att_tup->attname)) == 0);
+ new_tle = process_matched_tle(old_tle, new_tle);
+ /* keep scanning to detect multiple assignments to attr */
+ }
+ }
+
+ if (new_tle == NULL && commandType == CMD_INSERT)
+ {
+ /*
+ * Didn't find a matching tlist entry; if it's an INSERT,
+ * look for a default value, and add a tlist entry computing
+ * the default if we find one.
+ */
+ Node *new_expr;
+
+ new_expr = build_column_default(target_relation, attrno);
+
+ if (new_expr)
+ new_tle = makeTargetEntry(makeResdom(attrno,
+ att_tup->atttypid,
+ att_tup->atttypmod,
+ pstrdup(NameStr(att_tup->attname)),
+ false),
+ new_expr);
+ }
+
+ if (new_tle)
+ new_tlist = lappend(new_tlist, new_tle);
+ }
+
+ /*
+ * Copy all resjunk tlist entries to the end of the new tlist, and
+ * assign them resnos above the last real resno.
+ *
+ * Typical junk entries include ORDER BY or GROUP BY expressions (are
+ * these actually possible in an INSERT or UPDATE?), system attribute
+ * references, etc.
+ */
+ foreach(temp, tlist)
+ {
+ TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
+ Resdom *resdom = old_tle->resdom;
+
+ if (resdom->resjunk)
+ {
+ /* Get the resno right, but don't copy unnecessarily */
+ if (resdom->resno != attrno)
+ {
+ resdom = (Resdom *) copyObject((Node *) resdom);
+ resdom->resno = attrno;
+ old_tle = makeTargetEntry(resdom, old_tle->expr);
+ }
+ new_tlist = lappend(new_tlist, old_tle);
+ attrno++;
+ }
+ else
+ {
+ /* Let's just make sure we processed all the non-junk items */
+ if (resdom->resno < 1 || resdom->resno > numattrs)
+ elog(ERROR, "rewriteTargetList: bogus resno %d in targetlist",
+ resdom->resno);
+ }
+ }
+
+ parsetree->targetList = new_tlist;
+}
+
+
+/*
+ * Convert a matched TLE from the original tlist into a correct new TLE.
+ *
+ * This routine detects and handles multiple assignments to the same target
+ * attribute.
+ */
+static TargetEntry *
+process_matched_tle(TargetEntry *src_tle,
+ TargetEntry *prior_tle)
+{
+ Resdom *resdom = src_tle->resdom;
+ Node *priorbottom;
+ ArrayRef *newexpr;
+
+ if (prior_tle == NULL)
+ {
+ /*
+ * Normal case where this is the first assignment to the
+ * attribute.
+ */
+ return src_tle;
+ }
+
+ /*
+ * Multiple assignments to same attribute. Allow only if all are
+ * array-assign operators with same bottom array object.
+ */
+ if (src_tle->expr == NULL || !IsA(src_tle->expr, ArrayRef) ||
+ ((ArrayRef *) src_tle->expr)->refassgnexpr == NULL ||
+ prior_tle->expr == NULL || !IsA(prior_tle->expr, ArrayRef) ||
+ ((ArrayRef *) prior_tle->expr)->refassgnexpr == NULL ||
+ ((ArrayRef *) src_tle->expr)->refelemtype !=
+ ((ArrayRef *) prior_tle->expr)->refelemtype)
+ elog(ERROR, "Multiple assignments to same attribute \"%s\"",
+ resdom->resname);
+
+ /*
+ * Prior TLE could be a nest of ArrayRefs if we do this more than
+ * once.
+ */
+ priorbottom = ((ArrayRef *) prior_tle->expr)->refexpr;
+ while (priorbottom != NULL && IsA(priorbottom, ArrayRef) &&
+ ((ArrayRef *) priorbottom)->refassgnexpr != NULL)
+ priorbottom = ((ArrayRef *) priorbottom)->refexpr;
+ if (!equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr))
+ elog(ERROR, "Multiple assignments to same attribute \"%s\"",
+ resdom->resname);
+
+ /*
+ * Looks OK to nest 'em.
+ */
+ newexpr = makeNode(ArrayRef);
+ memcpy(newexpr, src_tle->expr, sizeof(ArrayRef));
+ newexpr->refexpr = prior_tle->expr;
+
+ return makeTargetEntry(resdom, (Node *) newexpr);
+}
+
+
+/*
+ * Make an expression tree for the default value for a column.
+ *
+ * If there is no default, return a NULL instead.
+ */
+static Node *
+build_column_default(Relation rel, int attrno)
+{
+ TupleDesc rd_att = rel->rd_att;
+ Form_pg_attribute att_tup = rd_att->attrs[attrno - 1];
+ Oid atttype = att_tup->atttypid;
+ int32 atttypmod = att_tup->atttypmod;
+ Node *expr = NULL;
+ Oid exprtype;
+
+ /*
+ * Scan to see if relation has a default for this column.
+ */
+ if (rd_att->constr && rd_att->constr->num_defval > 0)
+ {
+ AttrDefault *defval = rd_att->constr->defval;
+ int ndef = rd_att->constr->num_defval;
+
+ while (--ndef >= 0)
+ {
+ if (attrno == defval[ndef].adnum)
+ {
+ /*
+ * Found it, convert string representation to node tree.
+ */
+ expr = stringToNode(defval[ndef].adbin);
+ break;
+ }
+ }
+ }
+
+ if (expr == NULL)
+ {
+ /*
+ * No per-column default, so look for a default for the type itself.
+ */
+ if (att_tup->attisset)
+ {
+ /*
+ * Set attributes are represented as OIDs no matter what the set
+ * element type is, and the element type's default is irrelevant
+ * too.
+ */
+ }
+ else
+ {
+ expr = get_typdefault(atttype);
+ }
+ }
+
+ if (expr == NULL)
+ return NULL; /* No default anywhere */
+
+ /*
+ * Make sure the value is coerced to the target column
+ * type (might not be right type yet if it's not a
+ * constant!) This should match the parser's processing of
+ * non-defaulted expressions --- see
+ * updateTargetListEntry().
+ */
+ exprtype = exprType(expr);
+
+ if (exprtype != atttype)
+ {
+ expr = CoerceTargetExpr(NULL, expr, exprtype,
+ atttype, atttypmod);
+
+ /*
+ * This really shouldn't fail; should have checked the
+ * default's type when it was created ...
+ */
+ if (expr == NULL)
+ elog(ERROR, "Column \"%s\" is of type %s"
+ " but default expression is of type %s"
+ "\n\tYou will need to rewrite or cast the expression",
+ NameStr(att_tup->attname),
+ format_type_be(atttype),
+ format_type_be(exprtype));
+ }
+
+ /*
+ * If the column is a fixed-length type, it may need a
+ * length coercion as well as a type coercion.
+ */
+ expr = coerce_type_typmod(NULL, expr, atttype, atttypmod);
+
+ return expr;
+}
+
+
+/*
* matchLocks -
* match the list of locks and returns the matching rules
*/
rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock);
/*
+ * If it's an INSERT or UPDATE, rewrite the targetlist into standard
+ * form. This will be needed by the planner anyway, and doing it now
+ * ensures that any references to NEW.field will behave sanely.
+ */
+ if (event == CMD_INSERT || event == CMD_UPDATE)
+ rewriteTargetList(parsetree, rt_entry_relation);
+
+ /*
* Collect and apply the appropriate rules.
*/
rt_entry_locks = rt_entry_relation->rd_rules;