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)); + } } /*