mirror of
https://github.com/postgres/postgres.git
synced 2025-08-21 10:42:50 +03:00
Add support for MERGE ... WHEN NOT MATCHED BY SOURCE.
This allows MERGE commands to include WHEN NOT MATCHED BY SOURCE actions, which operate on rows that exist in the target relation, but not in the data source. These actions can execute UPDATE, DELETE, or DO NOTHING sub-commands. This is in contrast to already-supported WHEN NOT MATCHED actions, which operate on rows that exist in the data source, but not in the target relation. To make this distinction clearer, such actions may now be written as WHEN NOT MATCHED BY TARGET. Writing WHEN NOT MATCHED without specifying BY SOURCE or BY TARGET is equivalent to writing WHEN NOT MATCHED BY TARGET. Dean Rasheed, reviewed by Alvaro Herrera, Ted Yu and Vik Fearing. Discussion: https://postgr.es/m/CAEZATCWqnKGc57Y_JanUBHQXNKcXd7r=0R4NEZUVwP+syRkWbA@mail.gmail.com
This commit is contained in:
@@ -275,6 +275,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
struct SelectLimit *selectlimit;
|
||||
SetQuantifier setquantifier;
|
||||
struct GroupClause *groupclause;
|
||||
MergeMatchKind mergematch;
|
||||
MergeWhenClause *mergewhen;
|
||||
struct KeyActions *keyactions;
|
||||
struct KeyAction *keyaction;
|
||||
@@ -516,6 +517,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
%type <onconflict> opt_on_conflict
|
||||
%type <mergewhen> merge_insert merge_update merge_delete
|
||||
|
||||
%type <mergematch> merge_when_tgt_matched merge_when_tgt_not_matched
|
||||
%type <node> merge_when_clause opt_merge_when_condition
|
||||
%type <list> merge_when_list
|
||||
|
||||
@@ -770,11 +772,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
|
||||
SEQUENCE SEQUENCES
|
||||
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
|
||||
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
|
||||
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SOURCE SQL_P STABLE STANDALONE_P
|
||||
START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING_P STRIP_P
|
||||
SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
|
||||
|
||||
TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
|
||||
TABLE TABLES TABLESAMPLE TABLESPACE TARGET TEMP TEMPLATE TEMPORARY TEXT_P THEN
|
||||
TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
|
||||
TREAT TRIGGER TRIM TRUE_P
|
||||
TRUNCATE TRUSTED TYPE_P TYPES_P
|
||||
@@ -12424,50 +12426,66 @@ merge_when_list:
|
||||
| merge_when_list merge_when_clause { $$ = lappend($1,$2); }
|
||||
;
|
||||
|
||||
/*
|
||||
* A WHEN clause may be WHEN MATCHED, WHEN NOT MATCHED BY SOURCE, or WHEN NOT
|
||||
* MATCHED [BY TARGET]. The first two cases match target tuples, and support
|
||||
* UPDATE/DELETE/DO NOTHING actions. The third case does not match target
|
||||
* tuples, and only supports INSERT/DO NOTHING actions.
|
||||
*/
|
||||
merge_when_clause:
|
||||
WHEN MATCHED opt_merge_when_condition THEN merge_update
|
||||
merge_when_tgt_matched opt_merge_when_condition THEN merge_update
|
||||
{
|
||||
$5->matched = true;
|
||||
$5->condition = $3;
|
||||
$4->matchKind = $1;
|
||||
$4->condition = $2;
|
||||
|
||||
$$ = (Node *) $5;
|
||||
$$ = (Node *) $4;
|
||||
}
|
||||
| WHEN MATCHED opt_merge_when_condition THEN merge_delete
|
||||
| merge_when_tgt_matched opt_merge_when_condition THEN merge_delete
|
||||
{
|
||||
$5->matched = true;
|
||||
$5->condition = $3;
|
||||
$4->matchKind = $1;
|
||||
$4->condition = $2;
|
||||
|
||||
$$ = (Node *) $5;
|
||||
$$ = (Node *) $4;
|
||||
}
|
||||
| WHEN NOT MATCHED opt_merge_when_condition THEN merge_insert
|
||||
| merge_when_tgt_not_matched opt_merge_when_condition THEN merge_insert
|
||||
{
|
||||
$6->matched = false;
|
||||
$6->condition = $4;
|
||||
$4->matchKind = $1;
|
||||
$4->condition = $2;
|
||||
|
||||
$$ = (Node *) $6;
|
||||
$$ = (Node *) $4;
|
||||
}
|
||||
| WHEN MATCHED opt_merge_when_condition THEN DO NOTHING
|
||||
| merge_when_tgt_matched opt_merge_when_condition THEN DO NOTHING
|
||||
{
|
||||
MergeWhenClause *m = makeNode(MergeWhenClause);
|
||||
|
||||
m->matched = true;
|
||||
m->matchKind = $1;
|
||||
m->commandType = CMD_NOTHING;
|
||||
m->condition = $3;
|
||||
m->condition = $2;
|
||||
|
||||
$$ = (Node *) m;
|
||||
}
|
||||
| WHEN NOT MATCHED opt_merge_when_condition THEN DO NOTHING
|
||||
| merge_when_tgt_not_matched opt_merge_when_condition THEN DO NOTHING
|
||||
{
|
||||
MergeWhenClause *m = makeNode(MergeWhenClause);
|
||||
|
||||
m->matched = false;
|
||||
m->matchKind = $1;
|
||||
m->commandType = CMD_NOTHING;
|
||||
m->condition = $4;
|
||||
m->condition = $2;
|
||||
|
||||
$$ = (Node *) m;
|
||||
}
|
||||
;
|
||||
|
||||
merge_when_tgt_matched:
|
||||
WHEN MATCHED { $$ = MERGE_WHEN_MATCHED; }
|
||||
| WHEN NOT MATCHED BY SOURCE { $$ = MERGE_WHEN_NOT_MATCHED_BY_SOURCE; }
|
||||
;
|
||||
|
||||
merge_when_tgt_not_matched:
|
||||
WHEN NOT MATCHED { $$ = MERGE_WHEN_NOT_MATCHED_BY_TARGET; }
|
||||
| WHEN NOT MATCHED BY TARGET { $$ = MERGE_WHEN_NOT_MATCHED_BY_TARGET; }
|
||||
;
|
||||
|
||||
opt_merge_when_condition:
|
||||
AND a_expr { $$ = $2; }
|
||||
| { $$ = NULL; }
|
||||
@@ -17576,6 +17594,7 @@ unreserved_keyword:
|
||||
| SIMPLE
|
||||
| SKIP
|
||||
| SNAPSHOT
|
||||
| SOURCE
|
||||
| SQL_P
|
||||
| STABLE
|
||||
| STANDALONE_P
|
||||
@@ -17595,6 +17614,7 @@ unreserved_keyword:
|
||||
| SYSTEM_P
|
||||
| TABLES
|
||||
| TABLESPACE
|
||||
| TARGET
|
||||
| TEMP
|
||||
| TEMPLATE
|
||||
| TEMPORARY
|
||||
@@ -18206,6 +18226,7 @@ bare_label_keyword:
|
||||
| SMALLINT
|
||||
| SNAPSHOT
|
||||
| SOME
|
||||
| SOURCE
|
||||
| SQL_P
|
||||
| STABLE
|
||||
| STANDALONE_P
|
||||
@@ -18230,6 +18251,7 @@ bare_label_keyword:
|
||||
| TABLES
|
||||
| TABLESAMPLE
|
||||
| TABLESPACE
|
||||
| TARGET
|
||||
| TEMP
|
||||
| TEMPLATE
|
||||
| TEMPORARY
|
||||
|
@@ -40,9 +40,9 @@ static void setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
|
||||
* Make appropriate changes to the namespace visibility while transforming
|
||||
* individual action's quals and targetlist expressions. In particular, for
|
||||
* INSERT actions we must only see the source relation (since INSERT action is
|
||||
* invoked for NOT MATCHED tuples and hence there is no target tuple to deal
|
||||
* with). On the other hand, UPDATE and DELETE actions can see both source and
|
||||
* target relations.
|
||||
* invoked for NOT MATCHED [BY TARGET] tuples and hence there is no target
|
||||
* tuple to deal with). On the other hand, UPDATE and DELETE actions can see
|
||||
* both source and target relations, unless invoked for NOT MATCHED BY SOURCE.
|
||||
*
|
||||
* Also, since the internal join node can hide the source and target
|
||||
* relations, we must explicitly make the respective relation as visible so
|
||||
@@ -58,7 +58,7 @@ setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause,
|
||||
targetRelRTE = rt_fetch(targetRTI, pstate->p_rtable);
|
||||
sourceRelRTE = rt_fetch(sourceRTI, pstate->p_rtable);
|
||||
|
||||
if (mergeWhenClause->matched)
|
||||
if (mergeWhenClause->matchKind == MERGE_WHEN_MATCHED)
|
||||
{
|
||||
Assert(mergeWhenClause->commandType == CMD_UPDATE ||
|
||||
mergeWhenClause->commandType == CMD_DELETE ||
|
||||
@@ -70,11 +70,25 @@ setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause,
|
||||
setNamespaceVisibilityForRTE(pstate->p_namespace,
|
||||
sourceRelRTE, true, true);
|
||||
}
|
||||
else
|
||||
else if (mergeWhenClause->matchKind == MERGE_WHEN_NOT_MATCHED_BY_SOURCE)
|
||||
{
|
||||
/*
|
||||
* NOT MATCHED actions can't see target relation, but they can see
|
||||
* source relation.
|
||||
* NOT MATCHED BY SOURCE actions can see the target relation, but they
|
||||
* can't see the source relation.
|
||||
*/
|
||||
Assert(mergeWhenClause->commandType == CMD_UPDATE ||
|
||||
mergeWhenClause->commandType == CMD_DELETE ||
|
||||
mergeWhenClause->commandType == CMD_NOTHING);
|
||||
setNamespaceVisibilityForRTE(pstate->p_namespace,
|
||||
targetRelRTE, true, true);
|
||||
setNamespaceVisibilityForRTE(pstate->p_namespace,
|
||||
sourceRelRTE, false, false);
|
||||
}
|
||||
else /* MERGE_WHEN_NOT_MATCHED_BY_TARGET */
|
||||
{
|
||||
/*
|
||||
* NOT MATCHED [BY TARGET] actions can't see target relation, but they
|
||||
* can see source relation.
|
||||
*/
|
||||
Assert(mergeWhenClause->commandType == CMD_INSERT ||
|
||||
mergeWhenClause->commandType == CMD_NOTHING);
|
||||
@@ -95,10 +109,9 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
|
||||
Query *qry = makeNode(Query);
|
||||
ListCell *l;
|
||||
AclMode targetPerms = ACL_NO_RIGHTS;
|
||||
bool is_terminal[2];
|
||||
bool is_terminal[3];
|
||||
Index sourceRTI;
|
||||
List *mergeActionList;
|
||||
Node *joinExpr;
|
||||
ParseNamespaceItem *nsitem;
|
||||
|
||||
/* There can't be any outer WITH to worry about */
|
||||
@@ -122,12 +135,12 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
|
||||
/*
|
||||
* Check WHEN clauses for permissions and sanity
|
||||
*/
|
||||
is_terminal[0] = false;
|
||||
is_terminal[1] = false;
|
||||
is_terminal[MERGE_WHEN_MATCHED] = false;
|
||||
is_terminal[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] = false;
|
||||
is_terminal[MERGE_WHEN_NOT_MATCHED_BY_TARGET] = false;
|
||||
foreach(l, stmt->mergeWhenClauses)
|
||||
{
|
||||
MergeWhenClause *mergeWhenClause = (MergeWhenClause *) lfirst(l);
|
||||
int when_type = (mergeWhenClause->matched ? 0 : 1);
|
||||
|
||||
/*
|
||||
* Collect permissions to check, according to action types. We require
|
||||
@@ -157,12 +170,12 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
|
||||
/*
|
||||
* Check for unreachable WHEN clauses
|
||||
*/
|
||||
if (is_terminal[when_type])
|
||||
if (is_terminal[mergeWhenClause->matchKind])
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("unreachable WHEN clause specified after unconditional WHEN clause")));
|
||||
if (mergeWhenClause->condition == NULL)
|
||||
is_terminal[when_type] = true;
|
||||
is_terminal[mergeWhenClause->matchKind] = true;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -223,16 +236,15 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
|
||||
* side, so add that to the namespace.
|
||||
*/
|
||||
addNSItemToQuery(pstate, pstate->p_target_nsitem, false, true, true);
|
||||
joinExpr = transformExpr(pstate, stmt->joinCondition,
|
||||
EXPR_KIND_JOIN_ON);
|
||||
qry->mergeJoinCondition = transformExpr(pstate, stmt->joinCondition,
|
||||
EXPR_KIND_JOIN_ON);
|
||||
|
||||
/*
|
||||
* Create the temporary query's jointree using the joinlist we built using
|
||||
* just the source relation; the target relation is not included. The
|
||||
* quals we use are the join conditions to the merge target. The join
|
||||
* just the source relation; the target relation is not included. The join
|
||||
* will be constructed fully by transform_MERGE_to_join.
|
||||
*/
|
||||
qry->jointree = makeFromExpr(pstate->p_joinlist, joinExpr);
|
||||
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
|
||||
|
||||
/* Transform the RETURNING list, if any */
|
||||
qry->returningList = transformReturningList(pstate, stmt->returningList,
|
||||
@@ -260,11 +272,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
|
||||
|
||||
action = makeNode(MergeAction);
|
||||
action->commandType = mergeWhenClause->commandType;
|
||||
action->matched = mergeWhenClause->matched;
|
||||
|
||||
/* Use an outer join if any INSERT actions exist in the command. */
|
||||
if (action->commandType == CMD_INSERT)
|
||||
qry->mergeUseOuterJoin = true;
|
||||
action->matchKind = mergeWhenClause->matchKind;
|
||||
|
||||
/*
|
||||
* Set namespace for the specific action. This must be done before
|
||||
|
Reference in New Issue
Block a user