Undo not-so-hot decision to postpone insertion of default values into

INSERT statements to the planner.  Taking it out of the parser was right
(so that defaults don't get into stored rules), but it has to happen
before rewrite rule expansion, else references to NEW.field behave
incorrectly.  Accordingly, add a step to the rewriter to insert defaults
just before rewrite-rule expansion.
This commit is contained in:
Tom Lane 2002-04-05 05:47:05 +00:00
parent c9d70e2608
commit b9ae55f2aa
2 changed files with 348 additions and 265 deletions

View File

@ -15,7 +15,7 @@
* 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 $
*
*-------------------------------------------------------------------------
*/
@ -27,19 +27,10 @@
#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);
/*
@ -119,31 +110,25 @@ preprocess_targetlist(List *tlist,
/*
* 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.
*/
@ -154,28 +139,20 @@ expand_targetlist(List *tlist, int command_type,
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)
@ -183,7 +160,8 @@ expand_targetlist(List *tlist, int command_type,
/*
* 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.
@ -195,14 +173,20 @@ expand_targetlist(List *tlist, int command_type,
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");
@ -213,7 +197,7 @@ expand_targetlist(List *tlist, int command_type,
new_tle = makeTargetEntry(makeResdom(attrno,
atttype,
atttypmod,
pstrdup(attrname),
pstrdup(NameStr(att_tup->attname)),
false),
new_expr);
}
@ -222,230 +206,32 @@ expand_targetlist(List *tlist, int command_type,
}
/*
* 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;
}

View File

@ -7,7 +7,7 @@
* 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 $
*
*-------------------------------------------------------------------------
*/
@ -22,13 +22,15 @@
#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"
@ -38,6 +40,10 @@ static Query *rewriteRuleAction(Query *parsetree,
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);
@ -212,6 +218,289 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
}
/*
* 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
@ -764,6 +1053,14 @@ RewriteQuery(Query *parsetree, bool *instead_flag, List **qual_products)
*/
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.
*/