mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-02-05 19:09:58 +08:00
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).
This commit is contained in:
parent
80a65c0577
commit
01b4bb44fe
@ -17,6 +17,7 @@
|
||||
|
||||
#include "catalog/pg_type.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "utils/lsyscache.h"
|
||||
|
||||
|
||||
@ -90,6 +91,93 @@ makeVar(Index varno,
|
||||
return var;
|
||||
}
|
||||
|
||||
/*
|
||||
* makeWholeRowVar -
|
||||
* creates a Var node representing a whole row of the specified RTE
|
||||
*
|
||||
* A whole-row reference is a Var with varno set to the correct range
|
||||
* table entry, and varattno == 0 to signal that it references the whole
|
||||
* tuple. (Use of zero here is unclean, since it could easily be confused
|
||||
* with error cases, but it's not worth changing now.) The vartype indicates
|
||||
* a rowtype; either a named composite type, or RECORD. This function
|
||||
* encapsulates the logic for determining the correct rowtype OID to use.
|
||||
*/
|
||||
Var *
|
||||
makeWholeRowVar(RangeTblEntry *rte,
|
||||
Index varno,
|
||||
Index varlevelsup)
|
||||
{
|
||||
Var *result;
|
||||
Oid toid;
|
||||
|
||||
switch (rte->rtekind)
|
||||
{
|
||||
case RTE_RELATION:
|
||||
/* relation: the rowtype is a named composite type */
|
||||
toid = get_rel_type_id(rte->relid);
|
||||
if (!OidIsValid(toid))
|
||||
elog(ERROR, "could not find type OID for relation %u",
|
||||
rte->relid);
|
||||
result = makeVar(varno,
|
||||
InvalidAttrNumber,
|
||||
toid,
|
||||
-1,
|
||||
varlevelsup);
|
||||
break;
|
||||
case RTE_FUNCTION:
|
||||
toid = exprType(rte->funcexpr);
|
||||
if (type_is_rowtype(toid))
|
||||
{
|
||||
/* func returns composite; same as relation case */
|
||||
result = makeVar(varno,
|
||||
InvalidAttrNumber,
|
||||
toid,
|
||||
-1,
|
||||
varlevelsup);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* func returns scalar; instead of making a whole-row Var,
|
||||
* just reference the function's scalar output. (XXX this
|
||||
* seems a tad inconsistent, especially if "f.*" was
|
||||
* explicitly written ...)
|
||||
*/
|
||||
result = makeVar(varno,
|
||||
1,
|
||||
toid,
|
||||
-1,
|
||||
varlevelsup);
|
||||
}
|
||||
break;
|
||||
case RTE_VALUES:
|
||||
toid = RECORDOID;
|
||||
/* returns composite; same as relation case */
|
||||
result = makeVar(varno,
|
||||
InvalidAttrNumber,
|
||||
toid,
|
||||
-1,
|
||||
varlevelsup);
|
||||
break;
|
||||
default:
|
||||
|
||||
/*
|
||||
* RTE is a join or subselect. We represent this as a whole-row
|
||||
* Var of RECORD type. (Note that in most cases the Var will be
|
||||
* expanded to a RowExpr during planning, but that is not our
|
||||
* concern here.)
|
||||
*/
|
||||
result = makeVar(varno,
|
||||
InvalidAttrNumber,
|
||||
RECORDOID,
|
||||
-1,
|
||||
varlevelsup);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* makeTargetEntry -
|
||||
* creates a TargetEntry node
|
||||
|
@ -170,11 +170,9 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
|
||||
else
|
||||
{
|
||||
/* Not a table, so we need the whole row as a junk var */
|
||||
var = makeVar(rc->rti,
|
||||
InvalidAttrNumber,
|
||||
RECORDOID,
|
||||
-1,
|
||||
0);
|
||||
var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
|
||||
rc->rti,
|
||||
0);
|
||||
snprintf(resname, sizeof(resname), "wholerow%u", rc->rti);
|
||||
tle = makeTargetEntry((Expr *) var,
|
||||
list_length(tlist) + 1,
|
||||
|
@ -2015,12 +2015,6 @@ transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr)
|
||||
|
||||
/*
|
||||
* Construct a whole-row reference to represent the notation "relation.*".
|
||||
*
|
||||
* A whole-row reference is a Var with varno set to the correct range
|
||||
* table entry, and varattno == 0 to signal that it references the whole
|
||||
* tuple. (Use of zero here is unclean, since it could easily be confused
|
||||
* with error cases, but it's not worth changing now.) The vartype indicates
|
||||
* a rowtype; either a named composite type, or RECORD.
|
||||
*/
|
||||
static Node *
|
||||
transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, int location)
|
||||
@ -2028,80 +2022,14 @@ transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, int location)
|
||||
Var *result;
|
||||
int vnum;
|
||||
int sublevels_up;
|
||||
Oid toid;
|
||||
|
||||
/* Find the RTE's rangetable location */
|
||||
|
||||
vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
|
||||
|
||||
/* Build the appropriate referencing node */
|
||||
result = makeWholeRowVar(rte, vnum, sublevels_up);
|
||||
|
||||
switch (rte->rtekind)
|
||||
{
|
||||
case RTE_RELATION:
|
||||
/* relation: the rowtype is a named composite type */
|
||||
toid = get_rel_type_id(rte->relid);
|
||||
if (!OidIsValid(toid))
|
||||
elog(ERROR, "could not find type OID for relation %u",
|
||||
rte->relid);
|
||||
result = makeVar(vnum,
|
||||
InvalidAttrNumber,
|
||||
toid,
|
||||
-1,
|
||||
sublevels_up);
|
||||
break;
|
||||
case RTE_FUNCTION:
|
||||
toid = exprType(rte->funcexpr);
|
||||
if (type_is_rowtype(toid))
|
||||
{
|
||||
/* func returns composite; same as relation case */
|
||||
result = makeVar(vnum,
|
||||
InvalidAttrNumber,
|
||||
toid,
|
||||
-1,
|
||||
sublevels_up);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* func returns scalar; instead of making a whole-row Var,
|
||||
* just reference the function's scalar output. (XXX this
|
||||
* seems a tad inconsistent, especially if "f.*" was
|
||||
* explicitly written ...)
|
||||
*/
|
||||
result = makeVar(vnum,
|
||||
1,
|
||||
toid,
|
||||
-1,
|
||||
sublevels_up);
|
||||
}
|
||||
break;
|
||||
case RTE_VALUES:
|
||||
toid = RECORDOID;
|
||||
/* returns composite; same as relation case */
|
||||
result = makeVar(vnum,
|
||||
InvalidAttrNumber,
|
||||
toid,
|
||||
-1,
|
||||
sublevels_up);
|
||||
break;
|
||||
default:
|
||||
|
||||
/*
|
||||
* RTE is a join or subselect. We represent this as a whole-row
|
||||
* Var of RECORD type. (Note that in most cases the Var will be
|
||||
* expanded to a RowExpr during planning, but that is not our
|
||||
* concern here.)
|
||||
*/
|
||||
result = makeVar(vnum,
|
||||
InvalidAttrNumber,
|
||||
RECORDOID,
|
||||
-1,
|
||||
sublevels_up);
|
||||
break;
|
||||
}
|
||||
|
||||
/* location is not filled in by makeVar */
|
||||
/* location is not filled in by makeWholeRowVar */
|
||||
result->location = location;
|
||||
|
||||
/* mark relation as requiring whole-row SELECT access */
|
||||
|
@ -29,6 +29,10 @@ extern Var *makeVar(Index varno,
|
||||
int32 vartypmod,
|
||||
Index varlevelsup);
|
||||
|
||||
extern Var *makeWholeRowVar(RangeTblEntry *rte,
|
||||
Index varno,
|
||||
Index varlevelsup);
|
||||
|
||||
extern TargetEntry *makeTargetEntry(Expr *expr,
|
||||
AttrNumber resno,
|
||||
char *resname,
|
||||
|
@ -286,3 +286,41 @@ select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]);
|
||||
f
|
||||
(1 row)
|
||||
|
||||
--
|
||||
-- Test case derived from bug #5716: check multiple uses of a rowtype result
|
||||
--
|
||||
BEGIN;
|
||||
CREATE TABLE price (
|
||||
id SERIAL PRIMARY KEY,
|
||||
active BOOLEAN NOT NULL,
|
||||
price NUMERIC
|
||||
);
|
||||
NOTICE: CREATE TABLE will create implicit sequence "price_id_seq" for serial column "price.id"
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "price_pkey" for table "price"
|
||||
CREATE TYPE price_input AS (
|
||||
id INTEGER,
|
||||
price NUMERIC
|
||||
);
|
||||
CREATE TYPE price_key AS (
|
||||
id INTEGER
|
||||
);
|
||||
CREATE FUNCTION price_key_from_table(price) RETURNS price_key AS $$
|
||||
SELECT $1.id
|
||||
$$ LANGUAGE SQL;
|
||||
CREATE FUNCTION price_key_from_input(price_input) RETURNS price_key AS $$
|
||||
SELECT $1.id
|
||||
$$ LANGUAGE SQL;
|
||||
insert into price values (1,false,42), (10,false,100), (11,true,17.99);
|
||||
UPDATE price
|
||||
SET active = true, price = input_prices.price
|
||||
FROM unnest(ARRAY[(10, 123.00), (11, 99.99)]::price_input[]) input_prices
|
||||
WHERE price_key_from_table(price.*) = price_key_from_input(input_prices.*);
|
||||
select * from price;
|
||||
id | active | price
|
||||
----+--------+--------
|
||||
1 | f | 42
|
||||
10 | t | 123.00
|
||||
11 | t | 99.99
|
||||
(3 rows)
|
||||
|
||||
rollback;
|
||||
|
@ -117,3 +117,43 @@ select array[ row(1,2), row(3,4), row(5,6) ];
|
||||
-- Check ability to compare an anonymous row to elements of an array
|
||||
select row(1,1.1) = any (array[ row(7,7.7), row(1,1.1), row(0,0.0) ]);
|
||||
select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]);
|
||||
|
||||
--
|
||||
-- Test case derived from bug #5716: check multiple uses of a rowtype result
|
||||
--
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE price (
|
||||
id SERIAL PRIMARY KEY,
|
||||
active BOOLEAN NOT NULL,
|
||||
price NUMERIC
|
||||
);
|
||||
|
||||
CREATE TYPE price_input AS (
|
||||
id INTEGER,
|
||||
price NUMERIC
|
||||
);
|
||||
|
||||
CREATE TYPE price_key AS (
|
||||
id INTEGER
|
||||
);
|
||||
|
||||
CREATE FUNCTION price_key_from_table(price) RETURNS price_key AS $$
|
||||
SELECT $1.id
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
CREATE FUNCTION price_key_from_input(price_input) RETURNS price_key AS $$
|
||||
SELECT $1.id
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
insert into price values (1,false,42), (10,false,100), (11,true,17.99);
|
||||
|
||||
UPDATE price
|
||||
SET active = true, price = input_prices.price
|
||||
FROM unnest(ARRAY[(10, 123.00), (11, 99.99)]::price_input[]) input_prices
|
||||
WHERE price_key_from_table(price.*) = price_key_from_input(input_prices.*);
|
||||
|
||||
select * from price;
|
||||
|
||||
rollback;
|
||||
|
Loading…
Reference in New Issue
Block a user