]> granicus.if.org Git - postgresql/commitdiff
Fix incorrect generation of whole-row variables in planner.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 19 Oct 2010 19:08:37 +0000 (15:08 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 19 Oct 2010 19:09:23 +0000 (15:09 -0400)
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).

src/backend/nodes/makefuncs.c
src/backend/optimizer/prep/preptlist.c
src/backend/parser/parse_expr.c
src/backend/rewrite/rewriteHandler.c
src/include/nodes/makefuncs.h
src/test/regress/expected/rowtypes.out
src/test/regress/sql/rowtypes.sql

index ee15695b914b2e8dfa6da4ae9e7a9afefb054415..4b268f3c6b3bc2ac5a4566d71c77477af8e4046b 100644 (file)
@@ -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
index 2c97c71472a5feb074e0bf190aa417c41c1a8d9d..bc8b7709d37f5162d60c05266187fa71547d293e 100644 (file)
@@ -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,
index e49473cc8173adc44fb86cacec8ab474897f9272..addd0d4fffe769f21c6f03b540819c6987c61a49 100644 (file)
@@ -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 */
index 31b25957b606546f5d386ec5cf25478a4ecd301f..a332611a5866f1a949aa35785807df6d0897e8ff 100644 (file)
@@ -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);
index 3f59e9d40e3cd95f93d20850debea56ee276b515..8f1687fc44961d5a68cf3be4c8ff16f39bdea8de 100644 (file)
@@ -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,
index ee392909302c778219c5909467a03ffb578c8ef4..a21f7b8c06b2bb5673857236469adc1b9a4b1522 100644 (file)
@@ -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;
index 51c66f0d04f5d532f8455a7d357f7a0c3c17fe34..e5a77f79f65c0c0321c5b4c3ddd417cde48510db 100644 (file)
@@ -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;