diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index ee15695b914b2e8dfa6da4ae9e7a9afefb054415..4b268f3c6b3bc2ac5a4566d71c77477af8e4046b 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -107,6 +107,93 @@ makeVarFromTargetEntry(Index varno, 0); } +/* + * 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 2c97c71472a5feb074e0bf190aa417c41c1a8d9d..bc8b7709d37f5162d60c05266187fa71547d293e 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 e49473cc8173adc44fb86cacec8ab474897f9272..addd0d4fffe769f21c6f03b540819c6987c61a49 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 31b25957b606546f5d386ec5cf25478a4ecd301f..a332611a5866f1a949aa35785807df6d0897e8ff 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 3f59e9d40e3cd95f93d20850debea56ee276b515..8f1687fc44961d5a68cf3be4c8ff16f39bdea8de 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 ee392909302c778219c5909467a03ffb578c8ef4..a21f7b8c06b2bb5673857236469adc1b9a4b1522 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 51c66f0d04f5d532f8455a7d357f7a0c3c17fe34..e5a77f79f65c0c0321c5b4c3ddd417cde48510db 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;