diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index 35b0699..1f68bdf 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -25,7 +25,9 @@ PostgreSQL documentation
UPDATE [ ONLY ] table_name [ * ] [ [ AS ] alias ]
SET { column_name = { expression | DEFAULT } |
( column_name [, ...] ) = ( { expression | DEFAULT } [, ...] ) |
- ( column_name [, ...] ) = ( sub-SELECT )
+ ( column_name [, ...] ) = ( sub-SELECT ) |
+ ( * [, ...] ) = ( { expression | DEFAULT } [, ...] ) |
+ ( * [, ...] ) = ( sub-SELECT )
} [, ...]
[ FROM from_list ]
[ WHERE condition | WHERE CURRENT OF cursor_name ]
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index bd78e94..9c58f69 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1907,6 +1907,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
Node *qual;
ListCell *origTargetList;
ListCell *tl;
+ bool isStar = false;
qry->commandType = CMD_UPDATE;
pstate->p_is_update = true;
@@ -1941,6 +1942,51 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
nsitem->p_lateral_only = false;
nsitem->p_lateral_ok = true;
+ /*
+ * Check if (SET(*) = SELECT ...) is present. If it is present we
+ * resolve and populate the remaining needed MultiAssignRefs in the
+ * target list.
+ */
+ if (list_length(stmt->targetList) == 1)
+ {
+ ResTarget *current_val = linitial(stmt->targetList);
+
+ if (IsA((current_val->val), List))
+ {
+ Node *inner_val = linitial((List *) (current_val->val));
+ List *rel_cols_list;
+ int rteindex = 0;
+ int sublevels_up = 0;
+ int i = 0;
+
+ rteindex = RTERangeTablePosn(pstate, pstate->p_target_rangetblentry,
+ &sublevels_up);
+
+ expandRTE(pstate->p_target_rangetblentry, rteindex, sublevels_up,
+ current_val->location, false,
+ &(rel_cols_list), NULL);
+
+ if (IsA(inner_val, MultiAssignRef))
+ {
+ MultiAssignRef *orig_val = (MultiAssignRef *) (inner_val);
+
+ orig_val->ncolumns = list_length(rel_cols_list);
+
+ for (i = 1;i < list_length(rel_cols_list);i++)
+ {
+ MultiAssignRef *r = makeNode(MultiAssignRef);
+
+ r->source = orig_val->source;
+ r->colno = i + 1;
+ r->ncolumns = orig_val->ncolumns;
+
+ lappend((List *) (current_val->val), r);
+ }
+ }
+ }
+ }
+
+
qry->targetList = transformTargetList(pstate, stmt->targetList,
EXPR_KIND_UPDATE_SOURCE);
@@ -1986,30 +2032,54 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
continue;
}
if (origTargetList == NULL)
- elog(ERROR, "UPDATE target count mismatch --- internal error");
- origTarget = (ResTarget *) lfirst(origTargetList);
- Assert(IsA(origTarget, ResTarget));
+ {
+ if (!isStar)
+ elog(ERROR, "UPDATE target count mismatch --- internal error");
+ }
+ else
+ {
+ origTarget = (ResTarget *) lfirst(origTargetList);
+ Assert(IsA(origTarget, ResTarget));
+ }
- attrno = attnameAttNum(pstate->p_target_relation,
- origTarget->name, true);
- if (attrno == InvalidAttrNumber)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- origTarget->name,
- RelationGetRelationName(pstate->p_target_relation)),
- parser_errposition(pstate, origTarget->location)));
+ if (!(origTarget) || !(origTarget->name))
+ isStar = true;
- updateTargetListEntry(pstate, tle, origTarget->name,
- attrno,
- origTarget->indirection,
- origTarget->location);
+ if (!isStar)
+ attrno = attnameAttNum(pstate->p_target_relation,
+ origTarget->name, true);
+ else
+ attrno = attnameAttNum(pstate->p_target_relation,
+ tle->resname, true);
+
+ /*
+ * Check only if we do not have SET (*) else the expansion happened
+ * with relation's attribute names.
+ * No need for updating target list entry for SET (*) since it is already
+ * processed.
+ */
+ if (!isStar)
+ {
+ if (attrno == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ origTarget->name,
+ RelationGetRelationName(pstate->p_target_relation)),
+ parser_errposition(pstate, origTarget->location)));
+
+ updateTargetListEntry(pstate, tle, origTarget->name,
+ attrno,
+ origTarget->indirection,
+ origTarget->location);
+ }
/* Mark the target column as requiring update permissions */
target_rte->modifiedCols = bms_add_member(target_rte->modifiedCols,
attrno - FirstLowInvalidHeapAttributeNumber);
- origTargetList = lnext(origTargetList);
+ if (origTargetList)
+ origTargetList = lnext(origTargetList);
}
if (origTargetList != NULL)
elog(ERROR, "UPDATE target count mismatch --- internal error");
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c98c27a..c2bad88 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9485,6 +9485,45 @@ multiple_set_clause:
$$ = $2;
}
+ | '(' '*' ')' '=' ctext_row
+ {
+ ResTarget *res_col = makeNode(ResTarget);
+
+ /* Make a single node having all target values in a list */
+ res_col->val = (Node *) $5;
+ res_col->location = @2;
+
+ $$ = list_make1(res_col);
+ }
+ | '(' '*' ')' '=' select_with_parens
+ {
+ SubLink *sl = makeNode(SubLink);
+ int ncolumns = -1; /* We do not know the number of columns yet */
+
+ /*
+ * Create a MultiAssignRef source as representative for the entire set
+ * since we cannot look up attributes of target relation here.
+ */
+ ResTarget *res_col = makeNode(ResTarget);
+ MultiAssignRef *r = makeNode(MultiAssignRef);
+
+ /* 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;
+
+ r->source = (Node *) sl;
+ r->colno = 1;
+ r->ncolumns = ncolumns;
+ res_col->val = (Node *) list_make1(r);
+
+ res_col->location = @2;
+
+ $$ = list_make1(res_col);
+ }
;
set_target:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 4a8aaf6..4e371e8 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1295,7 +1295,7 @@ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
Assert(IsA(qtree, Query));
/* Check subquery returns required number of columns */
- if (count_nonjunk_tlist_entries(qtree->targetList) != maref->ncolumns)
+ if ((count_nonjunk_tlist_entries(qtree->targetList) != maref->ncolumns) && maref->ncolumns != -1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("number of columns does not match number of values"),
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 328e0c6..5947611 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -20,6 +20,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/value.h"
#include "parser/parsetree.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
@@ -123,6 +124,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
{
List *p_target = NIL;
ListCell *o_target;
+ bool isStar = false;
/* Shouldn't have any leftover multiassign items at start */
Assert(pstate->p_multiassign_exprs == NIL);
@@ -162,17 +164,110 @@ transformTargetList(ParseState *pstate, List *targetlist,
continue;
}
}
+ else if (IsA(res->val, List))
+ {
+ List *rel_cols_list;
+ ListCell *lc;
+ ListCell *lc_cols;
+ int rteindex;
+ int sublevels_up;
- /*
- * Not "something.*", so transform as a single expression
- */
- p_target = lappend(p_target,
- transformTargetEntry(pstate,
- res->val,
- NULL,
- exprKind,
- res->name,
- false));
+ /* SET (*) = ... is present */
+ isStar = true;
+
+ rteindex = RTERangeTablePosn(pstate, pstate->p_target_rangetblentry,
+ &sublevels_up);
+
+ expandRTE(pstate->p_target_rangetblentry, rteindex, sublevels_up,
+ res->location, false,
+ &(rel_cols_list), NULL);
+
+ if (list_length(rel_cols_list) != list_length((List *)(res->val)))
+ elog(ERROR, "number of columns does not match number of values");
+
+ forboth(lc, (List *) (res->val), lc_cols, rel_cols_list)
+ {
+ Node *current_val = lfirst(lc);
+ char *current_resname = strVal(lfirst(lc_cols));
+
+ /*
+ * Check for "something.*". Depending on the complexity of the
+ * "something", the star could appear as the last field in ColumnRef,
+ * or as the last indirection item in A_Indirection.
+ */
+ if (IsA(current_val, ColumnRef))
+ {
+ ColumnRef *cref = (ColumnRef *) current_val;
+
+ if (IsA(llast(cref->fields), A_Star))
+ {
+ List *current_result = ExpandColumnRefStar(pstate, cref,
+ true);
+ ListCell *lc_result;
+
+ /* It is something.*, expand into multiple items */
+ foreach(lc_result, current_result)
+ {
+ TargetEntry *tle_current = lfirst(lc_result);
+
+ tle_current->resname = current_resname;
+ }
+
+ p_target = list_concat(p_target, current_result);
+ continue;
+ }
+ }
+ else if (IsA(current_val, A_Indirection))
+ {
+ A_Indirection *ind = (A_Indirection *) current_val;
+
+ if (IsA(llast(ind->indirection), A_Star))
+ {
+ List *current_result = ExpandIndirectionStar(pstate, ind,
+ true, exprKind);
+ ListCell *lc_result;
+
+ /* It is something.*, expand into multiple items */
+ foreach(lc_result, current_result)
+ {
+ TargetEntry *tle_current = lfirst(lc_result);
+
+ tle_current->resname = current_resname;
+ }
+
+ p_target = list_concat(p_target,
+ current_result);
+ continue;
+ }
+ }
+
+ /*
+ * Not "something.*", so transform as a single expression
+ */
+ p_target = lappend(p_target,
+ transformTargetEntry(pstate,
+ current_val,
+ NULL,
+ exprKind,
+ current_resname,
+ false));
+ }
+ }
+
+ if (!isStar)
+ {
+ /*
+ * Not "SET (*) =... " and "something.*",
+ * so transform as a single expression
+ */
+ p_target = lappend(p_target,
+ transformTargetEntry(pstate,
+ res->val,
+ NULL,
+ exprKind,
+ res->name,
+ false));
+ }
}
/*