]> granicus.if.org Git - postgresql/commitdiff
Improve handling of "UPDATE ... SET (column_list) = row_constructor".
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 22 Nov 2016 20:19:57 +0000 (15:19 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 22 Nov 2016 20:20:10 +0000 (15:20 -0500)
Previously, the right-hand side of a multiple-column assignment, if it
wasn't a sub-SELECT, had to be a simple parenthesized expression list,
because gram.y was responsible for "bursting" the construct into
independent column assignments.  This had the minor defect that you
couldn't write ROW (though you should be able to, since the standard says
this is a row constructor), and the rather larger defect that unlike other
uses of row constructors, we would not expand a "foo.*" item into multiple
columns.

Fix that by changing the RHS to be just "a_expr" in the grammar, leaving
it to transformMultiAssignRef to separate the elements of a RowExpr;
which it will do only after performing standard transformation of the
RowExpr, so that "foo.*" behaves as expected.

The key reason we didn't do that before was the hard-wired handling of
DEFAULT tokens (SetToDefault nodes).  This patch deals with that issue by
allowing DEFAULT in any a_expr and having parse analysis throw an error
if SetToDefault is found in an unexpected place.  That's an improvement
anyway since the error can be more specific than just "syntax error".

The SQL standard suggests that the RHS could be any a_expr yielding a
suitable row value.  This patch doesn't really move the goal posts in that
respect --- you're still limited to RowExpr or a sub-SELECT --- but it does
fix the grammar restriction, so it provides some tangible progress towards
a full implementation.  And the limitation is now documented by an explicit
error message rather than an unhelpful "syntax error".

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

doc/src/sgml/ref/update.sgml
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/backend/parser/parse_target.c
src/include/parser/parse_target.h
src/test/regress/expected/update.out
src/test/regress/sql/update.sql

index c50434f85f98f0c7b74dc2831f01296914ec8c6d..2de0f4aad109a5c659dd7758829376d26124ef52 100644 (file)
@@ -24,7 +24,7 @@ PostgreSQL documentation
 [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
 UPDATE [ ONLY ] <replaceable class="PARAMETER">table_name</replaceable> [ * ] [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
     SET { <replaceable class="PARAMETER">column_name</replaceable> = { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } |
-          ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) |
+          ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) = [ ROW ] ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) |
           ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) = ( <replaceable class="PARAMETER">sub-SELECT</replaceable> )
         } [, ...]
     [ FROM <replaceable class="PARAMETER">from_list</replaceable> ]
@@ -420,12 +420,12 @@ UPDATE films SET kind = 'Dramatic' WHERE CURRENT OF c_films;
 
   <para>
    According to the standard, the source value for a parenthesized sub-list of
-   column names can be any row-valued expression yielding the correct number
-   of columns.  <productname>PostgreSQL</productname> only allows the source
-   value to be a parenthesized list of expressions (a row constructor) or a
-   sub-<literal>SELECT</>.  An individual column's updated value can be
-   specified as <literal>DEFAULT</> in the row-constructor case, but not
-   inside a sub-<literal>SELECT</>.
+   target column names can be any row-valued expression yielding the correct
+   number of columns.  <productname>PostgreSQL</productname> only allows the
+   source value to be a <link linkend="sql-syntax-row-constructors">row
+   constructor</link> or a sub-<literal>SELECT</>.  An individual column's
+   updated value can be specified as <literal>DEFAULT</> in the
+   row-constructor case, but not inside a sub-<literal>SELECT</>.
   </para>
  </refsect1>
 </refentry>
index 6901e08fd91db4619122fa38d7fc8485c72430fb..36f8c548ebf908c4e9a4f626961f183ed0358e7e 100644 (file)
@@ -644,8 +644,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
                {
                        List       *sublist = (List *) lfirst(lc);
 
-                       /* Do basic expression transformation (same as a ROW() expr) */
-                       sublist = transformExpressionList(pstate, sublist, EXPR_KIND_VALUES);
+                       /*
+                        * Do basic expression transformation (same as a ROW() expr, but
+                        * allow SetToDefault at top level)
+                        */
+                       sublist = transformExpressionList(pstate, sublist,
+                                                                                         EXPR_KIND_VALUES, true);
 
                        /*
                         * All the sublists must be the same length, *after*
@@ -752,10 +756,14 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
                Assert(list_length(valuesLists) == 1);
                Assert(selectStmt->intoClause == NULL);
 
-               /* Do basic expression transformation (same as a ROW() expr) */
+               /*
+                * Do basic expression transformation (same as a ROW() expr, but allow
+                * SetToDefault at top level)
+                */
                exprList = transformExpressionList(pstate,
                                                                                   (List *) linitial(valuesLists),
-                                                                                  EXPR_KIND_VALUES);
+                                                                                  EXPR_KIND_VALUES,
+                                                                                  true);
 
                /* Prepare row for assignment to target table */
                exprList = transformInsertRow(pstate, exprList,
@@ -1293,9 +1301,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
        }
 
        /*
-        * For each row of VALUES, transform the raw expressions.  This is also a
-        * handy place to reject DEFAULT nodes, which the grammar allows for
-        * simplicity.
+        * For each row of VALUES, transform the raw expressions.
         *
         * Note that the intermediate representation we build is column-organized
         * not row-organized.  That simplifies the type and collation processing
@@ -1305,8 +1311,12 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
        {
                List       *sublist = (List *) lfirst(lc);
 
-               /* Do basic expression transformation (same as a ROW() expr) */
-               sublist = transformExpressionList(pstate, sublist, EXPR_KIND_VALUES);
+               /*
+                * Do basic expression transformation (same as a ROW() expr, but here
+                * we disallow SetToDefault)
+                */
+               sublist = transformExpressionList(pstate, sublist,
+                                                                                 EXPR_KIND_VALUES, false);
 
                /*
                 * All the sublists must be the same length, *after* transformation
@@ -1329,17 +1339,12 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
                                                                                exprLocation((Node *) sublist))));
                }
 
-               /* Check for DEFAULT and build per-column expression lists */
+               /* Build per-column expression lists */
                i = 0;
                foreach(lc2, sublist)
                {
                        Node       *col = (Node *) lfirst(lc2);
 
-                       if (IsA(col, SetToDefault))
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                errmsg("DEFAULT can only appear in a VALUES list within INSERT"),
-                                                parser_errposition(pstate, exprLocation(col))));
                        colexprs[i] = lappend(colexprs[i], col);
                        i++;
                }
index 0ec1cd345b232c8673ba006db26c642600cbfa11..367bc2ecff0b57fa38348f6653801599cea9011c 100644 (file)
@@ -365,8 +365,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
                                qualified_name_list any_name any_name_list type_name_list
                                any_operator expr_list attrs
                                target_list opt_target_list insert_column_list set_target_list
-                               set_clause_list set_clause multiple_set_clause
-                               ctext_expr_list ctext_row def_list operator_def_list indirection opt_indirection
+                               set_clause_list set_clause
+                               def_list operator_def_list indirection opt_indirection
                                reloption_list group_clause TriggerFuncArgs select_limit
                                opt_select_limit opclass_item_list opclass_drop_list
                                opclass_purpose opt_opfamily transaction_mode_list_or_empty
@@ -454,7 +454,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>   case_expr case_arg when_clause case_default
 %type <list>   when_clause_list
 %type <ival>   sub_type
-%type <node>   ctext_expr
 %type <value>  NumericOnly
 %type <list>   NumericOnly_list
 %type <alias>  alias_clause opt_alias_clause
@@ -466,7 +465,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>  relation_expr
 %type <range>  relation_expr_opt_alias
 %type <node>   tablesample_clause opt_repeatable_clause
-%type <target> target_el single_set_clause set_target insert_column_item
+%type <target> target_el set_target insert_column_item
 
 %type <str>            generic_option_name
 %type <node>   generic_option_arg
@@ -9914,75 +9913,24 @@ set_clause_list:
                ;
 
 set_clause:
-                       single_set_clause                                               { $$ = list_make1($1); }
-                       | multiple_set_clause                                   { $$ = $1; }
-               ;
-
-single_set_clause:
-                       set_target '=' ctext_expr
-                               {
-                                       $$ = $1;
-                                       $$->val = (Node *) $3;
-                               }
-               ;
-
-/*
- * Ideally, we'd accept any row-valued a_expr as RHS of a multiple_set_clause.
- * However, per SQL spec the row-constructor case must allow DEFAULT as a row
- * member, and it's pretty unclear how to do that (unless perhaps we allow
- * DEFAULT in any a_expr and let parse analysis sort it out later?).  For the
- * moment, the planner/executor only support a subquery as a multiassignment
- * source anyhow, so we need only accept ctext_row and subqueries here.
- */
-multiple_set_clause:
-                       '(' set_target_list ')' '=' ctext_row
+                       set_target '=' a_expr
                                {
-                                       ListCell *col_cell;
-                                       ListCell *val_cell;
-
-                                       /*
-                                        * Break the ctext_row apart, merge individual expressions
-                                        * into the destination ResTargets.  This is semantically
-                                        * equivalent to, and much cheaper to process than, the
-                                        * general case.
-                                        */
-                                       if (list_length($2) != list_length($5))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                errmsg("number of columns does not match number of values"),
-                                                                parser_errposition(@5)));
-                                       forboth(col_cell, $2, val_cell, $5)
-                                       {
-                                               ResTarget *res_col = (ResTarget *) lfirst(col_cell);
-                                               Node *res_val = (Node *) lfirst(val_cell);
-
-                                               res_col->val = res_val;
-                                       }
-
-                                       $$ = $2;
+                                       $1->val = (Node *) $3;
+                                       $$ = list_make1($1);
                                }
-                       | '(' set_target_list ')' '=' select_with_parens
+                       | '(' set_target_list ')' '=' a_expr
                                {
-                                       SubLink *sl = makeNode(SubLink);
                                        int ncolumns = list_length($2);
                                        int i = 1;
                                        ListCell *col_cell;
 
-                                       /* First, convert bare SelectStmt into a SubLink */
-                                       sl->subLinkType = MULTIEXPR_SUBLINK;
-                                       sl->subLinkId = 0;              /* will be assigned later */
-                                       sl->testexpr = NULL;
-                                       sl->operName = NIL;
-                                       sl->subselect = $5;
-                                       sl->location = @5;
-
                                        /* Create a MultiAssignRef source for each target */
                                        foreach(col_cell, $2)
                                        {
                                                ResTarget *res_col = (ResTarget *) lfirst(col_cell);
                                                MultiAssignRef *r = makeNode(MultiAssignRef);
 
-                                               r->source = (Node *) sl;
+                                               r->source = (Node *) $5;
                                                r->colno = i;
                                                r->ncolumns = ncolumns;
                                                res_col->val = (Node *) r;
@@ -10641,17 +10589,22 @@ locked_rels_list:
                ;
 
 
+/*
+ * We should allow ROW '(' expr_list ')' too, but that seems to require
+ * making VALUES a fully reserved word, which will probably break more apps
+ * than allowing the noise-word is worth.
+ */
 values_clause:
-                       VALUES ctext_row
+                       VALUES '(' expr_list ')'
                                {
                                        SelectStmt *n = makeNode(SelectStmt);
-                                       n->valuesLists = list_make1($2);
+                                       n->valuesLists = list_make1($3);
                                        $$ = (Node *) n;
                                }
-                       | values_clause ',' ctext_row
+                       | values_clause ',' '(' expr_list ')'
                                {
                                        SelectStmt *n = (SelectStmt *) $1;
-                                       n->valuesLists = lappend(n->valuesLists, $3);
+                                       n->valuesLists = lappend(n->valuesLists, $4);
                                        $$ = (Node *) n;
                                }
                ;
@@ -12042,6 +11995,20 @@ a_expr:                c_expr                                                                  { $$ = $1; }
                                                                                                 list_make1($1), @2),
                                                                         @2);
                                }
+                       | DEFAULT
+                               {
+                                       /*
+                                        * The SQL spec only allows DEFAULT in "contextually typed
+                                        * expressions", but for us, it's easier to allow it in
+                                        * any a_expr and then throw error during parse analysis
+                                        * if it's in an inappropriate context.  This way also
+                                        * lets us say something smarter than "syntax error".
+                                        */
+                                       SetToDefault *n = makeNode(SetToDefault);
+                                       /* parse analysis will fill in the rest */
+                                       n->location = @1;
+                                       $$ = (Node *)n;
+                               }
                ;
 
 /*
@@ -13297,36 +13264,6 @@ opt_asymmetric: ASYMMETRIC
                        | /*EMPTY*/
                ;
 
-/*
- * The SQL spec defines "contextually typed value expressions" and
- * "contextually typed row value constructors", which for our purposes
- * are the same as "a_expr" and "row" except that DEFAULT can appear at
- * the top level.
- */
-
-ctext_expr:
-                       a_expr                                  { $$ = (Node *) $1; }
-                       | DEFAULT
-                               {
-                                       SetToDefault *n = makeNode(SetToDefault);
-                                       n->location = @1;
-                                       $$ = (Node *) n;
-                               }
-               ;
-
-ctext_expr_list:
-                       ctext_expr                                                              { $$ = list_make1($1); }
-                       | ctext_expr_list ',' ctext_expr                { $$ = lappend($1, $3); }
-               ;
-
-/*
- * We should allow ROW '(' ctext_expr_list ')' too, but that seems to require
- * making VALUES a fully reserved word, which will probably break more apps
- * than allowing the noise-word is worth.
- */
-ctext_row: '(' ctext_expr_list ')'                                     { $$ = $2; }
-               ;
-
 
 /*****************************************************************************
  *
index 63f7965532ef87b07d7a0afabc09ef2608cb1b4f..17d1cbf8b328d4fdb7f23ab0f54659ac84ebe6de 100644 (file)
@@ -106,7 +106,7 @@ static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c);
 static Node *transformSubLink(ParseState *pstate, SubLink *sublink);
 static Node *transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
                                   Oid array_type, Oid element_type, int32 typmod);
-static Node *transformRowExpr(ParseState *pstate, RowExpr *r);
+static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault);
 static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c);
 static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m);
 static Node *transformSQLValueFunction(ParseState *pstate,
@@ -299,7 +299,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
                        break;
 
                case T_RowExpr:
-                       result = transformRowExpr(pstate, (RowExpr *) expr);
+                       result = transformRowExpr(pstate, (RowExpr *) expr, false);
                        break;
 
                case T_CoalesceExpr:
@@ -348,8 +348,20 @@ transformExprRecurse(ParseState *pstate, Node *expr)
                        break;
 
                        /*
-                        * CaseTestExpr and SetToDefault don't require any processing;
-                        * they are only injected into parse trees in fully-formed state.
+                        * In all places where DEFAULT is legal, the caller should have
+                        * processed it rather than passing it to transformExpr().
+                        */
+               case T_SetToDefault:
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("DEFAULT is not allowed in this context"),
+                                        parser_errposition(pstate,
+                                                                               ((SetToDefault *) expr)->location)));
+                       break;
+
+                       /*
+                        * CaseTestExpr doesn't require any processing; it is only
+                        * injected into parse trees in a fully-formed state.
                         *
                         * Ordinarily we should not see a Var here, but it is convenient
                         * for transformJoinUsingClause() to create untransformed operator
@@ -358,7 +370,6 @@ transformExprRecurse(ParseState *pstate, Node *expr)
                         * references, which seems expensively pointless.  So allow it.
                         */
                case T_CaseTestExpr:
-               case T_SetToDefault:
                case T_Var:
                        {
                                result = (Node *) expr;
@@ -1486,9 +1497,9 @@ static Node *
 transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
 {
        SubLink    *sublink;
+       RowExpr    *rexpr;
        Query      *qtree;
        TargetEntry *tle;
-       Param      *param;
 
        /* We should only see this in first-stage processing of UPDATE tlists */
        Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE);
@@ -1496,64 +1507,139 @@ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
        /* We only need to transform the source if this is the first column */
        if (maref->colno == 1)
        {
-               sublink = (SubLink *) transformExprRecurse(pstate, maref->source);
-               /* Currently, the grammar only allows a SubLink as source */
-               Assert(IsA(sublink, SubLink));
-               Assert(sublink->subLinkType == MULTIEXPR_SUBLINK);
-               qtree = (Query *) sublink->subselect;
-               Assert(IsA(qtree, Query));
-
-               /* Check subquery returns required number of columns */
-               if (count_nonjunk_tlist_entries(qtree->targetList) != maref->ncolumns)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                errmsg("number of columns does not match number of values"),
-                                        parser_errposition(pstate, sublink->location)));
-
                /*
-                * Build a resjunk tlist item containing the MULTIEXPR SubLink, and
-                * add it to pstate->p_multiassign_exprs, whence it will later get
-                * appended to the completed targetlist.  We needn't worry about
-                * selecting a resno for it; transformUpdateStmt will do that.
+                * For now, we only allow EXPR SubLinks and RowExprs as the source of
+                * an UPDATE multiassignment.  This is sufficient to cover interesting
+                * cases; at worst, someone would have to write (SELECT * FROM expr)
+                * to expand a composite-returning expression of another form.
                 */
-               tle = makeTargetEntry((Expr *) sublink, 0, NULL, true);
-               pstate->p_multiassign_exprs = lappend(pstate->p_multiassign_exprs, tle);
+               if (IsA(maref->source, SubLink) &&
+                       ((SubLink *) maref->source)->subLinkType == EXPR_SUBLINK)
+               {
+                       /* Relabel it as a MULTIEXPR_SUBLINK */
+                       sublink = (SubLink *) maref->source;
+                       sublink->subLinkType = MULTIEXPR_SUBLINK;
+                       /* And transform it */
+                       sublink = (SubLink *) transformExprRecurse(pstate,
+                                                                                                          (Node *) sublink);
+
+                       qtree = (Query *) sublink->subselect;
+                       Assert(IsA(qtree, Query));
+
+                       /* Check subquery returns required number of columns */
+                       if (count_nonjunk_tlist_entries(qtree->targetList) != maref->ncolumns)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("number of columns does not match number of values"),
+                                                parser_errposition(pstate, sublink->location)));
 
-               /*
-                * Assign a unique-within-this-targetlist ID to the MULTIEXPR SubLink.
-                * We can just use its position in the p_multiassign_exprs list.
-                */
-               sublink->subLinkId = list_length(pstate->p_multiassign_exprs);
+                       /*
+                        * Build a resjunk tlist item containing the MULTIEXPR SubLink,
+                        * and add it to pstate->p_multiassign_exprs, whence it will later
+                        * get appended to the completed targetlist.  We needn't worry
+                        * about selecting a resno for it; transformUpdateStmt will do
+                        * that.
+                        */
+                       tle = makeTargetEntry((Expr *) sublink, 0, NULL, true);
+                       pstate->p_multiassign_exprs = lappend(pstate->p_multiassign_exprs,
+                                                                                                 tle);
+
+                       /*
+                        * Assign a unique-within-this-targetlist ID to the MULTIEXPR
+                        * SubLink.  We can just use its position in the
+                        * p_multiassign_exprs list.
+                        */
+                       sublink->subLinkId = list_length(pstate->p_multiassign_exprs);
+               }
+               else if (IsA(maref->source, RowExpr))
+               {
+                       /* Transform the RowExpr, allowing SetToDefault items */
+                       rexpr = (RowExpr *) transformRowExpr(pstate,
+                                                                                                (RowExpr *) maref->source,
+                                                                                                true);
+
+                       /* Check it returns required number of columns */
+                       if (list_length(rexpr->args) != maref->ncolumns)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("number of columns does not match number of values"),
+                                                parser_errposition(pstate, rexpr->location)));
+
+                       /*
+                        * Temporarily append it to p_multiassign_exprs, so we can get it
+                        * back when we come back here for additional columns.
+                        */
+                       tle = makeTargetEntry((Expr *) rexpr, 0, NULL, true);
+                       pstate->p_multiassign_exprs = lappend(pstate->p_multiassign_exprs,
+                                                                                                 tle);
+               }
+               else
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("source for a multiple-column UPDATE item must be a sub-SELECT or ROW() expression"),
+                                  parser_errposition(pstate, exprLocation(maref->source))));
        }
        else
        {
                /*
                 * Second or later column in a multiassignment.  Re-fetch the
-                * transformed query, which we assume is still the last entry in
-                * p_multiassign_exprs.
+                * transformed SubLink or RowExpr, which we assume is still the last
+                * entry in p_multiassign_exprs.
                 */
                Assert(pstate->p_multiassign_exprs != NIL);
                tle = (TargetEntry *) llast(pstate->p_multiassign_exprs);
+       }
+
+       /*
+        * Emit the appropriate output expression for the current column
+        */
+       if (IsA(tle->expr, SubLink))
+       {
+               Param      *param;
+
                sublink = (SubLink *) tle->expr;
-               Assert(IsA(sublink, SubLink));
                Assert(sublink->subLinkType == MULTIEXPR_SUBLINK);
                qtree = (Query *) sublink->subselect;
                Assert(IsA(qtree, Query));
+
+               /* Build a Param representing the current subquery output column */
+               tle = (TargetEntry *) list_nth(qtree->targetList, maref->colno - 1);
+               Assert(!tle->resjunk);
+
+               param = makeNode(Param);
+               param->paramkind = PARAM_MULTIEXPR;
+               param->paramid = (sublink->subLinkId << 16) | maref->colno;
+               param->paramtype = exprType((Node *) tle->expr);
+               param->paramtypmod = exprTypmod((Node *) tle->expr);
+               param->paramcollid = exprCollation((Node *) tle->expr);
+               param->location = exprLocation((Node *) tle->expr);
+
+               return (Node *) param;
        }
 
-       /* Build a Param representing the appropriate subquery output column */
-       tle = (TargetEntry *) list_nth(qtree->targetList, maref->colno - 1);
-       Assert(!tle->resjunk);
+       if (IsA(tle->expr, RowExpr))
+       {
+               Node       *result;
+
+               rexpr = (RowExpr *) tle->expr;
 
-       param = makeNode(Param);
-       param->paramkind = PARAM_MULTIEXPR;
-       param->paramid = (sublink->subLinkId << 16) | maref->colno;
-       param->paramtype = exprType((Node *) tle->expr);
-       param->paramtypmod = exprTypmod((Node *) tle->expr);
-       param->paramcollid = exprCollation((Node *) tle->expr);
-       param->location = exprLocation((Node *) tle->expr);
+               /* Just extract and return the next element of the RowExpr */
+               result = (Node *) list_nth(rexpr->args, maref->colno - 1);
+
+               /*
+                * If we're at the last column, delete the RowExpr from
+                * p_multiassign_exprs; we don't need it anymore, and don't want it in
+                * the finished UPDATE tlist.
+                */
+               if (maref->colno == maref->ncolumns)
+                       pstate->p_multiassign_exprs =
+                               list_delete_ptr(pstate->p_multiassign_exprs, tle);
+
+               return result;
+       }
 
-       return (Node *) param;
+       elog(ERROR, "unexpected expr type in multiassign list");
+       return NULL;                            /* keep compiler quiet */
 }
 
 static Node *
@@ -2081,7 +2167,7 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 }
 
 static Node *
-transformRowExpr(ParseState *pstate, RowExpr *r)
+transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault)
 {
        RowExpr    *newr;
        char            fname[16];
@@ -2091,7 +2177,8 @@ transformRowExpr(ParseState *pstate, RowExpr *r)
        newr = makeNode(RowExpr);
 
        /* Transform the field expressions */
-       newr->args = transformExpressionList(pstate, r->args, pstate->p_expr_kind);
+       newr->args = transformExpressionList(pstate, r->args,
+                                                                                pstate->p_expr_kind, allowDefault);
 
        /* Barring later casting, we consider the type RECORD */
        newr->row_typeid = RECORDOID;
index a76c33f40ea14c9b75db94a7236f246412b10324..d440dec5564f338e9d39d0f8f193557da761dce1 100644 (file)
@@ -91,7 +91,17 @@ transformTargetEntry(ParseState *pstate,
 {
        /* Transform the node if caller didn't do it already */
        if (expr == NULL)
-               expr = transformExpr(pstate, node, exprKind);
+       {
+               /*
+                * If it's a SetToDefault node and we should allow that, pass it
+                * through unmodified.  (transformExpr will throw the appropriate
+                * error if we're disallowing it.)
+                */
+               if (exprKind == EXPR_KIND_UPDATE_SOURCE && IsA(node, SetToDefault))
+                       expr = node;
+               else
+                       expr = transformExpr(pstate, node, exprKind);
+       }
 
        if (colname == NULL && !resjunk)
        {
@@ -210,10 +220,13 @@ transformTargetList(ParseState *pstate, List *targetlist,
  * the input list elements are bare expressions without ResTarget decoration,
  * and the output elements are likewise just expressions without TargetEntry
  * decoration.  We use this for ROW() and VALUES() constructs.
+ *
+ * exprKind is not enough to tell us whether to allow SetToDefault, so
+ * an additional flag is needed for that.
  */
 List *
 transformExpressionList(ParseState *pstate, List *exprlist,
-                                               ParseExprKind exprKind)
+                                               ParseExprKind exprKind, bool allowDefault)
 {
        List       *result = NIL;
        ListCell   *lc;
@@ -255,10 +268,17 @@ transformExpressionList(ParseState *pstate, List *exprlist,
                }
 
                /*
-                * Not "something.*", so transform as a single expression
+                * Not "something.*", so transform as a single expression.  If it's a
+                * SetToDefault node and we should allow that, pass it through
+                * unmodified.  (transformExpr will throw the appropriate error if
+                * we're disallowing it.)
                 */
-               result = lappend(result,
-                                                transformExpr(pstate, e, exprKind));
+               if (allowDefault && IsA(e, SetToDefault))
+                        /* do nothing */ ;
+               else
+                       e = transformExpr(pstate, e, exprKind);
+
+               result = lappend(result, e);
        }
 
        /* Shouldn't have any multiassign items here */
index 8d4ad6002691065ea73ca5f371cc2be6257bcc12..f85c618c43d21712d72b58a0cbfe5a8fe6dcfbe1 100644 (file)
@@ -20,7 +20,7 @@
 extern List *transformTargetList(ParseState *pstate, List *targetlist,
                                        ParseExprKind exprKind);
 extern List *transformExpressionList(ParseState *pstate, List *exprlist,
-                                               ParseExprKind exprKind);
+                                               ParseExprKind exprKind, bool allowDefault);
 extern void markTargetListOrigins(ParseState *pstate, List *targetlist);
 extern TargetEntry *transformTargetEntry(ParseState *pstate,
                                         Node *node, Node *expr, ParseExprKind exprKind,
index 49730ea3c5535fe50d306a7338d45d5d6ced70b8..609899e1f7486b6383e847e16a4c79e6f7ff93a6 100644 (file)
@@ -140,17 +140,15 @@ SELECT * FROM update_test;
     |     | 
 (4 rows)
 
--- these should work, but don't yet:
-UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 100)) AS v(i, j)
+-- *-expansion should work in this context:
+UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 100)) AS v(i, j)
   WHERE update_test.a = v.i;
-ERROR:  number of columns does not match number of values
-LINE 1: UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 100)) ...
-                                       ^
-UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 101)) AS v(i, j)
+-- you might expect this to work, but syntactically it's not a RowExpr:
+UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 101)) AS v(i, j)
   WHERE update_test.a = v.i;
-ERROR:  syntax error at or near "ROW"
-LINE 1: UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 101...
-                                       ^
+ERROR:  source for a multiple-column UPDATE item must be a sub-SELECT or ROW() expression
+LINE 1: UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 101)) ...
+                                        ^
 -- if an alias for the target table is specified, don't allow references
 -- to the original table name
 UPDATE update_test AS t SET b = update_test.b + 10 WHERE t.a = 10;
@@ -163,8 +161,8 @@ UPDATE update_test SET c = repeat('x', 10000) WHERE c = 'car';
 SELECT a, b, char_length(c) FROM update_test;
  a  |  b  | char_length 
 ----+-----+-------------
- 21 | 101 |            
     |     |            
+ 21 | 100 |            
  41 |  12 |       10000
  42 |  12 |       10000
 (4 rows)
index e0cf5d12a9235a334430cd629f352fc61f7355a2..ad58273b385fb5a7c9bb1e96b2f89ff2598849e5 100644 (file)
@@ -74,10 +74,11 @@ UPDATE update_test SET (b,a) = (select a+1,b from update_test);
 UPDATE update_test SET (b,a) = (select a+1,b from update_test where a = 1000)
   WHERE a = 11;
 SELECT * FROM update_test;
--- these should work, but don't yet:
-UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 100)) AS v(i, j)
+-- *-expansion should work in this context:
+UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 100)) AS v(i, j)
   WHERE update_test.a = v.i;
-UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 101)) AS v(i, j)
+-- you might expect this to work, but syntactically it's not a RowExpr:
+UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 101)) AS v(i, j)
   WHERE update_test.a = v.i;
 
 -- if an alias for the target table is specified, don't allow references