Skip to content

Commit a5a1288

Browse files
committed
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.
1 parent 2019e24 commit a5a1288

File tree

1 file changed

+101
-49
lines changed

1 file changed

+101
-49
lines changed

src/backend/optimizer/prep/preptlist.c

Lines changed: 101 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* Portions Copyright (c) 1994, Regents of the University of California
1616
*
1717
* IDENTIFICATION
18-
* $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.36 2000/04/12 17:15:23 momjian Exp $
18+
* $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.37 2000/07/22 06:19:04 tgl Exp $
1919
*
2020
*-------------------------------------------------------------------------
2121
*/
@@ -32,6 +32,9 @@
3232

3333
static List *expand_targetlist(List *tlist, int command_type,
3434
Index result_relation, List *range_table);
35+
static TargetEntry *process_matched_tle(TargetEntry *src_tle,
36+
TargetEntry *prior_tle,
37+
int attrno);
3538

3639

3740
/*
@@ -119,8 +122,9 @@ expand_targetlist(List *tlist, int command_type,
119122
List *temp;
120123

121124
/*
122-
* Keep a map of which tlist items we have transferred to new list. +1
123-
* here keeps palloc from complaining if old_tlist_len=0.
125+
* Keep a map of which tlist items we have transferred to new list.
126+
*
127+
* +1 here just keeps palloc from complaining if old_tlist_len==0.
124128
*/
125129
tlistentry_used = (bool *) palloc((old_tlist_len + 1) * sizeof(bool));
126130
memset(tlistentry_used, 0, (old_tlist_len + 1) * sizeof(bool));
@@ -141,6 +145,7 @@ expand_targetlist(List *tlist, int command_type,
141145

142146
/*
143147
* We match targetlist entries to attributes using the resname.
148+
* Junk attributes are not candidates to be matched.
144149
*/
145150
old_tlist_index = 0;
146151
foreach(temp, tlist)
@@ -149,26 +154,11 @@ expand_targetlist(List *tlist, int command_type,
149154
Resdom *resdom = old_tle->resdom;
150155

151156
if (!tlistentry_used[old_tlist_index] &&
152-
strcmp(resdom->resname, attrname) == 0 &&
153-
!resdom->resjunk)
157+
!resdom->resjunk &&
158+
strcmp(resdom->resname, attrname) == 0)
154159
{
155-
156-
/*
157-
* We can recycle the old TLE+resdom if right resno; else
158-
* make a new one to avoid modifying the old tlist
159-
* structure. (Is preserving old tlist actually
160-
* necessary?)
161-
*/
162-
if (resdom->resno == attrno)
163-
new_tle = old_tle;
164-
else
165-
{
166-
resdom = (Resdom *) copyObject((Node *) resdom);
167-
resdom->resno = attrno;
168-
new_tle = makeTargetEntry(resdom, old_tle->expr);
169-
}
160+
new_tle = process_matched_tle(old_tle, new_tle, attrno);
170161
tlistentry_used[old_tlist_index] = true;
171-
break;
172162
}
173163
old_tlist_index++;
174164
}
@@ -192,22 +182,15 @@ expand_targetlist(List *tlist, int command_type,
192182
{
193183
case CMD_INSERT:
194184
{
195-
#ifdef _DROP_COLUMN_HACK__
196-
Datum typedefault;
197-
198-
#else
199185
Datum typedefault = get_typdefault(atttype);
200-
201-
#endif /* _DROP_COLUMN_HACK__ */
202186
int typlen;
203187
Const *temp_const;
204188

205189
#ifdef _DROP_COLUMN_HACK__
206190
if (COLUMN_IS_DROPPED(att_tup))
207191
typedefault = PointerGetDatum(NULL);
208-
else
209-
typedefault = get_typdefault(atttype);
210192
#endif /* _DROP_COLUMN_HACK__ */
193+
211194
if (typedefault == PointerGetDatum(NULL))
212195
typlen = 0;
213196
else
@@ -247,11 +230,9 @@ expand_targetlist(List *tlist, int command_type,
247230
Var *temp_var;
248231

249232
#ifdef _DROP_COLUMN_HACK__
250-
Node *temp_node = (Node *) NULL;
251-
252233
if (COLUMN_IS_DROPPED(att_tup))
253234
{
254-
temp_node = (Node *) makeConst(atttype, 0,
235+
temp_var = (Var *) makeConst(atttype, 0,
255236
PointerGetDatum(NULL),
256237
true,
257238
false,
@@ -260,25 +241,20 @@ expand_targetlist(List *tlist, int command_type,
260241
}
261242
else
262243
#endif /* _DROP_COLUMN_HACK__ */
263-
temp_var = makeVar(result_relation, attrno, atttype,
264-
atttypmod, 0);
265-
#ifdef _DROP_COLUMN_HACK__
266-
if (!temp_node)
267-
temp_node = (Node *) temp_var;
268-
#endif /* _DROP_COLUMN_HACK__ */
244+
temp_var = makeVar(result_relation,
245+
attrno,
246+
atttype,
247+
atttypmod,
248+
0);
269249

270250
new_tle = makeTargetEntry(makeResdom(attrno,
271251
atttype,
272252
atttypmod,
273-
pstrdup(attrname),
253+
pstrdup(attrname),
274254
0,
275255
(Oid) 0,
276256
false),
277-
#ifdef _DROP_COLUMN_HACK__
278-
temp_node);
279-
#else
280257
(Node *) temp_var);
281-
#endif /* _DROP_COLUMN_HACK__ */
282258
break;
283259
}
284260
default:
@@ -304,13 +280,20 @@ expand_targetlist(List *tlist, int command_type,
304280

305281
if (!tlistentry_used[old_tlist_index])
306282
{
307-
Resdom *resdom;
283+
Resdom *resdom = old_tle->resdom;
308284

309-
resdom = (Resdom *) copyObject((Node *) old_tle->resdom);
310-
resdom->resno = attrno++;
311-
resdom->resjunk = true;
312-
new_tlist = lappend(new_tlist,
313-
makeTargetEntry(resdom, old_tle->expr));
285+
if (! resdom->resjunk)
286+
elog(ERROR, "Unexpected assignment to attribute \"%s\"",
287+
resdom->resname);
288+
/* Get the resno right, but don't copy unnecessarily */
289+
if (resdom->resno != attrno)
290+
{
291+
resdom = (Resdom *) copyObject((Node *) resdom);
292+
resdom->resno = attrno;
293+
old_tle = makeTargetEntry(resdom, old_tle->expr);
294+
}
295+
new_tlist = lappend(new_tlist, old_tle);
296+
attrno++;
314297
}
315298
old_tlist_index++;
316299
}
@@ -321,3 +304,72 @@ expand_targetlist(List *tlist, int command_type,
321304

322305
return new_tlist;
323306
}
307+
308+
309+
/*
310+
* Convert a matched TLE from the original tlist into a correct new TLE.
311+
*
312+
* This routine checks for multiple assignments to the same target attribute,
313+
* such as "UPDATE table SET foo = 42, foo = 43". This is OK only if they
314+
* are array assignments, ie, "UPDATE table SET foo[2] = 42, foo[4] = 43".
315+
* If so, we need to merge the operations into a single assignment op.
316+
* Essentially, the expression we want to produce in this case is like
317+
* foo = array_set(array_set(foo, 2, 42), 4, 43)
318+
*/
319+
static TargetEntry *process_matched_tle(TargetEntry *src_tle,
320+
TargetEntry *prior_tle,
321+
int attrno)
322+
{
323+
Resdom *resdom = src_tle->resdom;
324+
Node *priorbottom;
325+
ArrayRef *newexpr;
326+
327+
if (prior_tle == NULL)
328+
{
329+
/*
330+
* Normal case where this is the first assignment to the attribute.
331+
*
332+
* We can recycle the old TLE+resdom if right resno; else make a
333+
* new one to avoid modifying the old tlist structure. (Is preserving
334+
* old tlist actually necessary? Not sure, be safe.)
335+
*/
336+
if (resdom->resno == attrno)
337+
return src_tle;
338+
resdom = (Resdom *) copyObject((Node *) resdom);
339+
resdom->resno = attrno;
340+
return makeTargetEntry(resdom, src_tle->expr);
341+
}
342+
343+
/*
344+
* Multiple assignments to same attribute. Allow only if all are
345+
* array-assign operators with same bottom array object.
346+
*/
347+
if (src_tle->expr == NULL || !IsA(src_tle->expr, ArrayRef) ||
348+
((ArrayRef *) src_tle->expr)->refassgnexpr == NULL ||
349+
prior_tle->expr == NULL || !IsA(prior_tle->expr, ArrayRef) ||
350+
((ArrayRef *) prior_tle->expr)->refassgnexpr == NULL ||
351+
((ArrayRef *) src_tle->expr)->refelemtype !=
352+
((ArrayRef *) prior_tle->expr)->refelemtype)
353+
elog(ERROR, "Multiple assignments to same attribute \"%s\"",
354+
resdom->resname);
355+
/*
356+
* Prior TLE could be a nest of ArrayRefs if we do this more than once.
357+
*/
358+
priorbottom = ((ArrayRef *) prior_tle->expr)->refexpr;
359+
while (priorbottom != NULL && IsA(priorbottom, ArrayRef) &&
360+
((ArrayRef *) priorbottom)->refassgnexpr != NULL)
361+
priorbottom = ((ArrayRef *) priorbottom)->refexpr;
362+
if (! equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr))
363+
elog(ERROR, "Multiple assignments to same attribute \"%s\"",
364+
resdom->resname);
365+
/*
366+
* Looks OK to nest 'em.
367+
*/
368+
newexpr = makeNode(ArrayRef);
369+
memcpy(newexpr, src_tle->expr, sizeof(ArrayRef));
370+
newexpr->refexpr = prior_tle->expr;
371+
372+
resdom = (Resdom *) copyObject((Node *) resdom);
373+
resdom->resno = attrno;
374+
return makeTargetEntry(resdom, (Node *) newexpr);
375+
}

0 commit comments

Comments
 (0)