Prevent multicolumn expansion of "foo.*" in an UPDATE source expression.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 20 Nov 2016 19:26:19 +0000 (14:26 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 20 Nov 2016 19:26:19 +0000 (14:26 -0500)
Because we use transformTargetList() for UPDATE as well as SELECT
tlists, the code accidentally tried to expand a "*" reference into
several columns.  This is nonsensical, because the UPDATE syntax
provides exactly one target column to put the value into.  The
immediate result was that transformUpdateTargetList() got confused
and reported "UPDATE target count mismatch --- internal error".
It seems better to treat such a reference as a plain whole-row
variable, as it would be in other contexts.  (This could produce
useful results when the target column is of composite type.)

Fix by tweaking transformTargetList() to perform *-expansion only
conditionally, depending on its exprKind parameter.

Back-patch to 9.3.  The problem exists further back, but a fix would be
much more invasive before that, because transformTargetList() wasn't
told what kind of list it was working on.  Doesn't seem worth the
trouble given the lack of field reports.  (I only noticed it because
I was checking the code while trying to improve the documentation about
how we handle "foo.*".)

Discussion: <4308.1479595330@sss.pgh.pa.us>

src/backend/parser/parse_target.c
src/test/regress/expected/update.out
src/test/regress/sql/update.sql

index 2ee1270ec5d154ce476664b6f551668fdbe4289d..5763cf0386eb1b0143d87880c26508f4ae44a994 100644 (file)
@@ -113,17 +113,21 @@ transformTargetEntry(ParseState *pstate,
  * transformTargetList()
  * Turns a list of ResTarget's into a list of TargetEntry's.
  *
- * At this point, we don't care whether we are doing SELECT, UPDATE,
- * or RETURNING; we just transform the given expressions (the "val" fields).
- * However, our subroutines care, so we need the exprKind parameter.
+ * This code acts mostly the same for SELECT, UPDATE, or RETURNING lists;
+ * the main thing is to transform the given expressions (the "val" fields).
+ * The exprKind parameter distinguishes these cases when necessary.
  */
 List *
 transformTargetList(ParseState *pstate, List *targetlist,
                    ParseExprKind exprKind)
 {
    List       *p_target = NIL;
+   bool        expand_star;
    ListCell   *o_target;
 
+   /* Expand "something.*" in SELECT and RETURNING, but not UPDATE */
+   expand_star = (exprKind != EXPR_KIND_UPDATE_SOURCE);
+
    foreach(o_target, targetlist)
    {
        ResTarget  *res = (ResTarget *) lfirst(o_target);
@@ -133,35 +137,42 @@ transformTargetList(ParseState *pstate, List *targetlist,
         * "something", the star could appear as the last field in ColumnRef,
         * or as the last indirection item in A_Indirection.
         */
-       if (IsA(res->val, ColumnRef))
+       if (expand_star)
        {
-           ColumnRef  *cref = (ColumnRef *) res->val;
-
-           if (IsA(llast(cref->fields), A_Star))
+           if (IsA(res->val, ColumnRef))
            {
-               /* It is something.*, expand into multiple items */
-               p_target = list_concat(p_target,
-                                      ExpandColumnRefStar(pstate, cref,
-                                                          true));
-               continue;
-           }
-       }
-       else if (IsA(res->val, A_Indirection))
-       {
-           A_Indirection *ind = (A_Indirection *) res->val;
+               ColumnRef  *cref = (ColumnRef *) res->val;
 
-           if (IsA(llast(ind->indirection), A_Star))
+               if (IsA(llast(cref->fields), A_Star))
+               {
+                   /* It is something.*, expand into multiple items */
+                   p_target = list_concat(p_target,
+                                          ExpandColumnRefStar(pstate,
+                                                              cref,
+                                                              true));
+                   continue;
+               }
+           }
+           else if (IsA(res->val, A_Indirection))
            {
-               /* It is something.*, expand into multiple items */
-               p_target = list_concat(p_target,
-                                      ExpandIndirectionStar(pstate, ind,
-                                                            true, exprKind));
-               continue;
+               A_Indirection *ind = (A_Indirection *) res->val;
+
+               if (IsA(llast(ind->indirection), A_Star))
+               {
+                   /* It is something.*, expand into multiple items */
+                   p_target = list_concat(p_target,
+                                          ExpandIndirectionStar(pstate,
+                                                                ind,
+                                                                true,
+                                                                exprKind));
+                   continue;
+               }
            }
        }
 
        /*
-        * Not "something.*", so transform as a single expression
+        * Not "something.*", or we want to treat that as a plain whole-row
+        * variable, so transform as a single expression
         */
        p_target = lappend(p_target,
                           transformTargetEntry(pstate,
index 71b856f95c60525c2c0a214a92afe3e2e508e695..70b7f3a0b77d37b2facf239e46cd047fa64b5dd2 100644 (file)
@@ -52,6 +52,13 @@ SELECT * FROM update_test;
  100 | 20 | 
 (2 rows)
 
+-- fail, wrong data type:
+UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i, j)
+  WHERE update_test.b = v.j;
+ERROR:  column "a" is of type integer but expression is of type record
+LINE 1: UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i...
+                                   ^
+HINT:  You will need to rewrite or cast the expression.
 --
 -- Test multiple-set-clause syntax
 --
index a8a028f7101988eadae702deae190f717f72312c..a268a3bb307ccdb20fc4c1e5b928fa7b0a6eea2f 100644 (file)
@@ -35,6 +35,10 @@ UPDATE update_test SET a=v.i FROM (VALUES(100, 20)) AS v(i, j)
 
 SELECT * FROM update_test;
 
+-- fail, wrong data type:
+UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i, j)
+  WHERE update_test.b = v.j;
+
 --
 -- Test multiple-set-clause syntax
 --