Skip to content

Commit 01b4bb4

Browse files
committed
Fix incorrect generation of whole-row variables in planner.
A couple of places in the planner need to generate whole-row Vars, and were cutting corners by setting vartype = RECORDOID in the Vars, even in cases where there's an identifiable named composite type for the RTE being referenced. While we mostly got away with this, it failed when there was also a parser-generated whole-row reference to the same RTE, because the two Vars weren't equal() due to the difference in vartype. Fix by providing a subroutine the planner can call to generate whole-row Vars the same way the parser does. Per bug #5716 from Andrew Tipton. Back-patch to 9.0 where one of the bogus calls was introduced (the other one is new in HEAD).
1 parent 80a65c0 commit 01b4bb4

File tree

6 files changed

+175
-79
lines changed

6 files changed

+175
-79
lines changed

src/backend/nodes/makefuncs.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "catalog/pg_type.h"
1919
#include "nodes/makefuncs.h"
20+
#include "nodes/nodeFuncs.h"
2021
#include "utils/lsyscache.h"
2122

2223

@@ -90,6 +91,93 @@ makeVar(Index varno,
9091
return var;
9192
}
9293

94+
/*
95+
* makeWholeRowVar -
96+
* creates a Var node representing a whole row of the specified RTE
97+
*
98+
* A whole-row reference is a Var with varno set to the correct range
99+
* table entry, and varattno == 0 to signal that it references the whole
100+
* tuple. (Use of zero here is unclean, since it could easily be confused
101+
* with error cases, but it's not worth changing now.) The vartype indicates
102+
* a rowtype; either a named composite type, or RECORD. This function
103+
* encapsulates the logic for determining the correct rowtype OID to use.
104+
*/
105+
Var *
106+
makeWholeRowVar(RangeTblEntry *rte,
107+
Index varno,
108+
Index varlevelsup)
109+
{
110+
Var *result;
111+
Oid toid;
112+
113+
switch (rte->rtekind)
114+
{
115+
case RTE_RELATION:
116+
/* relation: the rowtype is a named composite type */
117+
toid = get_rel_type_id(rte->relid);
118+
if (!OidIsValid(toid))
119+
elog(ERROR, "could not find type OID for relation %u",
120+
rte->relid);
121+
result = makeVar(varno,
122+
InvalidAttrNumber,
123+
toid,
124+
-1,
125+
varlevelsup);
126+
break;
127+
case RTE_FUNCTION:
128+
toid = exprType(rte->funcexpr);
129+
if (type_is_rowtype(toid))
130+
{
131+
/* func returns composite; same as relation case */
132+
result = makeVar(varno,
133+
InvalidAttrNumber,
134+
toid,
135+
-1,
136+
varlevelsup);
137+
}
138+
else
139+
{
140+
/*
141+
* func returns scalar; instead of making a whole-row Var,
142+
* just reference the function's scalar output. (XXX this
143+
* seems a tad inconsistent, especially if "f.*" was
144+
* explicitly written ...)
145+
*/
146+
result = makeVar(varno,
147+
1,
148+
toid,
149+
-1,
150+
varlevelsup);
151+
}
152+
break;
153+
case RTE_VALUES:
154+
toid = RECORDOID;
155+
/* returns composite; same as relation case */
156+
result = makeVar(varno,
157+
InvalidAttrNumber,
158+
toid,
159+
-1,
160+
varlevelsup);
161+
break;
162+
default:
163+
164+
/*
165+
* RTE is a join or subselect. We represent this as a whole-row
166+
* Var of RECORD type. (Note that in most cases the Var will be
167+
* expanded to a RowExpr during planning, but that is not our
168+
* concern here.)
169+
*/
170+
result = makeVar(varno,
171+
InvalidAttrNumber,
172+
RECORDOID,
173+
-1,
174+
varlevelsup);
175+
break;
176+
}
177+
178+
return result;
179+
}
180+
93181
/*
94182
* makeTargetEntry -
95183
* creates a TargetEntry node

src/backend/optimizer/prep/preptlist.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,9 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
170170
else
171171
{
172172
/* Not a table, so we need the whole row as a junk var */
173-
var = makeVar(rc->rti,
174-
InvalidAttrNumber,
175-
RECORDOID,
176-
-1,
177-
0);
173+
var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
174+
rc->rti,
175+
0);
178176
snprintf(resname, sizeof(resname), "wholerow%u", rc->rti);
179177
tle = makeTargetEntry((Expr *) var,
180178
list_length(tlist) + 1,

src/backend/parser/parse_expr.c

Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2015,93 +2015,21 @@ transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr)
20152015

20162016
/*
20172017
* Construct a whole-row reference to represent the notation "relation.*".
2018-
*
2019-
* A whole-row reference is a Var with varno set to the correct range
2020-
* table entry, and varattno == 0 to signal that it references the whole
2021-
* tuple. (Use of zero here is unclean, since it could easily be confused
2022-
* with error cases, but it's not worth changing now.) The vartype indicates
2023-
* a rowtype; either a named composite type, or RECORD.
20242018
*/
20252019
static Node *
20262020
transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, int location)
20272021
{
20282022
Var *result;
20292023
int vnum;
20302024
int sublevels_up;
2031-
Oid toid;
20322025

20332026
/* Find the RTE's rangetable location */
2034-
20352027
vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
20362028

20372029
/* Build the appropriate referencing node */
2030+
result = makeWholeRowVar(rte, vnum, sublevels_up);
20382031

2039-
switch (rte->rtekind)
2040-
{
2041-
case RTE_RELATION:
2042-
/* relation: the rowtype is a named composite type */
2043-
toid = get_rel_type_id(rte->relid);
2044-
if (!OidIsValid(toid))
2045-
elog(ERROR, "could not find type OID for relation %u",
2046-
rte->relid);
2047-
result = makeVar(vnum,
2048-
InvalidAttrNumber,
2049-
toid,
2050-
-1,
2051-
sublevels_up);
2052-
break;
2053-
case RTE_FUNCTION:
2054-
toid = exprType(rte->funcexpr);
2055-
if (type_is_rowtype(toid))
2056-
{
2057-
/* func returns composite; same as relation case */
2058-
result = makeVar(vnum,
2059-
InvalidAttrNumber,
2060-
toid,
2061-
-1,
2062-
sublevels_up);
2063-
}
2064-
else
2065-
{
2066-
/*
2067-
* func returns scalar; instead of making a whole-row Var,
2068-
* just reference the function's scalar output. (XXX this
2069-
* seems a tad inconsistent, especially if "f.*" was
2070-
* explicitly written ...)
2071-
*/
2072-
result = makeVar(vnum,
2073-
1,
2074-
toid,
2075-
-1,
2076-
sublevels_up);
2077-
}
2078-
break;
2079-
case RTE_VALUES:
2080-
toid = RECORDOID;
2081-
/* returns composite; same as relation case */
2082-
result = makeVar(vnum,
2083-
InvalidAttrNumber,
2084-
toid,
2085-
-1,
2086-
sublevels_up);
2087-
break;
2088-
default:
2089-
2090-
/*
2091-
* RTE is a join or subselect. We represent this as a whole-row
2092-
* Var of RECORD type. (Note that in most cases the Var will be
2093-
* expanded to a RowExpr during planning, but that is not our
2094-
* concern here.)
2095-
*/
2096-
result = makeVar(vnum,
2097-
InvalidAttrNumber,
2098-
RECORDOID,
2099-
-1,
2100-
sublevels_up);
2101-
break;
2102-
}
2103-
2104-
/* location is not filled in by makeVar */
2032+
/* location is not filled in by makeWholeRowVar */
21052033
result->location = location;
21062034

21072035
/* mark relation as requiring whole-row SELECT access */

src/include/nodes/makefuncs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ extern Var *makeVar(Index varno,
2929
int32 vartypmod,
3030
Index varlevelsup);
3131

32+
extern Var *makeWholeRowVar(RangeTblEntry *rte,
33+
Index varno,
34+
Index varlevelsup);
35+
3236
extern TargetEntry *makeTargetEntry(Expr *expr,
3337
AttrNumber resno,
3438
char *resname,

src/test/regress/expected/rowtypes.out

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,41 @@ select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]);
286286
f
287287
(1 row)
288288

289+
--
290+
-- Test case derived from bug #5716: check multiple uses of a rowtype result
291+
--
292+
BEGIN;
293+
CREATE TABLE price (
294+
id SERIAL PRIMARY KEY,
295+
active BOOLEAN NOT NULL,
296+
price NUMERIC
297+
);
298+
NOTICE: CREATE TABLE will create implicit sequence "price_id_seq" for serial column "price.id"
299+
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "price_pkey" for table "price"
300+
CREATE TYPE price_input AS (
301+
id INTEGER,
302+
price NUMERIC
303+
);
304+
CREATE TYPE price_key AS (
305+
id INTEGER
306+
);
307+
CREATE FUNCTION price_key_from_table(price) RETURNS price_key AS $$
308+
SELECT $1.id
309+
$$ LANGUAGE SQL;
310+
CREATE FUNCTION price_key_from_input(price_input) RETURNS price_key AS $$
311+
SELECT $1.id
312+
$$ LANGUAGE SQL;
313+
insert into price values (1,false,42), (10,false,100), (11,true,17.99);
314+
UPDATE price
315+
SET active = true, price = input_prices.price
316+
FROM unnest(ARRAY[(10, 123.00), (11, 99.99)]::price_input[]) input_prices
317+
WHERE price_key_from_table(price.*) = price_key_from_input(input_prices.*);
318+
select * from price;
319+
id | active | price
320+
----+--------+--------
321+
1 | f | 42
322+
10 | t | 123.00
323+
11 | t | 99.99
324+
(3 rows)
325+
326+
rollback;

src/test/regress/sql/rowtypes.sql

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,43 @@ select array[ row(1,2), row(3,4), row(5,6) ];
117117
-- Check ability to compare an anonymous row to elements of an array
118118
select row(1,1.1) = any (array[ row(7,7.7), row(1,1.1), row(0,0.0) ]);
119119
select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]);
120+
121+
--
122+
-- Test case derived from bug #5716: check multiple uses of a rowtype result
123+
--
124+
125+
BEGIN;
126+
127+
CREATE TABLE price (
128+
id SERIAL PRIMARY KEY,
129+
active BOOLEAN NOT NULL,
130+
price NUMERIC
131+
);
132+
133+
CREATE TYPE price_input AS (
134+
id INTEGER,
135+
price NUMERIC
136+
);
137+
138+
CREATE TYPE price_key AS (
139+
id INTEGER
140+
);
141+
142+
CREATE FUNCTION price_key_from_table(price) RETURNS price_key AS $$
143+
SELECT $1.id
144+
$$ LANGUAGE SQL;
145+
146+
CREATE FUNCTION price_key_from_input(price_input) RETURNS price_key AS $$
147+
SELECT $1.id
148+
$$ LANGUAGE SQL;
149+
150+
insert into price values (1,false,42), (10,false,100), (11,true,17.99);
151+
152+
UPDATE price
153+
SET active = true, price = input_prices.price
154+
FROM unnest(ARRAY[(10, 123.00), (11, 99.99)]::price_input[]) input_prices
155+
WHERE price_key_from_table(price.*) = price_key_from_input(input_prices.*);
156+
157+
select * from price;
158+
159+
rollback;

0 commit comments

Comments
 (0)