diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml index c50434f85f98f0c7b74dc2831f01296914ec8c6d..2de0f4aad109a5c659dd7758829376d26124ef52 100644 --- a/doc/src/sgml/ref/update.sgml +++ b/doc/src/sgml/ref/update.sgml @@ -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> diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 6901e08fd91db4619122fa38d7fc8485c72430fb..36f8c548ebf908c4e9a4f626961f183ed0358e7e 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -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++; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0ec1cd345b232c8673ba006db26c642600cbfa11..367bc2ecff0b57fa38348f6653801599cea9011c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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; } - ; - /***************************************************************************** * diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 63f7965532ef87b07d7a0afabc09ef2608cb1b4f..17d1cbf8b328d4fdb7f23ab0f54659ac84ebe6de 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -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; diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index a76c33f40ea14c9b75db94a7236f246412b10324..d440dec5564f338e9d39d0f8f193557da761dce1 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -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 */ diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h index 8d4ad6002691065ea73ca5f371cc2be6257bcc12..f85c618c43d21712d72b58a0cbfe5a8fe6dcfbe1 100644 --- a/src/include/parser/parse_target.h +++ b/src/include/parser/parse_target.h @@ -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, diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index 49730ea3c5535fe50d306a7338d45d5d6ced70b8..609899e1f7486b6383e847e16a4c79e6f7ff93a6 100644 --- a/src/test/regress/expected/update.out +++ b/src/test/regress/expected/update.out @@ -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) diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql index e0cf5d12a9235a334430cd629f352fc61f7355a2..ad58273b385fb5a7c9bb1e96b2f89ff2598849e5 100644 --- a/src/test/regress/sql/update.sql +++ b/src/test/regress/sql/update.sql @@ -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