aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/Makefile1
-rw-r--r--src/backend/parser/analyze.c20
-rw-r--r--src/backend/parser/gram.y180
-rw-r--r--src/backend/parser/parse_agg.c10
-rw-r--r--src/backend/parser/parse_collate.c1
-rw-r--r--src/backend/parser/parse_expr.c4
-rw-r--r--src/backend/parser/parse_func.c3
-rw-r--r--src/backend/parser/parse_merge.c415
-rw-r--r--src/backend/parser/parse_relation.c23
-rw-r--r--src/backend/parser/parse_target.c3
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
{