mirror of
https://github.com/postgres/postgres.git
synced 2025-11-07 19:06:32 +03:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user