1
0
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:
Dean Rasheed
2024-03-30 10:00:26 +00:00
parent 46e5441fa5
commit 0294df2f1f
33 changed files with 1228 additions and 362 deletions

View File

@@ -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

View File

@@ -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