1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-25 01:02:05 +03:00

Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...

This SQL-standard feature allows a sub-SELECT yielding multiple columns
(but only one row) to be used to compute the new values of several columns
to be updated.  While the same results can be had with an independent
sub-SELECT per column, such a workaround can require a great deal of
duplicated computation.

The standard actually says that the source for a multi-column assignment
could be any row-valued expression.  The implementation used here is
tightly tied to our existing sub-SELECT support and can't handle other
cases; the Bison grammar would have some issues with them too.  However,
I don't feel too bad about this since other cases can be converted into
sub-SELECTs.  For instance, "SET (a,b,c) = row_valued_function(x)" could
be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
This commit is contained in:
Tom Lane
2014-06-18 13:22:25 -04:00
parent 230ba02d85
commit 8f889b1083
31 changed files with 805 additions and 122 deletions

View File

@ -9234,6 +9234,14 @@ single_set_clause:
}
;
/*
* 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
{
@ -9242,14 +9250,15 @@ multiple_set_clause:
/*
* Break the ctext_row apart, merge individual expressions
* into the destination ResTargets. XXX this approach
* cannot work for general row expressions as sources.
* 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(@1)));
parser_errposition(@5)));
forboth(col_cell, $2, val_cell, $5)
{
ResTarget *res_col = (ResTarget *) lfirst(col_cell);
@ -9258,6 +9267,36 @@ multiple_set_clause:
res_col->val = res_val;
}
$$ = $2;
}
| '(' set_target_list ')' '=' select_with_parens
{
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->colno = i;
r->ncolumns = ncolumns;
res_col->val = (Node *) r;
i++;
}
$$ = $2;
}
;
@ -11091,6 +11130,7 @@ a_expr: c_expr { $$ = $1; }
/* generate foo = ANY (subquery) */
SubLink *n = (SubLink *) $3;
n->subLinkType = ANY_SUBLINK;
n->subLinkId = 0;
n->testexpr = $1;
n->operName = list_make1(makeString("="));
n->location = @2;
@ -11111,6 +11151,7 @@ a_expr: c_expr { $$ = $1; }
/* Make an = ANY node */
SubLink *n = (SubLink *) $4;
n->subLinkType = ANY_SUBLINK;
n->subLinkId = 0;
n->testexpr = $1;
n->operName = list_make1(makeString("="));
n->location = @3;
@ -11127,6 +11168,7 @@ a_expr: c_expr { $$ = $1; }
{
SubLink *n = makeNode(SubLink);
n->subLinkType = $3;
n->subLinkId = 0;
n->testexpr = $1;
n->operName = $2;
n->subselect = $4;
@ -11286,6 +11328,7 @@ c_expr: columnref { $$ = $1; }
{
SubLink *n = makeNode(SubLink);
n->subLinkType = EXPR_SUBLINK;
n->subLinkId = 0;
n->testexpr = NULL;
n->operName = NIL;
n->subselect = $1;
@ -11307,6 +11350,7 @@ c_expr: columnref { $$ = $1; }
SubLink *n = makeNode(SubLink);
A_Indirection *a = makeNode(A_Indirection);
n->subLinkType = EXPR_SUBLINK;
n->subLinkId = 0;
n->testexpr = NULL;
n->operName = NIL;
n->subselect = $1;
@ -11319,6 +11363,7 @@ c_expr: columnref { $$ = $1; }
{
SubLink *n = makeNode(SubLink);
n->subLinkType = EXISTS_SUBLINK;
n->subLinkId = 0;
n->testexpr = NULL;
n->operName = NIL;
n->subselect = $2;
@ -11329,6 +11374,7 @@ c_expr: columnref { $$ = $1; }
{
SubLink *n = makeNode(SubLink);
n->subLinkType = ARRAY_SUBLINK;
n->subLinkId = 0;
n->testexpr = NULL;
n->operName = NIL;
n->subselect = $2;

View File

@ -20,6 +20,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
@ -49,6 +50,7 @@ static Node *transformAExprOf(ParseState *pstate, A_Expr *a);
static Node *transformAExprIn(ParseState *pstate, A_Expr *a);
static Node *transformBoolExpr(ParseState *pstate, BoolExpr *a);
static Node *transformFuncCall(ParseState *pstate, FuncCall *fn);
static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c);
static Node *transformSubLink(ParseState *pstate, SubLink *sublink);
static Node *transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
@ -255,6 +257,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformFuncCall(pstate, (FuncCall *) expr);
break;
case T_MultiAssignRef:
result = transformMultiAssignRef(pstate, (MultiAssignRef *) expr);
break;
case T_NamedArgExpr:
{
NamedArgExpr *na = (NamedArgExpr *) expr;
@ -1267,6 +1273,80 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
fn->location);
}
static Node *
transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
{
SubLink *sublink;
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);
/* 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.
*/
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
{
/*
* Second or later column in a multiassignment. Re-fetch the
* transformed query, 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);
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 appropriate 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;
}
static Node *
transformCaseExpr(ParseState *pstate, CaseExpr *c)
{
@ -1520,26 +1600,15 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
else if (sublink->subLinkType == EXPR_SUBLINK ||
sublink->subLinkType == ARRAY_SUBLINK)
{
ListCell *tlist_item = list_head(qtree->targetList);
/*
* Make sure the subselect delivers a single column (ignoring resjunk
* targets).
*/
if (tlist_item == NULL ||
((TargetEntry *) lfirst(tlist_item))->resjunk)
if (count_nonjunk_tlist_entries(qtree->targetList) != 1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("subquery must return a column"),
errmsg("subquery must return only one column"),
parser_errposition(pstate, sublink->location)));
while ((tlist_item = lnext(tlist_item)) != NULL)
{
if (!((TargetEntry *) lfirst(tlist_item))->resjunk)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("subquery must return only one column"),
parser_errposition(pstate, sublink->location)));
}
/*
* EXPR and ARRAY need no test expression or combining operator. These
@ -1548,6 +1617,12 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
sublink->testexpr = NULL;
sublink->operName = NIL;
}
else if (sublink->subLinkType == MULTIEXPR_SUBLINK)
{
/* Same as EXPR case, except no restriction on number of columns */
sublink->testexpr = NULL;
sublink->operName = NIL;
}
else
{
/* ALL, ANY, or ROWCOMPARE: generate row-comparing expression */

View File

@ -113,9 +113,9 @@ 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 necesssary.
*/
List *
transformTargetList(ParseState *pstate, List *targetlist,
@ -124,6 +124,9 @@ transformTargetList(ParseState *pstate, List *targetlist,
List *p_target = NIL;
ListCell *o_target;
/* Shouldn't have any leftover multiassign items at start */
Assert(pstate->p_multiassign_exprs == NIL);
foreach(o_target, targetlist)
{
ResTarget *res = (ResTarget *) lfirst(o_target);
@ -172,6 +175,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
false));
}
/*
* If any multiassign resjunk items were created, attach them to the end
* of the targetlist. This should only happen in an UPDATE tlist. We
* don't need to worry about numbering of these items; transformUpdateStmt
* will set their resnos.
*/
if (pstate->p_multiassign_exprs)
{
Assert(exprKind == EXPR_KIND_UPDATE_SOURCE);
p_target = list_concat(p_target, pstate->p_multiassign_exprs);
pstate->p_multiassign_exprs = NIL;
}
return p_target;
}
@ -234,6 +250,9 @@ transformExpressionList(ParseState *pstate, List *exprlist,
transformExpr(pstate, e, exprKind));
}
/* Shouldn't have any multiassign items here */
Assert(pstate->p_multiassign_exprs == NIL);
return result;
}
@ -1691,6 +1710,7 @@ FigureColnameInternal(Node *node, char **name)
}
break;
/* As with other operator-like nodes, these have no names */
case MULTIEXPR_SUBLINK:
case ALL_SUBLINK:
case ANY_SUBLINK:
case ROWCOMPARE_SUBLINK: