diff options
author | Alvaro Herrera | 2022-03-28 14:45:58 +0000 |
---|---|---|
committer | Alvaro Herrera | 2022-03-28 14:47:48 +0000 |
commit | 7103ebb7aae8ab8076b7e85f335ceb8fe799097c (patch) | |
tree | 0bc2faf176b58d2546de40c3c36d93a4cdf1aafe /src/backend/parser | |
parent | ae63017bdb316b16a9f201b10f1221598111d6c5 (diff) |
Add support for MERGE SQL command
MERGE performs actions that modify rows in the target table using a
source table or query. MERGE provides a single SQL statement that can
conditionally INSERT/UPDATE/DELETE rows -- a task that would otherwise
require multiple PL statements. For example,
MERGE INTO target AS t
USING source AS s
ON t.tid = s.sid
WHEN MATCHED AND t.balance > s.delta THEN
UPDATE SET balance = t.balance - s.delta
WHEN MATCHED THEN
DELETE
WHEN NOT MATCHED AND s.delta > 0 THEN
INSERT VALUES (s.sid, s.delta)
WHEN NOT MATCHED THEN
DO NOTHING;
MERGE works with regular tables, partitioned tables and inheritance
hierarchies, including column and row security enforcement, as well as
support for row and statement triggers and transition tables therein.
MERGE is optimized for OLTP and is parameterizable, though also useful
for large scale ETL/ELT. MERGE is not intended to be used in preference
to existing single SQL commands for INSERT, UPDATE or DELETE since there
is some overhead. MERGE can be used from PL/pgSQL.
MERGE does not support targetting updatable views or foreign tables, and
RETURNING clauses are not allowed either. These limitations are likely
fixable with sufficient effort. Rewrite rules are also not supported,
but it's not clear that we'd want to support them.
Author: Pavan Deolasee <pavan.deolasee@gmail.com>
Author: Álvaro Herrera <alvherre@alvh.no-ip.org>
Author: Amit Langote <amitlangote09@gmail.com>
Author: Simon Riggs <simon.riggs@enterprisedb.com>
Reviewed-by: Peter Eisentraut <peter.eisentraut@enterprisedb.com>
Reviewed-by: Andres Freund <andres@anarazel.de> (earlier versions)
Reviewed-by: Peter Geoghegan <pg@bowt.ie> (earlier versions)
Reviewed-by: Robert Haas <robertmhaas@gmail.com> (earlier versions)
Reviewed-by: Japin Li <japinli@hotmail.com>
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Reviewed-by: Tomas Vondra <tomas.vondra@enterprisedb.com>
Reviewed-by: Zhihong Yu <zyu@yugabyte.com>
Discussion: https://postgr.es/m/CANP8+jKitBSrB7oTgT9CY2i1ObfOt36z0XMraQc+Xrz8QB0nXA@mail.gmail.com
Discussion: https://postgr.es/m/CAH2-WzkJdBuxj9PO=2QaO9-3h3xGbQPZ34kJH=HukRekwM-GZg@mail.gmail.com
Discussion: https://postgr.es/m/20201231134736.GA25392@alvherre.pgsql
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/Makefile | 1 | ||||
-rw-r--r-- | src/backend/parser/analyze.c | 20 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 180 | ||||
-rw-r--r-- | src/backend/parser/parse_agg.c | 10 | ||||
-rw-r--r-- | src/backend/parser/parse_collate.c | 1 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 4 | ||||
-rw-r--r-- | src/backend/parser/parse_func.c | 3 | ||||
-rw-r--r-- | src/backend/parser/parse_merge.c | 415 | ||||
-rw-r--r-- | src/backend/parser/parse_relation.c | 23 | ||||
-rw-r--r-- | src/backend/parser/parse_target.c | 3 |
10 files changed, 643 insertions, 17 deletions
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile index 5ddb9a92f05..9f1c4022bbe 100644 --- a/src/backend/parser/Makefile +++ b/src/backend/parser/Makefile @@ -23,6 +23,7 @@ OBJS = \ parse_enr.o \ parse_expr.o \ parse_func.o \ + parse_merge.o \ parse_node.o \ parse_oper.o \ parse_param.o \ diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 61026753a3d..0144284aa35 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -39,6 +39,7 @@ #include "parser/parse_cte.h" #include "parser/parse_expr.h" #include "parser/parse_func.h" +#include "parser/parse_merge.h" #include "parser/parse_oper.h" #include "parser/parse_param.h" #include "parser/parse_relation.h" @@ -60,9 +61,6 @@ post_parse_analyze_hook_type post_parse_analyze_hook = NULL; static Query *transformOptionalSelectInto(ParseState *pstate, Node *parseTree); static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt); static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt); -static List *transformInsertRow(ParseState *pstate, List *exprlist, - List *stmtcols, List *icolumns, List *attrnos, - bool strip_indirection); static OnConflictExpr *transformOnConflictClause(ParseState *pstate, OnConflictClause *onConflictClause); static int count_rowexpr_columns(ParseState *pstate, Node *expr); @@ -76,8 +74,6 @@ static void determineRecursiveColTypes(ParseState *pstate, static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); static List *transformReturningList(ParseState *pstate, List *returningList); -static List *transformUpdateTargetList(ParseState *pstate, - List *targetList); static Query *transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt); static Query *transformDeclareCursorStmt(ParseState *pstate, @@ -330,6 +326,7 @@ transformStmt(ParseState *pstate, Node *parseTree) case T_InsertStmt: case T_UpdateStmt: case T_DeleteStmt: + case T_MergeStmt: (void) test_raw_expression_coverage(parseTree, NULL); break; default: @@ -354,6 +351,10 @@ transformStmt(ParseState *pstate, Node *parseTree) result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree); break; + case T_MergeStmt: + result = transformMergeStmt(pstate, (MergeStmt *) parseTree); + break; + case T_SelectStmt: { SelectStmt *n = (SelectStmt *) parseTree; @@ -438,6 +439,7 @@ analyze_requires_snapshot(RawStmt *parseTree) case T_InsertStmt: case T_DeleteStmt: case T_UpdateStmt: + case T_MergeStmt: case T_SelectStmt: case T_PLAssignStmt: result = true; @@ -956,7 +958,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) * attrnos: integer column numbers (must be same length as icolumns) * strip_indirection: if true, remove any field/array assignment nodes */ -static List * +List * transformInsertRow(ParseState *pstate, List *exprlist, List *stmtcols, List *icolumns, List *attrnos, bool strip_indirection) @@ -1593,7 +1595,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) * Generate a targetlist as though expanding "*" */ Assert(pstate->p_next_resno == 1); - qry->targetList = expandNSItemAttrs(pstate, nsitem, 0, -1); + qry->targetList = expandNSItemAttrs(pstate, nsitem, 0, true, -1); /* * The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a @@ -2418,9 +2420,9 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) /* * transformUpdateTargetList - - * handle SET clause in UPDATE/INSERT ... ON CONFLICT UPDATE + * handle SET clause in UPDATE/MERGE/INSERT ... ON CONFLICT UPDATE */ -static List * +List * transformUpdateTargetList(ParseState *pstate, List *origTlist) { List *tlist = NIL; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c6613af9fe6..9399fff610f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -278,6 +278,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); struct SelectLimit *selectlimit; SetQuantifier setquantifier; struct GroupClause *groupclause; + MergeWhenClause *mergewhen; struct KeyActions *keyactions; struct KeyAction *keyaction; } @@ -307,7 +308,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DropTransformStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt - ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt + ListenStmt LoadStmt LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt RuleActionStmt RuleActionStmtOrEmpty RuleStmt @@ -433,6 +434,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); any_operator expr_list attrs distinct_clause opt_distinct_clause target_list opt_target_list insert_column_list set_target_list + merge_values_clause set_clause_list set_clause def_list operator_def_list indirection opt_indirection reloption_list TriggerFuncArgs opclass_item_list opclass_drop_list @@ -506,6 +508,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <istmt> insert_rest %type <infer> opt_conf_expr %type <onconflict> opt_on_conflict +%type <mergewhen> merge_insert merge_update merge_delete + +%type <node> merge_when_clause opt_merge_when_condition +%type <list> merge_when_list %type <vsetstmt> generic_set set_rest set_rest_more generic_reset reset_rest SetResetClause FunctionSetResetClause @@ -734,7 +740,8 @@ 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 MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE + MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD + MINUTE_P MINVALUE MODE MONTH_P MOVE NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE NORMALIZE NORMALIZED @@ -1061,6 +1068,7 @@ stmt: | RefreshMatViewStmt | LoadStmt | LockStmt + | MergeStmt | NotifyStmt | PrepareStmt | ReassignOwnedStmt @@ -11123,6 +11131,7 @@ ExplainableStmt: | InsertStmt | UpdateStmt | DeleteStmt + | MergeStmt | DeclareCursorStmt | CreateAsStmt | CreateMatViewStmt @@ -11155,7 +11164,8 @@ PreparableStmt: SelectStmt | InsertStmt | UpdateStmt - | DeleteStmt /* by default all are $$=$1 */ + | DeleteStmt + | MergeStmt /* by default all are $$=$1 */ ; /***************************************************************************** @@ -11543,6 +11553,166 @@ set_target_list: /***************************************************************************** * * QUERY: + * MERGE + * + *****************************************************************************/ + +MergeStmt: + opt_with_clause MERGE INTO relation_expr_opt_alias + USING table_ref + ON a_expr + merge_when_list + { + MergeStmt *m = makeNode(MergeStmt); + + m->withClause = $1; + m->relation = $4; + m->sourceRelation = $6; + m->joinCondition = $8; + m->mergeWhenClauses = $9; + + $$ = (Node *)m; + } + ; + +merge_when_list: + merge_when_clause { $$ = list_make1($1); } + | merge_when_list merge_when_clause { $$ = lappend($1,$2); } + ; + +merge_when_clause: + WHEN MATCHED opt_merge_when_condition THEN merge_update + { + $5->matched = true; + $5->condition = $3; + + $$ = (Node *) $5; + } + | WHEN MATCHED opt_merge_when_condition THEN merge_delete + { + $5->matched = true; + $5->condition = $3; + + $$ = (Node *) $5; + } + | WHEN NOT MATCHED opt_merge_when_condition THEN merge_insert + { + $6->matched = false; + $6->condition = $4; + + $$ = (Node *) $6; + } + | WHEN MATCHED opt_merge_when_condition THEN DO NOTHING + { + MergeWhenClause *m = makeNode(MergeWhenClause); + + m->matched = true; + m->commandType = CMD_NOTHING; + m->condition = $3; + + $$ = (Node *)m; + } + | WHEN NOT MATCHED opt_merge_when_condition THEN DO NOTHING + { + MergeWhenClause *m = makeNode(MergeWhenClause); + + m->matched = false; + m->commandType = CMD_NOTHING; + m->condition = $4; + + $$ = (Node *)m; + } + ; + +opt_merge_when_condition: + AND a_expr { $$ = $2; } + | { $$ = NULL; } + ; + +merge_update: + UPDATE SET set_clause_list + { + MergeWhenClause *n = makeNode(MergeWhenClause); + n->commandType = CMD_UPDATE; + n->override = OVERRIDING_NOT_SET; + n->targetList = $3; + n->values = NIL; + + $$ = n; + } + ; + +merge_delete: + DELETE_P + { + MergeWhenClause *n = makeNode(MergeWhenClause); + n->commandType = CMD_DELETE; + n->override = OVERRIDING_NOT_SET; + n->targetList = NIL; + n->values = NIL; + + $$ = n; + } + ; + +merge_insert: + INSERT merge_values_clause + { + MergeWhenClause *n = makeNode(MergeWhenClause); + n->commandType = CMD_INSERT; + n->override = OVERRIDING_NOT_SET; + n->targetList = NIL; + n->values = $2; + $$ = n; + } + | INSERT OVERRIDING override_kind VALUE_P merge_values_clause + { + MergeWhenClause *n = makeNode(MergeWhenClause); + n->commandType = CMD_INSERT; + n->override = $3; + n->targetList = NIL; + n->values = $5; + $$ = n; + } + | INSERT '(' insert_column_list ')' merge_values_clause + { + MergeWhenClause *n = makeNode(MergeWhenClause); + n->commandType = CMD_INSERT; + n->override = OVERRIDING_NOT_SET; + n->targetList = $3; + n->values = $5; + $$ = n; + } + | INSERT '(' insert_column_list ')' OVERRIDING override_kind VALUE_P merge_values_clause + { + MergeWhenClause *n = makeNode(MergeWhenClause); + n->commandType = CMD_INSERT; + n->override = $6; + n->targetList = $3; + n->values = $8; + $$ = n; + } + | INSERT DEFAULT VALUES + { + MergeWhenClause *n = makeNode(MergeWhenClause); + n->commandType = CMD_INSERT; + n->override = OVERRIDING_NOT_SET; + n->targetList = NIL; + n->values = NIL; + $$ = n; + } + ; + +merge_values_clause: + VALUES '(' expr_list ')' + { + $$ = $3; + } + ; + +/***************************************************************************** + * + * QUERY: * CURSOR STATEMENTS * *****************************************************************************/ @@ -16155,8 +16325,10 @@ unreserved_keyword: | LOGGED | MAPPING | MATCH + | MATCHED | MATERIALIZED | MAXVALUE + | MERGE | METHOD | MINUTE_P | MINVALUE @@ -16734,8 +16906,10 @@ bare_label_keyword: | LOGGED | MAPPING | MATCH + | MATCHED | MATERIALIZED | MAXVALUE + | MERGE | METHOD | MINVALUE | MODE diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index ded0a14d723..3ef9e8ee5e1 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -434,6 +434,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) case EXPR_KIND_UPDATE_TARGET: errkind = true; break; + case EXPR_KIND_MERGE_WHEN: + if (isAgg) + err = _("aggregate functions are not allowed in MERGE WHEN conditions"); + else + err = _("grouping operations are not allowed in MERGE WHEN conditions"); + + break; case EXPR_KIND_GROUP_BY: errkind = true; break; @@ -879,6 +886,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_UPDATE_TARGET: errkind = true; break; + case EXPR_KIND_MERGE_WHEN: + err = _("window functions are not allowed in MERGE WHEN conditions"); + break; case EXPR_KIND_GROUP_BY: errkind = true; break; diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c index 6c793b72ec7..7582faabb37 100644 --- a/src/backend/parser/parse_collate.c +++ b/src/backend/parser/parse_collate.c @@ -485,6 +485,7 @@ assign_collations_walker(Node *node, assign_collations_context *context) case T_FromExpr: case T_OnConflictExpr: case T_SortGroupClause: + case T_MergeAction: (void) expression_tree_walker(node, assign_collations_walker, (void *) &loccontext); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 781c9709e6d..84be354f714 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -513,6 +513,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) case EXPR_KIND_INSERT_TARGET: case EXPR_KIND_UPDATE_SOURCE: case EXPR_KIND_UPDATE_TARGET: + case EXPR_KIND_MERGE_WHEN: case EXPR_KIND_GROUP_BY: case EXPR_KIND_ORDER_BY: case EXPR_KIND_DISTINCT_ON: @@ -1748,6 +1749,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_INSERT_TARGET: case EXPR_KIND_UPDATE_SOURCE: case EXPR_KIND_UPDATE_TARGET: + case EXPR_KIND_MERGE_WHEN: case EXPR_KIND_GROUP_BY: case EXPR_KIND_ORDER_BY: case EXPR_KIND_DISTINCT_ON: @@ -3075,6 +3077,8 @@ ParseExprKindName(ParseExprKind exprKind) case EXPR_KIND_UPDATE_SOURCE: case EXPR_KIND_UPDATE_TARGET: return "UPDATE"; + case EXPR_KIND_MERGE_WHEN: + return "MERGE WHEN"; case EXPR_KIND_GROUP_BY: return "GROUP BY"; case EXPR_KIND_ORDER_BY: diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index d91951e1f6c..f71a682cd65 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2611,6 +2611,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) /* okay, since we process this like a SELECT tlist */ pstate->p_hasTargetSRFs = true; break; + case EXPR_KIND_MERGE_WHEN: + err = _("set-returning functions are not allowed in MERGE WHEN conditions"); + break; case EXPR_KIND_CHECK_CONSTRAINT: case EXPR_KIND_DOMAIN_CHECK: err = _("set-returning functions are not allowed in check constraints"); diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c new file mode 100644 index 00000000000..5d0035a12b6 --- /dev/null +++ b/src/backend/parser/parse_merge.c @@ -0,0 +1,415 @@ +/*------------------------------------------------------------------------- + * + * parse_merge.c + * handle merge-statement in parser + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/parser/parse_merge.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/sysattr.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "parser/analyze.h" +#include "parser/parse_collate.h" +#include "parser/parsetree.h" +#include "parser/parser.h" +#include "parser/parse_clause.h" +#include "parser/parse_cte.h" +#include "parser/parse_expr.h" +#include "parser/parse_merge.h" +#include "parser/parse_relation.h" +#include "parser/parse_target.h" +#include "utils/rel.h" +#include "utils/relcache.h" + +static void setNamespaceForMergeWhen(ParseState *pstate, + MergeWhenClause *mergeWhenClause, + Index targetRTI, + Index sourceRTI); +static void setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte, + bool rel_visible, + bool cols_visible); + +/* + * 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. + * + * Also, since the internal join node can hide the source and target + * relations, we must explicitly make the respective relation as visible so + * that columns can be referenced unqualified from these relations. + */ +static void +setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause, + Index targetRTI, Index sourceRTI) +{ + RangeTblEntry *targetRelRTE, + *sourceRelRTE; + + targetRelRTE = rt_fetch(targetRTI, pstate->p_rtable); + sourceRelRTE = rt_fetch(sourceRTI, pstate->p_rtable); + + if (mergeWhenClause->matched) + { + Assert(mergeWhenClause->commandType == CMD_UPDATE || + mergeWhenClause->commandType == CMD_DELETE || + mergeWhenClause->commandType == CMD_NOTHING); + + /* MATCHED actions can see both target and source relations. */ + setNamespaceVisibilityForRTE(pstate->p_namespace, + targetRelRTE, true, true); + setNamespaceVisibilityForRTE(pstate->p_namespace, + sourceRelRTE, true, true); + } + else + { + /* + * NOT MATCHED actions can't see target relation, but they can see + * source relation. + */ + Assert(mergeWhenClause->commandType == CMD_INSERT || + mergeWhenClause->commandType == CMD_NOTHING); + setNamespaceVisibilityForRTE(pstate->p_namespace, + targetRelRTE, false, false); + setNamespaceVisibilityForRTE(pstate->p_namespace, + sourceRelRTE, true, true); + } +} + +/* + * transformMergeStmt - + * transforms a MERGE statement + */ +Query * +transformMergeStmt(ParseState *pstate, MergeStmt *stmt) +{ + Query *qry = makeNode(Query); + ListCell *l; + AclMode targetPerms = ACL_NO_RIGHTS; + bool is_terminal[2]; + Index sourceRTI; + List *mergeActionList; + Node *joinExpr; + ParseNamespaceItem *nsitem; + + /* There can't be any outer WITH to worry about */ + Assert(pstate->p_ctenamespace == NIL); + + qry->commandType = CMD_MERGE; + qry->hasRecursive = false; + + /* process the WITH clause independently of all else */ + if (stmt->withClause) + { + if (stmt->withClause->recursive) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("WITH RECURSIVE is not supported for MERGE statement"))); + + qry->cteList = transformWithClause(pstate, stmt->withClause); + qry->hasModifyingCTE = pstate->p_hasModifyingCTE; + } + + /* + * Check WHEN clauses for permissions and sanity + */ + is_terminal[0] = false; + is_terminal[1] = false; + foreach(l, stmt->mergeWhenClauses) + { + MergeWhenClause *mergeWhenClause = (MergeWhenClause *) lfirst(l); + int when_type = (mergeWhenClause->matched ? 0 : 1); + + /* + * Collect action types so we can check target permissions + */ + switch (mergeWhenClause->commandType) + { + case CMD_INSERT: + targetPerms |= ACL_INSERT; + break; + case CMD_UPDATE: + targetPerms |= ACL_UPDATE; + break; + case CMD_DELETE: + targetPerms |= ACL_DELETE; + break; + case CMD_NOTHING: + break; + default: + elog(ERROR, "unknown action in MERGE WHEN clause"); + } + + /* + * Check for unreachable WHEN clauses + */ + if (mergeWhenClause->condition == NULL) + is_terminal[when_type] = true; + else if (is_terminal[when_type]) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unreachable WHEN clause specified after unconditional WHEN clause"))); + } + + /* Set up the MERGE target table. */ + qry->resultRelation = setTargetTable(pstate, stmt->relation, + stmt->relation->inh, + false, targetPerms); + + /* + * MERGE is unsupported in various cases + */ + if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION && + pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot execute MERGE on relation \"%s\"", + RelationGetRelationName(pstate->p_target_relation)), + errdetail_relkind_not_supported(pstate->p_target_relation->rd_rel->relkind))); + if (pstate->p_target_relation->rd_rel->relhasrules) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot execute MERGE on relation \"%s\"", + RelationGetRelationName(pstate->p_target_relation)), + errdetail("MERGE is not supported for relations with rules."))); + + /* Now transform the source relation to produce the source RTE. */ + transformFromClause(pstate, + list_make1(stmt->sourceRelation)); + sourceRTI = list_length(pstate->p_rtable); + nsitem = GetNSItemByRangeTablePosn(pstate, sourceRTI, 0); + + /* + * Check that the target table doesn't conflict with the source table. + * This would typically be a checkNameSpaceConflicts call, but we want a + * more specific error message. + */ + if (strcmp(pstate->p_target_nsitem->p_names->aliasname, + nsitem->p_names->aliasname) == 0) + ereport(ERROR, + errcode(ERRCODE_DUPLICATE_ALIAS), + errmsg("name \"%s\" specified more than once", + pstate->p_target_nsitem->p_names->aliasname), + errdetail("The name is used both as MERGE target table and data source.")); + + qry->targetList = expandNSItemAttrs(pstate, nsitem, 0, false, + exprLocation(stmt->sourceRelation)); + + qry->rtable = pstate->p_rtable; + + /* + * Transform the join condition. This includes references to the target + * 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); + + /* + * 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 + * will be constructed fully by transform_MERGE_to_join. + */ + qry->jointree = makeFromExpr(pstate->p_joinlist, joinExpr); + + /* + * We now have a good query shape, so now look at the WHEN conditions and + * action targetlists. + * + * Overall, the MERGE Query's targetlist is NIL. + * + * Each individual action has its own targetlist that needs separate + * transformation. These transforms don't do anything to the overall + * targetlist, since that is only used for resjunk columns. + * + * We can reference any column in Target or Source, which is OK because + * both of those already have RTEs. There is nothing like the EXCLUDED + * pseudo-relation for INSERT ON CONFLICT. + */ + mergeActionList = NIL; + foreach(l, stmt->mergeWhenClauses) + { + MergeWhenClause *mergeWhenClause = lfirst_node(MergeWhenClause, l); + MergeAction *action; + + 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; + + /* + * Set namespace for the specific action. This must be done before + * analyzing the WHEN quals and the action targetlist. + */ + setNamespaceForMergeWhen(pstate, mergeWhenClause, + qry->resultRelation, + sourceRTI); + + /* + * Transform the WHEN condition. + * + * Note that these quals are NOT added to the join quals; instead they + * are evaluated separately during execution to decide which of the + * WHEN MATCHED or WHEN NOT MATCHED actions to execute. + */ + action->qual = transformWhereClause(pstate, mergeWhenClause->condition, + EXPR_KIND_MERGE_WHEN, "WHEN"); + + /* + * Transform target lists for each INSERT and UPDATE action stmt + */ + switch (action->commandType) + { + case CMD_INSERT: + { + List *exprList = NIL; + ListCell *lc; + RangeTblEntry *rte; + ListCell *icols; + ListCell *attnos; + List *icolumns; + List *attrnos; + + pstate->p_is_insert = true; + + icolumns = checkInsertTargets(pstate, + mergeWhenClause->targetList, + &attrnos); + Assert(list_length(icolumns) == list_length(attrnos)); + + action->override = mergeWhenClause->override; + + /* + * Handle INSERT much like in transformInsertStmt + */ + if (mergeWhenClause->values == NIL) + { + /* + * We have INSERT ... DEFAULT VALUES. We can handle + * this case by emitting an empty targetlist --- all + * columns will be defaulted when the planner expands + * the targetlist. + */ + exprList = NIL; + } + else + { + /* + * Process INSERT ... VALUES with a single VALUES + * sublist. We treat this case separately for + * efficiency. The sublist is just computed directly + * as the Query's targetlist, with no VALUES RTE. So + * it works just like a SELECT without any FROM. + */ + + /* + * Do basic expression transformation (same as a ROW() + * expr, but allow SetToDefault at top level) + */ + exprList = transformExpressionList(pstate, + mergeWhenClause->values, + EXPR_KIND_VALUES_SINGLE, + true); + + /* Prepare row for assignment to target table */ + exprList = transformInsertRow(pstate, exprList, + mergeWhenClause->targetList, + icolumns, attrnos, + false); + } + + /* + * Generate action's target list using the computed list + * of expressions. Also, mark all the target columns as + * needing insert permissions. + */ + rte = pstate->p_target_nsitem->p_rte; + forthree(lc, exprList, icols, icolumns, attnos, attrnos) + { + Expr *expr = (Expr *) lfirst(lc); + ResTarget *col = lfirst_node(ResTarget, icols); + AttrNumber attr_num = (AttrNumber) lfirst_int(attnos); + TargetEntry *tle; + + tle = makeTargetEntry(expr, + attr_num, + col->name, + false); + action->targetList = lappend(action->targetList, tle); + + rte->insertedCols = + bms_add_member(rte->insertedCols, + attr_num - FirstLowInvalidHeapAttributeNumber); + } + } + break; + case CMD_UPDATE: + { + pstate->p_is_insert = false; + action->targetList = + transformUpdateTargetList(pstate, + mergeWhenClause->targetList); + } + break; + case CMD_DELETE: + break; + + case CMD_NOTHING: + action->targetList = NIL; + break; + default: + elog(ERROR, "unknown action in MERGE WHEN clause"); + } + + mergeActionList = lappend(mergeActionList, action); + } + + 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; + + assign_query_collations(pstate, qry); + + return qry; +} + +static void +setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte, + bool rel_visible, + bool cols_visible) +{ + ListCell *lc; + + foreach(lc, namespace) + { + ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc); + + if (nsitem->p_rte == rte) + { + nsitem->p_rel_visible = rel_visible; + nsitem->p_cols_visible = cols_visible; + break; + } + } +} diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index cb9e177b5e5..7efa5f15d72 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -701,6 +701,17 @@ scanNSItemForColumn(ParseState *pstate, ParseNamespaceItem *nsitem, colname), parser_errposition(pstate, location))); + /* + * In a MERGE WHEN condition, no system column is allowed except tableOid + */ + if (pstate->p_expr_kind == EXPR_KIND_MERGE_WHEN && + attnum < InvalidAttrNumber && attnum != TableOidAttributeNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("cannot use system column \"%s\" in MERGE WHEN condition", + colname), + parser_errposition(pstate, location))); + /* Found a valid match, so build a Var */ if (attnum > InvalidAttrNumber) { @@ -3095,11 +3106,12 @@ expandNSItemVars(ParseNamespaceItem *nsitem, * for the attributes of the nsitem * * pstate->p_next_resno determines the resnos assigned to the TLEs. - * The referenced columns are marked as requiring SELECT access. + * The referenced columns are marked as requiring SELECT access, if + * caller requests that. */ List * expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem, - int sublevels_up, int location) + int sublevels_up, bool require_col_privs, int location) { RangeTblEntry *rte = nsitem->p_rte; List *names, @@ -3133,8 +3145,11 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem, false); te_list = lappend(te_list, te); - /* Require read access to each column */ - markVarForSelectPriv(pstate, varnode); + if (require_col_privs) + { + /* Require read access to each column */ + markVarForSelectPriv(pstate, varnode); + } } Assert(name == NULL && var == NULL); /* lists not the same length? */ diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 204d2857733..e6445c7bafe 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1308,6 +1308,7 @@ ExpandAllTables(ParseState *pstate, int location) expandNSItemAttrs(pstate, nsitem, 0, + true, location)); } @@ -1370,7 +1371,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem, if (make_target_entry) { /* expandNSItemAttrs handles permissions marking */ - return expandNSItemAttrs(pstate, nsitem, sublevels_up, location); + return expandNSItemAttrs(pstate, nsitem, sublevels_up, true, location); } else { |