aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/nodes/makefuncs.c87
-rw-r--r--src/backend/optimizer/prep/preptlist.c8
-rw-r--r--src/backend/parser/parse_expr.c76
-rw-r--r--src/backend/rewrite/rewriteHandler.c18
-rw-r--r--src/include/nodes/makefuncs.h4
-rw-r--r--src/test/regress/expected/rowtypes.out38
-rw-r--r--src/test/regress/sql/rowtypes.sql40
7 files changed, 183 insertions, 88 deletions
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index ee15695b914..4b268f3c6b3 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -108,6 +108,93 @@ makeVarFromTargetEntry(Index varno,
}
/*
+ * 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
*/
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 2c97c71472a..bc8b7709d37 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -139,11 +139,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,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e49473cc817..addd0d4fffe 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -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 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 31b25957b60..a332611a586 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -52,7 +52,8 @@ static TargetEntry *process_matched_tle(TargetEntry *src_tle,
static Node *get_assignment_input(Node *node);
static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation,
List *attrnos);
-static void rewriteTargetListUD(Query *parsetree, Relation target_relation);
+static void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
+ Relation target_relation);
static void markQueryForLocking(Query *qry, Node *jtnode,
bool forUpdate, bool noWait, bool pushedDown);
static List *matchLocks(CmdType event, RuleLock *rulelocks,
@@ -1110,7 +1111,8 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
* ordering isn't actually critical at the moment.
*/
static void
-rewriteTargetListUD(Query *parsetree, Relation target_relation)
+rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
+ Relation target_relation)
{
Var *var;
const char *attrname;
@@ -1135,11 +1137,9 @@ rewriteTargetListUD(Query *parsetree, Relation target_relation)
* Emit whole-row Var so that executor will have the "old" view row
* to pass to the INSTEAD OF trigger.
*/
- var = makeVar(parsetree->resultRelation,
- InvalidAttrNumber,
- RECORDOID,
- -1,
- 0);
+ var = makeWholeRowVar(target_rte,
+ parsetree->resultRelation,
+ 0);
attrname = "wholerow";
}
@@ -1858,11 +1858,11 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
else if (event == CMD_UPDATE)
{
rewriteTargetListIU(parsetree, rt_entry_relation, NULL);
- rewriteTargetListUD(parsetree, rt_entry_relation);
+ rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation);
}
else if (event == CMD_DELETE)
{
- rewriteTargetListUD(parsetree, rt_entry_relation);
+ rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation);
}
else
elog(ERROR, "unrecognized commandType: %d", (int) event);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 3f59e9d40e3..8f1687fc449 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -32,6 +32,10 @@ extern Var *makeVar(Index varno,
extern Var *makeVarFromTargetEntry(Index varno,
TargetEntry *tle);
+extern Var *makeWholeRowVar(RangeTblEntry *rte,
+ Index varno,
+ Index varlevelsup);
+
extern TargetEntry *makeTargetEntry(Expr *expr,
AttrNumber resno,
char *resname,
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index ee392909302..a21f7b8c06b 100644
--- a/src/test/regress/expected/rowtypes.out
+++ b/src/test/regress/expected/rowtypes.out
@@ -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;
diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql
index 51c66f0d04f..e5a77f79f65 100644
--- a/src/test/regress/sql/rowtypes.sql
+++ b/src/test/regress/sql/rowtypes.sql
@@ -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;