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:
parent
2019e24dd8
commit
a5a12887a1
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user