Make update lists like 'UPDATE tab SET foo[1] = bar, foo[3] = baz'

work as expected.  THe underlying implementation is essentially
'SET foo = array_set(foo, 1, bar)', so we have to turn the items
into nested invocations of array_set() to make it work correctly.
Side effect: we now complain about 'UPDATE tab SET foo = bar, foo = baz'
which is illegal per SQL92 but we didn't detect it before.
This commit is contained in:
Tom Lane 2000-07-22 06:19:04 +00:00
parent 2019e24dd8
commit a5a12887a1
1 changed files with 101 additions and 49 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.36 2000/04/12 17:15:23 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.37 2000/07/22 06:19:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -32,6 +32,9 @@
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);
/*
@ -119,8 +122,9 @@ expand_targetlist(List *tlist, int command_type,
List *temp;
/*
* Keep a map of which tlist items we have transferred to new list. +1
* here keeps palloc from complaining if old_tlist_len=0.
* Keep a map of which tlist items we have transferred to new list.
*
* +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));
@ -141,6 +145,7 @@ expand_targetlist(List *tlist, int command_type,
/*
* We match targetlist entries to attributes using the resname.
* Junk attributes are not candidates to be matched.
*/
old_tlist_index = 0;
foreach(temp, tlist)
@ -149,26 +154,11 @@ expand_targetlist(List *tlist, int command_type,
Resdom *resdom = old_tle->resdom;
if (!tlistentry_used[old_tlist_index] &&
strcmp(resdom->resname, attrname) == 0 &&
!resdom->resjunk)
!resdom->resjunk &&
strcmp(resdom->resname, attrname) == 0)
{
/*
* 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?)
*/
if (resdom->resno == attrno)
new_tle = old_tle;
else
{
resdom = (Resdom *) copyObject((Node *) resdom);
resdom->resno = attrno;
new_tle = makeTargetEntry(resdom, old_tle->expr);
}
new_tle = process_matched_tle(old_tle, new_tle, attrno);
tlistentry_used[old_tlist_index] = true;
break;
}
old_tlist_index++;
}
@ -192,22 +182,15 @@ expand_targetlist(List *tlist, int command_type,
{
case CMD_INSERT:
{
#ifdef _DROP_COLUMN_HACK__
Datum typedefault;
#else
Datum typedefault = get_typdefault(atttype);
#endif /* _DROP_COLUMN_HACK__ */
int typlen;
Const *temp_const;
#ifdef _DROP_COLUMN_HACK__
if (COLUMN_IS_DROPPED(att_tup))
typedefault = PointerGetDatum(NULL);
else
typedefault = get_typdefault(atttype);
#endif /* _DROP_COLUMN_HACK__ */
if (typedefault == PointerGetDatum(NULL))
typlen = 0;
else
@ -247,11 +230,9 @@ expand_targetlist(List *tlist, int command_type,
Var *temp_var;
#ifdef _DROP_COLUMN_HACK__
Node *temp_node = (Node *) NULL;
if (COLUMN_IS_DROPPED(att_tup))
{
temp_node = (Node *) makeConst(atttype, 0,
temp_var = (Var *) makeConst(atttype, 0,
PointerGetDatum(NULL),
true,
false,
@ -260,25 +241,20 @@ expand_targetlist(List *tlist, int command_type,
}
else
#endif /* _DROP_COLUMN_HACK__ */
temp_var = makeVar(result_relation, attrno, atttype,
atttypmod, 0);
#ifdef _DROP_COLUMN_HACK__
if (!temp_node)
temp_node = (Node *) temp_var;
#endif /* _DROP_COLUMN_HACK__ */
temp_var = makeVar(result_relation,
attrno,
atttype,
atttypmod,
0);
new_tle = makeTargetEntry(makeResdom(attrno,
atttype,
atttypmod,
pstrdup(attrname),
pstrdup(attrname),
0,
(Oid) 0,
false),
#ifdef _DROP_COLUMN_HACK__
temp_node);
#else
(Node *) temp_var);
#endif /* _DROP_COLUMN_HACK__ */
break;
}
default:
@ -304,13 +280,20 @@ expand_targetlist(List *tlist, int command_type,
if (!tlistentry_used[old_tlist_index])
{
Resdom *resdom;
Resdom *resdom = old_tle->resdom;
resdom = (Resdom *) copyObject((Node *) old_tle->resdom);
resdom->resno = attrno++;
resdom->resjunk = true;
new_tlist = lappend(new_tlist,
makeTargetEntry(resdom, old_tle->expr));
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++;
}
old_tlist_index++;
}
@ -321,3 +304,72 @@ expand_targetlist(List *tlist, int command_type,
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);
}