diff options
| author | Dean Rasheed | 2024-03-17 13:58:59 +0000 |
|---|---|---|
| committer | Dean Rasheed | 2024-03-17 13:58:59 +0000 |
| commit | c649fa24a42ba89bf5460c7110e4fc8eeca65959 (patch) | |
| tree | de7d51489c6c6fff56fddad66c0ced2aa427d6a5 /src/backend/parser | |
| parent | 6a004f1be87d34cfe51acf2fe2552d2b08a79273 (diff) | |
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
Diffstat (limited to 'src/backend/parser')
| -rw-r--r-- | src/backend/parser/analyze.c | 19 | ||||
| -rw-r--r-- | src/backend/parser/gram.y | 14 | ||||
| -rw-r--r-- | src/backend/parser/parse_agg.c | 2 | ||||
| -rw-r--r-- | src/backend/parser/parse_cte.c | 10 | ||||
| -rw-r--r-- | src/backend/parser/parse_expr.c | 34 | ||||
| -rw-r--r-- | src/backend/parser/parse_func.c | 1 | ||||
| -rw-r--r-- | src/backend/parser/parse_merge.c | 7 | ||||
| -rw-r--r-- | src/backend/parser/parse_relation.c | 7 | ||||
| -rw-r--r-- | src/backend/parser/parse_target.c | 4 |
9 files changed, 75 insertions, 23 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 7f23d18b370..40ea19e6f10 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -72,7 +72,6 @@ static void determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist); static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); -static List *transformReturningList(ParseState *pstate, List *returningList); static Query *transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt); static Query *transformDeclareCursorStmt(ParseState *pstate, @@ -551,7 +550,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); - qry->returningList = transformReturningList(pstate, stmt->returningList); + qry->returningList = transformReturningList(pstate, stmt->returningList, + EXPR_KIND_RETURNING); /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; @@ -978,7 +978,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) /* Process RETURNING, if any. */ if (stmt->returningList) qry->returningList = transformReturningList(pstate, - stmt->returningList); + stmt->returningList, + EXPR_KIND_RETURNING); /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; @@ -2454,7 +2455,8 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); - qry->returningList = transformReturningList(pstate, stmt->returningList); + qry->returningList = transformReturningList(pstate, stmt->returningList, + EXPR_KIND_RETURNING); /* * Now we are done with SELECT-like processing, and can get on with @@ -2551,10 +2553,11 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist) /* * transformReturningList - - * handle a RETURNING clause in INSERT/UPDATE/DELETE + * handle a RETURNING clause in INSERT/UPDATE/DELETE/MERGE */ -static List * -transformReturningList(ParseState *pstate, List *returningList) +List * +transformReturningList(ParseState *pstate, List *returningList, + ParseExprKind exprKind) { List *rlist; int save_next_resno; @@ -2571,7 +2574,7 @@ transformReturningList(ParseState *pstate, List *returningList) pstate->p_next_resno = 1; /* transform RETURNING identically to a SELECT targetlist */ - rlist = transformTargetList(pstate, returningList, EXPR_KIND_RETURNING); + rlist = transformTargetList(pstate, returningList, exprKind); /* * Complain if the nonempty tlist expanded to nothing (which is possible diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3ad99fffe1d..39a801a1c38 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -733,7 +733,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED - MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD + MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE @@ -12374,6 +12374,7 @@ MergeStmt: USING table_ref ON a_expr merge_when_list + returning_clause { MergeStmt *m = makeNode(MergeStmt); @@ -12382,6 +12383,7 @@ MergeStmt: m->sourceRelation = $6; m->joinCondition = $8; m->mergeWhenClauses = $9; + m->returningList = $10; $$ = (Node *) m; } @@ -15795,6 +15797,14 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *) n; } + | MERGE_ACTION '(' ')' + { + MergeSupportFunc *m = makeNode(MergeSupportFunc); + + m->msftype = TEXTOID; + m->location = @1; + $$ = (Node *) m; + } ; @@ -17492,6 +17502,7 @@ col_name_keyword: | JSON_SCALAR | JSON_SERIALIZE | LEAST + | MERGE_ACTION | NATIONAL | NCHAR | NONE @@ -17881,6 +17892,7 @@ bare_label_keyword: | MATERIALIZED | MAXVALUE | MERGE + | MERGE_ACTION | METHOD | MINVALUE | MODE diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 9d151a880b8..bee7d8346a3 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -468,6 +468,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) errkind = true; break; case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: errkind = true; break; case EXPR_KIND_VALUES: @@ -915,6 +916,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, errkind = true; break; case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: errkind = true; break; case EXPR_KIND_VALUES: diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c index 3c88c9abbae..6826d4f36a1 100644 --- a/src/backend/parser/parse_cte.c +++ b/src/backend/parser/parse_cte.c @@ -126,13 +126,6 @@ transformWithClause(ParseState *pstate, WithClause *withClause) CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); ListCell *rest; - /* MERGE is allowed by parser, but unimplemented. Reject for now */ - if (IsA(cte->ctequery, MergeStmt)) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MERGE not supported in WITH query"), - parser_errposition(pstate, cte->location)); - for_each_cell(rest, withClause->ctes, lnext(withClause->ctes, lc)) { CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest); @@ -153,7 +146,8 @@ transformWithClause(ParseState *pstate, WithClause *withClause) /* must be a data-modifying statement */ Assert(IsA(cte->ctequery, InsertStmt) || IsA(cte->ctequery, UpdateStmt) || - IsA(cte->ctequery, DeleteStmt)); + IsA(cte->ctequery, DeleteStmt) || + IsA(cte->ctequery, MergeStmt)); pstate->p_hasModifyingCTE = true; } diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 9300c7b9abc..d44b1f2ab2f 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -54,6 +54,7 @@ static Node *transformAExprDistinct(ParseState *pstate, A_Expr *a); static Node *transformAExprNullIf(ParseState *pstate, A_Expr *a); static Node *transformAExprIn(ParseState *pstate, A_Expr *a); static Node *transformAExprBetween(ParseState *pstate, A_Expr *a); +static Node *transformMergeSupportFunc(ParseState *pstate, MergeSupportFunc *f); static Node *transformBoolExpr(ParseState *pstate, BoolExpr *a); static Node *transformFuncCall(ParseState *pstate, FuncCall *fn); static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref); @@ -227,6 +228,11 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformGroupingFunc(pstate, (GroupingFunc *) expr); break; + case T_MergeSupportFunc: + result = transformMergeSupportFunc(pstate, + (MergeSupportFunc *) expr); + break; + case T_NamedArgExpr: { NamedArgExpr *na = (NamedArgExpr *) expr; @@ -541,6 +547,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) case EXPR_KIND_LIMIT: case EXPR_KIND_OFFSET: case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: case EXPR_KIND_VALUES: case EXPR_KIND_VALUES_SINGLE: case EXPR_KIND_CHECK_CONSTRAINT: @@ -1354,6 +1361,31 @@ transformAExprBetween(ParseState *pstate, A_Expr *a) } static Node * +transformMergeSupportFunc(ParseState *pstate, MergeSupportFunc *f) +{ + /* + * All we need to do is check that we're in the RETURNING list of a MERGE + * command. If so, we just return the node as-is. + */ + if (pstate->p_expr_kind != EXPR_KIND_MERGE_RETURNING) + { + ParseState *parent_pstate = pstate->parentParseState; + + while (parent_pstate && + parent_pstate->p_expr_kind != EXPR_KIND_MERGE_RETURNING) + parent_pstate = parent_pstate->parentParseState; + + if (!parent_pstate) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("MERGE_ACTION() can only be used in the RETURNING list of a MERGE command"), + parser_errposition(pstate, f->location)); + } + + return (Node *) f; +} + +static Node * transformBoolExpr(ParseState *pstate, BoolExpr *a) { List *args = NIL; @@ -1767,6 +1799,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_LIMIT: case EXPR_KIND_OFFSET: case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: case EXPR_KIND_VALUES: case EXPR_KIND_VALUES_SINGLE: case EXPR_KIND_CYCLE_MARK: @@ -3115,6 +3148,7 @@ ParseExprKindName(ParseExprKind exprKind) case EXPR_KIND_OFFSET: return "OFFSET"; case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: return "RETURNING"; case EXPR_KIND_VALUES: case EXPR_KIND_VALUES_SINGLE: diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index fdb3e6df338..0cbc950c95c 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2599,6 +2599,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) errkind = true; break; case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: errkind = true; break; case EXPR_KIND_VALUES: diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c index a7d8ba7e98c..04ed5e66dda 100644 --- a/src/backend/parser/parse_merge.c +++ b/src/backend/parser/parse_merge.c @@ -234,6 +234,10 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt) */ qry->jointree = makeFromExpr(pstate->p_joinlist, joinExpr); + /* Transform the RETURNING list, if any */ + qry->returningList = transformReturningList(pstate, stmt->returningList, + EXPR_KIND_MERGE_RETURNING); + /* * We now have a good query shape, so now look at the WHEN conditions and * action targetlists. @@ -391,9 +395,6 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt) qry->mergeActionList = mergeActionList; - /* RETURNING could potentially be added in the future, but not in SQL std */ - qry->returningList = NULL; - qry->hasTargetSRFs = false; qry->hasSubLinks = pstate->p_hasSubLinks; diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 6f5d9e26925..427b7325db8 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -2341,9 +2341,10 @@ addRangeTableEntryForCTE(ParseState *pstate, cte->cterefcount++; /* - * We throw error if the CTE is INSERT/UPDATE/DELETE without RETURNING. - * This won't get checked in case of a self-reference, but that's OK - * because data-modifying CTEs aren't allowed to be recursive anyhow. + * We throw error if the CTE is INSERT/UPDATE/DELETE/MERGE without + * RETURNING. This won't get checked in case of a self-reference, but + * that's OK because data-modifying CTEs aren't allowed to be recursive + * anyhow. */ if (IsA(cte->ctequery, Query)) { diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 5b92502b217..ea522b932b2 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1820,6 +1820,10 @@ FigureColnameInternal(Node *node, char **name) /* make GROUPING() act like a regular function */ *name = "grouping"; return 2; + case T_MergeSupportFunc: + /* make MERGE_ACTION() act like a regular function */ + *name = "merge_action"; + return 2; case T_SubLink: switch (((SubLink *) node)->subLinkType) { |
