Skip to content

Commit c5f365f

Browse files
committed
Prevent multicolumn expansion of "foo.*" in an UPDATE source expression.
Because we use transformTargetList() for UPDATE as well as SELECT tlists, the code accidentally tried to expand a "*" reference into several columns. This is nonsensical, because the UPDATE syntax provides exactly one target column to put the value into. The immediate result was that transformUpdateTargetList() got confused and reported "UPDATE target count mismatch --- internal error". It seems better to treat such a reference as a plain whole-row variable, as it would be in other contexts. (This could produce useful results when the target column is of composite type.) Fix by tweaking transformTargetList() to perform *-expansion only conditionally, depending on its exprKind parameter. Back-patch to 9.3. The problem exists further back, but a fix would be much more invasive before that, because transformTargetList() wasn't told what kind of list it was working on. Doesn't seem worth the trouble given the lack of field reports. (I only noticed it because I was checking the code while trying to improve the documentation about how we handle "foo.*".) Discussion: <4308.1479595330@sss.pgh.pa.us>
1 parent 0832f2d commit c5f365f

File tree

3 files changed

+59
-21
lines changed

3 files changed

+59
-21
lines changed

src/backend/parser/parse_target.c

+32-21
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,15 @@ transformTargetList(ParseState *pstate, List *targetlist,
122122
ParseExprKind exprKind)
123123
{
124124
List *p_target = NIL;
125+
bool expand_star;
125126
ListCell *o_target;
126127

127128
/* Shouldn't have any leftover multiassign items at start */
128129
Assert(pstate->p_multiassign_exprs == NIL);
129130

131+
/* Expand "something.*" in SELECT and RETURNING, but not UPDATE */
132+
expand_star = (exprKind != EXPR_KIND_UPDATE_SOURCE);
133+
130134
foreach(o_target, targetlist)
131135
{
132136
ResTarget *res = (ResTarget *) lfirst(o_target);
@@ -136,35 +140,42 @@ transformTargetList(ParseState *pstate, List *targetlist,
136140
* "something", the star could appear as the last field in ColumnRef,
137141
* or as the last indirection item in A_Indirection.
138142
*/
139-
if (IsA(res->val, ColumnRef))
143+
if (expand_star)
140144
{
141-
ColumnRef *cref = (ColumnRef *) res->val;
142-
143-
if (IsA(llast(cref->fields), A_Star))
145+
if (IsA(res->val, ColumnRef))
144146
{
145-
/* It is something.*, expand into multiple items */
146-
p_target = list_concat(p_target,
147-
ExpandColumnRefStar(pstate, cref,
148-
true));
149-
continue;
150-
}
151-
}
152-
else if (IsA(res->val, A_Indirection))
153-
{
154-
A_Indirection *ind = (A_Indirection *) res->val;
147+
ColumnRef *cref = (ColumnRef *) res->val;
155148

156-
if (IsA(llast(ind->indirection), A_Star))
149+
if (IsA(llast(cref->fields), A_Star))
150+
{
151+
/* It is something.*, expand into multiple items */
152+
p_target = list_concat(p_target,
153+
ExpandColumnRefStar(pstate,
154+
cref,
155+
true));
156+
continue;
157+
}
158+
}
159+
else if (IsA(res->val, A_Indirection))
157160
{
158-
/* It is something.*, expand into multiple items */
159-
p_target = list_concat(p_target,
160-
ExpandIndirectionStar(pstate, ind,
161-
true, exprKind));
162-
continue;
161+
A_Indirection *ind = (A_Indirection *) res->val;
162+
163+
if (IsA(llast(ind->indirection), A_Star))
164+
{
165+
/* It is something.*, expand into multiple items */
166+
p_target = list_concat(p_target,
167+
ExpandIndirectionStar(pstate,
168+
ind,
169+
true,
170+
exprKind));
171+
continue;
172+
}
163173
}
164174
}
165175

166176
/*
167-
* Not "something.*", so transform as a single expression
177+
* Not "something.*", or we want to treat that as a plain whole-row
178+
* variable, so transform as a single expression
168179
*/
169180
p_target = lappend(p_target,
170181
transformTargetEntry(pstate,

src/test/regress/expected/update.out

+18
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ SELECT * FROM update_test;
5656
100 | 20 |
5757
(2 rows)
5858

59+
-- fail, wrong data type:
60+
UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i, j)
61+
WHERE update_test.b = v.j;
62+
ERROR: column "a" is of type integer but expression is of type record
63+
LINE 1: UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i...
64+
^
65+
HINT: You will need to rewrite or cast the expression.
5966
--
6067
-- Test multiple-set-clause syntax
6168
--
@@ -133,6 +140,17 @@ SELECT * FROM update_test;
133140
| |
134141
(4 rows)
135142

143+
-- these should work, but don't yet:
144+
UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 100)) AS v(i, j)
145+
WHERE update_test.a = v.i;
146+
ERROR: number of columns does not match number of values
147+
LINE 1: UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 100)) ...
148+
^
149+
UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 101)) AS v(i, j)
150+
WHERE update_test.a = v.i;
151+
ERROR: syntax error at or near "ROW"
152+
LINE 1: UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 101...
153+
^
136154
-- if an alias for the target table is specified, don't allow references
137155
-- to the original table name
138156
UPDATE update_test AS t SET b = update_test.b + 10 WHERE t.a = 10;

src/test/regress/sql/update.sql

+9
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ UPDATE update_test SET a=v.i FROM (VALUES(100, 20)) AS v(i, j)
4040

4141
SELECT * FROM update_test;
4242

43+
-- fail, wrong data type:
44+
UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i, j)
45+
WHERE update_test.b = v.j;
46+
4347
--
4448
-- Test multiple-set-clause syntax
4549
--
@@ -70,6 +74,11 @@ UPDATE update_test SET (b,a) = (select a+1,b from update_test);
7074
UPDATE update_test SET (b,a) = (select a+1,b from update_test where a = 1000)
7175
WHERE a = 11;
7276
SELECT * FROM update_test;
77+
-- these should work, but don't yet:
78+
UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 100)) AS v(i, j)
79+
WHERE update_test.a = v.i;
80+
UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 101)) AS v(i, j)
81+
WHERE update_test.a = v.i;
7382

7483
-- if an alias for the target table is specified, don't allow references
7584
-- to the original table name

0 commit comments

Comments
 (0)